Embedded template preparation
Embed PDF upload and template editing in your application. In review mode, your backend defines the fields; users review the suggested positions, move or resize them, and save. You can also upload through your own screen and embed the editor after upload.
Preparation modes
Review
Your backend defines the fields and their settings. Users review their positions,
move or resize them, and save. Set mode: TemplatePreparationMode.Review.

Full
Users can add or remove fields and edit their properties. Omit mode or set
mode: TemplatePreparationMode.Full to use the full editor.

These screenshots use a sample PDF and illustrative field positions.
SDK versions
Use @korala/api-client 1.32.0 or later for session creation and
@korala/react 1.4.0 or later for KoralaTemplateEditor. If you import DTOs
separately, use @korala/public-dtos 1.32.0 or later. The REST API and plain
iframe integration below do not require the React package.
Create a session on your server
Authenticate with an HMAC API key. Never pass API credentials to the browser. Authorize the person in your own application before creating a session.
import { FieldType, TemplatePreparationMode, type TemplatePreparationFieldDto } from '@korala/api-client';
const fields: TemplatePreparationFieldDto[] = [
{ fieldType: FieldType.Text, label: 'Legal name', mergeFieldName: 'recipient_name', required: true },
{ fieldType: FieldType.Signature, label: 'Signature', mergeFieldName: 'recipient_signature', required: true },
{ fieldType: FieldType.Date, label: 'Date signed', mergeFieldName: 'signed_at', required: true, signingDate: true },
];
const session = await korala.templates.createPreparationSession({
mode: TemplatePreparationMode.Review,
fields,
name: 'Agreement',
roleName: 'Signer',
parentOrigin: 'https://app.example.com',
});
// Return session.embedUrl to your authorized browser.The REST equivalent is POST /api/v1/template-preparation-sessions.
The response contains templateId, token, embedUrl, and expiresAt.
A new-template session creates an inactive PDF template and one role, inherits the
API key’s sandbox mode, and expires after one hour. Access is limited to that
template; the token cannot list templates, send documents, or use other API
routes. Treat both the token and embed URL as credentials. If a session becomes
unavailable, create a fresh session. Abandoned templates remain inactive and can be deleted through your
server’s ordinary template API.
parentOrigin must be an exact HTTPS origin with no path or trailing slash.
HTTP is allowed only for localhost and 127.0.0.1 during development.
Simplified review mode
Set mode: TemplatePreparationMode.Review on your server to give people an
upload-and-review flow. Your backend must supply fields: a non-empty list of
field definitions with unique variable keys. The example above configures three
fields; Korala does not prescribe their names or count. Set signingDate: true
on a date field to fill it automatically when signing. In review mode, Korala
asks AI for positions for your allowed field keys only.
AI cannot introduce field definitions or change their types. Korala discards
unknown keys, duplicate keys and rectangles outside the PDF page. If AI omits a
field, the editor supplies a default position for review. Scans without
extractable text may need manual positioning. People can select a field,
move it, resize it, or move it to another page. They cannot add or remove fields,
rename variables, change signer roles, or edit field properties. These
restrictions also apply to the browser API.
If detection is unavailable, the editor offers a prepared default layout for review. Saving requires the same backend-defined fields and settings. Reopening a review session preserves the approved layout and skips automatic positioning.
The default Full mode retains the configurable editor described below. Use
that mode for integrations that need additional fields or template settings.
When reopening, supply matching field types, keys, labels, required flags, and
signing-date settings. Review mode accepts coordinate fields without defaults,
shared fill, conditions, or AcroForm bindings. Use the full editor for
those source templates.
Field definitions
Your backend owns these settings. The browser can change page, position and size.
| Property | Contract |
|---|---|
fieldType | Required: text, signature, initials, date, checkbox, dropdown, or radio. Repeated types are allowed. |
label | Required, 1–100 characters; the editor displays this name. |
mergeFieldName | Required, unique in the session, at most 100 characters. Start with an ASCII letter; use letters, digits and underscores. |
required | Required boolean. Set true for fields the signer must complete. |
signingDate | Optional boolean, default false. Set true on a date field to use the date of signing. Other types cannot enable it. |
options | Required for dropdowns: 1–200 non-empty strings, at most 500 characters each. Other types cannot set it. |
groupKey | Required for radios; optional for checkboxes. Members with the same key form a mutually exclusive choice group. Up to 256 characters. |
groupRequired | Optional boolean, default false. Require at least one choice in the group; needs groupKey. Set individual group members to required: false. |
For example, your backend can configure a dropdown and two required-as-a-group radio choices:
const choiceFields: TemplatePreparationFieldDto[] = [
{ fieldType: FieldType.Dropdown, label: 'Region', mergeFieldName: 'region', required: true, options: ['North', 'South'] },
{ fieldType: FieldType.Radio, label: 'Email', mergeFieldName: 'contact_email', required: false, groupKey: 'contact_method', groupRequired: true },
{ fieldType: FieldType.Radio, label: 'Phone', mergeFieldName: 'contact_phone', required: false, groupKey: 'contact_method', groupRequired: true },
];The browser can position these fields, but cannot change dropdown options or
group settings. Choose checkbox for an independent yes/no value and radio
with groupKey for one choice from a group.
Review sessions require at least one definition. There is no preparation-specific field-count cap; normal API request-size limits still apply. Korala positions large field lists in sequential AI batches. Both the hosted upload and editor-only paths use this contract. A 15-field configuration uses the same editor and completion event as a three-field agreement.
PDFs with AcroForm fields
In review mode, Korala uses embedded AcroForm names and rectangles as hints when suggesting positions for your backend-defined fields. It does not import extra fields, signer roles, choices or variable names from the PDF. Review fields use coordinate placement; Korala does not bind them to the PDF’s native widgets. The uploaded PDF retains its existing form content.
An existing template with AcroForm-bound fields cannot open in review mode. Use Full mode for those templates, or upload the PDF as a new template and configure coordinate fields through your backend. Full mode uses the existing AcroForm field-detection flow, with suggestions for users to review.
Viewer controls
Select a field on the PDF and drag it into place. Drag the diagonal grip inside its bottom-right corner to resize. The footer contains zoom controls and, for multipage PDFs, page navigation. On multipage PDFs, or when there are more than three fields, a selector lets you find a field and open its page. The footer stays one row on desktop and two rows on phones.
Use + or − to zoom, or open the percentage menu for a preset or Fit width. Zoom changes the display, not the saved PDF coordinates. To move a field to another page, select it and navigate to the destination. Open the Field actions menu (•••) beside the field selector, then choose Move to page 2 (for example). The menu names the selected field and its current page. Choose Go to field to return to its location. Moving changes the field’s page; it does not create a copy.
Use your own upload screen
Your backend can use the regular template upload API, then create a review session for that uploaded template. Keep the template ID associated with the authorized user on your backend.
// Backend: authorize the user, then request an upload URL.
const upload = await korala.templates.createUploadUrl({
filename: 'agreement.pdf',
contentType: 'application/pdf',
});
// Return upload.uploadUrl to your upload screen. The browser PUTs the PDF
// to that URL with Content-Type: application/pdf, then notifies your backend.
// Backend: confirm the upload after checking ownership of upload.templateId.
await korala.templates.confirmUpload(upload.templateId, { name: 'Agreement' });
await korala.templates.addSignerRole(upload.templateId, {
name: 'Recipient',
signingOrder: 1,
});
const session = await korala.templates.createPreparationSession({
templateId: upload.templateId,
mode: TemplatePreparationMode.Review,
fields,
parentOrigin: 'https://app.example.com',
});The uploaded template must have one signer role. Leave its fields empty; Korala prepares your configured fields in an editing draft and starts positioning when the embedded editor opens. Users skip the hosted upload screen. Save returns the same uploaded template ID. A template that already has matching fields opens the approved layout instead and skips detection.
Edit an existing PDF template
Create another session from your backend using the original approved template ID:
const session = await korala.templates.createPreparationSession({
templateId: existingTemplateId,
mode: TemplatePreparationMode.Review,
fields,
parentOrigin: 'https://app.example.com',
});The editor opens the existing PDF and fields directly. Editing currently requires one signer role and the same sandbox/live mode as your API key. It creates an inactive draft; the original template stays usable during review. Save publishes the approved PDF and fields atomically and returns the original template ID, keeping its signer role ID. If the original changes during review, save returns 409 and you must create a fresh session. Abandoned editing drafts remain inactive.
Embed the editor
embedUrl comes from session.embedUrl in the server-side
createPreparationSession() response. Return that value to the authorized user
through your own backend endpoint. For example, your /api/template-preparation
handler can return it after authorizing the request:
// Your backend; use the fields defined above and keep HMAC credentials here.
const session = await korala.templates.createPreparationSession({
mode: TemplatePreparationMode.Review,
fields,
name: 'Agreement',
roleName: 'Recipient',
parentOrigin: 'https://app.example.com',
});
return Response.json({ embedUrl: session.embedUrl });Your frontend requests that URL from your backend, then renders the editor.
/api/template-preparation below belongs to your application.
import { KoralaTemplateEditor } from '@korala/react';
const response = await fetch('/api/template-preparation', { method: 'POST' });
if (!response.ok) throw new Error('Could not open template preparation');
const { embedUrl } = await response.json();
<KoralaTemplateEditor
embedUrl={embedUrl}
height={800}
onComplete={(templateId) => saveTemplateReference(templateId)}
/>Close or replace your modal in onComplete, or show your own success screen.
The hosted editor shows a saved-template confirmation while it remains mounted.
onLoaded is optional. onComplete runs after the server validates the
approved fields and activates the template. Duplicate completion events are
ignored by the component. Keep your own template mapping on your server and
verify the template through your authenticated API before accepting a browser
callback as authoritative.
Preparation accepts PDFs up to 20 MB (or the lower plan limit). Rotated pages must be exported with page rotation flattened before upload; the editor refuses them before field placement so signing coordinates remain consistent.
Review mode starts positioning after upload or after opening a newly uploaded, empty template. You can choose Review fields now if detection is unavailable; Korala uses default positions for your configured fields.
In full mode, accept or reject AI suggestions, then adjust accepted fields in the visual editor. If detection fails, retry it or place fields manually.
In full mode, completion requires three fields on the session’s signer role:
- Required text with
mergeFieldName: "legal_name"(legal name). - Required signature.
- Required date with
signingDate: true(date of signing).
These fields must have no default value, shared fill, or conditional requirement. Every field must fit within its PDF page. Newly placed text fields start as Legal name; change their merge name in field properties when using them for another purpose. Newly placed dates start as dates of signing; turn that off in properties for ordinary dates. For detected fields, mark the legal-name text field as Legal name in properties and turn on Date of signing for the appropriate date. Fields can be removed, but save remains blocked until the required three are present.
Edits are persisted as an inactive draft. Save activates the template and closes the session to further edits. Retrying completion returns the same ID until expiry. The approved PDF is a separate copy, so the original upload URL cannot overwrite it after approval. Create a fresh session for a new PDF.
Hosted iframe without React
Set an iframe’s src to embedUrl. The credential is in its URL fragment,
which is not sent in HTTP requests or referrers. The editor posts only to the
session’s configured parentOrigin:
{
source: 'korala-template-preparation',
event: 'completed', // or 'loaded'
templateId: '...',
}Check both event.origin against the hosted editor’s origin and
event.source against your iframe’s contentWindow. Events never include
the session token. The existing EMBED_ALLOWED_ORIGINS deployment policy also
applies to preparation pages.
Scoped browser API
The hosted page uses Authorization: Bearer <session.token> with
/api/v1/template-preparation. This token works only on these routes:
| Method | Route | Purpose |
|---|---|---|
| GET | / | Template details and session state |
| GET | /upload-url | Presigned PDF upload URL before confirmation |
| POST | /confirm | Validate PDF and freeze its uploaded contents |
| POST | /fields | Add a field using the normal template field DTO |
| PATCH | /fields/:fieldId | Update a field belonging to this template |
| DELETE | /fields/:fieldId | Remove a field from this template |
| POST | /detection | Start or retry AI suggestions |
| GET | /detection | Detection status and suggested fields |
| PATCH | /proposed/:fieldId/accept | Accept a suggestion for the session role; body accepts required |
| PATCH | /proposed/:fieldId/reject | Reject a suggestion |
| POST | /proposed/accept-all | Accept suggestions above minConfidence |
| POST | /review-positions | Prepare the fixed review layout; optional useDefaultPositions bypasses detection |
| POST | /complete | Validate, activate, and return templateId |
Signer-role assignments are always pinned to the session role. A concurrent mutation receives 409; wait for pending edits before saving. An expired or unknown token receives 401. Existing document generation and embedded signing APIs continue to use the returned approved template ID.