Exeq Documentation

Exeq lets you embed PDF form building and document signing into any website. Everything runs client-side in the browser — no backend required.

Live Demo

Try the full experience — build a template or sign a sample NDA.

Installation

1npm install @unlev/exeq

Import the components and styles in your React app:

1import { DesignerView, SignerView } from '@unlev/exeq';
2import '@unlev/exeq/styles';

Template Editor

The editor lets you open a PDF and place form fields on it. Everything runs locally in the browser — your files are never uploaded to any server. You can pre-fill or pre-sign fields that belong to you (the "Sender" role) before exporting the template for signers.

1import { DesignerView } from '@unlev/exeq';
2import '@unlev/exeq/styles';
3
4function Editor() {
5  return (
6    <DesignerView
7      apiKey="your-api-key"
8      initialPdfUrl="/contracts/blank.pdf"
9      onSave={(template) => {
10        // Save the template JSON to your server
11        console.log(template);
12      }}
13    />
14  );
15}

DesignerView Props

PropTypeRequiredDescription
apiKeystringYesYour Exeq API key
initialPdfUrlstringNoURL to a PDF to pre-load
initialTemplateTemplateNoExisting template to resume editing
onSave(template: Template) => voidNoCalled when "Export Template" is clicked. If omitted, downloads JSON.

What the editor does

  • Renders each PDF page as an image
  • Drag fields from the palette onto the page, or click to place
  • Select a field to edit its properties (type, label, placeholder, required, assignee)
  • Drag fields to reposition, resize via the corner handle
  • Assign fields to signer roles — "Sender" is for you (the host), other roles are for signers
  • Pre-fill or pre-sign Sender fields before exporting
  • Blackout and whiteout fields for redaction
  • Export the template JSON

Document Signing

Shows a PDF with pre-placed fields for the signer to fill out. Fields belonging to the Sender (pre-filled by the host) appear as read-only. The signer fills their fields, then the completed PDF is generated client-side.

1import { SignerView } from '@unlev/exeq';
2import '@unlev/exeq/styles';
3
4function Signer() {
5  return (
6    <SignerView
7      apiKey="your-api-key"
8      initialPdfUrl="/contracts/template.pdf"
9      initialTemplate={template}
10      initialSigner="Signer 1"
11      initialValues={{
12        "Full Name": "Jane Smith",
13        "Email": "jane@example.com",
14      }}
15      onComplete={(blob) => {
16        // Upload the signed PDF to your server
17        const formData = new FormData();
18        formData.append('file', blob, 'signed.pdf');
19        fetch('/api/upload', { method: 'POST', body: formData });
20      }}
21    />
22  );
23}

SignerView Props

PropTypeRequiredDescription
apiKeystringYesYour Exeq API key
initialPdfUrlstringNoURL to the PDF
initialTemplateTemplateNoTemplate object with fields and signer roles
initialSignerstringNoSigner role name. Defaults to "Signer 1"
initialValuesRecord<string, string>NoPre-fill fields by label (case-insensitive) or ID
callbackUrlstringNoURL to POST the signed PDF to
onComplete(blob: Blob) => voidNoCallback with the signed PDF Blob
submitLabelstringNoLabel for the final submit button. Defaults to "Complete"
signerOrderstring[]NoSigning order for multi-party documents. Signers complete one at a time. If omitted, non-Sender roles go first, then Sender.

What the signer sees

  • PDF rendered with all fields visible
  • Fields assigned to this signer are editable (highlighted)
  • Fields assigned to other roles show as read-only with pre-filled values
  • Prev/Next buttons navigate through fields in document order
  • "Complete" generates the final PDF with all values overlaid

Multi-party Signing

For documents that require multiple signers, use the signerOrder prop to define who signs in what order. Signers complete their fields one at a time — only the current signer's fields are active, while other signers' fields appear greyed out.

1<SignerView
2  apiKey="your-api-key"
3  initialPdfUrl="/contracts/template.pdf"
4  initialTemplate={template}
5  signerOrder={['Signer 1', 'Sender']}  // recipient signs first, then sender
6  onComplete={(blob) => {
7    // Final PDF with all signatures from all parties
8    uploadToServer(blob);
9  }}
10/>

