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 PDF
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, includeIf fix)