6.4 KiB
date, title
| date | title |
|---|---|
| 2026-05-07 | Pdf Placeholder Selection Fields |
Summary
Extend PDF placeholders so radio and dropdown fields can be configured from the existing Documenso placeholder syntax:
{{FIELD_TYPE, RECIPIENT, key=value, key=value}}
Do not introduce a new delimiter style. Existing applications may already generate placeholders in this format, so the new selection-field behavior should fit into it.
Goals
- Keep the current placeholder grammar unchanged.
- Support checkbox placeholders with option lists, checked values, validation, direction, required, read-only, and font size.
- Support radio placeholders with option lists, default/preselected values, direction, required, read-only, and font size.
- Support dropdown placeholders with option lists, default value, required, read-only, and font size.
- Use
optionsas the only public list key in PDF placeholders. - Convert
optionsinto internalfieldMeta.valuesduring parsing. - Make generated fields usable immediately in the editor, signing UI, preview renderer, and final PDF export.
Non-Goals
- No semicolon placeholder syntax.
- No
valuesalias in PDF placeholder syntax. - No database migration.
- No behavior change for existing placeholders such as
{{text, r1, required=true}}.
Placeholder Syntax
Use the existing comma-separated placeholder format:
{{checkbox, r1, options=Email|SMS|Phone, checked=Email|Phone, validationRule=atLeast, validationLength=1}}
{{radio, r1, options=Card|Bank transfer|Check, defaultValue=Check}}
{{radio, r1, options=Basic|Pro|Enterprise, selected=Pro, direction=horizontal}}
{{dropdown, r1, options=United States|Canada|United Kingdom}}
{{dropdown, r2, options=Sales|Legal|Finance, defaultValue=Legal}}
Use | inside options because , is already the top-level placeholder delimiter.
Parsing rules:
- Split top-level placeholder tokens on unescaped commas.
- Split metadata tokens on the first unescaped equals sign.
- Split
optionson unescaped pipes. - Trim option values and drop empty values.
- Preserve option order.
- Support escaped delimiters:
\,,\=, and\|. - Treat field type values case-insensitively.
Field Type Mapping
checkboxmaps toFieldType.CHECKBOX.radiomaps toFieldType.RADIO.dropdownmaps toFieldType.DROPDOWN.
Metadata Mapping
Checkbox
Example:
{{checkbox, r1, options=Email|SMS|Phone, checked=Email|Phone, validationRule=atLeast, validationLength=1}}
Normalize to:
{
type: FieldType.CHECKBOX,
fieldMeta: {
type: 'checkbox',
validationRule: 'Select at least',
validationLength: 1,
values: [
{ id: 1, value: 'Email', checked: true },
{ id: 2, value: 'SMS', checked: false },
{ id: 3, value: 'Phone', checked: true },
],
},
}
Accepted keys:
optionscheckeddirection=vertical|horizontalvalidationRule=atLeast|exactly|atMostvalidationLength=1required=true|falsereadOnly=true|falsefontSize=12
Map checkbox validation aliases internally: atLeast -> Select at least, exactly -> Select exactly, atMost -> Select at most.
Checkbox placeholders do not support label or placeholder metadata.
Radio
Example:
{{radio, r1, options=Card|Bank transfer|Check, selected=Bank transfer}}
Normalize to:
{
type: FieldType.RADIO,
fieldMeta: {
type: 'radio',
values: [
{ id: 1, value: 'Card', checked: false },
{ id: 2, value: 'Bank transfer', checked: true },
{ id: 3, value: 'Check', checked: false },
],
},
}
Accepted keys:
optionsselected,default, ordefaultValuedirection=vertical|horizontalrequired=true|falsereadOnly=true|falsefontSize=12
Radio placeholders do not support label or placeholder metadata.
Dropdown
Example:
{{dropdown, r1, options=Sales|Legal|Finance, defaultValue=Legal}}
Normalize to:
{
type: FieldType.DROPDOWN,
fieldMeta: {
type: 'dropdown',
values: [{ value: 'Sales' }, { value: 'Legal' }, { value: 'Finance' }],
defaultValue: 'Legal',
},
}
Accepted keys:
optionsselected,default, ordefaultValuerequired=true|falsereadOnly=true|falsefontSize=12
defaultValue should only be set if it matches one parsed option.
Dropdown placeholders do not support label or placeholder metadata.
Code Touchpoints
packages/lib/server-only/pdf/helpers.ts- Extend
parseFieldMetaFromPlaceholdersooptionsnormalizes into checkbox/radio/dropdownfieldMeta.values. - Add delimiter-aware helpers for commas, equals signs, and pipes.
- Extend
packages/lib/server-only/pdf/auto-place-fields.ts- Replace plain comma splitting with delimiter-aware splitting.
- Preserve the existing positional structure: field type, recipient, metadata.
packages/lib/types/field-meta.ts- Keep current internal schemas: checkbox/radio/dropdown still store options as
fieldMeta.values.
- Keep current internal schemas: checkbox/radio/dropdown still store options as
packages/ui/primitives/document-flow/field-content.tsx- Display a radio fallback when a placeholder-created radio has no options.
- Docs:
apps/docs/content/docs/users/documents/advanced/pdf-placeholders.mdxapps/docs/content/docs/developers/api/fields.mdx
Test Plan
Unit tests:
options=Yes|No|Maybebecomes stable radio values.selected=Nomarks only the matching radio option checked.- Checkbox
options,checked,validationRule, andvalidationLengthnormalize correctly. - Dropdown
optionsanddefaultValuenormalize correctly. - Escaped delimiters parse correctly, for example
options=Sales\|Ops|Legal\, Compliance|A\=B.
E2E/API tests:
- Add a PDF fixture with checkbox, radio, and dropdown placeholders using the current syntax.
- Verify created fields have schema-compatible metadata and expected options/defaults.
Suggested verification:
npm run test -w @documenso/lib -- server-only/pdf/helpers.test.ts
npm run test:dev -w @documenso/app-tests -- e2e/auto-placing-fields/auto-place-fields-document.spec.ts
npm run test:dev -w @documenso/app-tests -- e2e/envelope-editor-v2/envelope-fields.spec.ts
npx tsc --noEmit -p packages/lib/tsconfig.json
npx tsc --noEmit -p apps/remix/tsconfig.json
Do not use npm run build for routine verification unless explicitly requested.
Decisions
- Keep the existing placeholder format.
- Use only
optionspublicly. - Keep
valuesas an internal metadata field only. - Use
|as the option delimiter insideoptions.