Templates
Templates let you define a reusable document layout — signer roles, field positions, and merge variables — so you can generate ready-to-sign documents without reconfiguring fields each time.
How Templates Work
- Upload a PDF or DOCX file as the base document
- Define signer roles (e.g., “Client”, “Approver”) — these are placeholders that get assigned to real people when you create a document
- Place fields on the template and assign each to a signer role
- Create documents from the template by mapping real signers to roles
Creating a Template
Template creation follows the same two-step upload flow as documents:
TypeScript
import { KoralaClient } from '@korala/api-client';
import fs from 'fs';
const korala = new KoralaClient({
apiKeyId: 'your-api-key-id',
apiSecret: 'your-api-secret',
});
// Step 1: Get upload URL
const { templateId, uploadUrl, key } = await korala.templates.createUploadUrl({
filename: 'nda-template.pdf',
contentType: 'application/pdf',
});
// Step 2: Upload the file
const pdfBuffer = fs.readFileSync('nda-template.pdf');
await fetch(uploadUrl, {
method: 'PUT',
body: pdfBuffer,
headers: { 'Content-Type': 'application/pdf' },
});
// Step 3: Confirm and set metadata
const template = await korala.templates.confirmUpload(templateId, {
name: 'NDA Template',
description: 'Standard non-disclosure agreement',
});
console.log(`Template created: ${template.id}`);Templates support both PDF and DOCX files. DOCX files are automatically converted to PDF for preview, and merge fields ({{placeholder}}) are detected automatically.
Signer Roles
Signer roles are named placeholders that define who needs to sign, without specifying the actual person. When you create a document from a template, you map real signers to these roles.
Adding Roles
TypeScript
// Add roles with signing order
const client = await korala.templates.addSignerRole(templateId, {
name: 'Client',
signingOrder: 1,
});
const approver = await korala.templates.addSignerRole(templateId, {
name: 'Approver',
signingOrder: 2,
});Managing Roles
// List all roles
const roles = await korala.templates.getSignerRoles(templateId);
// Update a role
await korala.templates.updateSignerRole(templateId, roleId, {
name: 'Company Representative',
signingOrder: 2,
});
// Delete a role
await korala.templates.deleteSignerRole(templateId, roleId);Template Fields
Fields define where signers need to fill in information. Each field is positioned on a specific page and assigned to a signer role.
Adding Fields
TypeScript
// Add a signature field for the Client role
await korala.templates.addField(templateId, {
signerRoleId: clientRoleId,
fieldType: 'signature',
pageNumber: 1,
xPosition: 100,
yPosition: 650,
width: 200,
height: 50,
});
// Add a date field
await korala.templates.addField(templateId, {
signerRoleId: clientRoleId,
fieldType: 'date',
pageNumber: 1,
xPosition: 350,
yPosition: 650,
width: 150,
height: 30,
});
// Add a text field with a merge variable
await korala.templates.addField(templateId, {
signerRoleId: clientRoleId,
fieldType: 'text',
pageNumber: 1,
xPosition: 100,
yPosition: 200,
width: 300,
height: 30,
mergeFieldName: 'company_name',
defaultValue: 'Acme Corp',
});Field Types
| Type | Description |
|---|---|
signature | Drawn or uploaded signature image |
initials | Signer’s initials |
date | Date value |
text | Free-text input |
checkbox | Boolean checkbox |
Managing Fields
// List all fields
const fields = await korala.templates.getFields(templateId);
// Update field position or assigned role
await korala.templates.updateField(templateId, fieldId, {
xPosition: 150,
yPosition: 700,
signerRoleId: newRoleId,
});
// Delete a field
await korala.templates.deleteField(templateId, fieldId);DOCX Merge Fields
When you upload a DOCX template, Korala automatically detects {{placeholder}} merge fields in the document text. These are substituted with real values when generating documents.
// Get detected merge fields
const mergeFields = await korala.templates.getMergeFields(templateId);
console.log(mergeFields);
// [
// { name: 'company_name', occurrences: 3 },
// { name: 'effective_date', occurrences: 1 },
// { name: 'client_address', occurrences: 2 },
// ]Merge fields are only available for DOCX templates. PDF templates use positioned fields instead.
Anchor Placement (DOCX)
Absolute xPosition/yPosition coordinates are fragile on DOCX templates: when
merge fields are substituted, the document reflows and repaginates, so a
coordinate captured against the preview may no longer line up. Anchor
placement fixes this — a field anchors to a literal string in the document body
and is positioned wherever that string actually renders, regardless of reflow or
page count. This is the same model as DocuSign “anchor tabs”.
Set placement: 'anchor' and provide an anchorText:
await korala.templates.addField(templateId, {
signerRoleId: fundLeadRole.id,
fieldType: 'signature',
placement: 'anchor',
anchorText: 'Fund Lead Signature:',
anchorPosition: 'right', // place the field to the right of the matched text
anchorOffsetX: 8,
width: 200,
height: 50,
});| Property | Type | Description |
|---|---|---|
placement | enum | coordinate (default) or anchor |
anchorText | string | Literal string to locate in the rendered document. Must not contain {{ }}. |
anchorPosition | enum | replace, left, right, above, below — where the field sits relative to each match |
anchorOffsetX / anchorOffsetY | number | Offset in points applied after resolution |
hideAnchor | boolean | White-out a visible marker after resolving it |
Key behaviors:
- Every occurrence is placed. If the anchor string appears three times, three fields are created. Use a unique marker if you want exactly one.
- Invisible markers. Make the anchor text white in your Word document — it is
still extracted for positioning but is invisible to signers, so no clean-up is
needed. Alternatively set
hideAnchor: trueto cover a visible marker. - Coordinate fields still work. Mix anchor and coordinate fields freely. For
coordinate fields on documents whose page count varies, set
pageNumber: -1to target the last page.
Anchor placement is resolved per generation, so it requires no preview clicking and stays correct as merge content changes.
Creating Documents from Templates
Map real signers to template roles and optionally fill merge field variables:
TypeScript
const document = await korala.templates.createDocument(templateId, {
name: 'NDA — Acme Corp',
signers: [
{
roleId: clientRoleId,
name: 'Jane Smith',
email: '[email protected]',
},
{
roleId: approverRoleId,
name: 'Bob Johnson',
email: '[email protected]',
},
],
variables: {
company_name: 'Acme Corp',
effective_date: '2024-03-01',
client_address: '123 Main St',
},
});
console.log(`Document created: ${document.id}`);
console.log(`Status: ${document.status}`); // 'draft'The created document is in draft status. Send it for signing to start the signing workflow.
Generating PDFs Without Signing
Generate a filled PDF from a template without starting a signing workflow. Useful for previews or non-signing document generation:
const result = await korala.templates.generate(templateId, {
variables: {
company_name: 'Acme Corp',
effective_date: '2024-03-01',
},
});
console.log(`Download: ${result.downloadUrl}`);Managing Templates
// List all templates
const templates = await korala.templates.list();
// Get template with full details (roles + fields)
const template = await korala.templates.get(templateId);
console.log(`Roles: ${template.signerRoles.length}`);
console.log(`Fields: ${template.fields.length}`);
// Update name, description, or active status
await korala.templates.update(templateId, {
name: 'Updated NDA Template',
description: 'Revised for 2024',
isActive: true,
});
// Deactivate a template
await korala.templates.update(templateId, { isActive: false });
// Delete a template
await korala.templates.delete(templateId);Replacing the Template File
Replace the underlying PDF/DOCX without losing signer roles or field positions:
// Step 1: Get replacement upload URL
const { uploadUrl, key } = await korala.templates.replaceFile(templateId, {
filename: 'nda-v2.pdf',
contentType: 'application/pdf',
});
// Step 2: Upload new file
await fetch(uploadUrl, {
method: 'PUT',
body: fs.readFileSync('nda-v2.pdf'),
headers: { 'Content-Type': 'application/pdf' },
});
// Step 3: Confirm replacement
await korala.templates.confirmReplaceFile(templateId, { key });After replacing a template file, verify that existing field positions still align with the new document layout.