Configuration
Customize FlagShark's behavior with the .flagshark.yml configuration file.
FlagShark works with zero configuration — run flagshark scan and it detects flags across 13 languages with no setup required. When you need to tune thresholds, exclude paths, suppress noisy flags, or add a custom in-house SDK, you do it with two files: .flagshark.yml (structured config) and .flagsharkignore (file-skip rules, gitignore syntax).
Discovery
When flagshark scan runs, the engine walks up the directory tree from the current working directory, checking each directory for .flagshark.yml first, then .flagshark.yaml, stopping when it reaches your home directory or /. The first match wins. This means the config can live anywhere in the tree above the directory you scan from — typically your repo root.
Two CLI flags override discovery:
--config <path>— load this specific file, skip the upward walk.--no-config— skip discovery entirely; run with built-in defaults only.
Full .flagshark.yml reference
The block below shows every supported top-level key with representative values. All keys are optional — omit any section to accept its default.
# .flagshark.yml
# Staleness threshold in days (positive integer). Default: 30
threshold: 30
# Input filter — controls which files are scanned.
excludes:
paths: ['examples/**'] # Gitignore-style directory globs
files: ['**/*.test.ts'] # Gitignore-style file globs
presets: [test-files, snapshots] # Built-in preset bundles (see Presets below)
# Output filter — hides specific flag names from results. Supports glob.
suppress:
flags: ['INTERNAL_DEBUG_*', 'PERMANENT_KILLSWITCH']
# Per-path threshold overrides. Checked before the top-level threshold.
paths:
- match: 'src/critical/**'
threshold: 7 # Stricter: flag stale after 7 days here
# Custom SDK providers (see Custom providers below).
providers:
- language: go
name: ACME Flags
importPattern: 'github.com/acme/feature-flags'
methods:
- { name: IsEnabled, flagKeyIndex: 0 }
# Platform integration credentials (see Platform integration below).
platforms:
launchdarkly:
project: my-project-key
environment: production
Key reference
| Key | Type | Default | Description |
|---|---|---|---|
threshold | positive integer | 30 | Global staleness threshold in days |
excludes | object | {} | Input-side path/file/preset exclusions |
suppress | object | {} | Output-side flag name suppression (glob) |
paths | array | [] | Per-path threshold overrides |
providers | array | [] | Custom SDK provider definitions |
output | object | {} | Report format, grouping, sorting |
healthScore | object | {} | Scoring signal weights |
engine | map | {} | Per-language parser override (regex | tree-sitter) |
platforms | map | — | Platform integration credentials |
Excludes vs suppress
These two keys solve different problems:
excludesis an input filter. Files matched byexcludes.paths,excludes.files, or anexcludes.presetsbundle are never scanned. Use it to keep test files, generated code, or example directories out of the results entirely.suppressis an output filter. The files are still scanned, but any flag whose name matches a pattern insuppress.flagsis removed from the report before display. Use it for intentionally permanent flags (kill-switches, emergency rollbacks) that you never intend to clean up.
Both excludes.files and suppress.flags accept glob patterns (*, **, ?).
Presets
excludes.presets lets you opt into named bundles of glob patterns maintained by FlagShark. They cover common conventions across multiple languages and test frameworks, so you don't have to enumerate every pattern yourself.
| Preset | What it skips |
|---|---|
test-files | *.test.ts, *.spec.js, *_test.go, *Test.java, *_spec.rb, and equivalents across all 13 supported languages |
snapshots | *.snap, __snapshots__/** |
examples | examples/**, example/**, demo/**, demos/** |
stories | *.stories.ts, *.stories.tsx, *.story.ts, and variants |
fixtures | __fixtures__/**, fixtures/** |
generated | *.generated.ts, *.gen.go, generated/**, and variants |
You can stack multiple presets:
excludes:
presets: [test-files, snapshots, generated]
Always-skipped baseline — the following directories are always excluded regardless of config, so you never need to list them:
node_modules, .git, dist, build, coverage, __pycache__, vendor, .next, .turbo
Per-path thresholds
The top-level threshold applies everywhere. The paths array lets you override it for specific subtrees using gitignore-style globs in the match field. Useful when different parts of the codebase have different cleanup expectations.
threshold: 30 # Default: 30-day threshold everywhere
paths:
- match: 'src/critical/**'
threshold: 7 # Stricter: flag stale after 7 days in critical code
- match: 'infra/killswitches/**'
threshold: 90 # Looser: kill-switches are expected to live longer
The first paths entry whose match glob matches the file wins. If no entry matches, the top-level threshold applies.
Custom providers
FlagShark auto-detects 13 known SDKs (LaunchDarkly, Unleash, Statsig, and others — see Supported Languages) without any config. The providers array is only needed for in-house wrappers or unusual third-party SDKs not in the built-in list.
Each custom provider has this shape:
| Field | Type | Required | Description |
|---|---|---|---|
language | enum | Yes | One of: typescript, javascript, go, python, java, kotlin, swift, ruby, csharp, php, rust, cpp, objc |
name | string | Yes | Display name shown in reports |
importPattern | string | No | Import path substring to match (e.g. github.com/acme/flags) |
enabled | boolean | No | Set to false to disable without deleting. Default: true |
methods | array | Yes | List of method configs (see below) |
Each entry in methods:
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Exact method/function name to match |
flagKeyIndex | integer | Yes | Zero-based index of the flag-key argument |
Example — a Go in-house wrapper that wraps feature-flag evaluation behind a single IsEnabled(flagKey) call:
providers:
- language: go
name: ACME Flags
importPattern: 'github.com/acme/feature-flags'
methods:
- { name: IsEnabled, flagKeyIndex: 0 }
- { name: GetVariant, flagKeyIndex: 0 }
For TypeScript/JavaScript providers, omit importPattern if you want the methods matched regardless of the import path, or set it to the npm package name (e.g. @acme/flags).
Output, health score, and engine
output
Controls how results are rendered when using flagshark scan:
| Field | Options | Default | Description |
|---|---|---|---|
format | text, json, sarif, markdown, csv | text | Report format |
groupBy | file, provider, signal, none | file | How to group stale flags |
sortBy | age, name, count | age | Sort order within groups |
color | auto, always, never | auto | Terminal color output |
maxDisplay | positive integer | 10 | Max flags shown (same as --verbose removes this cap) |
output:
format: json
groupBy: signal
sortBy: name
color: never
maxDisplay: 25
CLI flags override config values — e.g. passing --format on the command line takes precedence over output.format in .flagshark.yml.
healthScore
The health score weights each staleness signal when computing the 0–100 score:
healthScore:
weights:
age: 1.0 # Default weight for age signal
lowUsage: 0.5 # Default weight for low-usage signal
hardcoded: 2.0 # Default weight for hardcoded value signal
Increase a weight to make that signal count more heavily toward the score, or set it to 0 to exclude it from scoring.
The hardcoded weight is currently reserved and has no effect — no hardcoded signal is emitted today. It is accepted by the schema for forward compatibility.
engine
Override the parser used for a given language. Most users never need this; it exists for edge cases where the default parser produces false positives or misses detections in unusual code patterns.
engine:
typescript: tree-sitter
go: regex
Valid values: regex | tree-sitter. Which engine is the default for each language is shown in Supported Languages. The built-in defaults are: tier-1 languages (TypeScript, JavaScript, Go, Python) use tree-sitter; all remaining languages use regex — you normally don't need to set engine at all.
Platform integration
The platforms block holds credentials and settings that let FlagShark cross-reference your flag platform to detect missing-in-platform and archived-in-platform signals. Each key is the platform name; the value is platform-specific.
platforms:
launchdarkly:
project: my-project-key
environment: production
The API token is read from an environment variable — LAUNCHDARKLY_API_TOKEN by default — which you can override per-platform with a token_env: key; full setup (token scopes, behavior on errors, caching) is on the LaunchDarkly integration page.
See LaunchDarkly integration for the full setup guide, including how to supply the API token via environment variable.
.flagsharkignore
Place a .flagsharkignore file in your repo root (or anywhere in the upward-walk path) to skip files using standard gitignore syntax. This is useful when you want to exclude files without editing .flagshark.yml, or when the exclusion list is long enough that it belongs in its own file.
# Skip all generated proto files
**/*.pb.go
# Skip the legacy monolith directory
legacy/**
# But re-include one subdirectory inside it
!legacy/active-features/**
The ! prefix re-includes a previously excluded path, just like .gitignore. The --no-ignore-file CLI flag skips .flagsharkignore discovery entirely.
Migrating from the old config format
.flagshark.yaml (or .flagshark.yml) that was written against the old documentation, it will fail validation immediately — the schema is strict and rejects every key the old docs described. Remove or replace the following: