# Edge middleware

> Decides, per request, who gets Markdown — and what goes back, headers and all.

Source: https://docs.buildonto.dev/sdk/middleware
Section: Serve SDK

---

Edge middleware\[01\]

## Edge middleware

Decides, per request, who gets Markdown. Agents get the page's prebuilt `.md`; everyone else gets your page, untouched.

### What it does

\[02\]

your edge · @ontosdk/next middleware

Someone asks yoursite.com for /pricing

1.  01Who's asking
    
    A known agent's user-agent, an `Accept: text/markdown` header, or `?onto`. Anyone else goes straight through.
    
2.  02Note the visit
    
    Sent to Onto without waiting — only when a site key is set.
    
3.  03Hand over the Markdown
    
    The prebuilt `/.onto/pricing.md`, fetched from your own site. No file, and the page is served as usual.
    
4.  04Add your context
    
    Serve Pro and above, when you've written some for the route. This step waits for Onto.
    

Markdown for agents, your page for everyone else

### Try a request

\[03\]

GET /pricing

GPTBotClaudeBotA browserPlain curlLink previewGooglebot

Accept: text/markdown?onto on the URL_to see what agents get_Your siteRoute has a prebuilt .md_only prerendered pages do_Site key setServe Pro or above, with context for /pricing

1.  Asking for Markdown — user-agent matches GPTBot (OpenAI)
2.  Note the visit — sent without waiting
3.  Fetch /.onto/pricing.md from your own site
4.  Add your context — asked, but Free gets nothing back

HTTP/1.1 200
Content-Type: text/markdown; charset=utf-8
Cache-Control: no-store, must-revalidate
Vary: User-Agent, Accept
X-Onto-Trace: GPTBot/1.2 (+https://openai.com/gptbot)
X-Onto-Bot: GPTBot (OpenAI)
X-Onto-Matched: true

\# Pricing

> Plans and what each includes

\*\*Source:\*\* /pricing
…

### Set it up

\[04\]

`npx onto-next init` writes this for you. If you already had a `middleware.ts`, it's left alone — call `ontoMiddleware(req, ontoConfig)` from yours.

typescriptmiddleware.tsCopy

```
import { NextRequest } from 'next/server';
import { ontoMiddleware } from '@ontosdk/next/middleware';
import ontoConfig from './onto.config';

export const middleware = (req: NextRequest) => ontoMiddleware(req, ontoConfig);

export const config = {
  matcher: [
    '/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)',
  ],
};
```

Passing the config is what lets it answer `/llms.txt` itself. Paths under `/_next` and anything with a file extension are always passed through.

### Who counts as an agent

\[05\]

Matches

Named crawlersGPTBot, ChatGPT-User, OAI-SearchBot, ClaudeBot, Claude-User, PerplexityBot, Meta-ExternalAgent, Amazonbot, CCBot, Bytespider and more — 42 entries

Catch-allsGPT, Claude, OpenAI, Anthropic, Perplexity, Mistral — anywhere in the user-agent

HTTP librariescurl, Wget, axios, node-fetch, python-requests, httpx, Go-http-client

Left outGooglebot and Bingbot — search keeps getting your HTML

A match is any entry appearing in the user-agent, ignoring case; the longest match names the bot. Link-preview fetchers like `facebookexternalhit` are on the list too, so they receive Markdown.

### What it reads

\[06\]

VariableUsed for

`ONTO_API_KEY`Your site key. Without it nothing is recorded and no context is added — Markdown is still served.

`ONTO_API_URL`Where visits and context requests go. Defaults to `https://api.buildonto.dev`; `ONTO_DASHBOARD_URL` still works.

**See what agents get.** Add `?onto` to any URL on your deployed site — the response carries `X-Onto-Debug: true`.

Copy as Markdown[](/sdk/middleware.md "Open the raw Markdown")

---
## Structured Data (JSON-LD)
```json
{
  "@context": "https://schema.org",
  "@type": "WebSite",
  "name": "Onto Docs",
  "url": "https://docs.buildonto.dev",
  "description": "How to use Onto: serve AI agents Markdown from your Next.js site, call the Read API, connect over MCP, and read the AIO score.",
  "inLanguage": "en",
  "publisher": {
    "@type": "Organization",
    "name": "Onto",
    "url": "https://buildonto.dev"
  }
}
```