> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orbitlab.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Routing (orbitlab.json)

> Configure redirects, rewrites, and headers for your app at the edge with orbitlab.json.

Add an `orbitlab.json` file to your repository to control how requests to your app are handled—**redirects**, **rewrites**, **custom headers**, trailing-slash behavior, and clean URLs. These rules run at the edge (in front of your app), so they apply before a request ever reaches your container.

The file is parsed automatically on every deployment. There is nothing to enable.

## Where the file lives

Place `orbitlab.json` at the root of your project. In a monorepo, put it in the same folder you set as your **Root Directory** (e.g. `apps/web/orbitlab.json`).

Only the routing keys below are read—any other keys are ignored, so you can safely keep unrelated settings in the same file.

## A minimal example

```json theme={null}
{
  "redirects": [
    { "source": "/old-blog/:slug", "destination": "/blog/:slug", "permanent": true }
  ],
  "rewrites": [
    { "source": "/api/:path*", "destination": "https://api.example.com/:path*" }
  ],
  "headers": [
    {
      "source": "/(.*)",
      "headers": [
        { "key": "X-Frame-Options", "value": "DENY" },
        { "key": "Strict-Transport-Security", "value": "max-age=63072000" }
      ]
    }
  ],
  "trailingSlash": false,
  "cleanUrls": true
}
```

## Redirects

Send visitors from one path to another with an HTTP redirect.

```json theme={null}
{
  "redirects": [
    { "source": "/docs", "destination": "/documentation", "permanent": true },
    { "source": "/promo", "destination": "/sale", "statusCode": 302 }
  ]
}
```

| Field             | Type    | Description                                                                  |
| ----------------- | ------- | ---------------------------------------------------------------------------- |
| `source`          | string  | Path pattern to match (see [Source patterns](#source-patterns)).             |
| `destination`     | string  | Where to send the request. Can reference captured params.                    |
| `permanent`       | boolean | `true` → `308` (default), `false` → `307`. Ignored when `statusCode` is set. |
| `statusCode`      | number  | Explicit redirect status (`300`–`399`). Overrides `permanent`.               |
| `has` / `missing` | array   | Optional conditions (see [Conditions](#conditions-has--missing)).            |

## Rewrites

Serve content from a different path or another origin **without changing the URL** in the browser.

```json theme={null}
{
  "rewrites": [
    { "source": "/blog/:path*", "destination": "/posts/:path*" },
    { "source": "/api/:path*", "destination": "https://api.example.com/:path*" }
  ]
}
```

* **Internal rewrite** — when `destination` is an absolute path (e.g. `/posts/:path*`), the URL is rewritten and the request continues to your app.
* **Proxy rewrite** — when `destination` is a full URL (e.g. `https://api.example.com/:path*`), the request is proxied to that origin. The upstream `Host` header is set to the destination host, and HTTPS upstreams are supported.

| Field             | Type   | Description                                                  |
| ----------------- | ------ | ------------------------------------------------------------ |
| `source`          | string | Path pattern to match.                                       |
| `destination`     | string | Absolute path (internal) or full `http(s)://` URL (proxied). |
| `has` / `missing` | array  | Optional conditions.                                         |

## Headers

Attach custom response headers to matching requests. Header rules do **not** stop the request—after headers are set, it continues on to rewrites and your app.

```json theme={null}
{
  "headers": [
    {
      "source": "/assets/:path*",
      "headers": [{ "key": "Cache-Control", "value": "public, max-age=31536000, immutable" }]
    }
  ]
}
```

| Field             | Type   | Description                                                |
| ----------------- | ------ | ---------------------------------------------------------- |
| `source`          | string | Path pattern to match.                                     |
| `headers`         | array  | List of `{ "key": string, "value": string }` header pairs. |
| `has` / `missing` | array  | Optional conditions.                                       |

## Trailing slash & clean URLs

```json theme={null}
{
  "trailingSlash": false,
  "cleanUrls": true
}
```

* **`trailingSlash`** — `false` strips the trailing slash (`/about/` → `/about`); `true` enforces one (`/about` → `/about/`) for paths without a file extension. Both use a `308` redirect. The root `/` is never affected.
* **`cleanUrls`** — redirects `/page.html` → `/page` with a `308`.

## Source patterns

The `source` field supports a familiar path-matching syntax:

| Pattern       | Matches                        | Example         |
| ------------- | ------------------------------ | --------------- |
| `/exact/path` | An exact path                  | `/pricing`      |
| `:param`      | A single path segment          | `/user/:id`     |
| `:param*`     | Zero or more segments          | `/files/:path*` |
| `:param+`     | One or more segments           | `/files/:path+` |
| `:param?`     | An optional segment            | `/blog/:slug?`  |
| `*`           | A wildcard (any characters)    | `/legacy/*`     |
| `(regex)`     | A raw regular-expression group | `/(.*)`         |

Named parameters can be reused in `destination` by writing the same `:param` token. For example, `"/old/:slug"` → `"/new/:slug"` carries the captured segment across.

## Conditions (has / missing)

Both redirects, rewrites, and headers accept optional `has` and `missing` arrays to match only when certain request attributes are present (`has`) or absent (`missing`).

```json theme={null}
{
  "redirects": [
    {
      "source": "/dashboard",
      "destination": "/login",
      "missing": [{ "type": "cookie", "key": "session" }]
    }
  ]
}
```

Each condition object supports:

| Field   | Description                                                            |
| ------- | ---------------------------------------------------------------------- |
| `type`  | One of `header`, `query`, `cookie`, or `host`.                         |
| `key`   | The header/query/cookie name. Not required for `host`.                 |
| `value` | Optional exact value. When omitted, the rule matches on presence only. |

### Per-domain (host) rules

When your service has several domains attached, use a `host` condition to apply a rule to only one of them. This lets a single app serve different content per subdomain:

```json theme={null}
{
  "rewrites": [
    {
      "source": "/:path*",
      "destination": "/tools/:path*",
      "has": [{ "type": "host", "value": "tools.example.com" }]
    },
    {
      "source": "/:path*",
      "destination": "/landing/:path*",
      "has": [{ "type": "host", "value": "www.example.com" }]
    }
  ]
}
```

<Note>
  `orbitlab.json` routes **within a single service**. Host conditions differentiate between domains that are already attached to *this* service—they can't send traffic to a different service. To point a domain at a specific service, attach it to that service from the dashboard (see [Domains](/domains)). To reach an external origin, use a rewrite whose `destination` is a full URL.
</Note>

## Order of evaluation

Rules are applied at the edge in this order, then the request falls through to your app:

1. `cleanUrls`
2. `trailingSlash`
3. `redirects`
4. `headers`
5. `rewrites`
6. Your application

Redirects and proxy rewrites are terminal (they stop further processing). Header rules and internal rewrites continue down the chain.

## Managing rules from the dashboard

You can also manage routing from the dashboard/API. When both exist, **the dashboard configuration takes precedence over the file**: rule lists are combined with dashboard rules matched first, and the flags (`trailingSlash`, `cleanUrls`) use the dashboard value when set. Removing `orbitlab.json` from your repo clears the file-based rules on the next deploy, while dashboard rules stay in place.

## Limits

To keep edge routing fast, the following limits apply:

| Setting                               | Limit           |
| ------------------------------------- | --------------- |
| Redirects                             | 256             |
| Rewrites                              | 128             |
| Header rules                          | 128             |
| Headers per rule                      | 32              |
| `has` / `missing` conditions per rule | 16 each         |
| Pattern length                        | 4096 characters |

If `orbitlab.json` is invalid, the deploy continues and the previous routing rules are kept—check your build logs for a validation warning.
