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.