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/KqlREADME
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
