---
audience: developers
summary: Register a .docx with placeholders, fill it with structured data.
title: Templates
path: concepts/templates
status: published
---


# Templates

A template is a pre-formatted `.docx` containing `{{ variable }}` markers that get replaced at instantiation time with structured data. Use templates when you want a designer-controlled visual artefact (letterhead, contract, branded report) that produces one document per request without the AI/agent needing to know about the visual design at all.

This is different from [themes](/docs/scaiscribe/concepts/themes): a theme controls styling that applies on top of any spec; a template is a specific document skeleton you fill in.

## End-to-end flow

```
[Designer] creates letterhead.docx with {{ recipient }}, {{ date }}, etc.
            │
            ▼
[Operator] POST /v1/templates    ← register once
            │
            ▼
[App / Agent] POST /v1/documents/from_template
              { template_id, variables: {...} }    ← fill each time
            │
            ▼
[ScaiScribe] returns a new doc_id you can finalize, edit, preview
```

The template lives in object storage; the database keeps a row with the placeholder catalogue and metadata. Each instantiation produces a brand-new document (with its own `doc_id`) — templates are immutable; the instantiation is the variable thing.

## Register a template

```python
with open("letterhead.docx", "rb") as f:
    template = scribe.upload_template(
        file=f.read(),
        name="letterhead",
        format="docx",
    )
print(template.template_id)       # tpl_01HXYZ...
print(template.placeholders)      # ['recipient', 'date', 'subject']
```

The server parses the binary, extracts every `{{ name }}` marker, and returns the list. If your template references `{{ recipient.address.street }}`, the placeholder list reports `recipient.address.street` — nested paths are first-class.

## Instantiate

```python
doc = scribe.from_template(
    template_id="tpl_01HXYZ...",
    variables={
        "recipient": "Marketing department",
        "date": "2026-06-14",
        "subject": "Q4 review highlights",
    },
)
print(doc.doc_id)                  # doc_01HXYZ...
```

The response is a normal `DocumentEnvelope` — same shape `create_document` returns. From here you can finalize, preview, or patch the spec like any other doc.

## Templating engine

Under the hood, ScaiScribe uses [`docx-templates`](https://github.com/guigrpa/docx-templates) with `cmdDelimiter: ["{{", "}}"]`. **Every** command lives inside `{{ ... }}` — there is no `{% ... %}` block syntax. The grammar at a glance:

| Marker | Effect |
|---|---|
| `{{ name }}` | Substitute the value. |
| `{{ user.email }}` | Nested path access. |
| `{{ INS expression }}` or `{{ = expression }}` | Insert (explicit / shorthand). |
| `{{ IF active }}…{{ END-IF }}` | Conditional block. |
| `{{ FOR row IN rows }}…{{ $row.cell }}…{{ END-FOR row }}` | Loop block. Note the `$` prefix on the loop variable inside the body. |
| `{{ ALIAS x INS something }} … {{ *x }}` | Reusable inline alias. |
| `{{ LINK ({url, label}) }}` | Hyperlink. |

The expression inside any command is JavaScript — filtering, arithmetic, formatting via `toFixed`, ternaries all work inline.

For the complete grammar (every command, every quirk, the placeholder extraction story, the limitations vs. upstream docx-templates) see the **[Templates reference](/docs/scaiscribe/reference/templates)**.

## Format coverage

| Format | Substitution | Expressions | FOR / IF | Slide / row replication | Image insertion |
|---|---|---|---|---|---|
| **DOCX** | ✓ (docx-templates) | ✓ full JS | ✓ | ✓ table-row replication | ✓ via `IMAGE` (inlined image objects) |
| **PPTX** | ✓ | ✓ safe subset | ✓ text-frame level | ✓ slide-level | ✗ — roadmap |
| **XLSX** | ✓ | ✓ safe subset | ✓ row level | ✓ row replication with formula refs | n/a |

DOCX goes through the [`docx-templates`](https://github.com/guigrpa/docx-templates) library and gets the full grammar (loops, conditionals, aliases, JS expression evaluation in a VM sandbox).

PPTX and XLSX use a hand-rolled engine in `workers/node/lib/ooxml-template-fill.js` (Tier 2):

- **Expressions** — safe subset of JS via a recursive-descent parser. No `eval`, no `new`, no arrow functions, no arbitrary globals. Supports path access, method chains (with a per-type allowlist: strings get `toUpperCase`/`toLowerCase`/`slice`/`replace`/…, numbers get `toFixed`/`toLocaleString`/…, arrays get `length`/`join`/`slice`/…), arithmetic, comparisons, logical ops, and ternaries.
- **`{{ FOR x IN list }} … {{ END-FOR x }}`** — replicate paragraphs (PPTX) or rows (XLSX) per item. Inside the body, `$x` is the current item and `$idx` the 0-based index. PPTX has TWO FOR scopes: text-frame level (within one shape) and slide level (a whole slide cloned per item).
- **`{{ IF expr }} … {{ END-IF }}`** — gate content on a truthy expression.
- **Missing variables** render as empty strings (not errors) + emit `TEMPLATE_VARIABLE_MISSING` warning.
- **Run-span coalescing** preserves a token across multiple `<a:r>` / `<w:r>` runs when PowerPoint or Excel splits it during authoring.

What's NOT supported in PPTX/XLSX expressions (DOCX still has these via docx-templates):
- Arrow functions / lambdas (`items.filter(x => x.urgent)`)
- The `IMAGE`, `LINK`, `HTML`, `EXEC`, `ALIAS` commands
- Arbitrary globals (`Math`, `Date`, `JSON`, etc.)

## What you can't do

- **Use a template AND a theme together for the body content.** The template controls every visual aspect — it IS the styling. The theme isn't consulted during template instantiation. (Themes still apply to spec-driven documents you create separately.)
- **Patch a template-instantiated document and re-render against the template.** Once instantiated, the document is a normal spec — the template binding is lost. The instantiation is one-shot.
- **Use FOR/IF in PPTX or XLSX templates.** v1.x — see the matrix above. DOCX gets the full grammar today.

## Quotas

The `templates_count` axis (default 20 per tenant) limits how many templates you can register. Instantiation doesn't have a separate quota — every instantiated doc is just a normal doc, counted under `documents_count`.

## When templates are the right tool

Use templates when:
- The visual design is owned by a non-technical stakeholder
- You produce the same document shape over and over with different data
- The design is more elaborate than themes alone can express (specific tables, header/footer art, complex page geometry)
- You need pixel-stable output across runs

Don't use templates when:
- The document structure varies per request (build the spec dynamically instead)
- An agent is supposed to author content semantically (let the agent compose elements via MCP tools)
- You need to edit the produced document after generation (you can, but the template binding is one-shot)

## Where to next

- [Tutorials → Templates](/docs/scaiscribe/tutorials/templates) — concrete walkthrough end-to-end.
- [Themes](/docs/scaiscribe/concepts/themes) — when to use a theme instead.
- [Reference → API](/docs/scaiscribe/reference/api) — `/v1/templates` and `/v1/documents/from_template` shapes.
