-
Notifications
You must be signed in to change notification settings - Fork 74
Making Templates
You can install custom HTML templates from a manifest URL (remote templates), or upload them as a .zip file (local templates).
Invio supports three types of templates:
| Type | Description | Updatable |
|---|---|---|
| Built-in | Shipped with Invio (professional-modern, minimalist-clean) |
No |
| Remote | Installed from a manifest URL | Yes (from source URL) |
| Local | Uploaded as a .zip file | No (re-upload to update) |
- A Template is an HTML file rendered with a simple Mustache-like engine.
- Data comes from the invoice and from Business Settings.
- Built-in IDs:
professional-modern,minimalist-clean. - Installed templates are stored in the DB (HTML) and optional assets under
/app/data/templates/<id>/<version>/....
Use Settings → Templates → "Remote Template" → "Install from manifest", or call the API:
curl -u $ADMIN_USER:$ADMIN_PASS \
-H "Content-Type: application/json" \
-X POST "$BACKEND_URL/api/v1/templates/install-from-manifest" \
-d '{"url":"https://example.com/invio-template/manifest.yaml"}'Manifest (YAML or JSON) minimal schema:
id: clean-lines
name: Clean Lines
version: "1.0.0"
invio: ">=0.1.0"
html:
path: index.html
url: https://example.com/clean-lines/index.html
# license: MIT
# source:
# manifestUrl: https://example.com/clean-lines/manifest.yaml
# homepage: https://example.com/clean-lines/Remote templates can be updated by clicking the "Update" button, which re-fetches from the original manifest URL.
Use Settings → Templates → "Local Template" → upload a .zip file, or call the API:
curl -u $ADMIN_USER:$ADMIN_PASS \
-X POST "$BACKEND_URL/api/v1/templates/upload" \
-F "file=@my-template.zip"Your .zip file must contain:
my-template.zip
├── manifest.yaml (or manifest.json)
└── index.html (or path specified in manifest)
id: my-custom-template
name: My Custom Template
version: "1.0.0"
html:
path: index.htmlNote: Local templates do not have an html.url field since the HTML is included in the zip.
- Create your template HTML file
- Create a
manifest.yamlwith your template metadata - Zip both files together (files should be at the root of the zip)
- Upload via Settings → Templates → "Local Template"
Security guardrails: Templates cannot include iframes/objects/embeds or inline on* handlers.
-
Remote templates: Update via
POST /api/v1/templates/:id/update(re-fetches from manifest URL) -
Local templates: Re-upload a new .zip file with the same
idto replace - Delete any installed template:
DELETE /api/v1/templates/:id(built-ins are protected)
Available variables your HTML can use (subset):
Company
-
companyName,companyAddress,companyEmail,companyPhone,companyTaxId -
logoUrl(prefer this; system tries to inline remote logos as data URIs)
Invoice
-
invoiceNumber,issueDate,dueDate,currency,status
Customer
-
customerName,customerEmail,customerPhone,customerAddress,customerTaxId
Items
-
items(array of{ description, quantity, unitPrice, lineTotal, notes })
Totals
-
subtotal,discountAmount?,discountPercentage?,taxRate?,taxAmount?,total -
taxSummary?(array of{ label, percent, taxable, amount }) andhasTaxSummary -
netSubtotal(subtotal after discount, before tax)
Payment/Notes
-
paymentTerms?,paymentMethods?,bankAccount?,notes?
Extras
-
brandLogoLeftis always true in current renderer -
highlightColorandhighlightColorLightfor custom branding
Internationalization
- Template labels (like "INVOICE", "BILL TO", "TOTAL") are automatically translated based on the
localesetting - The backend injects localized labels via the i18n system
- No need to hardcode labels in your template — the system handles this
- See Guides/Adding Translations for details on adding new languages
Blocks (conditionals/loops):
{{#items}}
<tr>
<td>{{description}}</td>
<td class="num">{{quantity}}</td>
<td class="num">{{unitPrice}}</td>
<td class="num">{{lineTotal}}</td>
</tr>
{{/items}}
Defaults: You can use {{var || 'Default'}} to fall back when a value is empty.