Skip to main content

Templates as code

If you prefer your editor and git over a web UI, the formfeed CLI keeps every template as a folder of plain files. You get a local preview with hot reload, offline validation with the production engine, snapshot tests, and a publish step for CI. The web editor and the files stay interchangeable: what you push shows up in the editor, and what someone edits there you can pull.

You need Node.js 22 or later.

1. Log in​

npx formfeed login

The CLI prints a short code and opens app.formfeed.dev/device. Approve it, pick a workspace, and the CLI stores an API key named "Formfeed CLI on" and your machine's name. That key is a live key with the scopes of your role in the organisation, so renders with it are metered. On a server or over SSH, add --no-browser and open the printed URL yourself.

To work with a free test key instead, create one on the API keys page and store it with npx formfeed login ff_test_…. In CI, skip the login and set FORMFEED_API_KEY. When several are present, --api-key wins over FORMFEED_API_KEY, which wins over the stored key.

npx formfeed whoami # workspace, plan and usage of the key in use

2. Create a project​

Start from your workspace's published templates, together with the organisation's shared partials they may include:

npx formfeed init my-templates --from-workspace
cd my-templates

Or start empty. Without --from-workspace you get a hello starter template. --engine sets the template language of new templates (jinja2, the default, liquid or handlebars):

npx formfeed init my-templates --engine liquid

Either way you get this layout (the full reference is in Project layout):

my-templates/
formfeed.json project settings
templates/
invoice/
template.json name, kind, engine
template.html the body
style.css
header.html optional running header
footer.html optional running footer
settings.json paper, margins, locale…
data/default.json sample data (one file per data set)
offer/
template.json kind docx
template.docx a Word template: the document instead of the HTML files
settings.json
data/default.json
partials/ shared fragments

A Word or PowerPoint template is a folder with template.docx or template.pptx, edited in Word or PowerPoint.

3. Preview with hot reload​

npx formfeed dev invoice
# ➜ preview on http://localhost:4400

--port picks another port, --data the data set the preview starts with, and --open opens the browser. Edit template.html, style.css or a data file, and the browser shows the new pages as soon as you save, with headers, footers and page numbers. Diagnostics (unknown variables, unclosed tags, unknown filters) appear with their line numbers. The preview runs offline; nothing is sent to Formfeed until you ask for a true render. For a Word or PowerPoint template, the page lists the findings of its tags by part and paragraph, and the true render shows the converted PDF. See Dev server.

4. Validate and test​

npx formfeed validate # compile every template and check it against its default data set; exit 1 on errors
npx formfeed test # render every data set and compare the HTML (XML for Word and PowerPoint) with approved snapshots
npx formfeed test -u # approve the current output as the new snapshot

validate checks against the default data set unless --data <name> names another; test --data <name> runs one data set only. Both take a slug to check one template, and both run offline with the same engine as production. Put validate in a pre-commit hook. For assertions in your own test suite (Vitest or Jest), see Testing templates.

5. Render a real PDF​

npx formfeed render invoice --data default --out invoice.pdf

This fills your local files with the data set on your machine, sends the document through the API and writes the PDF production would produce. --data takes a data set name or the path of a JSON file; --remote renders the published version in the workspace instead of the local files. It is free with a test key; with the live key formfeed login stores, it costs units.

A Word or PowerPoint template is filled on your machine instead, with the engine the render servers use, because the API renders only saved versions of such templates:

npx formfeed render offer --output docx --out offer.docx # the filled document, no render
npx formfeed render offer --output pdf --out offer.pdf # filled here, converted by the API

Without --output, the template's default output decides (office.output in settings.json, else the document's own format). The PDF comes from POST /pdf/convert, which needs the Starter plan or above; the filled document needs only a key. QR codes, barcodes and SVG pictures are drawn by @resvg/resvg-js, an optional dependency npm installs with the CLI where the package supports the platform; without it they are left out with a warning.

6. Push and publish​

npx formfeed templates diff # what changed against the workspace
npx formfeed templates push --dry-run # which templates are new, modified or unchanged
npx formfeed templates push # create draft versions from your files
npx formfeed templates push --publish # …and publish them
npx formfeed templates push --channel staging # …or point the staging channel at them

push skips templates whose files have not changed since the last pull or push (unless --publish or --channel asks for more), and --message gives the new versions a note. It refuses to overwrite when the template got a newer version in the workspace since your last pull or push, saved in the editor or pushed by someone else: it answers with a conflict instead. Run npx formfeed templates pull <slug>, merge, and push again, or pass --force to push over it.

When a template stores a data schema, --publish and --channel also refuse a change that would break callers of the current version, until you add --allow-breaking; see Release channels. npx formfeed types generates types for your application code from the same schemas (Typed template data).

7. Let CI do it​

Validate and preview on every pull request, publish on merge. Use a test key for the checks and a live key only for the publish step:

# .github/workflows/templates.yml
name: templates
on:
pull_request: { paths: ['templates/**', 'partials/**'] }
push: { branches: [main], paths: ['templates/**', 'partials/**'] }

jobs:
templates:
runs-on: ubuntu-latest
env:
FORMFEED_API_KEY: ${{ secrets.FORMFEED_TEST_KEY }}
steps:
- uses: actions/checkout@v4
with: { fetch-depth: 0 }
- uses: actions/setup-node@v4
with: { node-version: 22 }
- run: npx formfeed validate --ci
- run: npx formfeed test --ci
- if: github.event_name == 'pull_request'
run: npx formfeed ci preview --base origin/main --ci
- if: github.event_name == 'push'
run: npx formfeed templates push --publish --message "${{ github.sha }}" --ci
env:
FORMFEED_API_KEY: ${{ secrets.FORMFEED_LIVE_KEY }}

formfeed ci preview renders only the templates changed since the base ref (hence fetch-depth: 0) into formfeed-preview/ and writes a Markdown summary, formfeed-preview.md, you can post as a pull-request comment. More in CI usage. On GitHub, the Formfeed GitHub Action runs the same steps and keeps the comment up to date for you.

Where to go next​