---
title: "KQL (Kirby Query Language)"
description: "Extends the official KQL plugin – swaps basic auth for bearer tokens, caches queries, and runs them in the language the request names."
canonical_url: "https://kirby.tools/docs/headless/usage/kql"
---

# KQL (Kirby Query Language)

> Extends the official KQL plugin – swaps basic auth for bearer tokens, caches queries, and runs them in the language the request names.

Kirby Headless provides an enhanced KQL endpoint at `/api/kql` with bearer token authentication, automatic caching, and multi-language support. This extends the [official KQL plugin](https://github.com/getkirby/kql) with features commonly needed in headless setups.

## Configuration

Enable bearer token authentication in your `config.php`:

```php [config.php]
return [
    'headless' => [
        'token' => 'your-secret-token'
    ],
    'kql' => [
        'auth' => 'bearer'
    ]
];
```

<note>

See the [Authentication](/docs/headless/configuration/authentication) page for detailed setup instructions.

</note>

## Making Requests

Send KQL queries to `/api/kql` with the bearer token in the `Authorization` header:

```ts
const response = await fetch("https://example.com/api/kql", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    Authorization: `Bearer ${process.env.KIRBY_API_TOKEN}`,
  },
  body: JSON.stringify({
    query: "site",
    select: {
      title: true,
      children: {
        query: "site.children",
        select: ["title", "url"],
      },
    },
  }),
});

const data = await response.json();
```

## Multi-Language Support

For multi-language sites, set the language using the `X-Language` header or a `?language=` query parameter. Kirby's API reads the query parameter first, so it wins where a request carries both:

```ts
const response = await fetch("https://example.com/api/kql", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.KIRBY_API_TOKEN}`,
    "X-Language": "de",
  },
  body: JSON.stringify({
    query: "page('home')",
    select: {
      title: true,
    },
  }),
});
```

## Cache Control

Query responses go through Kirby's pages cache, which is inactive until you enable it – without `cache.pages`, Kirby hands out a dummy cache and every query is answered from scratch:

```php [config.php]
return [
    'cache' => [
        'pages' => [
            'active' => true
        ]
    ]
];
```

Once the cache is on, disable it per request with the `X-Cacheable` header:

```ts
const response = await fetch("https://example.com/api/kql", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.KIRBY_API_TOKEN}`,
    "X-Cacheable": "false",
  },
  body: JSON.stringify({
    query: "site",
    select: { title: true },
  }),
});
```

The cache key hashes the query and carries the language the request resolved to, so the same query answers each language from its own entry. Pick one way to name the language and keep to it: `?language=de` is part of the hashed query, `X-Language: de` is not, so a frontend sending both conventions caches the same answer twice.

<note>

Cached responses live in Kirby's pages cache, which Kirby flushes after every content change made through the Panel or the API – only edits written straight to disk leave a stale entry behind.

</note>

---

Every page of this site as Markdown: <https://kirby.tools/sitemap.md>
