Skip to content

Advanced Datasource Options

Ekit allows advanced configuration of datasources directly in the YAML frontmatter. This enables dynamic sorting, filtering, and pagination without writing backend code.


Example

---
layout: default

datasources:
  acteurs:
    from: "acteurs"
    sort:
      field: "{{ternary (eq query.type 'acteurs') 'nom' 'titre'}}"
      dir: "{{ternary (eq query.s 'desc') 'desc' 'asc'}}"
    filter:
      field: "{{ternary (eq query.type 'acteurs') 'nom' 'titre'}}"
      op: "contains"
      value: "{{query.q}}"
    limit: 9
---

sort

Defines how results are ordered.

sort:
  field: "nom"
  dir: "asc"

Parameters

  • field: field used for sorting
  • dir: asc or desc

Dynamic example

sort:
  field: "{{ternary (eq query.type 'acteurs') 'nom' 'titre'}}"
  dir: "{{ternary (eq query.s 'desc') 'desc' 'asc'}}"

filter

Filters results before rendering.

filter:
  field: "nom"
  op: "contains"
  value: "{{query.q}}"

Parameters

  • field: field to filter
  • op: operator — only contains is supported (it is also the default)
  • value: value searched in the field (case-insensitive text match). An empty value disables the filter.

Example

filter:
  field: "{{ternary (eq query.type 'acteurs') 'nom' 'titre'}}"
  op: "contains"
  value: "{{query.q}}"

Operators

The rendering engine currently applies contains only. Other operators (eq, gt, in…) are ignored, and the editor flags them.


Pagination

List datasources are paginated with a cursor, read from the URL: ?cursor=… for the next page, ?cursor=…&back=true for the previous one. The engine provides, for each list datasource:

  • cursors.<alias> — cursor of the next page
  • prevCursors.<alias> — cursor of the previous page
  • hasMore.<alias> — true when a next page exists
  • hasPrev.<alias> — true when a previous page exists (false on the first page, also when you come back to it with Previous)
---
datasources:
  articles:
    from: articles
    limit: 10
    sort:
      field: date
      dir: desc
---
<ul>
  {{#each ekdata.articles}}<li>{{this.title}}</li>{{/each}}
</ul>

{{#if hasPrev.articles}}
  <a href="/{{lang}}/articles/?cursor={{jsonUrl prevCursors.articles}}&back=true">Previous</a>
{{/if}}
{{#if hasMore.articles}}
  <a href="/{{lang}}/articles/?cursor={{jsonUrl cursors.articles}}">Next</a>
{{/if}}

Write the path of the page before ?cursor= (/{{lang}}/articles/, or /{{lang}}/ on the home page): with the <base> tag of the layout, a link starting with ?cursor= alone would open another page.

  • limit sets the page size (25 without limit); the order follows sort (entries with the same value keep a stable order, none is skipped or repeated between pages).
  • Use hasPrev rather than query.cursor to show Previous: after going back to the first page, the address still contains a cursor.
  • The cursor parameter of the URL applies to every list datasource of the page: paginate one list per page.
  • The cursor and page keys of a datasource are not used by the engine — pagination only relies on the URL.

Number of results and pages (count)

Add count: true to a list datasource to show how many entries it has and how many pages:

  • total.<alias> — number of entries, filter included (all pages together)
  • pages.<alias> — number of pages, according to limit
  • page.<alias> — number of the page displayed (1 on the first page); available even without count
---
datasources:
  articles:
    from: articles
    limit: 10
    count: true
    filter:
      field: title
      op: contains
      value: '{{query.q}}'
---
{{#if total.articles}}<p>{{total.articles}} articles</p>{{/if}}

{{#if hasPrev.articles}}
  <a href="/{{lang}}/articles/?cursor={{jsonUrl prevCursors.articles}}&back=true">Previous</a>
{{/if}}
{{#if (gt pages.articles 1)}}<span>Page {{page.articles}} / {{pages.articles}}</span>{{/if}}
{{#if hasMore.articles}}
  <a href="/{{lang}}/articles/?cursor={{jsonUrl cursors.articles}}">Next</a>
{{/if}}
  • The page number travels with the cursor: keep the Previous / Next links as above, there is nothing to compute in the template.
  • Enable count only on pages that show these numbers: counting costs a little more than reading a page.
  • On a very large table with a filter, counting may take too long: total and pages are then empty (null). Always show them inside {{#if}}.
  • An old link (shared before count was added) still works; only page.<alias> is empty on it.

limit

Limits number of returned records.

limit: 9

Often used with cursor. Without limit, a page has 25 entries. The maximum is 200; higher values are capped.


expose — use the data in JavaScript

By default, a datasource is only available to the template. Add expose: true to make its resolved value available to the page's JavaScript as ekit.data.<alias>:

datasources:
  products:
    from: products
    limit: 20
    expose: true
const products = ekit.data.products ?? [];
products.forEach(p => console.log(p.title, p.price));
  • The data is resolved during server rendering: no extra request.
  • Only exposed datasources are sent to the browser. Every field of the returned entries is included, so do not expose tables containing data you do not want to publish.
  • A single entry (uid / slug) is an object or null; a Method datasource is the response body or null.
  • Exposed data is limited to 1 MB per page.

when

Loads the datasource only when the value is not empty, false, 0 or null. Otherwise ekdata.<alias> is empty ([], or null for a single record or a Method).

datasources:
  results:
    from: articles
    when: '{{query.q}}'        # only when a search is submitted
    filter:
      field: title
      value: '{{query.q}}'

Datasources declared in partials

A partial can declare its own datasources in its front matter. On a page, Ekit runs:

  • the datasources of the view;
  • the datasources of every partial the page uses: included by the view or by its layout, including partials included by other partials ({{> header}} → {{> menu}}).

The datasources of partials that the page does not include are not run: no query, no Method call. There are three exceptions, so that nothing breaks:

Partial not included, but… Its datasource runs
the page uses the alias (ekdata.<alias>, lookup ekdata "<alias>", hasMore.<alias>…) Yes
the datasource has expose: true (read by your JavaScript) Yes
the page uses record and loads no entry itself Its single-entry datasources (uid / slug) run

When Ekit cannot know in advance what a page uses, it runs all the datasources of all partials. This happens with a partial whose name is computed ({{> (name)}}), with the whole ekdata object ({{#each ekdata}}, {{json ekdata}}, lookup ekdata variable), or with the whole page context passed to a helper ({{json this}} outside a loop).

Same alias in several files

On a page, each alias runs one datasource. When several files used by the same page declare the same alias:

  1. a partial's datasource replaces the view's;
  2. between partials, the one whose file name comes last wins (character order of the full file name: blog-item.hbs comes before blog.hbs, uppercase before lowercase).

The editor flags these collisions: give each datasource its own alias. Two views can use the same alias without any problem, since they are never on the same page.


Request-driven behavior

All datasource options support dynamic expressions.

Example:

?type=acteurs&q=marie&s=desc

Will: - filter records where nom contains "marie" - sort descending - adapt field dynamically


Why use advanced datasources

  • No backend code needed
  • Fully dynamic pages
  • Works with query parameters
  • Supports large datasets efficiently

  • Template syntax
  • Query parameters
  • Datasources basics