Skip to main content

Rendering

Endpoints that turn a Template into output. Two of them stream a rendered document and one resolves typed render parameters against a phase context.

Rendering runs through a fixed pre-block pipeline: look up the Template, resolve its input parameters, run its processors to fetch data, build the render context, then hand off to the output handler that produces the final format. Supported output formats include pdf, html, docx, xlsx, and text; the handler defaults to the Template's own handler unless the request overrides it.

GET /api/templates/{id}/_render

Render a Template using query parameters. The {id} is the Template's natural id (ownerId:slug), resolved through the read_access scope.

Authentication: Required

Pre-blocks: template.ensureTemplates, template.lookup(params.id), template.retrievePayload, template.resolveParams, template.processInputs, template.context, template.handler(query.output)

Query Parameters:

ParameterTypeDescription
outputstringOutput format (pdf, html, docx, xlsx, text); overrides the Template's default handler
progressstringProgress identifier for tracking a long-running render
downloadbooleanWhen set, sends the response as an attachment
filenamestringCustom download filename (defaults to {slug}.{output})
payloadIdstringId of a payload stored by a prior render, retrieved by template.retrievePayload
paramsobjectInput parameter values

Unknown query parameters are allowed, so input-specific values can be passed directly alongside the parameters above.

Example Request:

GET /api/templates/admin:sales-order-report/_render?output=pdf&download=true&filename=report.pdf&params[shipState]=NY

Response:

Returns the rendered document with the Content-Type for the chosen output format. When download is set, a Content-Disposition: attachment header is added with filename (or the Template slug). The socket timeout is raised to 60 minutes to accommodate long-running renders.

Draft error page

When a draft Template's processors fail during a render, the response is an HTML error page listing the processor errors instead of an HTTP error. A published Template surfaces the failure as a normal error response.

The response body is a binary document (PDF/DOCX/XLSX) or a host-specific HTML/text string, so it is not captured as a docs fixture here. This endpoint is exercised by tests/api/templates/{id}/_render/route-spec.js.


POST /api/templates/{id}/_render

Render a Template using a request body for parameters and render options. Unlike the GET route, POST does not accept a payloadId (it has no template.retrievePayload pre-block).

Authentication: Required

Pre-blocks: template.ensureTemplates, template.lookup(params.id), template.resolveParams, template.processInputs, template.context, template.handler

Query Parameters:

ParameterTypeDescription
outputstringOutput format (can also be set in the body)
progressstringProgress identifier
downloadbooleanSend as attachment (can also be set in the body)
filenamestringCustom download filename (can also be set in the body)
paramsobjectInput parameter values (can also be set in the body)

Request Body:

FieldTypeDescription
outputstringOutput format override
progressstringProgress identifier
downloadbooleanSend as attachment
filenamestringCustom download filename
paramsobjectInput parameter values

Unknown fields are allowed, so input-specific values can be included alongside the fields above.

Example Request:

{
"output": "pdf",
"download": true,
"filename": "quarterly-report.pdf",
"params": {
"report_title": "Q1 Sales Report",
"start_date": "2024-01-01",
"end_date": "2024-03-31",
"region": "north"
}
}

Response:

Returns the rendered document with the appropriate Content-Type and, when download is set, a Content-Disposition header. The socket timeout is 60 minutes.

As with the GET route, the response is a binary document or a host-specific HTML/text string, so it is not captured as a docs fixture here.


POST /api/templates/{id}/_render-params

Resolve a map of typed input descriptors against a phase render context. This is used to preview resolved input values (for example, to populate the UI data grid) before a full render, and is the only JSON-returning endpoint on this page.

Authentication: Required

Pre-blocks: template.ensureTemplates, template.lookup(params.id), permission.template.write(pre.template)

Write access means hasFeature('templates') AND being a designer of the Template. The template.lookup pre-block resolves the {id} natural id through the read_access scope and answers 404 before the permission check or handler runs when the Template is not visible to the caller.

Request Body:

FieldTypeRequiredDescription
phaseContextobjectYesThe render context available at the current phase (for example { inputs: { ... }, $data: [ ... ] }). Required because the full context may not exist yet when this is called from a processor.
paramInputsobjectYesMap of output key to a typed input descriptor { entry, value }

Each paramInputs entry resolves according to its entry type:

entryResolution
literalPass value through unchanged
templateRead phaseContext.inputs[value]
expressionEvaluate value as a JavaScript expression in the render sandbox
nunjucksRender value as a Nunjucks template string
dataPass phaseContext.$data through
recordPluck the value field from each phaseContext.$data row

Example Request:

{
"phaseContext": {
"inputs": {
"shipState": "NY"
},
"$data": [
{ "shipCity": "New York", "shipCountry": "US" },
{ "shipCity": "New York", "shipCountry": "US" }
]
},
"paramInputs": {
"shipCity": { "entry": "record", "value": "shipCity" },
"shipState": { "entry": "template", "value": "shipState" },
"shipDate": { "entry": "literal", "value": "2026-01-15" }
}
}

Response:

Responds 200 with a plain object { result: { <key>: <value> } }, one resolved value per paramInputs key. The handler returns the object directly (no .created()), so the status is 200, not 201, and there is no Location header. A resolution failure is wrapped as 400.

Resolve render parametersPOST /api/templates/admin:sales-order-report/_render-params
POST /api/templates/{id}/_render-params resolves a map of typed input descriptors against a phase render context, returning { result: { <key>: <value> } }. Requires template write access (hasFeature templates + isDesigner). Payload is { phaseContext (required), paramInputs (required) }: each paramInputs entry is { entry, value } where entry is one of literal (pass value through), template (read phaseContext.inputs[value]), expression (evaluate JS), nunjucks (render a template string), data (pass phaseContext.$data through), or record (pluck a field from each phaseContext.$data row). The handler returns 200 with a plain object (not 201, no Location); resolution failures are 400. The .md’s payloadId/expiresAt response shape does not exist for this route.
Request body
{
"phaseContext": {
"inputs": {
"shipState": "NY"
},
"$data": [
{
"shipCity": "New York",
"shipCountry": "US"
},
{
"shipCity": "New York",
"shipCountry": "US"
}
]
},
"paramInputs": {
"shipCity": {
"entry": "record",
"value": "shipCity"
},
"shipState": {
"entry": "template",
"value": "shipState"
},
"shipDate": {
"entry": "literal",
"value": "2026-01-15"
}
}
}
Response · 200
{
"_links": {
"self": {
"href": "https://informer.example.com/api/templates/admin%3Asales-order-report/_render-params"
}
},
"result": {
"shipCity": "New York",
"shipState": "NY",
"shipDate": "2026-01-15"
}
}
Captured from the API examples test suite; ids and timestamps are normalized.
No stored-payload response

This route does not return a payloadId or expiresAt, and it does not write a stored-payload row. It only resolves descriptors and returns their values.

Not found:

A Template id that does not exist (or is not visible to the caller) answers 404 from the template.lookup pre-block, before the permission check or handler runs.

Resolve render parameters for an unknown templatePOST /api/templates/admin:does-not-exist/_render-params
template.lookup resolves the {id} natural id through the read_access scope; a template that does not exist (or is not visible to the caller) yields a 404 before the permission check or handler run.
Request body
{
"phaseContext": {},
"paramInputs": {
"region": {
"entry": "literal",
"value": "north"
}
}
}
Response · 404
{
"statusCode": 404,
"error": "Not Found",
"message": "Not Found"
}
Captured from the API examples test suite; ids and timestamps are normalized.