SkillAgentSearch skills...

Kql

Kirby's Query Language API combines the flexibility of Kirby's data structures, the power of GraphQL and the simplicity of REST.

Install / Use

/learn @getkirby/Kql
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

Kirby QL

Kirby's Query Language API combines the flexibility of Kirby's data structures, the power of GraphQL and the simplicity of REST.

The Kirby QL API takes POST requests with standard JSON objects and returns highly customized results that fit your application.

Playground

You can play in our KQL sandbox. The sandbox is based on the Kirby starterkit.

ℹ️ Source code of the playground is available on GitHub.

Example

Given a POST request to: /api/query

{
  "query": "page('photography').children",
  "select": {
    "url": true,
    "title": true,
    "text": "page.text.markdown",
    "images": {
      "query": "page.images",
      "select": {
        "url": true
      }
    }
  },
  "pagination": {
    "limit": 10
  }
}
<details open> <summary>🆗 Response</summary>
{
  "code": 200,
  "result": {
    "data": [
      {
        "url": "https://example.com/photography/trees",
        "title": "Trees",
        "text": "Lorem <strong>ipsum</strong> …",
        "images": [
          {
            "url": "https://example.com/media/pages/photography/trees/1353177920-1579007734/cheesy-autumn.jpg"
          },
          {
            "url": "https://example.com/media/pages/photography/trees/1940579124-1579007734/last-tree-standing.jpg"
          },
          {
            "url": "https://example.com/media/pages/photography/trees/3506294441-1579007734/monster-trees-in-the-fog.jpg"
          }
        ]
      },
      {
        "url": "https://example.com/photography/sky",
        "title": "Sky",
        "text": "<h1>Dolor sit amet</h1> …",
        "images": [
          {
            "url": "https://example.com/media/pages/photography/sky/183363500-1579007734/blood-moon.jpg"
          },
          {
            "url": "https://example.com/media/pages/photography/sky/3904851178-1579007734/coconut-milkyway.jpg"
          }
        ]
      }
    ],
    "pagination": {
      "page": 1,
      "pages": 1,
      "offset": 0,
      "limit": 10,
      "total": 2
    }
  },
  "status": "ok"
}
</details>

Installation

Manual

Download and copy this repository to /site/plugins/kql of your Kirby installation.

Composer

composer require getkirby/kql

Documentation

API Endpoint

KQL adds a new query API endpoint to your Kirby API (i.e. yoursite.com/api/query). This endpoint requires authentication.

You can switch off authentication in your config at your own risk:

return [
  'kql' => [
    'auth' => false
  ]
];

Sending POST Requests

You can use any HTTP request library in your language of choice to make regular POST requests to your /api/query endpoint. In this example, we are using the fetch API and JavaScript to retrieve data from our Kirby installation.

const api = "https://yoursite.com/api/query";
const username = "apiuser";
const password = "strong-secret-api-password";

const headers = {
  Authorization: "Basic " + Buffer.from(`${username}:${password}`).toString("base64"),
  "Content-Type": "application/json",
  Accept: "application/json",
};

const response = await fetch(api, {
  method: "post",
  body: JSON.stringify({
    query: "page('notes').children",
    select: {
      title: true,
      text: "page.text.kirbytext",
      slug: true,
      date: "page.date.toDate('d.m.Y')",
    },
  }),
  headers,
});

console.log(await response.json());

query

With the query, you can fetch data from anywhere in your Kirby site. You can query fields, pages, files, users, languages, roles and more.

Queries Without Selects

When you don't pass the select option, Kirby will try to come up with the most useful result set for you. This is great for simple queries.

Fetching the Site Title
const response = await fetch(api, {
  method: "post",
  body: JSON.stringify({
    query: "site.title",
  }),
  headers,
});

console.log(await response.json());
<details> <summary>🆗 Response</summary>
{
  code: 200,
  result: "Kirby Starterkit",
  status: "ok"
}
</details>
Fetching a List of Page IDs
const response = await fetch(api, {
  method: "post",
  body: JSON.stringify({
    query: "site.children",
  }),
  headers,
});

console.log(await response.json());
<details> <summary>🆗 Response</summary>
{
  code: 200,
  result: [
    "photography",
    "notes",
    "about",
    "error",
    "home"
  ],
  status: "ok"
}
</details>

Running Field Methods

Queries can even execute field methods.

const response = await fetch(api, {
  method: "post",
  body: JSON.stringify({
    query: "site.title.upper",
  }),
  headers,
});

console.log(await response.json());
<details> <summary>🆗 Response</summary>
{
  code: 200,
  result: "KIRBY STARTERKIT",
  status: "ok"
}
</details>

select

