Templates
A Template is the view layer — it connects your structured entity data to a visual document. The document is a rendering of your data, not the other way around.
A Template references an Asset (the HTML or PDF content), declares the data it needs (inputs), transforms that data into the document's context, and defines the participants who interact with it (roles). Use JSONata expressions in field values, visibility conditions, and data transforms to make documents fully data-driven.
YAML Format
Templates come in two flavors. HTML templates are rendered with Handlebars; a data.transform expression builds the viewmodel Handlebars needs from your entity inputs. PDF templates work with existing PDF files and declare each field explicitly with value expressions and widget positioning.
apiVersion: signstack/v1beta2
kind: Template
metadata:
key: onboarding_form
version: 1.0.0
name: Onboarding Form
spec:
type: pdf
content: onboarding_form_pdf@1.0.0 # Asset wrapping the PDF file
inputs:
- key: employee
schema: employee@1.0.0
roles:
- key: employee_signer
name: Employee
fields: # Every field declared with positioning
- key: employee_name
type: text_field
value:
expr: "$.employee.firstName & ' ' & $.employee.lastName"
widget:
page: 1
x: 100
y: 200
width: 200
height: 20
- key: employee_signature
type: signature
role: employee_signer
widget:
page: 2
x: 100
y: 500
width: 200
height: 50
apiVersion: signstack/v1beta2
kind: Template
metadata:
key: offer_letter
version: 1.0.0
name: Offer Letter
description: Candidate offer letter with dynamic terms
spec:
type: html
content: offer_letter_html@1.0.0 # Asset containing the HBS content
inputs:
- key: employee
schema: employee@1.0.0
- key: position
schema: position@1.0.0
data: # Builds the viewmodel that fills the Asset's HBS below
transform: |
{
"candidate_name": $.employee.firstName & ' ' & $.employee.lastName,
"salary": $format_currency($.position.salary)
}
roles:
- key: candidate
name: Job Candidate
functions:
format_currency: format_currency@1.0.0
And the Asset it references (offer_letter_html@1.0.0):
apiVersion: signstack/v1beta2
kind: Asset
metadata:
key: offer_letter_html
version: 1.0.0
spec:
type: html
data:
schema: offer_letter_viewmodel@1.0.0 # Validates the viewmodel shape the HBS expects
content: |
<h1>Offer of Employment</h1>
<p>Dear {{candidate_name}},</p>
<p>We're pleased to offer you the role at a salary of {{salary}}.</p>
<p>Please sign below to accept:</p>
<div data-field="candidate_signature"
data-type="signature"
data-role="candidate"></div>
{{candidate_name}} and {{salary}} match the keys the data.transform produced — both sides are validated against offer_letter_viewmodel@1.0.0 so the contract between transform output and HBS placeholders is explicit. The data-field element auto-detects as a signature field, wired to the candidate role via data-role — no fields: block needed on the template for this case.
Full Handlebars features — partials, helpers, conditionals — are supported. The HBS content plus any partials, stylesheets, and images live in the referenced Asset.
Spec Fields
| Field | Required | Description |
|---|---|---|
type |
Yes | pdf or html. Determines rendering approach. |
content |
Yes | Asset reference (key@version) — the base document. |
inputs |
No | Data inputs this template needs. Each references a Schema. |
data |
No | HTML only. JSONata expression that maps entity inputs to the Handlebars viewmodel. Output must match the Asset's data.schema. |
roles |
No | Participant roles who interact with this document. |
fields |
No | Field definitions. For PDF, each field needs explicit widget positioning. For HTML, fields auto-detect from data-field attributes — declare here only to override or gate. |
functions |
No | Custom function aliases. Keys used as #x3C;alias>() in expressions. |
Field Types
| Type | Description |
|---|---|
text_field |
Text input or display field |
check_box |
Checkbox |
signature |
Signature capture |
initial |
Initials capture |
signed_date |
Auto-filled date when signed |
image |
Image field |
line |
Decorative line |
Field Properties
| Property | Required | Description |
|---|---|---|
key |
Yes | Unique identifier (snake_case) |
type |
Yes | Field type (see table above) |
role |
No | Role key this field belongs to |
value |
No | Default value — literal ("John", 42, true) or expression ({ expr: '$.employee.name' }) |
includeIf |
No | JSONata expression — field is included only when this evaluates to true. If omitted, the field is always included. |
widget |
PDF: Yes | PDF positioning: page, x, y, width, height, optional style |
Widget Style
widget:
page: 1
x: 100 # pixels from the LEFT edge of the page
y: 200 # pixels from the TOP edge of the page
width: 200
height: 20
style:
fontSize: 12
fontColor: '#000000'
fontFamily: Helvetica
Examples
Minimal PDF Template
apiVersion: signstack/v1beta2
kind: Template
metadata:
key: simple_nda
version: 1.0.0
name: Simple NDA
spec:
type: pdf
content: nda_base_pdf@1.0.0
roles:
- key: signer
name: Signer
fields:
- key: signature
type: signature
role: signer
widget:
page: 1
x: 100
y: 700
width: 200
height: 50
- key: date
type: signed_date
role: signer
widget:
page: 1
x: 350
y: 700
width: 100
height: 20
PDF Template with Data Inputs
apiVersion: signstack/v1beta2
kind: Template
metadata:
key: employee_onboarding_form
version: 1.0.0
name: Employee Onboarding Form
description: Standard employee onboarding and NDA agreement
spec:
type: pdf
content: onboarding_base_pdf@1.0.0
inputs:
- key: employee
name: Employee Information
schema: employee@1.0.0
- key: company
name: Company Details
schema: company@1.0.0
roles:
- key: employee
name: Employee
- key: hr_manager
name: HR Manager
required: '$count($.managers) > 0'
fields:
- key: employee_name
type: text_field
role: employee
value:
expr: "$.employee.firstName & ' ' & $.employee.lastName"
widget:
page: 1
x: 100
y: 200
width: 200
height: 20
style:
fontSize: 12
- key: employee_signature
type: signature
role: employee
widget:
page: 2
x: 100
y: 500
width: 200
height: 50
- key: hr_signature
type: signature
role: hr_manager
includeIf: '$exists($.hr_manager)'
widget:
page: 2
x: 100
y: 600
width: 200
height: 50
HTML Template with Data Transform
HTML templates use a data.transform to map inputs into the Handlebars viewmodel the Asset expects:
apiVersion: signstack/v1beta2
kind: Template
metadata:
key: offer_letter_workflow
version: 1.0.0
name: Offer Letter Workflow
spec:
type: html
content: offer_letter@1.0.0
functions:
format_currency: format_currency@1.0.0
get_full_name: get_full_name@1.0.0
inputs:
- key: employee
schema: employee@1.0.0
- key: position
schema: position@1.0.0
- key: manager
name: Hiring Manager
schema: employee@1.0.0
required: '$.position.level > 3'
data:
transform: |
{
"candidate": {
"fullName": $get_full_name($.employee.firstName, $.employee.lastName),
"email": $.employee.email
},
"position": {
"title": $.position.title,
"department": $.position.department,
"salary": $format_currency($.position.salary)
},
"manager": $.manager,
"startDate": $.position.startDate,
"requiresManagerApproval": $.position.level > 3
}
roles:
- key: candidate
name: Job Candidate
output:
schema: executed_offer_output@1.0.0
transform: |
{
"employeeId": $.employee.id,
"positionId": $.position.id,
"acceptedAt": $now(),
"startDate": $.position.startDate,
"salary": $.position.salary
}
- key: manager
name: Hiring Manager
required: '$.position.level > 3'
# HTML fields are auto-detected. Only specify to add includeIf or override behavior.
fields:
- key: manager_signature
type: signature
role: manager
includeIf: '$.position.level > 3'
Dynamic Fields with Expressions
Value Expressions
Use value.expr to calculate field values from entity data:
# Simple mapping
- key: client_name
value:
expr: '$.client_info.name'
# Combining data
- key: full_address
value:
expr: "$.client_info.street & ', ' & $.client_info.city & ' ' & $.client_info.state"
# Calculated value with custom function
- key: total_with_tax
value:
expr: '$format_currency($.deal.amount * 1.0875)'
Conditional Field Inclusion
Use includeIf to include/exclude fields based on data:
# Show only for enterprise clients
- key: enterprise_discount
includeIf: '$.client.tier = "Enterprise"'
# Show when deal exceeds threshold
- key: manager_approval
includeIf: '$.deal.value > 100000'
Template roles can also declare an output — a JSONata transform that projects a clean, schema-typed payload from a signed document. Blueprints consume it as docOutputs.<doc>.<role> in onComplete, letting templates encapsulate their internals behind a stable contract. See Template Role Outputs.
PDF vs HTML Templates
| Aspect | HTML | |
|---|---|---|
| Content asset | .html/.hbs file with dependencies |
.pdf file |
| Fields | Auto-detected from data-field HTML attributes |
Defined explicitly with widget positioning (x/y coordinates) |
| Data transform | data.transform maps inputs → Handlebars viewmodel |
Not used (per-field value expressions access inputs directly) |
| Styling | Via CSS assets in the referenced Asset | N/A (PDF layout is fixed) |
| Dynamic content | Full Handlebars templating + field values | Field values only |
Pick whichever fits your situation: HTML when you want fully dynamic, data-driven documents authored from scratch; PDF when you have an existing fixed-layout document you want to fill from data.
Versioning
Templates use semantic versioning. A template moves from Draft to a concrete published version (1.0.0); to make further changes, you cut a new version (1.1.0, 2.0.0). Published versions are immutable — every blueprint or workflow referencing offer_letter@1.0.0 keeps rendering exactly as designed, even after 2.0.0 ships.
Draft → 1.0.0 (published, immutable)
↓ edit + publish
1.1.0 (published, immutable)
↓ edit + publish
2.0.0 (published, immutable)
Pick the version bump based on the change:
- MAJOR (2.0.0) — Breaking changes (removed input, role rename, field removal)
- MINOR (1.1.0) — Backwards-compatible additions (new optional input, new field)
- PATCH (1.0.1) — Bug fixes (transform correction,
includeIffix)
Related Concepts
- Assets — The content layer templates reference
- Schemas — Data validation for template inputs
- Custom Functions — Reusable logic in expressions
- Blueprints — Compose templates into multi-step workflows
- JSONata Reference — Expression syntax
