Code references

See where your code reads each flag, and know when a finished flag is safe to delete, from a scan your CI runs on every push.

A flag that’s served one variant for a week has finished its job, but code may still read it. Code references close that gap: a scan in your CI finds every place your code quotes a flag’s key and sends featflags the file, line and the line itself. The flag’s Code references tab then lists them with links to your code, and the flag list marks a flag that’s ready to archive and that no scanned repository’s default branch reads any more as safe to remove.

Your code never leaves CI: only each reference’s path, line number and that one line (up to 200 characters) are sent.

What counts as a reference

A reference is a flag key written as a whole quoted string on one line: "new-checkout", 'new-checkout' or `new-checkout`, in any language, comments included. The scan reads the files git tracks, so .gitignore applies, and skips binary files, files over 1 MiB and minified bundles.

A key the scan can’t see isn’t found: one behind a constant defined elsewhere, or built from parts ('new-' + name). If your code does that, a flag can look safe to remove when it isn’t, so check its tab before deleting it.

References outlive the flag: archived or deleted, it still shows where code reads it, until a scan no longer finds it.

The token

The scan needs an API token that can write the project’s code references: a service token with the writer role in the project. Create one under Settings → API tokens and store it as a secret in your CI.

GitHub Actions

Add a workflow that scans on every push to your default branch:

# .github/workflows/featflags.yml
name: featflags code references
on:
  push:
    branches: [main]
jobs:
  code-refs:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: featflags/code-refs@v1
        with:
          project: web
          token: ${{ secrets.FEATFLAGS_TOKEN }}

The action downloads the featflags CLI release it pins, checks it against its checksum, scans and uploads. It names the repository owner/name, links each reference to GitHub, and learns the default branch from the event. It runs on Linux, macOS and Windows runners.

Input Default
project The project’s key.
token The API token.
api-url https://app.featflags.io
path . The directory to scan.
repo the repository, owner/name Its name in featflags.
branch, commit the pushed branch and commit, or the pull request’s What was scanned.
default-branch the repository’s The branch whose references decide whether a flag is still read.
url-template file links on GitHub Links to a reference, with {commit}, {branch}, {path} and {line} filled in.

To see other branches’ references on the flag page too, scan them as well (on: push without branches, or pull_request). Only the default branch decides what’s safe to remove.

GitLab CI

Install the CLI in the job and run the scan, with FEATFLAGS_TOKEN as a masked CI/CD variable:

featflags-code-refs:
  image: alpine:3
  rules:
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
  variables:
    FEATFLAGS_API_URL: https://app.featflags.io
    FEATFLAGS_PROJECT: web
  script:
    - apk add --no-cache curl git
    - curl -fsSL https://raw.githubusercontent.com/featflags/cli/main/install.sh | FEATFLAGS_INSTALL_DIR=/usr/local/bin sh
    - featflags code-refs scan --repo "$CI_PROJECT_PATH" --branch "$CI_COMMIT_BRANCH" --default-branch "$CI_DEFAULT_BRANCH"
        --url-template "$CI_PROJECT_URL/-/blob/{commit}/{path}#L{line}"

Anywhere else

In a clone, featflags code-refs scan works out the repository (from the origin remote), branch, commit and default branch itself, and links references on GitHub and GitLab remotes. --dry-run prints what it found without uploading:

featflags code-refs scan --project web --dry-run

Each upload replaces the last scan of that repository and branch. A repository keeps its 100 most recently scanned branches.

Managing them

Settings → Code references lists each repository with its scanned branches, and deletes a repository’s references when you retire it. An AI assistant connected to featflags can read them too, and its stale-flags tool answers which flags are safe to remove.