Skip to content

Let business users edit content

Content fields are the parts of a template that a content editor may change in Studio without seeing its code: a logo, an accent colour, payment terms, a closing line. A developer marks them once. A content editor then fills them in a form, previews the document, and publishes it.

Open the template in Workspace → Editor and choose + → Add fields.json at the end of the tab bar under its source. List the fields there:

fields.json
[
{
"key": "accent",
"label": "Accent colour",
"type": "colour",
"group": "Brand"
},
{ "key": "logo", "label": "Logo", "type": "image", "group": "Brand" },
{
"key": "terms",
"label": "Payment terms",
"type": "richText",
"group": "Footer"
}
]
Property What it does
key Required. The name the template reads it by: a letter, then letters, digits or _, up to 64.
label Required. What the form shows.
type Required. One of the types below.
group Fields with the same group are shown together under it.
help A hint shown under the input.
multiline A text field that holds several lines.
maxLength The most characters a text or richText value holds.
options Required for choice, and only allowed there: the values it offers.
type Value Default limit
text Plain text, one line unless multiline 2,000 characters
richText Paragraphs, bold, italic, links and bulleted or numbered lists, as HTML 20,000 characters
image An uploaded PNG, JPEG or SVG
colour A colour such as #1f4fd1
toggle On or off
choice One of options
number A number
date A date such as 2026-10-16

A template has up to 100 fields, and their values take up to 64 KB together. The Invoice example template comes with an accent colour, a logo and payment details already marked.

The values are in the data as $content:

template.html
<style>
h1 { color:{{$content.accent}}; }
</style>
{{#if $content.logo}}<img src="{{$content.logo}}" alt="" />{{/if}}
<h1>Invoice</h1>
{{#each items}}
<p style="border-color: {{@root.$content.accent}}">{{description}}</p>
{{/each}}
<footer>{{{$content.terms}}}</footer>
  • Write rich text with three braces, {{{$content.terms}}}, so its formatting renders. Whatever is typed or pasted, only paragraphs, line breaks, bold, italic, links (http, https and mailto) and lists are kept.
  • Inside {{#each}} and other blocks, read the values as @root.$content.key.
  • An image value is the uploaded image’s ID; use it as an src as above.
  • A field without a value renders as empty.
  • The content.json tab holds the values. The Bindings tab and autocomplete list them.
  • Request data cannot set $content: a render or preview whose data has a $content key answers 400 BAD_REQUEST.

Publish the template once yourself. A content editor can only publish a template that has been published before.

In Manage → Team, invite the person with the role Content editor, or select someone and Change role. A content editor sees only Content in Studio and can:

  • change the values of content fields, preview the document with sample data, and publish it;
  • upload images for image fields;
  • read templates, render them, and read the workspace’s renders.

They cannot change a template’s code, its sample data or workspace files, and cannot see API keys, webhooks, the team or the audit log.

A content editor opens Content, which lists the templates that have content fields and whether their changes are published, and picks one:

  1. Change the fields in the form, and click Update preview to see the document with the template’s sample data. For a template that fills an Excel workbook, click Download spreadsheet instead.
  2. Click Save. Each save is kept as a revision; History shows the earlier versions and what each changed, and Use this version loads one back into the form.
  3. Click Publish. Documents render with the new values from then on.

A content editor’s publish never ships a developer’s unfinished work. It is refused with 403 FORBIDDEN while the draft has changes other than content values that are not published yet; a developer publishes those first.

When two people change the same template, a save keeps what the other saved in the meantime:

  • Two content editors: each save keeps the other’s values and changes only the fields this editor changed.
  • A content editor and a developer with the template open: the developer’s next save keeps the content editor’s values, unless the developer changed content.json too.

A value left over from a field a developer removed is dropped at the next save.

Save content values with PUT /v1/templates/{id}/content and the template:content scope, plus template:read. It replaces the values on the latest draft and keeps everything else:

Terminal window
curl -X PUT "$PDF_URL/v1/templates/$TEMPLATE_ID/content" \
-H "x-api-key: $API_KEY" \
-H "content-type: application/json" \
-d '{"values":{"accent":"#1f4fd1","terms":"<p>Due in <strong>30 days</strong>.</p>"}}'
  • Values must fit their fields; a key no field declares, or a value of the wrong kind, answers 400 BAD_REQUEST. A template without content fields answers 400 too.
  • Send "expectedRevision" to refuse the change with 409 CONFLICT if the draft has changed since you read it.
  • GET /v1/templates?content=true lists the templates that have content fields. Each template carries publishedRevision, the draft revision its live version was published from.
  • Publishing with template:publish but without template:write follows the rule above.
  • Uploading an image for an image field needs template:content or asset:manage.

Each save is recorded in the audit log as template.content_updated, with the fields it changed. A workspace export carries fields.json and content.json, and importing it replaces the values in the target environment.