Integrations
Cloudflare Pages
Cloudflare Pages can host your datafiles, and the Catalog in a project of its own, published by a GitHub Actions workflow.
This page builds on GitHub Actions. Set up its project, .nvmrc and checks workflow first, then use the publish workflow below in place of the one there.
Create a Pages project#
Create a Cloudflare Pages project for the datafiles. The publish workflow uploads to it by name.
Turn off the project's automatic deployments, so Cloudflare does not deploy alongside your workflow. The branch build controls guide shows how.
Add the secrets#
Follow Cloudflare's direct upload guide to create these two secrets in the repository's Settings > Secrets and variables > Actions:
CLOUDFLARE_ACCOUNT_IDCLOUDFLARE_API_TOKEN
Publish the datafiles#
This workflow runs on every push to main or master. It checks, builds, pushes the state, then deploys the datafiles directory:
name: Publishon: push: branches: - main - masterpermissions: contents: writeconcurrency: group: featurevisor-publish cancel-in-progress: falsejobs: ci: name: Publish runs-on: ubuntu-latest timeout-minutes: 10 steps: - uses: actions/checkout@v7 with: ref: ${{ github.ref_name }} fetch-depth: 0 - uses: actions/setup-node@v7 with: node-version-file: .nvmrc cache: npm - name: Install dependencies run: npm ci - name: Lint run: npx featurevisor lint - name: Build run: npx featurevisor build - name: Test specs run: npx featurevisor test - name: Git configs run: | git config user.name "${{ github.actor }}" git config user.email "${{ github.actor }}@users.noreply.github.com" - name: Push back to origin run: | git add -A .featurevisor if ! git diff --cached --quiet; then git commit -m "[skip ci] Revision $(cat .featurevisor/REVISION)" fi git push origin "HEAD:${{ github.ref_name }}" - name: Upload to Cloudflare Pages run: | echo "<html><body>It works.</body></html>" > datafiles/index.html npx wrangler pages deploy datafiles --project-name="YOUR_CLOUDFLARE_PAGES_PROJECT_NAME" env: CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}The workflow pushes the state files before it deploys. If the push is rejected, run it again from the latest main and never force push. If the deployment fails, keep the pushed state and run it again. The concurrency and branch protection rules from GitHub Actions apply here too.
Each target's datafile is then at its path under the project:
https://<your-project>.pages.dev/featurevisor-all.jsonhttps://<your-project>.pages.dev/production/featurevisor-web.json, with environments
A custom domain replaces the first part of the URL. Applications fetch these URLs with an SDK.
Publish the Catalog#
To publish the Catalog, export it and deploy it to a second Pages project, so its HTML fallback starts at that site's root. Use this workflow instead of the one above, not alongside it:
name: Publish with Catalogon: push: branches: - main - masterpermissions: contents: writeconcurrency: group: featurevisor-publish cancel-in-progress: falsejobs: ci: name: Publish runs-on: ubuntu-latest timeout-minutes: 10 steps: - uses: actions/checkout@v7 with: ref: ${{ github.ref_name }} fetch-depth: 0 - uses: actions/setup-node@v7 with: node-version-file: .nvmrc cache: npm - name: Install dependencies run: npm ci - name: Lint run: npx featurevisor lint - name: Build run: npx featurevisor build - name: Test specs and save report run: npx featurevisor test --json > test-report.json - name: Git configs run: | git config user.name "${{ github.actor }}" git config user.email "${{ github.actor }}@users.noreply.github.com" - name: Push back to origin run: | git add -A .featurevisor if ! git diff --cached --quiet; then git commit -m "[skip ci] Revision $(cat .featurevisor/REVISION)" fi git push origin "HEAD:${{ github.ref_name }}" - name: Export Catalog run: npx featurevisor catalog export --out-dir=./catalog --test-report=./test-report.json - name: Upload to Cloudflare Pages run: | echo "<html><body>It works.</body></html>" > datafiles/index.html npx wrangler pages deploy datafiles --project-name="YOUR_CLOUDFLARE_PAGES_PROJECT_NAME" env: CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} - name: Upload Catalog to Cloudflare Pages run: npx wrangler pages deploy catalog --project-name="YOUR_CATALOG_PAGES_PROJECT_NAME" env: CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}Keep fetch-depth: 0 on checkout. Fetching the full history lets the exported Catalog show earlier changes, not only the latest commit.
Replace both project name placeholders with your datafile and Catalog project names.
Use the same Cloudflare account and token setup described above, with access to the Catalog project. Upload the complete catalog directory, including index.html, assets and data. Do not replace its generated index.html with the placeholder used for the datafile deployment.
Cloudflare Pages serves a single page application's root HTML for unmatched paths when the deployment has no top level 404.html. Keep that file absent to use Catalog's default browser history routing. Entity links such as https://<catalog-project>.pages.dev/features/checkout then load the Catalog on direct visits and refreshes.
If your hosting setup supplies a 404 page or cannot provide that fallback, export with --hash-router instead. After deployment, open an entity link directly and refresh it to verify the routing.
Keep the order build, test, then catalog export, and do not rebuild or change definitions between the report and export, because the report's fingerprint includes the allocation state the build writes. Run these commands as GitHub Actions steps, where a failed test stops deployment.
Catalog exports include project definitions, Git history and the supplied test results. Reports can contain test contexts, expected values and actual values. A Pages project is public unless you put access control such as Cloudflare Access in front of it, so a Catalog published this way shows every definition, unlaunched work included, to anyone with the URL. Keep its generated UI and data together, and follow the export retention guidance when updating an existing deployment.
Related#
- GitHub Actions
- Deployment
- The Cloudflare example repository, which publishes datafiles without the Catalog

