Project layout
A Formfeed project is a folder with a formfeed.json. Each template is a folder of plain files below templates/, named after the template's slug.
formfeed.json workspace binding and options
templates/
invoice-de/
template.json name, kind (pdf, image, docx or pptx), engine, description, tags
template.html body in the template language
style.css optional stylesheet
head.html optional extra <head> content (fonts, scripts)
header.html, footer.html optional running header and footer
settings.json page settings: paper, margins, header and footer size, image size, locale, PDF options…
design.json layers of a template made in the visual editor (image templates only)
schema.json data schema (optional, inferred when absent); used by formfeed types and the publish check
i18n.json translations (optional)
data/
default.json sample data sets, one file each; the same sets the editor shows
many-lines.json
tests/
__snapshots__/ approved snapshots (`formfeed test`), one per data set
default.html
many-lines.html
partials/
footer.html fragments the templates include
files/
logo.png the workspace file library: asset('logo.png')
brand/header.svg folders become part of the name: asset('brand/header.svg')
.formfeed/
state.json what was last pulled or pushed (written by the CLI)
brand.json the organisation's brand kit (formfeed brand pull)
formfeed templates pull writes this layout; push reads it. Files map one-to-one to the tabs in the web editor, so a template can be edited in either place. The folder names come from formfeed.json (templatesDir, partialsDir, filesDir; see Configuration).
The files of a template
| File | Required | Content |
|---|---|---|
template.html | yes, or template.docx / template.pptx | The body in the template's language (Jinja2, Liquid or Handlebars); see Template languages |
template.json | no | name, kind (pdf, image, docx, pptx), engine (jinja2, liquid, handlebars), description, tags. Missing fields default to a title made from the slug (invoice-de becomes Invoice De), pdf (or the kind of the document in the folder), the engine of formfeed.json, no description and no tags. push uses them when it creates a template that does not exist yet; for an existing template it changes none of them. |
style.css, head.html | no | The template's stylesheet and extra <head> content |
header.html, footer.html | no | The running header and footer of a PDF; they travel as settings.header.html and settings.footer.html |
settings.json | no | The template's settings without the header and footer HTML; see Settings below |
design.json | no | The layers of an image template made in the visual editor; it travels as settings.design |
schema.json | no | The JSON Schema of the data. Without it, formfeed types and the publish check infer one from the data sets; see Typed template data |
i18n.json | no | Translations for the t helper: locale → key → text |
data/<name>.json | no | Data sets. default.json is the version's sample data (sample_data in the API), the set validate and ci preview use; without it, the first file in alphabetical order takes its place. The other files are the named data sets the editor shows (data_sets). test renders every one. |
tests/__snapshots__/<name>.html | no | Approved snapshots of formfeed test, one per data set |
A template made in the visual editor also has design.json: its layers, from which the editor writes template.html and style.css. Edit those two by hand and they no longer match the design; formfeed validate warns about that (design-stale), and the app then opens the template as code until someone chooses to keep the code or to apply the design again.
Partials
partials/ holds the fragments templates include by name ({% include "footer" %} reads partials/footer or partials/footer.html; a name may contain folders). There are two kinds:
- Partials of the project.
templates pushsends every partial a template includes with the template's version, andtemplates pullwrites the partials of the pulled version back into the folder. A template that includes a partial without a file failsvalidateand is refused bypush. - Shared partials of the organisation.
formfeed partials pullwrites them as<name>.htmland records their versions in.formfeed/state.json;partials pushsends changes back. They are not part of any template version: every render uses their current version, sotemplates pushleaves them out.
Sync state
After each pull or push the CLI records the remote version's number and checksum and a hash of the local files in .formfeed/state.json.
pushskips folders whose files have not changed since then. With--publishit pushes them anyway and publishes the new version; with--channelalone it points the channel at the recorded version.pushsends the recorded checksum asbase_checksum. When a newer version was saved in between, in the editor or by another push, the API answers409 conflictand nothing is pushed: pull the template, merge your changes, and push again, or push with--forceto overwrite.--dry-runshows new, modified, unchanged and ignored folders without touching the server.
Commit .formfeed/state.json if the whole team pushes from the repository and nobody else publishes, ignore it if everyone pulls their own copy. When CI publishes, do not commit it: CI does not commit back what it records, so the committed state goes stale with the first publish; see CI usage.
The file library
files/ mirrors the workspace file library. A file's path below the folder is its library name, and templates reach it with asset('brand/header.svg') or a relative path such as <img src="logo.png">.
formfeed files pushuploads new and changed files (compared by hash, so an unchanged logo is never uploaded again). The same name replaces the file in the library for every template.formfeed files pulldownloads the files the folder is missing and replaces local files whose content differs from the library's.formfeed devserves the folder, soasset()and relative image paths work offline.- A file the library would refuse is skipped with a message: a name with characters other than letters, digits,
.,-and_(plus/between folders), a type other than PNG, JPEG, WebP, GIF, SVG, AVIF and PDF, an empty file, or one over 10 MB.
The files folder is not versioned with a template: a file is shared by every template of the workspace.
Word and PowerPoint templates
A Word or PowerPoint template keeps its document in the folder instead of template.html:
templates/
offer/
template.json kind docx, engine, name, description, tags
template.docx the Word document with tags in its text (template.pptx for PowerPoint)
settings.json default output, locale, time zone, currency
schema.json, i18n.json as for HTML templates
data/
default.json
tests/
__snapshots__/
default.xml the filled document's XML (`formfeed test`)
- A folder holds exactly one of
template.html,template.docxandtemplate.pptx; a folder with two of them is an error. The file decides the kind, and atemplate.jsonwhosekindsays otherwise is an error too. - There is no
style.css,head.html,header.html,footer.htmlor partial: the document brings its own styles, header and footer, and office templates cannot include partials. templates pullwrites the document of exactly the version it pulls, and removes the HTML files when a template's kind changed.templates pushuploads the document only when its SHA-256 differs fromfileSha256, the hash.formfeed/state.jsonrecorded at the last pull or push. Otherwise the new version keeps the latest document and changes only settings, data, schema or translations.templates diffcompares the document by its hash and names the sizes; open both files in Word or PowerPoint to see what changed.formfeed testwrites the snapshots as.xml: the XML of every filled part, one element per line, filled without loading or drawing pictures, so a diff is readable and repeatable.
Settings
settings.json holds the template's settings, the same object the editor's Page setup writes. Settings example for an HTML template:
{
"paper": { "format": "A4", "landscape": false },
"margin": {
"top": "20mm",
"right": "15mm",
"bottom": "20mm",
"left": "15mm"
},
"header": { "height": "12mm" },
"footer": { "height": "10mm" },
"locale": "de-DE",
"timezone": "Europe/Berlin",
"currency": "EUR",
"tailwind": true
}
A Word or PowerPoint template's settings hold the output a render gets when it names none, pdf or the document's own format (the default), and the formatting of its helpers:
{
"office": { "output": "pdf" },
"locale": "de-DE",
"timezone": "Europe/Berlin",
"currency": "EUR"
}