Verdocs - Developer Documentation
GuidesTutorials

Fill templates from your data

Give template fields a data key once, then send each envelope with your own data and we place every value in the fields that use it.

When your system already knows what goes in a document, such as a customer's name, a policy number, or a start date, you can send those values with the envelope instead of matching them to field names yourself. You give each field a data key once, and from then on every send passes your data as it is, and we place each value in every field that uses its key.

Give fields a data key

A data key is your name for a value, such as applicant.full_name. Keys use letters, digits, _, and -, with . between levels, up to 255 characters. Text, date, checkbox, radio, and dropdown fields can have one, and several fields can share a key when they show the same value (a name repeated on every page, for example).

You set data_key when you create or update a template field, and the template builder sets it from the "Fill with" menu in the field's Inspector panel. For documents you send without a template, a text tag's k: option sets it, as in {{textbox:Applicant;k:applicant.full_name}} (see Tagging dynamic documents).

Send your data

Pass your data as prefill_data when you create the envelope. Nested objects and dotted keys mean the same key, so these two requests fill the same fields:

import {createEnvelope} from '@verdocs/js-sdk';

const envelope = await createEnvelope(endpoint, {
  template_id: templateId,
  recipients: [{role_name: 'Applicant', first_name: 'Jordan', last_name: 'Rivera', email: 'jordan@example.com'}],
  prefill_data: {
    applicant: {full_name: 'Jordan Rivera', state: 'TX'},
    policy: {effective_date: '2026-11-01', premium: 1234.5, coverage: {flood: true}},
  },
});

// Same thing, flat
const flat = {'applicant.full_name': 'Jordan Rivera', 'applicant.state': 'TX', 'policy.effective_date': '2026-11-01'};

Each kind of field takes its value a little differently:

FieldSend
TextText, a number, or true/false. A number in a plain text field shows with grouping and two decimals ("1,234.50"). A number field stores the number itself, and also accepts text written its locale's way, like "12.450,50" for de-DE.
DateAn ISO 8601 date, like 2026-11-01
Checkboxtrue or false (we also take "yes", "no", 1, and 0)
DropdownThe option's id or its label
RadioThe label or name of the option to select

When you also send a fields entry for a field, that entry wins, so you can still override one placement while the rest come from your data.

See what happened

Whenever you send prefill_data, the response includes a prefill_result. It lists keys by name only, never their values, so it's safe to log.

{
  "placements_filled": 6,
  "unused_keys": ["applicant.ful_name"],
  "missing_keys": ["policy.coverage.flood"],
  "errors": [{"key": "applicant.state", "field": "state", "message": "No dropdown option matches this value"}],
  "format_problems": [{"key": "applicant.email", "field": "email", "message": "Not a valid email"}],
  "hints": [{"key": "applicant.ful_name", "did_you_mean": "applicant.full_name"}]
}

unused_keys are keys you sent that no field uses, and hints suggest the key you most likely meant. missing_keys are keys on the fields that you didn't send. errors are values that couldn't go in their field at all, such as "maybe" for a checkbox, so those fields keep their default.

format_problems are values we did place, but that don't fit the field's format, number range, input mask, pattern, or character limit (the checks in Formatting text fields), or a date field's allowed dates. The signer has to correct those before they can finish, which they can't do on a read-only field, so these are worth fixing at the source.

Strict mode

Set prefill_options: {strict: true} when you'd rather we refuse a send than let a mismatch through. We return a 400 with the same prefill_result, and nothing is created or sent, when a key goes unused, a value has an error or a format problem, or you left out the key for a read-only field. Leaving out a key for a field the signer can fill in is fine, since they'll fill it in themselves.

Strict mode pairs well with dry_run: true, which runs every check a real send makes and returns the value each field would get, without creating anything. js-sdk's previewEnvelope wraps it. Dry runs need a template_id.

Bulk sends

POST /v2/envelopes/bulk takes prefill_data on each entry, so every envelope in the batch gets its own data, and prefill_options once for the whole batch. The response adds prefill_results, one per entry in the same order, with null for entries that didn't send any data. In strict mode we check every entry before any envelope goes out, so one bad row stops the batch instead of leaving it half sent.

{
  "template_id": "d2338742-f3a1-465b-8592-806587413cc1",
  "prefill_options": {"strict": true},
  "entries": [
    {"recipients": [{"role_name": "Applicant", "first_name": "Jordan", "last_name": "Rivera", "email": "jordan@example.com"}], "prefill_data": {"applicant": {"full_name": "Jordan Rivera"}}},
    {"recipients": [{"role_name": "Applicant", "first_name": "Sam", "last_name": "Lee", "email": "sam@example.com"}], "prefill_data": {"applicant": {"full_name": "Sam Lee"}}}
  ]
}

Sends without a template

prefill_data works the same way when you send an envelope without a template. We match it against the data_key on the fields in your request and on the k: options in your documents' text tags. A field that already has a default, from your request or from its tag, keeps it.

Know what a template expects

getTemplateDataContract lists every key a template reads, with its type, how many fields use it, its options, and whether it's needed (a read-only field the signer can't fill in). It also returns a JSON Schema for the template's prefill_data, which you can use to check your payloads before you send them, and a contract_hash that changes only when the keys, their types, their options, or whether they're needed do. Storing that hash in a CI check is an easy way to hear about a template change before it reaches your sends.

import {getTemplateDataContract} from '@verdocs/js-sdk';

const {keys, json_schema, contract_hash} = await getTemplateDataContract(endpoint, templateId);

On this page