# Redirects and rewrites

Put a file named `_redirects` at the top level of the folder you publish. Each line is one rule: where a
visitor arrives, where they go, and optionally how.

```text
# Pages that moved
/old-pricing        /pricing
/blog/:year/:slug   /posts/:year/:slug   302

# Everything under /docs now lives on another site
/docs/*             https://docs.example.com/:splat

# A single-page app: every unknown path gets the app
/*                  /index.html          200
```

The format is the one Netlify and Cloudflare Pages read, so a file written for either works here as long
as it uses the parts listed below. It works on every plan.

On plans with editing in the browser, the dashboard's editor opens `_redirects` like any text file, and
can create one at the top level. Saving publishes a new version, and the rules change with it.

## How a rule is written

`from to [status]`, separated by spaces.

- **`from`** is a path starting with `/`. A segment written `:name` matches any one segment, and a path
  ending in `/*` matches everything below it, including the path itself (`/docs/*` matches `/docs`,
  `/docs/` and `/docs/a/b`). Matching is case-sensitive, like the files beside it. A trailing slash makes
  no difference: `/about` and `/about/` meet the same rule.
- **`to`** is a path on this site or a full `http://` or `https://` address. `:name` puts back what that
  segment matched, and `:splat` what the `*` matched.
- **status** is optional and defaults to `301`.

| Status | What the visitor gets |
| --- | --- |
| `301`, `308` | A permanent redirect. |
| `302`, `303`, `307` | A temporary redirect. |
| `200` | The target file, served at the address they asked for. The address bar does not change. |
| `404` | The target file, with a 404 status — a "not found" page for one part of the site. |

A query string on the visitor's address is kept when a redirect's target has none of its own, so
`/old-pricing?ref=mail` arrives at `/pricing?ref=mail`.

Lines starting with `#` are comments, and so is anything after a `#` on a rule's line.

## Files win, unless you say otherwise

Rules are tried from the top, and the first one that matches is used. A rule only applies where **no
file** answers the path — that is what lets `/* /index.html 200` sit at the bottom without hiding your
images and scripts.

To override a file that exists, end the status with `!`:

```text
/index.html   /new-home   302!
```

## Calling your own API

On paid plans, a `200` rule can send a path on to an API of yours, with the keys it needs kept off the
page. Declare the API's origin and its headers first — in the site's **API connections** tab, with
`drop2run set upstream`, or with the MCP server's `update_site` — then point a rule at it:

```text
/api/chat/*    https://api.openai.com/v1/chat/:splat         200
/api/notes/*   https://xyzcompany.supabase.co/rest/v1/notes/:splat   200
```

Your page calls `/api/chat/completions` on its own address; the call goes to the origin with your
headers attached, and the answer comes back to the page.

- **The origin has to be declared.** A rule to an `https://` origin the site has not declared answers 502
  until you declare it, and so does one whose origin you remove later.
- **A header value is never shown again.** It is encrypted when you save it; the dashboard shows its name
  and last four characters.
- **GET only, unless you say otherwise.** Each upstream lists the methods visitors may send. Anything else
  answers 405. Opening `POST`, `PUT`, `PATCH` or `DELETE` lets anybody who can open the site write with
  your key.
- **Headers from the page are filtered.** Only a short list passes through — `accept`, `content-type`,
  `range`, `if-none-match` and a few others — so a fixed header the provider needs, such as
  `anthropic-version`, goes on the upstream instead.
- **These rules win over files.** A proxied path is never served from a file of the same name, and its
  answers are never cached.
- **Put an exact route above a `/*` route on the same path.** The first rule that matches is used, and
  `/api/notes/*` matches `/api/notes` itself, with an empty `:splat`. So with the `/*` line first, a call
  to `/api/notes` goes to `…/notes/` — trailing slash included — which many APIs answer with a 404 page,
  and the proxy turns an HTML page into a 502 `content_type_not_allowed`. The exact line below it is never
  reached.
- **Long calls should stream.** A call waits up to 100 seconds for the first byte and 60 seconds between
  chunks. If the visitor closes the page while an answer is streaming, the call to your API is cancelled;
  a call still waiting for its first byte runs to the end. For a model call, ask it to stream.
- A request body can be up to 10 MB.
- **Writes come from your own pages.** A `POST`, `PUT`, `PATCH` or `DELETE` has to carry your site's own
  address as its `Origin`; one sent from a page on another site answers 403.
- **What comes back is an API's answer.** An HTML or XML answer is refused with 502, and so is a redirect
  that leads outside the route's own path.
- Each visitor also has a short rate limit; past it, calls answer 429 with a `Retry-After`.
- A site published from documents (Markdown, PDF and the rest) does not proxy.

**Anybody who can open a public site can call these routes**, from the page or from a script. Use a key
with the least access it needs, set a spending cap at the provider if it offers one, and put an internal
tool behind a password or invite-only — the proxy sits behind both. Proxied requests count against a
monthly allowance for the whole account; past it, the routes answer 429 until the month turns.

## What it does not do

- A `200` or `404` rule points at a file in the same site, or — for `200` — at an API you have declared
  above. Serving a page from any other address is refused, and so is a target containing `..`.
- Rules cannot claim paths under `/__d2r/`, which the platform answers on every site for passwords,
  forms and comments.
- Conditions on the query string, the visitor's country or language, and rules keyed on a whole domain
  are not supported.
- On a site published from documents (Markdown, PDF and the rest), the reader owns its paths and the
  file is ignored.
- Response headers cannot be set this way.

A line this does not understand is skipped, and the rest of the file still applies. At most 1,000 rules
are read, from a file of at most 64 KB.

The `_redirects` file itself is not published: visiting `/_redirects` answers 404. Rules belong to the
version they were published with, so rolling back to an earlier version brings its rules back with it,
and a preview link of a version uses that version's rules.

On a site with a password or an invitation list, the visitor signs in first; no rule runs before that.
