Paradoc
Guides

Sealing

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: '... {{#with parties.client}}Sign: {{signature "client-sig"}}{{/with}} ...',
  signatures: {
    'client-sig': {
      party: { role: 'client' },
      type: 'signature',
      placement: 'auto',                                   // marker-located
    },
    '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:

PlacementHow the position is foundUse when
'auto'An invisible marker is injected at the slot's placeholder during rendering and located in the converted PDFFlowing text templates (markdown, HTML)
anchorLiteral document text is found in the converted PDF; occurrence picks a match when the text repeats, otherwise it must be uniqueText known at design time, position unknown
absoluteDeclared page/x/y coordinatesPDF 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.

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:

const signable = await draft.seal({ resolver })

signable.canonicalPdfBytes // Uint8Array
signable.canonicalPdfHash  // sha256:...
signable.signatureMap      // provider-neutral PDF coordinates

Seal 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/core'

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 'auto' 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, canonicalized

Legacy blocks

Layers using signatureBlocks (absolute) and anchorBlocks (text anchors) continue to seal unchanged during the deprecation window. New artifacts should declare signatures; the legacy fields will be removed in a future major release.

Which operation to use

GoalOperation
Fill a layer in its native formatrenderLayer()
Resolve placements without sealing.prepareSeal({ adapter? })
Prepare a PDF layer for signing.seal()
Prepare a non-PDF layer for signing.seal({ adapter })

On this page