GitHub Actions
Formfeed templates (formfeed-dev/formfeed-action) is a GitHub Action for repositories that keep their templates as files. It runs the formfeed command line in your workflow: every push is validated and tested, a pull request shows the documents it changes, and the default branch publishes them.
Before you start
- A Formfeed project in the repository: a folder with
formfeed.json, atemplates/folder with one folder per template and, if templates include partials, apartials/folder.npx formfeed initcreates it, andnpx formfeed init --from-workspacealso pulls the published templates of your workspace. See Templates as code. - Two API keys as repository secrets (Settings → Secrets and variables → Actions), named as in the workflow below:
FORMFEED_TEST_KEY, a test key (ff_test_…) with the default scopes, for validation, tests and previews. Its renders cost nothing.FORMFEED_LIVE_KEY, a key with the scopetemplate:write(which includestemplate:read), for the publish step. See Scopes.
- The region. The action has no input for it: the command line takes it from
regioninformfeed.json, or fromFORMFEED_REGIONin the job'senv, and useseuwithout either, which is the only region that renders today. See Configuration.
The workflow
name: templates
on:
pull_request:
paths: ['templates/**', 'partials/**']
push:
branches: [main]
paths: ['templates/**', 'partials/**']
jobs:
templates:
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write # for the preview comment
steps:
- uses: actions/checkout@v4
- uses: formfeed-dev/formfeed-action@v1
with:
api-key: ${{ secrets.FORMFEED_TEST_KEY }}
- if: github.event_name == 'push'
uses: formfeed-dev/formfeed-action@v1
with:
api-key: ${{ secrets.FORMFEED_LIVE_KEY }}
validate: false
test: false
publish: true
The first step runs on every event with the test key. On a pull request it validates, tests, renders the previews and comments; on a push it validates and tests only, because preview: auto renders on pull requests only. The second step runs on pushes to main only, with the live key, and publishes. Adjust paths when formfeed.json names other folders for templates and partials, and working-directory when the project is not at the root of the repository.
What each run does
- Validates every template (
formfeed validate): its syntax, variables the default data set does not have, header and footer, partials it includes that the project lacks, for an invoice template the_invoiceblock of every data set, and for a template that declares PDF/UA-1 (pdf.ua) the findings its source already shows. Offline; no key needed. - Tests every data set (
formfeed test): each is compiled and analysed, and its output, the assembled HTML with header and footer, is compared with the approved snapshot intemplates/<slug>/tests/__snapshots__/<data-set>.html(.xmlfor Word and PowerPoint templates). Approve the current output withnpx formfeed test -uand commit the snapshots. A data set without a snapshot is reported and does not fail the step. - Previews a pull request: the templates it changes are rendered through the API (
formfeed ci preview), every template when a file in thepartials/folder changed. Each is rendered from the files of the pull request with its default data set, so nothing has to be pushed first; Word and PowerPoint templates are filled in the job and converted to PDF through the API, which needs the Starter plan or above. The files are uploaded as the artifactformfeed-preview, together with the summary, also when a render failed. The summary, a table with the data set, the page count and a link for every document, is posted on the pull request as one comment that each run updates. Its links are signed and expire; the files in the artifact stay as long as GitHub keeps artifacts. Every preview is also a render in the workspace's render log, withformfeed ci previewasmeta.source. - Publishes, when
publishis set:formfeed templates push --publishwith the commit's SHA as change note, orformfeed templates push --channel <name>whenchannelis set. See Publishing.
An error fails the step and with it the job, with every finding in the log: a validation error, a failed snapshot comparison, a template that could not be rendered, a push the API refused. Warnings, such as a variable the default data set does not have, are printed and fail nothing.
Inputs
| Input | Default | What it does |
|---|---|---|
api-key | A Formfeed API key. Validation and tests run without one; previews and publishing need one. A test key (ff_test_…) renders previews for free. | |
working-directory | . | The folder of the Formfeed project, the one that holds formfeed.json. |
version | the release the action came with | The version of the formfeed command line the steps run, from npm. |
node-version | 22 | The Node.js version the command line runs on. |
validate | true | Run formfeed validate. |
test | true | Run formfeed test against the approved snapshots. |
preview | auto | Render the changed templates. auto does it on pull requests when api-key is set; true renders on every event, false never. |
base | the pull request's base branch | The git ref the preview compares against, such as origin/main. Outside pull requests there is no base branch: with preview: true set it, or the step fails. |
comment | true | Post the preview's summary on the pull request, updating the action's own comment. Only on pull_request events; needs pull-requests: write. |
publish | false | Push the templates and publish them. Set it on your default branch only, with a key that has template:write. |
channel | With publish, push to this release channel instead of publishing. Named channels need the Starter plan or above. | |
github-token | ${{ github.token }} | The token that writes the comment. |
Inputs are strings: write true and false as shown, any other value counts as off.
Outputs
| Output | What it is |
|---|---|
summary | The absolute path of formfeed-preview.md on the runner when a preview ran; empty otherwise. |
Give the step an id to use it in a later step of the same job, for example to show the summary on the run's page as well:
- id: formfeed
uses: formfeed-dev/formfeed-action@v1
with:
api-key: ${{ secrets.FORMFEED_TEST_KEY }}
- if: steps.formfeed.outputs.summary != ''
run: cat "$SUMMARY" >> "$GITHUB_STEP_SUMMARY"
env:
SUMMARY: ${{ steps.formfeed.outputs.summary }}
Keys and permissions
- Keys belong in the repository's secrets. Use a test key for pull requests: its renders cost nothing and its files are deleted after a day; on the Free plan they carry the test mark. The publish step needs a key with
template:write; give it no more scopes than that. Templates belong to the workspace, not to an environment, so any key with that scope publishes, a test key too: keep it off the key your pull requests use. - Permissions:
contents: readfor the checkout, andpull-requests: writefor the comment. Without the second, the action says so as a warning and the previews are still in the artifact. - Pull requests from forks run without your secrets, so
autoskips the preview there; validation and tests still run. - History: the preview compares against the base branch, so the action fetches the history it needs. A checkout with
fetch-depth: 0saves that step.
Publishing
- Every template, every run.
templates push --publishmakes a new version of every template in the project and publishes it, those that did not change included, with the commit's SHA as change note. A template the workspace does not have yet is created. Withchannel, a template whose files are still what.formfeed/state.jsonrecorded keeps the version recorded there, every other one gets a new version, and the channel is pointed at it. .formfeed/state.json. The push sends the checksum recorded there asbase_checksum, and the action does not commit the file the push updates. If the repository holds the file, the push is refused with409 conflictas soon as a template's latest version in the workspace is no longer the one the file recorded, which the action's own publishing brings about; then runformfeed templates pulland commit the file again. A repository that publishes only through the action can leave the file out (in.gitignore): the push then sends no checksum and publishes the repository's files even when someone published another version in the editor in between, which stays in the template's history. See Project layout.- Breaking schema changes. A template that stores a data schema is checked before it goes live, and a change that would break callers is refused with
409 schema_breaking_change. The action has no input for--allow-breaking: publish such a version yourself, with Publish anyway in the editor orformfeed templates push --publish --allow-breaking. See The schema check. - Publish later instead. To try a change before it is live, set
channel: stagingon the publish step and movepublishedin a separate, manual step; see A typical workflow.
Versions
@v1 is the latest release of the action's first major version; a change that would break a workflow that works today comes as v2. To stay on one release, name it in full, such as @v1.0.0. The input version pins the command line the steps run: its default is the release the action was published with, and setting it yourself keeps a workflow on the one you tested.
The steps written out, without the action, and the same for GitLab, are in CI usage.