SealingLast updated on
Last updated on
Prepare canonical PDFs for signing
Rendering produces a document in its native layer format. Sealing prepares a filled form for signing by producing one canonical, flattened PDF, its SHA-256 hash, and the signature-field map. Conversion may happen anywhere; placement and hashing always run locally, so both are verifiable.
Declare signature slots
A layer declares its signing fields once, in a signatures map. Each slot
binds a party to a placement:
.inlineLayer('agreement', {
mimeType: 'text/markdown',
text: '... Sign: {{signature(parties.client, "client-sig")}} ...',
signatures: {
'client-sig': {
party: { role: 'client' },
type: 'signature',
placement: 'flow', // sits where the text puts it
},
'witness-sig': {
party: { role: 'witness' },
type: 'signature',
placement: {
anchor: { text: 'Witnessed by:', offsetY: 12 }, // text-located
width: 200,
height: 40,
},
},
'client-date': {
party: { role: 'client' },
type: 'date_signed',
placement: { page: 2, x: 400, y: 700, width: 100, height: 20 }, // fixed
},
},
})Three placement strategies, one engine:
| Placement | How the position is found | Use when |
|---|---|---|
'flow' | An invisible marker is injected at the slot's placeholder during rendering and located in the converted PDF | Flowing text templates (markdown, HTML) |
anchor | Literal document text is found in the converted PDF; occurrence picks a match when the text repeats, otherwise it must be unique | Text known at design time, position unknown |
| absolute | Declared page/x/y coordinates | PDF templates, fixed layouts |
Misconfiguration fails before any rendering or conversion with a
SealConfigError naming every problem: unknown party roles, a
required-signature party no slot places, or a required slot without a
signatory.
validate() and paradoc validate report the problems the definition alone
shows, before any data exists: a slot for an undeclared role, a
required-signature party with no slot on a layer that declares signatures,
a 'flow' slot on a PDF or DOCX layer or of a type other than signature or
initials, and a 'flow' slot that no signature(...) or initials(...)
directive in the layer's template places. See
paradoc validate.
Seal a PDF layer locally
PDF layers need no adapter or network call. Paradoc renders the layer,
resolves placements, flattens, and hashes the exact canonical bytes. The
resolver that reads the PDF template's bytes is bound where the form is
constructed — p.form(schema, { resolver }) — so draft already carries
it:
const signable = await draft.seal()
signable.canonicalPdfBytes // Uint8Array
signable.canonicalPdfHash // sha256:...
signable.signatureMap // provider-neutral PDF coordinatesSeal markdown, HTML, or DOCX
Non-PDF layers need a converter: a SealAdapter that turns the rendered
document into PDF bytes and nothing else. Placement is not the converter's
job — core locates anchors and markers itself with the built-in locator, so a
pure byte converter works with zero configuration:
import { hostedSealAdapter } from '@paradoc/sdk'
const signable = await draft.seal({
adapter: hostedSealAdapter({ apiKey }),
})A custom converter is one honest function:
import type { SealAdapter } from '@paradoc/sdk'
const adapter: SealAdapter = {
async convert({ document }) {
return { pdf: await myHtmlToPdf(document.content) }
},
}If an adapter does resolve placements itself (returning a complete
signatureMap), core respects it and skips the locator. Pass locate to
substitute a custom locator tier.
For 'flow' slots, core renders twice: a marker pass locates every
placeholder in the converted PDF, then a clean render becomes the canonical
document. If the marker's width shifts a line wrap between passes, sealing
fails loud rather than misplacing a field.
Inspect before sealing
prepareSeal() resolves the signature map without flattening, hashing, or
leaving the draft phase — useful for envelope flows that need coordinates
before committing:
const prep = await draft.prepareSeal({ adapter })
prep.pdf // the converted PDF the coordinates describe (pre-flatten)
prep.signatureMap // resolved fields
prep.provenance // per field: 'declared' | 'anchor' | 'marker'
prep.warnings // e.g. slots skipped for unfilled party indexes
const signable = await draft.seal({ adapter }) // same map, canonicalizedOlder artifacts
Layers in older schema versions could declare slots in signatureBlocks and
anchorBlocks. Those fields are removed (see the
version history). Run
paradoc migrate to move each block into signatures under the same id.
Which operation to use
| Goal | Operation |
|---|---|
| Fill a layer in its native format | createLayerRenderer() |
| Resolve placements without sealing | .prepareSeal({ adapter? }) |
| Prepare a PDF layer for signing | .seal() |
| Prepare a non-PDF layer for signing | .seal({ adapter }) |