Data Loaders
Data loaders let pages fetch and transform data before rendering. A file named
page.jqnext to apage.htmlruns a jqlite expression against the request context and makes the result available as.datain 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 header | Response |
|---|---|
text/html (default) | Renders page.html with .data from page.jq |
application/json | Returns 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
- File-Based Routing -- How files map to URLs
- Templates & JQT -- Template syntax and data context
- Routes -- Full API routes with
route.grove