Templates
A template is a ready-made starting point an app offers instead of a blank workspace. When someone opens the app they see a gallery of cards; picking one copies a set of starter files into their session and, optionally, gives the agent a focused instruction for building on top of them.
Templates are what turn a general builder into a product: the way Craft ships "Dashboard", "E-commerce" and "Blog" starters, or a document agent ships "Case study" and "Business letter" layouts. The user starts from something real in seconds instead of describing a whole project from zero.
Everything on this page is the real, complete contract — there are no other
fields or behaviors. Author templates from Studio → open the app menu →
Templates, or by hand in templates.yaml + a templates/ folder.
What templates are for
A blank workspace asks the user to describe everything from zero, and the agent to build it from nothing — slow, and different every time. A template flips that: the user picks a card and the agent starts from a known, working base it only has to adapt. Concretely, templates buy you three things:
- Speed and a real starting point. The user goes from idea to a working draft in one click, not a long back-and-forth. They pick "Dashboard" and a full dashboard is already in their workspace.
- Quality and consistency. The starter is a finished, proven file set (a correct stack, a designed layout). Every project begins from the same solid base instead of whatever the agent improvised that day.
- Focus for the agent. Each template's
system_prompttells the agent exactly what it's building on and what not to touch ("build on top, don't re-scaffold, keep the stack"), so it succeeds on the first try instead of guessing the structure.
Add templates when the app's job is to help someone start or produce something that has a recognizable shape — a website builder (landing, blog, dashboard…), a document generator (letter, case study, report…), a diagram or spreadsheet tool. If users would otherwise describe the same kind of thing over and over, those are your templates.
You don't need templates when the app has no "starting artifact" — a
conversational assistant, a background automation, a one-shot classifier. There
is nothing to seed, so skip templates.yaml entirely.
Template vs. blank vs. default: offer several templates when there are
genuinely different starting points; offer none for an app with no artifact; use
a single default: template when there's exactly one
engine the agent always authors into (a slide studio, a fixed app shell) and a
picker would just be a pointless click.
The shape
Templates are declared in a top-level templates.yaml in the app bundle.
Each entry is one gallery card:
templates:
- id: dashboard # required — the card's id (becomes template_id at runtime)
name: "Dashboard" # shown on the card
description: "Admin dashboard with charts, tables and auth." # optional, shown on the card
preview_path: "templates/dashboard/dist/index.html" # the thumbnail (see "Preview" below)
seed_dir: "templates/dashboard/files/" # the folder copied into the session workspace
system_prompt: | # injected while this template is active (see below)
You are building from the "Dashboard" starter — a complete app already
scaffolded in the workspace. Build the user's product ON TOP of it. Do not
re-scaffold and do not swap the stack.
Those seven keys are the entire schema of a template:
| Key | Required | What it does |
|---|---|---|
id | yes | Card id. At runtime the picked id is the session's template_id. Lowercase letters, digits and dashes. |
name | yes | Title on the gallery card. |
description | no | One line under the title. |
preview_path | yes | Bundle path to the file rendered as the card's thumbnail. |
seed_dir | yes | Bundle folder copied into the fresh session workspace when the card is chosen. |
system_prompt | yes | Extra instruction injected while this template is active. Leave "" if you truly want none. |
default | no | true auto-seeds this template when the user starts without picking one (see Default templates). |
There is no template "type" or "kind" field, and no text-templating
language (no Jinja/Handlebars placeholders). A template is just a folder of
real files (seed_dir) plus a picture of the result (preview_path) plus
an instruction (system_prompt). The familiar "types" below are naming
conventions, not settings.
Folder layout
Keep each template self-contained under templates/<id>/:
templates.yaml # the catalog (all cards)
templates/
dashboard/
files/ # seed_dir — copied into the workspace
package.json
src/…
dist/
index.html # preview_path — a built, self-contained page
files/is theseed_dir: exactly what the user's empty workspace is filled with. Put the whole starting project here.- The preview lives next to
files/(e.g.dist/index.htmlorpreview.png), never inside it — the preview is shown in the gallery, it is not copied into the workspace.
Preview
A template preview has no type or kind field. The gallery decides how to
render preview_path purely from the file's extension — you never declare
it, you just point at the right kind of file:
preview_path extension | How the gallery renders it | Use it for |
|---|---|---|
.png .jpg .jpeg .webp .gif .avif | raster image — a sharp <img> at its real aspect ratio | a rendered document / slide / sheet (a real picture of the result) |
.html .htm (or anything else) | HTML page — a live <iframe> | a built web app, or a self-contained mock/poster |
That is the entire choice: a raster-image file, or an HTML file. There are only these two.
Don't confuse this with the other "preview types" in Digitorn. This extension rule is only for a template's gallery thumbnail. It is not
ui.workspace.render_mode(react/html/markdown/… — the workspace panel, see Client manifest), notusePreviewAttach().kind(static/devserver/web— the SDK live build, see SDK), and not thepreviewshottool'skind(html/embed). The wordhtmlmeans a different thing in each of those — here it just means "pointpreview_pathat an.htmlfile." See Which "preview" is which?.
The preview file and the other files in its folder are served together, so
a built single-page app whose dist/index.html references sibling
dist/assets/* renders correctly. Build such previews with a relative base
(e.g. Vite base: './') and, for client routing, a hash router, so the
static preview works without a server.
To set a preview in Studio, add the image or page anywhere under the template
folder and click the ⭐ on it — that writes preview_path for you.
Producing a real image preview
A live preview.html (or a built dist/index.html) iframe is the zero-friction
default and is genuinely high-fidelity for a web/SPA template. For a document
template (docx / sheets / slides) a real rendered image of the page usually
looks better on the card than an HTML mock — and you can produce one truthfully.
On the Digitorn cloud host the toolchain is provisioned (LibreOffice soffice,
Google Chrome, and Poppler's pdftoppm), so an agent building the template can
render the actual result with plain shell commands — no server, no external
service, and nothing to fabricate:
# Document template → a true first-page image
soffice --headless --convert-to pdf --outdir templates/<id> templates/<id>/files/template.docx
pdftoppm -jpeg -r 100 -singlefile templates/<id>/template.pdf templates/<id>/preview
rm -f templates/<id>/template.pdf # keep only preview.jpg, beside files/
# Web/SPA template → a true screenshot of the built app
google-chrome-stable --headless --no-sandbox --hide-scrollbars \
--window-size=1280,800 --screenshot=templates/<id>/preview.png \
"file://$PWD/templates/<id>/dist/index.html"
Then point preview_path at the produced file (preview.jpg / preview.png).
Two honest caveats: this depends on those binaries being present (they are on the
cloud host; a local machine may not have them), and the right thing to do when a
command fails is to fall back to a truthful preview.html, never to invent a
screenshot.
What happens when a template is used
- The user picks a card. Its
idbecomes the session'stemplate_id. - On the first turn,
seed_diris copied into the session workspace — but only while that workspace is still empty (dotfiles ignored). It never overwrites an existing file, so it is a safe no-op on every later turn. - The template's
system_promptis injected as guidance on that seeding turn, and while the template stays active it is also prepended to the agent's system prompt each turn (as "Active starter template: <name>"), so the agent keeps building the right thing.
That is the whole runtime behavior. There is no build step, no variable substitution, no server — just a file copy plus an instruction.
Default templates
Set default: true on one template to auto-seed it when the user starts a
session without picking from the gallery. This is for apps built around a
single engine the agent authors into — a slide studio, a fixed app shell —
where there should be no picker click.
templates:
- id: studio
name: "Studio"
default: true # auto-seeded into an empty workspace on the first turn
seed_dir: "templates/studio/files/"
preview_path: "templates/studio/dist/index.html"
system_prompt: |
The studio project is already scaffolded in the workspace. Author into it;
never re-scaffold or swap the stack.
Rules: only the first default template is used; it seeds only into a still-empty
workspace and never clobbers; an explicit pick from the gallery always wins over
the default. A default template still needs a seed_dir; give it a
preview_path too if it should also show in the gallery.
Four complete examples
1. Built web app (SPA starter, Craft-style)
A complete React/Vite project the agent extends. The preview is the built app so the gallery shows the real thing.
templates:
- id: dashboard
name: "Dashboard"
description: "Admin dashboard: analytics, tables, auth, settings. React + Vite + Tailwind + shadcn."
preview_path: "templates/dashboard/dist/index.html"
seed_dir: "templates/dashboard/files/"
system_prompt: |
You are building from the "Dashboard" starter — a COMPLETE app already
scaffolded in the workspace (React 19 + Vite + Tailwind + shadcn/ui +
react-router with a hash router and vite base './'). Build the user's
product ON TOP of it. Do NOT re-scaffold, do NOT swap the stack, keep the
hash router and base './' so the live preview renders.
templates/dashboard/
files/ # the whole project: package.json, src/, index.html, …
dist/index.html # built preview (base './', hash router) + dist/assets/*
2. Document render (docx / sheets / slides page, image preview)
The seed is an editable source document; the preview is a rendered image of
the finished result, so the card looks like the document it produces. Produce that
image the honest way — see Producing a real image preview
(soffice → PDF → pdftoppm), never a fabricated screenshot.
templates:
- id: company-case-study
name: "Case study"
description: "A one-page client story: challenge, solution, measurable results."
preview_path: "templates/company-case-study/preview.png"
seed_dir: "templates/company-case-study/files/"
system_prompt: |
You are building from the "Case study" template — a designer-grade
one-page layout already in the workspace: `template.docx` (with
{{ variables }} for every text) and `document.docx` (a filled sample).
`template.md` lists every variable. The design is final: fill the
variables with the user's real content, then render — never rebuild the
layout by hand.
templates/company-case-study/
files/
template.docx # the source (variables)
document.docx # a filled sample
template.md # lists every variable
preview.png # rendered thumbnail
3. Self-contained HTML poster (diagram / schema, web preview)
The preview is a single .html file that stands alone in an iframe — good for a
diagram, an ERD, or any one-page visual.
templates:
- id: saas-multitenant
name: "SaaS (multi-tenant)"
description: "A multi-tenant SaaS database schema: orgs, users, roles, billing."
preview_path: "templates/saas-multitenant/erd-poster.html"
seed_dir: "templates/saas-multitenant/files/"
system_prompt: |
Start from the "SaaS (multi-tenant)" schema already in the workspace.
Extend it for the user's domain; keep the tenant-isolation pattern
(every table scoped by org_id) intact.
templates/saas-multitenant/
files/ # the editable schema source
erd-poster.html # self-contained preview (no external assets)
4. Blank / minimal starter
The smallest valid template: a seed folder and a plain preview. Use it as the "start from scratch, but with the conventions already in place" option.
templates:
- id: blank
name: "Blank"
description: "An empty project with just the folder conventions in place."
preview_path: "templates/blank/preview.png"
seed_dir: "templates/blank/files/"
system_prompt: "" # no extra guidance — a truly blank start
templates/blank/
files/
README.md # the only seeded file
preview.png
Authoring in Studio
Open the app menu → Templates. The panel is a file manager scoped to the selected template's folder:
- New / Duplicate / Delete a template (writes
templates.yamlfor you). - Meta tab —
id,name,description,seed_dir,preview_path. - Files tab — a full folder tree: create files and folders, rename, move
(drag and drop), upload files or a whole folder.
files/is badged "seed → workspace", and the folder holding your preview is badged "preview" (derived frompreview_path). Click ⭐ on any file to make it the preview. - Prompt tab — the template's
system_prompt. - Preview tab — the rendered card (image or iframe).
Common mistakes
- Preview inside
seed_dir. The preview belongs next tofiles/(e.g.templates/<id>/preview.png), not inside it — otherwise it gets copied into the user's workspace. - Absolute asset paths in an SPA preview. A built preview must use a
relative base (
base: './') and a hash router, or it renders blank in the gallery iframe. - Expecting placeholder substitution.
seed_dirfiles are copied verbatim. Any "fill in the blanks" is done by the agent followingsystem_prompt, not by the platform. - Seed not applied. Seeding only happens into an empty workspace and never overwrites — an existing session keeps its files.