Templates
The Templates module is the central content engine in Docuget. Every email body,
PDF invoice, chart visualization, video script, and data export is a template —
stored in a single templates table, rendered by Nunjucks, and routed to the
appropriate output format by source_type.
For a high-level architecture overview, see Template System. For email-specific usage, see Email Templates.
Template Fields
| Field | Type | Default | Description |
|---|---|---|---|
id |
string | auto | TypeID (prefix: template) |
company_id |
UUID | required | Multi-tenant scope |
description |
string | — | Display name |
template_key |
string | null | Unique per company, for programmatic lookup (e.g. calendar_event_email) |
template_category |
string | null | Purpose grouping: email, invoice, echart, data_science, video, report, system |
source_type |
string | "markdown" |
Rendering engine — see Source Types |
dest_type |
string | "html" |
Default output format |
dest_filename |
string | "template.html" |
Suggested output filename |
content |
JSONB | null | Template content. Text-based: {"text":"...markup..."}. Structured (echart, json): raw JSON object. Supports {{ placeholder }} syntax via Nunjucks |
replacer |
string | "nunjucks" |
Templating engine (currently only Nunjucks) |
seed_json |
JSON | null | Default values merged with caller data |
source_json |
JSON | null | Schema definition for structured input data |
lang_code |
string | "en" |
Language code |
is_active |
boolean | true |
Soft-delete flag |
ai_template_id |
string | null | FK to ai_prompt_template for AI content generation |
readme |
string | null | Markdown documentation for the template — purpose, data binding, authorship, links, AI notes. Edited in the Readme tab; not used at render time |
webhook_on_create |
string | null | Webhook triggered after render |
webhook_on_update |
string | null | Webhook triggered on template update |
team_id |
string | null | Optional team scope |
Source Types
Each template's source_type determines how it is rendered and which output
formats are available.
| Source Type | Engine | Available Outputs | Use Case |
|---|---|---|---|
html |
Nunjucks | HTML, PDF | Emails, web content |
text |
Nunjucks | Text, CSV, HTML | Plain text, logs |
markdown |
Nunjucks + Marked | HTML, PDF, Markdown, Text | Documentation, rich content |
typst |
Nunjucks + Typst compiler | PDF, PNG, SVG | Invoices, certificates, reports |
json |
Nunjucks | JSON, HTML, CSV, SVG, Text | API responses, data export |
xml |
Nunjucks | XML, HTML, PDF, Text | CFDI (Mexican tax), integrations |
svg |
Nunjucks | SVG, PNG, PDF | Icons, simple graphics |
latex |
Nunjucks | Academic documents | |
echart |
Nunjucks + ECharts SSR | SVG, PNG, PDF, HTML | Charts and visualizations |
tabular |
Nunjucks + data converters | CSV, XLSX, JSON, HTML, Markdown | Tabular data views |
remotion |
Nunjucks + Remotion | MP4, WebM, GIF | Video rendering |
Template Editor
The template editor is a split-panel UI in the frontend:
- Left panel — filterable list of templates with icons by
dest_type, search, and create/delete - Right panel — tabbed editor for the selected template
Editor Tabs
| Tab | Description |
|---|---|
| Editor | CodeMirror content editor (Markdown mode) with source type and dest type selectors |
| Preview | For echart/echart-js templates: server-side render preview (SVG inline). Calls POST /v1/templates/:id/render |
| Seed JSON | JSON editor for default values (seed_json) — merged with caller data at render time |
| Source JSON | JSON editor for input schema (source_json) — defines the expected data structure |
| Webhook (Create) | Webhook picker — select a webhook to trigger after render |
| Webhook (Update) | Webhook picker — select a webhook to trigger on template update |
Form Fields
The editor toolbar exposes:
- Description — template display name
- Source Type — dropdown: html, text, markdown, typst, json, xml, svg, latex, echart, remotion
- Dest Type — context-aware dropdown (changes based on source type)
- Dest Filename — output filename
- Replacer — templating engine (default: nunjucks)
- Active — toggle on/off
AI Integration
Templates have built-in AI assist. The earlier split Generate / Enhance buttons were replaced by a single unified panel.
AI Sidecar
One toggle opens the AI Sidecar — a CodeMirror request box on the right of
the editor. You describe what you want in plain language; if the open record is
empty the assistant creates, if it has content it edits — no separate
mode. It sends the record's source_type, dest_type and current content as
context (so you don't re-enter them) and streams the result back live (SSE,
so long generations don't hit the edge timeout). Apply / Undo / Save-now gate
the result. Uses the template_assistant prompt. Create a blank record with
New.
Draft README with AI
The Readme tab has a ✨ Draft with AI button that documents the template
from its own record (description, source_type, body/layers, form_fields,
seed_json) via the readme_writer prompt. It streams markdown into the field
(with a pre-draft Undo), writes in the operator's current app language, and
preserves human-written notes (authorship/links) when refining an existing
readme.
AI Prompt Templates
The AI system behind the Sidecar and Draft-README is configurable. Admins can
manage prompt templates at /ai/prompt-templates (Intelligence Labs > AI >
Prompt Templates).
Each AI prompt template has:
| Field | Description |
|---|---|
key |
Unique identifier (e.g. template_assistant, readme_writer, echart_generator) |
system_prompt |
System role instructions for Claude |
user_prompt_template |
Nunjucks template for the user message — receives user inputs as variables |
model |
Claude model to use |
max_tokens |
Token limit |
temperature |
Creativity level (0-1) |
input_fields |
Dynamic form fields shown to the user (text, textarea, select, number, toggle, json) |
output_format |
Expected output: text, typst, json, markdown, html |
The AI Prompt Template Manager has 5 tabs: General, System Prompt, User Prompt, Input Fields, and Test (with embedded live testing).
Seeded AI Templates
Platform-global AI prompt templates (company_id NULL — available to every tenant) provided out of the box:
| Key | Purpose |
|---|---|
template_assistant |
Create or edit a template from one free-form request (powers the AI Sidecar) |
readme_writer |
Draft a template's README from its own record, in the operator's language |
flow_generator |
Generate a runnable workflow (FlowPack) from a description |
echart_generator |
Generate ECharts option JSON from chart type, description, and data sample |
The legacy
template_generator/template_enhancerprompts are superseded bytemplate_assistant.
Nunjucks Filters
All templates use the same Nunjucks environment with these filters available.
Date & Time
| Filter | Input | Output | Example |
|---|---|---|---|
localdate |
Date | Localized string (es-MX) | {{ start | localdate }} → "21 de marzo de 2026" |
isodate |
Date | ISO 8601 string | {{ start | isodate }} → "2026-03-21T14:30:00-06:00" |
dddd |
Date | YYYY-MM-DD | {{ date | dddd }} → "2026-03-21" |
hhmm |
Date | HH:MM:SS | {{ time | hhmm }} → "14:30:00" |
todate |
"mm/dd/yyyy" | ISO Date | {{ date | todate }} |
ddmmyyyy_to_iso |
"dd/mm/yyyy" | ISO Date | {{ date | ddmmyyyy_to_iso }} |
timestamp |
prefix string | prefix + current date | {{ "Generated: " | timestamp }} |
Numbers & Currency
| Filter | Input | Output | Example |
|---|---|---|---|
money2number |
"$22,345.00" | "22345.00" | {{ total | money2number }} |
iva |
"1000" | "160.00" | {{ subtotal | iva }} (16% Mexican tax) |
number |
any | passthrough | Identity filter for numeric context |
Strings & Encoding
| Filter | Input | Output | Example |
|---|---|---|---|
encodebase64 |
string | Base64 string | {{ data | encodebase64 }} |
emailname |
"user@domain.com" | "user" | {{ email | emailname }} |
contains |
string | boolean | {{ text | contains("search") }} (case-insensitive) |
toJSON |
any | JSON string | {{ value | toJSON }} |
object |
object | JSON string | {{ config | object }} |
Images & Media
| Filter | Input | Output | Example |
|---|---|---|---|
qrcode |
URL string | SVG markup | {{ url | qrcode }} |
img_url_b64 |
image URL | Base64 string | {{ png_url | img_url_b64 }} |
typst_png_image |
image URL | Typst #image(...) code |
{{ photo_url | typst_png_image }} |
Maps & Links
| Filter | Input | Output | Example |
|---|---|---|---|
osm_map_link |
{latitude, longitude} |
OpenStreetMap URL | {{ location | osm_map_link }} |
google_map_link |
{latitude, longitude} |
Google Maps URL | {{ location | google_map_link }} |
link_new_window |
"url|text" | HTML <a> tag |
{{ "https://example.com|Click" | link_new_window }} |
Short References
| Filter | Input | Output | Example |
|---|---|---|---|
ref_id |
@code + company_id | Full TypeID | {{ "@k7f2n" | ref_id(company_id) }} |
Composable Rendering
These filters call the render engine recursively (max depth: 5).
| Filter | Input | Output | Example |
|---|---|---|---|
render |
template ID, @shortref, or key | Rendered content | {{ "invoice_header" | render("html") }} |
render_key |
template key + company_id | Rendered content | {{ "monthly_chart" | render_key("svg", company_id) }} |
render_chart |
template key or @code | Chart SVG/PNG/HTML | {{ "sales-chart" | render_chart("svg", 800, 400) }} |
For binary outputs (PNG, PDF), these filters return a base64 data URI.
Data Format Conversion
| Filter | Input | Output | Example |
|---|---|---|---|
to_yaml |
object | YAML string | {{ config | to_yaml }} |
to_csv |
array of objects | CSV string | {{ rows | to_csv(["name", "score"]) }} |
to_toml |
object | TOML string | {{ settings | to_toml }} |
to_json |
any | JSON string | {{ data | to_json(2) }} |
to_md_table |
array of objects | Markdown table | {{ rows | to_md_table }} |
Rendering Pipeline
When a template is rendered, the system follows this pipeline:
1. Lookup → Fetch template by ID or (company_id, template_key)
2. Merge → seed_json defaults + caller data (caller wins)
3. Resolve → @shortref codes → full IDs
4. Resolve → $fn: directives → live query results
5. Render → Nunjucks processes {{ placeholders }}
6. Route → source_type dispatcher (HTML passthrough, Marked, Typst, ECharts SSR, etc.)
7. Convert → Optional format conversion (e.g. SVG → PNG, JSON → XLSX)
8. Cache → Optional output caching (cache_ttl)
9. Deliver → Return inline content, file download, or metadata
Steps 3-4 only apply when rendering through the universal
Render Engine. Direct template rendering
(POST /v1/templates/:id/render) skips to step 5.
Seed JSON Merging
The seed_json field provides default values. When rendering, these are
deep-merged with caller-provided data, with caller values winning:
// seed_json (defaults)
{ "title": "Untitled", "color": "#000", "show_footer": true }
// caller data
{ "title": "Q1 Report", "items": [...] }
// merged result (passed to Nunjucks)
{ "title": "Q1 Report", "color": "#000", "show_footer": true, "items": [...] }
Special seed_json keys:
_text_template— plain-text email fallback (rendered separately)width,height— ECharts chart dimensionswidget_config— ChartWidget display settings (auto-refresh, format)style— XLSX styling options (headerStyle, alternateRowColor, borders)
API Reference
All endpoints require authentication (Bearer token).
Template CRUD
GET /v1/templates List templates
GET /v1/templates/:id Get by ID
GET /v1/templates/by-key/:key Get by key (requires ?company_id=)
POST /v1/templates Create template
PUT /v1/templates/:id Update template
DELETE /v1/templates/:id Delete template
List filters:
| Parameter | Type | Description |
|---|---|---|
company_id |
string | Filter by company |
template_category |
string | Filter by category |
source_type |
string | Filter by source type |
dest_type |
string | Filter by destination type |
is_active |
boolean | Filter active/inactive |
search |
string | Search description and key |
page |
number | Page number |
pageSize |
number | Results per page |
Render
POST /v1/templates/:id/render Render with data (direct)
POST /v1/templates/:id/preview-data Preview tabular data (resolve $fn:)
curl examples:
# Create a Typst invoice template
curl -X POST https://api.docuget.com/v1/templates \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"company_id": "550e8400-...",
"description": "Standard Invoice",
"template_key": "invoice_standard",
"template_category": "invoice",
"source_type": "typst",
"dest_type": "pdf",
"dest_filename": "invoice.pdf",
"content": "#set page(paper: \"letter\")\n= Invoice {{ invoice_number }}\n\nDate: {{ date }}\nTotal: ${{ total }}",
"seed_json": {
"invoice_number": "",
"date": "",
"total": "0.00"
}
}'
# Render it
curl -X POST https://api.docuget.com/v1/templates/template_01hjd3.../render \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"invoice_number": "INV-2026-001",
"date": "2026-03-21",
"total": "1,500.00"
}'
# List email templates
curl "https://api.docuget.com/v1/templates?company_id=550e8400-...&template_category=email" \
-H "Authorization: Bearer $TOKEN"
# Lookup by key
curl "https://api.docuget.com/v1/templates/by-key/calendar_event_email?company_id=550e8400-..." \
-H "Authorization: Bearer $TOKEN"
AI Prompt Templates
GET /v1/ai/templates List AI prompt templates
GET /v1/ai/templates/:id Get by ID
POST /v1/ai/templates Create
PUT /v1/ai/templates/:id Update
DELETE /v1/ai/templates/:id Delete
POST /v1/ai/completions Execute a prompt (returns AI response)
GET /v1/ai/logs Completion logs
Webhooks
Templates can optionally trigger webhooks on two events:
| Field | Trigger |
|---|---|
webhook_on_create |
After a template is rendered (e.g. POST the rendered output to an external service) |
webhook_on_update |
When the template itself is modified (e.g. notify a CMS of content changes) |
Webhooks are configured via the Webhook tabs in the template editor. See Webhooks for full webhook documentation.
Note: Automatic webhook execution after render is not yet wired — webhooks are currently stored as references for manual triggering.
Caching
Templates use a multi-layer caching strategy:
| Layer | Scope | TTL | Cleared on |
|---|---|---|---|
| Template list cache | In-memory (BoundedCache, 500 entries) | 1 hour | Create, update, delete |
| Template by-ID cache | In-memory | 1 hour | Update, delete |
| Render output cache | render_job table |
Configurable (cache_ttl) |
Cache expiry or manual invalidation |
| ECharts chart cache | chart_cache table (optional) |
Configurable | Template update |
Examples
HTML Email Template
<div style="font-family: sans-serif; max-width: 600px;">
<h2>{{ title }}</h2>
<p>{{ date | localdate }}</p>
{% if location %}<p>Location: {{ location }}</p>{% endif %}
{% if description %}<p>{{ description }}</p>{% endif %}
<img src="{{ event_url | qrcode }}" width="150" />
</div>
Typst Invoice
#set page(paper: "letter", margin: 2cm)
#set text(font: "Inter", size: 10pt)
= Invoice {{ invoice_number }}
*Date:* {{ date | dddd }}
*Due:* {{ due_date | dddd }}
#table(
columns: (1fr, auto, auto, auto),
[*Item*], [*Qty*], [*Price*], [*Total*],
{% for item in line_items %}
[{{ item.name }}], [{{ item.qty }}], [${{ item.price }}], [${{ item.total }}],
{% endfor %}
)
#align(right)[*Total: ${{ total }}*]
ECharts Visualization
{
"xAxis": { "type": "category" },
"yAxis": { "type": "value" },
"series": [{
"type": "bar",
"data": "$fn:group_count(invoices, status, 10)",
"itemStyle": { "color": "#4472C4" }
}],
"tooltip": { "trigger": "axis" }
}
Composable Template (HTML embedding a chart and table)
<h1>{{ title }}</h1>
<div class="chart">
{{ "sales-chart" | render_chart("svg", 800, 400) }}
</div>
<h2>Details</h2>
{{ rows | to_md_table(["product", "revenue", "units"]) }}
<footer>Generated {{ "" | timestamp }}</footer>