# template-preparation

Source: https://docs.korala.ai/guides/template-preparation

---

# 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`.

![Review mode with API-defined fields, a compact field selector and zoom controls](https://docs.korala.ai/images/template-preparation/review.png)

### Full

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

![Full mode with the field palette and editable field properties](https://docs.korala.ai/images/template-preparation/full.png)

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.

```ts
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:

```ts
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.

```ts
// 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:

```ts
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:

```ts
// 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.

```tsx
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`:

```ts
{
  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.