If signerOrder is omitted, the default order is: non-Sender roles first (in their template order), then Sender last. The PDF is only generated after the final signer completes their fields.

Mail Merge (Pre-fill Fields)

Use the initialValues prop to pre-fill form fields programmatically. This enables a mail-merge workflow where your app fills in the data and the user only needs to review and sign — no manual data entry required.

Keys are matched against field labels (case-insensitive) first, then field IDs. Field labels are enforced to be unique in the designer, so there are no conflicts.

1<SignerView
2  apiKey="your-api-key"
3  initialPdfUrl="/contracts/lease.pdf"
4  initialTemplate={leaseTemplate}
5  initialSigner="Tenant"
6  initialValues={{
7    "Tenant Name": "Jane Smith",
8    "Email": "jane@example.com",
9    "Move-in Date": "2026-06-01",
10    "Monthly Rent": "$2,400",
11    "Unit Number": "4B",
12  }}
13  onComplete={(blob) => {
14    // The signed PDF has all values baked in
15    uploadToServer(blob);
16  }}
17/>

Fields matched by initialValues appear pre-filled in the signing UI. The signer can still edit them unless they belong to a different role (e.g. Sender fields are read-only).

Utilities & Types

Utility Functions

1import { renderPdfPages, generateFilledPdf, downloadPdf } from '@unlev/exeq';
2
3// Render PDF pages to images
4const pages = await renderPdfPages(pdfUrlOrBytes);
5
6// Generate a filled PDF with form values overlaid
7const bytes = await generateFilledPdf(pdfSource, fields);
8
9// Trigger browser download
10downloadPdf(bytes, 'signed-document.pdf');

Types

1import type { FormField, Template, FieldType, RenderedPage } from '@unlev/exeq';

Additional Components

ComponentDescription
PdfViewerLow-level PDF page renderer with draggable field overlays
SignatureCanvasFreehand signature/initials drawing canvas
FieldPropertyPanelField property editor (type, label, assignee)
FieldNavigatorPrev/Next navigation through signer fields
SignerRoleSelectorManage signer roles

Template JSON Schema

The template JSON exported by the editor has this structure:

1{
2  "pdfUrl": "https://yoursite.com/contract.pdf",
3  "signerRoles": ["Sender", "Signer 1"],
4  "fields": [
5    {
6      "id": "550e8400-e29b-41d4-a716-446655440000",
7      "type": "text",
8      "textSubtype": "freeform",
9      "label": "Full Name",
10      "placeholder": "Enter your full name",
11      "required": true,
12      "assignee": "Signer 1",
13      "page": 0,
14      "x": 15.5,
15      "y": 42.3,
16      "width": 25,
17      "height": 3,
18      "fontSize": 12,
19      "value": ""
20    }
21  ]
22}

Coordinate system

  • page: 0-indexed page number
  • x, y: position as percentage of page dimensions (0–100). Origin is top-left.
  • width, height: size as percentage of page dimensions (0–100)
  • fontSize: font size in points (used for text fields)

Field Types

TypeDescriptionValue formatNotes
textText inputStringSet textSubtype: freeform, number, date, email, phone
signatureFreehand signaturePNG data URLSupports ink color (black, blue)
initialsSmaller freehand drawingPNG data URLSame as signature, smaller default size
signed-dateAuto-filled dateLocale date stringAuto-fills when the signer signs
checkboxToggle checkbox"true" or ""Renders a checkmark when checked
blackoutBlack redaction rectangleN/ADesigner-only. Covers content with a solid black box.
whiteoutWhite redaction rectangleN/ADesigner-only. Covers content with a solid white box.

Typical Workflow

  1. Design — Use <DesignerView /> to create a template. Open a PDF, place fields, assign roles.
  2. Pre-fill — Fill Sender fields (company name, your signature, dates). These are baked into the template.
  3. Export — Use the onSave callback to capture the template JSON. Host the PDF on your server.
  4. Sign — Render <SignerView /> with the template. Use initialValues for mail-merge pre-fill.
  5. Collect — Receive the signed PDF via the onComplete callback.