← All migration plugins

Migrate from PandaDoc to Anvil

PandaDoc templates are blocks and merge tokens, not flat PDFs with tabs. The plugin picks a migration target per template and collapses the async send into one Anvil call.
Install in Claude Code
/plugin marketplace add anvilco/anvil-plugins
/plugin install pandadoc-anvil-migration@anvil-plugins
Type both into Claude Code: the first adds Anvil's plugin catalog, the second installs the PandaDoc plugin. Then ask Claude to migrate.
The mental model
A PandaDoc document built from a template becomes an Anvil Etch packet built from a Cast or dynamic doc – roles become signers, fields become signer-assigned field aliases, and tokens become fill data, not signer fields.
Cast is Anvil's word for a template: a reusable document with named fields your code fills through the API.

Two migration targets, chosen per template

PandaDoc has no flat-PDF export, so there is no single right way to move a template. The plugin picks a target per template – dynamic doc or rendered PDF – and explains the tradeoff.
export-pandadoc-templates.ts
Exports template structure – roles, fields, and tokens – and renders a base PDF per template with field geometry where available. Standalone; no PandaDoc SDK required.

PandaDoc to Anvil, term by term

Most of a migration is vocabulary. Here is the map the plugin works from – it keeps these names straight so you do not have to.
PandaDocAnvilWhat it is
DocumentEtch packetThe signing transaction. createEtchPacket returns an etchPacketEid.
Template (template_uuid)Cast (castEid) or dynamic docA reusable template.
Role (roles[].name)Signer idA named signing slot bound to a recipient at create time.
Field (signature, text, date…)FieldA signable or fillable widget assigned to a signer.
field_id / merge_fieldField alias (aliasId)Your data key for a field.
Token ({{Client.CompanyName}})Fill data (data.payloads)A body merge variable – not a signer field. This is the one that trips people up.
signing_orderroutingOrderSigning order; equal values sign in parallel.
Signing sessiongenerateEtchSignURLThe embedded signing URL.
send silent: false / truesignerType: 'email' / 'embedded'Anvil emails the signer vs. you host the signing UI.
Content blocks / rich textDynamic-doc content nodesStructured document body, on the dynamic-doc target.
document.completedetchPacketCompleteAll signers done; documents downloadable.
recipient_completedsignerCompleteOne signer finished.
API-Key headerANVIL_API_KEYAuth. One key for everything.

What the rewrite looks like

PandaDoc’s create, poll, then send sequence is asynchronous. Anvil’s createEtchPacket is a single synchronous call – the poll loop goes away entirely.
Before – PandaDoc
// 1. Create (async – returns status "document.uploaded")
const created = await documentsApi.createDocument({ ... })

// 2. Poll until status === "document.draft"
let doc = await documentsApi.statusDocument({ id: created.id })
while (doc.status !== 'document.draft') {
  await new Promise((r) => setTimeout(r, 2000))
  doc = await documentsApi.statusDocument({ id: created.id })
}

// 3. Send (emails signers)
await documentsApi.sendDocument({ ... })
After – Anvil
const {
  data,
  errors,
} = await anvilClient.createEtchPacket({
  variables: {
    files: [ ... ],
    data: { ... },
    signers: [ ... ],
  },
})
Anvil sends synchronously – there is no document.uploaded → document.draft polling and no separate send call.

How the migration runs

The plugin works through six steps, showing you what it found and what it plans to change before it touches your PandaDoc integration.
Step 1 of 6
Discovery
Scans your codebase for every integration point – SDK imports, API calls, environment variables, webhook handlers, and database columns holding provider IDs.

Where PandaDoc and Anvil differ

No migration is a pure find-and-replace. These are the gaps the plugin raises during mapping – each tagged with what it will cost you to close, from a straight swap to a decision worth making up front.
Works the sameA direct equivalent – nothing to decide.
Minor changeThe same capability, reached a different way in the API call.
Plan for reworkSupported, but you will write code for it.
Create, poll, send
Works the same
Collapses into a single createEtchPacket – a simplification, not a gap.
Signing order
Works the same
routingOrder is a direct equivalent.
Embedded session
Works the same
A signing session becomes generateEtchSignURL in AnvilEmbedFrame.
Roles and fields
Minor change
Roles become signer ids; fields become signer-assigned field aliases.
Tokens
Minor change
A conceptual remap: merge tokens become fill data in data.payloads, not signer fields.
Block and content templates
Plan for rework
Dynamic docs recover the structural fidelity a flat PDF loses. This is the big one – see the migration targets above.
Pricing tables and line items
Plan for rework
Repeating rows are supported; quote logic is not a signing concern and stays in your app.
Conditional content
Plan for rework
Inline conditionals map to clause marks; block and section conditionals are only partially covered.
Signer verification (SMS, passcode)
Plan for rework
Build an auth wall in front of the sign URL if verification is compliance-critical.
OAuth multi-tenant
Plan for rework
PandaDoc’s OAuth lets your app act on behalf of other accounts. Anvil supports this: register an Anvil OAuth app for each tenant to authorize, or give each tenant a child organization with its own API key. Both are Enterprise features.

Coming from somewhere else?

We publish a purpose-built migration plugin for each of these too.
See every migration plugin

Get a demo
(from a real person)

Schedule some time on our calendar to talk through your specific use case and see which Anvil products can help.
    Want to try Anvil first?
    Want to try Anvil first?