Data Loaders

Data loaders let pages fetch and transform data before rendering. A file named page.jq next to a page.html runs a jqlite expression against the request context and makes the result available as .data in the template.

Prerequisites: File-Based Routing, Templates & JQT. What you'll learn: The page.jq convention, jqlite input and output, and content negotiation.

The page.jq Convention

Place a file named page.jq alongside page.html in any directory. The data loader runs before the template renders:

products/
  [id]/
    page.html     # template
    page.jq       # data loader

When a request arrives for /products/42, Grove executes page.jq first, then passes its output to page.html as .data.

jqlite Input

The data loader receives an object with the request's path parameters and query string:

{
  "params": { "id": "42" },
  "query": { "search": "term" }
}

You reference these fields using standard jq syntax inside page.jq.

Writing a Data Loader

A page.jq file contains a single jqlite expression. It transforms the input into the shape your template needs:

{ "product_id": .params.id, "type": "product" }

The output object becomes .data in the template:

<h1>Product {​{ .data.product_id }​}</h1>
<p>Type: {​{ .data.type }​}</p>

Accessing Parameters

Path parameters from dynamic segments are available under .params:

{ "id": .params.id }

Query string parameters are available under .query:

{ "search_term": .query.search, "page": .query.page }

Content Negotiation

When a request includes Accept: application/json, Grove skips template rendering and returns the data loader's output as JSON directly. This lets the same URL serve both HTML pages and API responses:

Accept headerResponse
text/html (default)Renders page.html with .data from page.jq
application/jsonReturns page.jq output as JSON

This is useful for building pages that also serve as lightweight JSON endpoints without needing a separate route.grove file.

Pages Without Data Loaders

If no page.jq exists, the .data field in the template context is an empty object. Templates can still use .page, .params, and .query without a data loader.

Example: Product Detail

Directory structure:

products/[id]/
  page.html
  page.jq

page.jq:

{ "product_id": .params.id, "type": "product" }

page.html:

---
title: Product Detail
---
<h1>{​{ .page.title }​}</h1>
<p>Product ID: {​{ .data.product_id }​}</p>
<p>Type: {​{ .data.type }​}</p>

See Also