Word and PowerPoint templates
An office template is a Word document (kind docx) or a PowerPoint presentation (kind pptx) that you write in Word or PowerPoint yourself. Tags in Jinja2, Liquid or Handlebars are typed into the document text like any other text, and a render fills them with data. The result keeps everything the file already has: styles, headers and footers, page setup, tables, pictures and slide design.
Offer {{ offer.number }} for {{ customer.name }}
A render produces the filled document (docx or pptx) or a PDF of it. Use an office template when the document is maintained in Word or PowerPoint by people who do not write HTML, or when the recipient needs an editable file. For documents designed in HTML and CSS, use a PDF template.
Create an office template
- In the app
- With the API
- As code
- On the Templates page, choose New template.
- Set Kind to Word document or PowerPoint presentation, and pick the Engine whose tag syntax you want to type.
- Choose Start from the Formfeed example (an offer with sample tags) to get a file with example tags and sample data that fills it, or Upload my own file and Choose file….
- Create template.
The template opens on its own page (see below). To change the document, download it, edit it in Word or PowerPoint, and upload it again with Replace file….
Send the file as multipart/form-data to POST /v1/templates:
curl https://api-eu.formfeed.dev/v1/templates \
-H "Authorization: Bearer $FORMFEED_API_KEY" \
-F name="Offer" -F slug=offer -F kind=docx -F engine=jinja2 \
-F 'sample_data={"customer": {"name": "Olvarest GmbH"}}' \
-F publish=true \
-F file=@offer.docx
file is the document. The other fields are form fields: settings, sample_data, data_sets, data_schema, i18n and tags as JSON text, publish as true or false. The key needs the scope template:write. New versions, downloads and the SDK calls are in the Templates API.
A template folder holds template.docx or template.pptx instead of template.html, and template.json names the kind. formfeed templates push uploads the document when it changed. See Project layout and Templates as code below.
A template file can be up to 20 MB (413 file_too_large above that). Its type is read from its content, never from its name: a Word template takes a .docx file, a PowerPoint template a .pptx file. Anything else answers 415 file_type_unsupported: macro-enabled files (.docm, .pptm), Word and PowerPoint templates and slide shows (.dotx, .potx, .ppsx), the old binary formats (.doc, .ppt), and a file of the other kind. Open such a file in Word or PowerPoint and save it as .docx or .pptx first. A password-protected file answers 422 source_encrypted; remove the password before you upload it.
Tags
Type tags into the document as plain text, in the syntax of the template's engine. Everything the template languages offer for text works: variables, filters, if, for, set, comments.
- Jinja2
- Liquid
- Handlebars
Dear {{ customer.contact }},
Total: {{ offer.total | money }}, valid until {{ offer.valid_until | date("dd.MM.yyyy") }}
{% if offer.discount %}including {{ offer.discount }} % discount{% endif %}
Dear {{ customer.contact }},
Total: {{ offer.total | money }}, valid until {{ offer.valid_until | date: "dd.MM.yyyy" }}
{% if offer.discount %}including {{ offer.discount }} % discount{% endif %}
Dear {{customer.contact}},
Total: {{money offer.total}}, valid until {{date offer.valid_until "dd.MM.yyyy"}}
{{#if offer.discount}}including {{offer.discount}} % discount{{/if}}
- Formatting follows the tag. The value takes the formatting of the tag's first character. Make
{{ customer.name }}bold in Word, and the name is bold. Text outside tags is not touched. - Word may split a tag. Spell checking, a correction or a change of formatting in the middle of a tag makes Word store it in several pieces. Formfeed joins them again, as long as the tag stays in one paragraph.
- Autocorrect inside tags is undone. Word turns
"into typographic quotes as you type, which would breakdate("dd.MM.yyyy"). Inside{{ }},{% %}and{# #}, Formfeed turns typographic quotes back into straight ones, en and em dashes into-,…into..., non-breaking spaces into spaces, and drops zero-width characters. The Tags panel notes each change (office-tag-autocorrected). Text outside tags keeps Word's typography. - Values are text. Whatever a tag prints is inserted as text, HTML included, so data cannot change the document's structure. Line breaks and tabs in the data become line breaks and tabs in the document; characters that documents cannot contain are removed.
<,>and&work as typed:{% if total > 1000 %}.
Loops and conditions over paragraphs and rows
A block tag ({% for %}, {% if %}, {% else %}, {% endfor %}, Liquid's {% tablerow %}, {{#each}}, {{else}}, {{/if}} and so on) that is alone in its paragraph stands for that whole paragraph. The paragraph disappears from the result, so a loop repeats the paragraphs between its tags, and a condition shows or hides them. A {% set %} or a comment alone in its paragraph disappears with its paragraph too, so it leaves no empty line:
{% for line in offer.lines %}
• {{ line.description }}: {{ line.total | money }}
{% endfor %}
If that paragraph is also alone in its table row, meaning every other cell of the row is empty, the tag stands for the whole row. A table with a row holding {% for line in offer.lines %}, a data row and a row holding {% endfor %} repeats the data row once per line, and the two tag rows are removed:
| Description | Qty | Total |
|---|---|---|
{% for line in offer.lines %} | ||
{{ line.description }} | {{ line.qty }} | {{ line.total | money }} |
{% endfor %} |
The rule is: alone in the paragraph, alone in the row. A block tag inside running text stays inline and affects only that text: {% if paid %}PAID{% endif %}.
- The opening and the closing tag of a block must be in the same place: both in the body, both in the same table cell, header, footer or text box. Otherwise the template reports
office-block-unbalanced. - The structure Word and PowerPoint need survives: a tag paragraph that carries a section break is emptied instead of removed, a table cell or text box whose paragraphs were all tags keeps an empty paragraph, and a table left without rows (a loop over an empty list) disappears.
- A loop cannot repeat footnotes, endnotes or comments (
office-reference-in-loop). Move them out of the loop.
What cannot be templated
| In the document | What happens | Code |
|---|---|---|
| A tag that crosses a paragraph, a table cell, a hyperlink, a content control, a field or a tracked change | Refused: the tag cannot be joined without moving text out of its element. Retype the tag so it starts and ends inside the same element. | office-tag-spans-elements |
A tag inside a field code (page numbers, HYPERLINK, INCLUDETEXT, …) | Left as it is, with a warning. Fields keep their own function; only visible text is filled. | office-tag-in-field |
| A tag in the address of a link inserted with Word's link dialog | Left as it is. | — |
| Tracked changes | Tags in inserted text are filled, deleted text is ignored, and the revision marks stay. Accept all changes before you upload. | — |
{% include %}, {% render %}, {% extends %}, {% import %}, {% from %}, {{> partial }} | Refused: partials hold HTML, which a Word or PowerPoint file cannot contain. Use Word's own headers, footers and building blocks. | unsupported-in-office |
The template page lists these findings per part and paragraph. An error stops the render with template_syntax_error or unsupported_in_office, and the problem's location names the part and the paragraph instead of a line and a column:
{
"type": "https://docs.formfeed.dev/errors/template-syntax",
"title": "Template syntax error",
"status": 422,
"detail": "word/document.xml: The tag {{ customer.name }} is split across a paragraph, a link, a field or a tracked change. Retype it in one go.",
"code": "template_syntax_error",
"location": { "part": "word/document.xml", "paragraph": 3 },
"request_id": "req_3f9a1c2e7b4d40a8b6e1c0d9a2"
}
The part is the file inside the document: word/document.xml is the body, word/header2.xml a header, ppt/slides/slide3.xml the third slide. Paragraphs are counted from 1 within that part, as the Tags panel numbers them (¶3).
Where tags are filled
- Word: the body (text boxes included), every header and footer, footnotes and endnotes.
- PowerPoint: every slide, including text boxes and tables, and the speaker notes.
Tags anywhere else stay as typed: in Word comments, charts, SmartArt and the document's properties, and on PowerPoint's slide masters and layouts.
Helpers
The helpers that return text work as in HTML templates: money, date, number, t, numToWords and the rest, with the template's locale, time zone and currency. nl2br and safe return the plain text (a line break in it becomes a line break in the document), and asset returns the address of a library file as text.
Helpers that draw something insert a picture:
| Helper | In the document |
|---|---|
qrcode, epcQr | A QR code or GiroCode. size sets width and height; without it, the code's own size (160 px). |
barcode | A barcode at its natural size, which height, width and scale of the helper decide. |
image | The picture at an https: address, a data URI or a name in the file library. width and height set the size, alt becomes the picture's alternative text. fit has no effect in a document. |
pageBreak | A page break (Word only). |
- Jinja2
- Liquid
- Handlebars
{{ qrcode(offer.url, { size: 96 }) }}
{{ image(customer.logo, { width: "40mm" }) }}
{{ epcQr({ name: company.name, iban: company.iban, amount: offer.total, reference: offer.number }) }}
{{ offer.url | qrcode: size: 96 }}
{{ customer.logo | image: width: "40mm" }}
{{ company.iban | epcQr: name: company.name, amount: offer.total, reference: offer.number }}
{{qrcode offer.url size=96}}
{{image customer.logo width="40mm"}}
{{epcQr name=company.name iban=company.iban amount=offer.total reference=offer.number}}
- Sizes are pixels at 96 dpi when given as numbers, or lengths with a unit:
px,pt,mm,cm,in. With one ofwidthandheight, the other keeps the aspect ratio. Without a size, a picture takes its natural size, and in Word it is never wider than the text column. - Pictures are fetched by Formfeed with the same checks as in HTML templates: only
https:addresses, and private and internal addresses are refused. PNG and JPEG go into the document as they are; SVG (every code is one), WebP, GIF and AVIF are drawn as PNG first. - A picture that cannot be loaded is left out. The render still succeeds and its answer does not mention it; the quick preview and
formfeed renderlist it as a warning, so try new data there first. - A document can hold at most 50 pictures, codes and page breaks, with 20 MB of picture data in total (
office_document_too_large). chartandmarkdownreturn HTML and are not supported in office templates (unsupported-in-office).
The brand kit is available as brand: {{ brand.legal_footer }} in a footer, {{ image(brand.logo.primary, { height: "12mm" }) }} for the logo.
PowerPoint
Presentations follow the same rules, with these differences:
- A slide cannot hold a picture inside text, so a code or picture becomes a picture shape. A text box that holds nothing but the helper is replaced by the picture, placed in the box: draw a box where the code goes and type
{{ qrcode(offer.url) }}into it. Without a size of its own, or with one larger than the box, the picture is fitted into the box and keeps its aspect ratio; several pictures in one box share it from top to bottom. In a text box that grows with its text (PowerPoint's default for new text boxes), only the box's width counts. If the text box also holds text, the text stays and the picture is placed over the box. A placeholder whose position comes from the slide layout puts the picture in the middle of the slide. pageBreakhas no meaning on a slide and is left out.- Loops repeat paragraphs inside a text box and rows of a table. Whole slides cannot be repeated.
- The quick preview is for Word only; check presentations with the PDF preview.
The office template page
Office templates open their own page instead of the code editor. The document is analysed in your browser, so the panels update as soon as you choose a file. The left side has six panels:
| Panel | What it does |
|---|---|
| File | The document with its size. Replace file… chooses a new document (uploaded when you save the next version; Keep the saved file takes the choice back), Download gets the current one. For Word, Fonts lists the fonts the document names, marks embedded ones, and notes every font the converter will replace. |
| Tags | Every tag, grouped by part (document, header, footer, footnotes, endnotes, slide, notes) with its paragraph number, and the findings of the checks above. Copy a tag from here. |
| Data | The data sets as JSON and the schema. Insert in the schema copies the field's tag, ready to paste into Word or PowerPoint. |
| Settings | Output when a render names none (the template's own format or PDF), and Locale, Time zone and Currency for the helpers. Paper, margins, headers and footers come from the document. |
| Versions | Saved versions and release channels, as for HTML templates. |
| API | The request that renders this template, in eight languages, with its slug and the data set, and a test request, as in the editor's API panel. |
Save version and Publish work as in the editor. The right side shows two previews:
- Quick preview (Word only) fills the document in your browser and shows it as a web page, instantly and approximately: Word and the PDF can break lines and pages differently. Zoom with the buttons, Fit,
Ctrland the mouse wheel, orCtrlwith+,-and0; a click on the percentage goes back to 100 %. Warnings, such as a picture that could not be loaded, stand above the preview. Filled document downloads the filled file, from the Starter plan on. - PDF renders the saved file with the current data and settings on our servers and shows the PDF the API returns. Choose True render. It costs nothing; on the Free plan the PDF carries the test mark. A replaced file must be saved first.
Outputs
A Word template renders docx or pdf, a PowerPoint template pptx or pdf; any other output answers 400 invalid_request. Without output, a render produces settings.office.output if the request's or the template's settings set it, else the template's own format:
curl https://api-eu.formfeed.dev/v1/renders \
-H "Authorization: Bearer $FORMFEED_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"template": "offer",
"data": { "customer": { "name": "Olvarest GmbH" } },
"output": "pdf"
}'
The answer is a render as for every template (Rendering). A docx or pptx output has no page_count, because Word and PowerPoint lay out the pages themselves.
The PDF is converted with LibreOffice from the filled document, so page setup, headers, footers and page numbers come from the file. Before the conversion, links to pictures or files outside the document (Word's Link to file) are removed, because the converter loads nothing; hyperlinks stay. pdf.metadata.author, subject and keywords from the settings are written into the PDF; pdf.metadata.title is not used for office templates.
Post-processing (merge, watermark, password) and the PDF tools work on that PDF; on a docx or pptx output, post answers 400 invalid_request. The PDF can be a ZUGFeRD or Factur-X e-invoice like the PDF of an HTML template: declare einvoice in the template's settings or send post.einvoice, with the _invoice block in the data. It cannot be a PDF/UA-1 file: pdf.ua on an office template answers 400 invalid_request, so an e-invoice from a Word template is a PDF/A-3b without it. To convert a docx or pptx output later, pass its render id to /pdf/convert.
Filled documents are always downloaded as attachments. html and url sources cannot produce docx or pptx.
Fonts
The PDF is only as exact as the fonts the converter has. It has these families installed:
- Carlito, Caladea, and Liberation Sans, Serif and Mono;
- the DejaVu families (Sans, Serif, Mono and their condensed and light cuts);
- Noto Sans, Noto Serif and Noto Mono, with the Noto families for nearly every other script, CJK (Chinese, Japanese, Korean), Arabic, Hebrew, Devanagari and Thai among them, plus Noto Color Emoji and OpenSymbol.
A font the document names by one of these names is used as it is. Fonts that the document neither embeds nor finds there are replaced:
- Calibri, Cambria, Arial, Helvetica, Times New Roman and Courier New are replaced by metric-compatible fonts (Carlito, Caladea, Liberation), so lines and pages break the same.
- Any other font, including Aptos, Office's default font since 2024, is replaced by a font with different metrics, so the PDF can look different from Word. The File panel warns about each one.
To get an exact PDF, embed the fonts in the document: in Word, File › Options › Save › Embed fonts in the file. The organisation's uploaded fonts are not embedded into office documents automatically.
Plans, units and limits
| Plans | Live renders of office templates, the Filled document download in the app, and office conversion need the Starter plan or higher; on Free, a live render answers 403 forbidden with "feature": "office". Free workspaces can create office templates (within the plan's five templates) and test-render them to PDF, with the test mark. A test render that asks for docx or pptx there answers 403 test_output_unavailable. |
| Units | A docx or pptx output costs 1 unit. A PDF output follows the PDF rule, 1 unit per started 5 pages. Test keys cost nothing. See units. |
| Template file | 20 MB (file_too_large); inside, at most 2,000 parts, 200 MB unpacked and 20 MB for a single part (office_document_invalid or office_document_too_large). |
| Filled document | 50 MB before compression, 50 pictures, codes and page breaks, 20 MB of pictures (office_document_too_large). The plan's output size limit applies to every output (output_too_large). |
| Pages | The plan's page limit applies to the PDF (page_limit_exceeded). |
| Time | Filling the document may take 10 seconds, the whole render including the conversion 60 seconds (render_timeout). |
Templates as code
In a project, an office template is a folder with template.json ("kind": "docx" or "pptx"), template.docx or template.pptx, and the usual data/, schema.json and settings.json.
formfeed templates pulldownloads the document of the pulled version;pushuploads it only when its content changed since the last sync.formfeed validateandformfeed devlist the findings per part and paragraph.formfeed renderfills the document on your machine and writes thedocxorpptx; for PDF it converts the filled file through/pdf/convert, which needs the Starter plan. That PDF is a plain one: an e-invoice the settings declare is made only by the pushed template, withformfeed render --remote.formfeed devhas no local preview of a Word or PowerPoint document. Press True render once; from then on it converts and shows the PDF again on every save.formfeed testsnapshots the filled XML of each part astests/__snapshots__/<data set>.xml, so a diff shows what changed. Pictures are not part of the snapshot.
Codes and SVG pictures are drawn locally when the optional @resvg/resvg-js package is installed next to the CLI (npm install -g @resvg/resvg-js for a global CLI); WebP, GIF and AVIF pictures are left out locally with a warning, as the API alone draws them.
Errors
| Code | Status | When |
|---|---|---|
template_syntax_error | 422 | A tag cannot be read, or a check above failed. location names the part and paragraph. |
template_runtime_error | 422 | A tag failed with this data. |
unsupported_in_office | 422 | An include, a partial, chart or markdown. |
office_document_too_large | 422 | The file, the filled document or its pictures are above the limits. |
office_document_invalid | 422 | The file is damaged or fails the safety checks. |
source_encrypted | 422 | The uploaded file is password-protected. |
file_type_unsupported | 415 | The upload is not a Word or PowerPoint file of the template's kind, or it contains macros. |
file_too_large | 413 | The template file is larger than 20 MB. |
forbidden | 403 | A live render, or a conversion through /pdf/convert, on a plan without office documents ("feature": "office"). |
test_output_unavailable | 403 | A Free test render asked for docx or pptx. |
invalid_request | 400 | An output the template's kind cannot produce, post on a docx or pptx output, pdf.ua, or an office template created without file. |
output_too_large | 422 | The output is larger than the plan allows. |
page_limit_exceeded | 422 | The PDF has more pages than the plan allows. |
source_invalid | 422 | The converter could not read the filled document. |
office_conversion_failed | 422 | The filled document could not be converted to PDF. |
render_timeout | 504 | Filling took longer than 10 seconds, or the whole render longer than 60. |
office_unavailable | 503 | The converter is busy or not reachable; retry after Retry-After. |