Templates
In the previous article, we covered the "data-first" principle and how Entities provide the authoritative data for your workflow. Now, let's explore Templates—the "view" layer that makes this data visible and interactive on your documents.
A Template is a reusable blueprint for a single document. It defines the underlying PDF, the interactive fields placed on it, and the data it requires. If an Entity is the raw data, the Template is the form that gives it structure and meaning within a document context.
1. The "Manifest": Declaring Requirements
A Template clearly declares its dependencies—the participants and data it needs to function correctly.
roles: This is the "cast list." It defines the participant placeholders this document requires, each with a uniquekey(e.g.,primary_buyer,seller_agent).- entitySlots: This is the "data list." It defines the
Entitydata this document needs, referencing specificSchemasby theirkey(e.g.,client_info,deal_info).
2. The Base Document: PDF
SignStack Templates work by overlaying interactive elements onto a base PDF document. You upload your existing PDF (like a standard real estate form or a company contract), and that becomes the static background for your template.
3. The "Logic": Fields and Expressions
Fields are the interactive elements placed onto the PDF. They connect your data to your participants. Each field definition includes its key, type (e.g., Signature, Text), and visual properties (widgets defining position, size, etc.).
Most importantly, Fields use JSONata expressions to create powerful, data-driven logic:
assignedToRoleKey:Assigns a field to a specific Role defined in the template (e.g., assigning"buyer_signature_field"to the"primary_buyer"role key).valueExpression:This expression calculates the field's value "just-in-time" by executing logic using data from theEntityContext.- Simple Example (1-to-1 Mapping): Display the client's name:
client_info.name - Advanced Example (Combining Data): Calculate a fee based on deal size and client status:
"$formatNumber(client_info.tier = 'Premium' and deal_info.value > 50000 ? deal_info.value * 0.02 : 0, '$,0.00')"
- Simple Example (1-to-1 Mapping): Display the client's name:
displayCondition:This expression controls visibility by executing logic using data from the EntityContext. If it returnstrue, the field is shown; otherwise, it's hidden.deal_info.requestedDiscount > userInfo.discountLimit
💡 Tip: Need help writing a complex expression? Modern Large Language Models (LLMs) are excellent at generating JSONata expressions!
4. Previewing with Scenarios
How do you check if your dynamic valueExpressions and displayConditions are working correctly while you're designing your Template? SignStack provides Scenarios for this.
A Scenario is a reusable set of sample Entity data. Within the Template editor, you can select a Scenario, and the document preview will instantly update, evaluating all your expressions using that specific data set.
This allows for rapid iteration and testing, ensuring your dynamic fields behave as expected before you even build a Blueprint.
We'll cover Scenarios in more detail in their own Core Concepts article.
5. Outputs: Extracting Data from Completed Steps
While Fields control what participants see and interact with, Outputs define what data should be extracted after a participant completes their signing task. Outputs are JSONata expressions that can access both entity data and the field values entered by the participant, transforming them into structured results for subsequent workflow steps.
Structure:
interface Output {
key: string; // Unique identifier (e.g., "approvalSummary")
roleKey: string; // Which role this output belongs to
schemaKey: string; // Schema that defines the output structure
schemaVersion: string; // Version of the schema
expression: string; // JSONata expression to evaluate
}
Example:
{
"outputs": [
{
"key": "client_summary",
"roleKey": "primary_buyer",
"schemaKey": "clientSummarySchema",
"schemaVersion": "1.0.0",
"expression": "{ 'name': client_info.name, 'phone': fields.phone_number_field, 'signedAt': $now() }"
}
]
}
Output Expression Context
Output expressions have access to:
- Entity data - Access entities directly by key (e.g.,
client_info.name,deal_info.value) - Field values - Access resolved field values via
fields.fieldKey(e.g.,fields.phoneNumberField). This includes:- Values entered by the participant (overrides)
- Original field values if no participant input (literals or evaluated expressions)
Why Use Outputs?
- Separation of Concerns: Fields handle presentation and input; Outputs handle data extraction and transformation.
- Role-Specific Results: Each output is tied to a specific role, so you can define different data extractions for different participants.
- Workflow Integration: Outputs are automatically available in
onCompleteMappersvia thedocOutputscontext (e.g.,docOutputs.documentKey.outputKey), allowing you to evolve your entities based on computed results.
Outputs vs. Fields
| Aspect | Fields | Outputs |
|---|---|---|
| Purpose | Display data, collect input | Extract and transform data |
| When evaluated | During document rendering | After step completion |
| Visible to participant | Yes | No (internal) |
| Used in mappers | No | Yes, via docOutputs.docKey.outputKey |
💡 Tip: Use Outputs when you need to compute derived values (totals, summaries, status flags) that should flow into subsequent workflow steps without cluttering your field definitions.
6. Centralized Logic
By defining this logic inside the Template, you are centralizing your business rules within SignStack. Your application code stays clean—it just provides the Entities, and SignStack handles the complex presentation and field-level logic automatically.