KQL becomes really powerful by its flexible way to control the result set with the select option.

Select Single Properties and Fields

To include a property or field in your results, list them as an array. Check out our reference for available properties for pages, users, files, etc.

const response = await fetch(api, {
  method: "post",
  body: JSON.stringify({
    query: "site.children",
    select: ["title", "url"],
  }),
  headers,
});

console.log(await response.json());
<details> <summary>🆗 Response</summary>
{
  code: 200,
  result: {
    data: [
      {
        title: "Photography",
        url: "/photography"
      },
      {
        title: "Notes",
        url: "/notes"
      },
      {
        title: "About us",
        url: "/about"
      },
      {
        title: "Error",
        url: "/error"
      },
      {
        title: "Home",
        url: "/"
      }
    ],
    pagination: {
      page: 1,
      pages: 1,
      offset: 0,
      limit: 100,
      total: 5
    }
  },
  status: "ok"
}
</details>

You can also use the object notation and pass true for each key/property you want to include.

const response = await fetch(api, {
  method: "post",
  body: JSON.stringify({
    query: "site.children",
    select: {
      title: true,
      url: true,
    },
  }),
  headers,
});

console.log(await response.json());
<details> <summary>🆗 Response</summary>
{
  code: 200,
  result: {
    data: [
      {
        title: "Photography",
        url: "/photography"
      },
      {
        title: "Notes",
        url: "/notes"
      },
      {
        title: "About us",
        url: "/about"
      },
      {
        title: "Error",
        url: "/error"
      },
      {
        title: "Home",
        url: "/"
      }
    ],
    pagination: { ... }
  },
  status: "ok"
}
</details>

Using Queries for Properties and Fields

Instead of passing true, you can also pass a string query to specify what you want to return for each key in your select object.

const response = await fetch(api, {
  method: "post",
  body: JSON.stringify({
    query: "site.children",
    select: {
      title: "page.title",
    },
  }),
  headers,
});

console.log(await response.json());
<details> <summary>🆗 Response</summary>
{
  code: 200,
  result: {
    data: [
      {
        title: "Photography",
      },
      {
        title: "Notes",
      },
      ...
    ],
    pagination: { ... }
  },
  status: "ok"
}
</details>

Executing Field Methods

const response = await fetch(api, {
  method: "post",
  body: JSON.stringify({
    query: "site.children",
    select: {
      title: "page.title.upper",
    },
  }),
  headers,
});

console.log(await response.json());
<details> <summary>🆗 Response</summary>
{
  code: 200,
  result: {
    data: [
      {
        title: "PHOTOGRAPHY",
      },
      {
        title: "NOTES",
      },
      ...
    ],
    pagination: { ... }
  },
  status: "ok"
}
</details>

Creating Aliases

String queries are a perfect way to create aliases or return variations of the same field or property multiple times.

const response = await fetch(api, {
  method: "post",
  body: JSON.stringify({
    query: "page('notes').children",
    select: {
      title: "page.title",
      upperCaseTitle: "page.title.upper",
      lowerCaseTitle: "page.title.lower",
      guid: "page.id",
      date: "page.date.toDate('d.m.Y')",
      timestamp: "page.date.toTimestamp",
    },
  }),
  headers,
});

console.log(await response.json());
<details> <summary>🆗 Response</summary>
{
  code: 200,
  result: {
    data: [
      {
        title: "Explore the universe",
        upperCaseTitle: "EXPLORE THE UNIVERSE",
        lowerCaseTitle: "explore the universe",
        guid: "notes/explore-the-universe",
        date: "21.04.2018",
        timestamp: 1524316200
      },
      { ... },
      { ... },
      ...
    ],
    pagination: { ... }
  },
  status: "ok"
}
</details>

Subqueries

With such string queries you can of course also include nested data

const response = await fetch(api, {
  method: "post",
  body: JSON.stringify({
    query: "page('photography').children",
    select: {
      title: "page.title",
      images: "page.images",
    },
  }),
  headers,
});

console.log(await response.json());
<details> <summary>🆗 Response</summary>
{
  code: 200,
  result: {
    data: [
      {
        title: "Trees",
        images: [
          "photography/trees/cheesy-autumn.jpg",
          "photography/trees/last-tree-standing.jpg",
          "photography/trees/monster-trees-in-the-fog.jpg",
          "photography/trees/sharewood-forest.jpg",
          "photography/trees/stay-in-the-car.jpg"
        ]
      },
      { ... },
      { ... },
      ...
    ],
    pa
View on GitHub
GitHub Stars150
CategoryDevelopment
Updated21d ago
Forks5

Languages

PHP

Security Score

100/100

Audited on Mar 20, 2026

No findings