FlagShark detects feature flags by analyzing the method calls in your code. It knows that client.BoolVariation("my-flag", ctx, false) is a LaunchDarkly flag evaluation and that unleash.isEnabled("my-toggle") is an Unleash toggle check. But how does it know? And what happens when your team uses a custom in-house flag library that FlagShark has never seen before?
The answer is .flagshark.yaml -- a configuration file that lives in the root of your repository and tells FlagShark exactly how your codebase evaluates feature flags. It maps SDK import paths to method signatures and tells the detection engine where to find the flag key in each method call.
This guide walks through the .flagshark.yaml schema, provides ready-to-use configurations for popular providers, and shows you how to configure detection for custom in-house flag libraries that are unique to your organization.
What .flagshark.yaml does
When FlagShark analyzes a pull request, it parses the code changes using tree-sitter (a production-grade parser that understands the actual syntax of 11 programming languages). It looks for method calls that match the patterns defined in .flagshark.yaml and extracts the flag key from the arguments.
The detection flow works like this:
- FlagShark receives a webhook for a pull request
- It fetches the diff and parses the changed files
- For each file, it identifies the programming language and finds matching providers from
.flagshark.yaml - It scans for method calls matching the configured method names
- It verifies the import/package path matches the configured provider
- It extracts the flag key from the argument at the configured index
- The detected flag is tracked in FlagShark's lifecycle database
Without a .flagshark.yaml file, FlagShark falls back to built-in detection patterns for common providers. But the YAML configuration gives you full control over what gets detected and how.
The schema: Understanding every field
Here is the complete schema for .flagshark.yaml:
version: "1.0"
global_settings:
enable_fallback_detection: true # Use built-in patterns when no provider matches
strict_import_matching: false # If true, only detect flags from exact import matches
providers:
- name: "Provider Display Name" # Human-readable name for this provider
languages: ["go", "typescript"] # Which languages this provider applies to
import_pattern: "github.com/..." # The import/package path to match
import_aliases: ["alias1"] # Alternative import aliases used in code
description: "What this provider does"
enabled: true # Toggle detection on/off without removing config
methods:
- name: "MethodName" # The method/function name to detect
flag_key_index: 0 # Position of the flag key in the argument list
context_index: 1 # Position of the context argument (optional)
min_params: 3 # Minimum number of parameters expected
examples: # Usage examples (documentation only)
- 'client.MethodName("flag-key", ctx, false)'
Let's break down each field in detail.
Global settings
global_settings:
enable_fallback_detection: true
strict_import_matching: false
| Setting | Type | Default | Purpose |
|---|---|---|---|
enable_fallback_detection | boolean | true | When no provider in your config matches a file, FlagShark uses built-in patterns to detect common flag SDK calls. Set to false if you only want to detect flags from explicitly configured providers. |
strict_import_matching | boolean | false | When true, FlagShark only detects flags when the exact import path is found in the file. When false, it also matches by method name alone if the import is not detected (useful for codebases with indirect imports or dependency injection). |
For most teams, the defaults are correct. Set strict_import_matching: true if you have method names like isEnabled or getValue that appear in non-flag-related code and are causing false positives.
Provider fields
| Field | Required | Type | Description |
|---|---|---|---|
name | Yes | string | Display name shown in FlagShark's dashboard and PR comments |
languages | No | string[] | Languages this provider applies to. Omit to apply to all languages. Valid values: go, typescript, javascript, python, java, kotlin, swift, ruby, csharp, php, rust, cpp, objc |
import_pattern | Yes | string | The import or package path that identifies this SDK. For Go: the module path. For JS/TS: the npm package name. For Python: the module name. |
import_aliases | No | string[] | Alternative names the import might be aliased as in code (e.g., import ld from 'launchdarkly' uses alias ld) |
description | No | string | Human-readable description (documentation only) |
enabled | No | boolean | Default true. Set to false to temporarily disable detection without removing the config. |
Method fields
| Field | Required | Type | Description |
|---|---|---|---|
name | Yes | string | The exact method name to detect. Case-sensitive. |
flag_key_index | Yes | integer | Zero-based index of the flag key in the argument list. Set to -1 for methods that return all flags (no specific key). |
context_index | No | integer | Zero-based index of the context/user argument. Used for richer flag metadata. |
min_params | Yes | integer | Minimum number of arguments the method call must have to be considered a valid flag evaluation. Prevents matching unrelated methods with the same name but different signatures. |
examples | No | string[] | Usage examples for documentation. Not used for detection. |
Understanding flag_key_index
The flag_key_index is the most important field to get right. It tells FlagShark which argument in the method call contains the flag key string.
Index: 0 1 2
| | |
client.BoolVariation("my-flag", context, false)
^^^^^^^^
flag_key_index: 0
Index: 0 1
| |
zeroflag.Bool(ctx, "feature-flag")
^^^^^^^^^^^^^^
flag_key_index: 1
Index: 0 1 2
| | |
client.Treatment("user-key", "feature-flag", nil)
^^^^^^^^^^^^^^
flag_key_index: 1
If the method does not take a specific flag key (for example, a method that returns all flags at once), set flag_key_index: -1:
- name: "AllBools"
flag_key_index: -1 # Returns all flags, no specific key
min_params: 1
Ready-to-use configurations for popular providers
Below are complete, tested configurations for the most popular feature flag providers. Copy the relevant sections into your .flagshark.yaml.
LaunchDarkly
LaunchDarkly has language-specific SDKs with slightly different method signatures. Here are configurations for the three most common languages.
Go:
- name: "LaunchDarkly Go SDK"
languages: ["go"]
import_pattern: "github.com/launchdarkly/go-server-sdk/v7"
import_aliases: ["ld", "launchdarkly", "ldclient"]
enabled: true
methods:
- name: "BoolVariation"
flag_key_index: 0
context_index: 1
min_params: 3
- name: "StringVariation"
flag_key_index: 0
context_index: 1
min_params: 3
- name: "IntVariation"
flag_key_index: 0
context_index: 1
min_params: 3
- name: "Float64Variation"
flag_key_index: 0
context_index: 1
min_params: 3
- name: "JSONVariation"
flag_key_index: 0
context_index: 1
min_params: 3
- name: "BoolVariationDetail"
flag_key_index: 0
context_index: 1
min_params: 3
- name: "StringVariationDetail"
flag_key_index: 0
context_index: 1
min_params: 3
- name: "IntVariationDetail"
flag_key_index: 0
context_index: 1
min_params: 3
- name: "Float64VariationDetail"
flag_key_index: 0
context_index: 1
min_params: 3
- name: "JSONVariationDetail"
flag_key_index: 0
context_index: 1
min_params: 3
TypeScript/JavaScript (Node.js server SDK):
- name: "LaunchDarkly JS SDK"
languages: ["typescript", "javascript"]
import_pattern: "@launchdarkly/node-server-sdk"
import_aliases: ["ld", "LaunchDarkly"]
enabled: true
methods:
- name: "variation"
flag_key_index: 0
min_params: 3
- name: "boolVariation"
flag_key_index: 0
min_params: 3
- name: "stringVariation"
flag_key_index: 0
min_params: 3
- name: "intVariation"
flag_key_index: 0
min_params: 3
- name: "jsonVariation"
flag_key_index: 0
min_params: 3
- name: "variationDetail"
flag_key_index: 0
min_params: 3
Python:
- name: "LaunchDarkly Python SDK"
languages: ["python"]
import_pattern: "ldclient"
import_aliases: ["ld", "launchdarkly"]
enabled: true
methods:
- name: "variation"
flag_key_index: 0
min_params: 3
- name: "variation_detail"
flag_key_index: 0
min_params: 3
- name: "bool_variation"
flag_key_index: 0
min_params: 3
Unleash
Unleash uses consistent method names across its SDKs, but the import paths differ by language.
Go:
- name: "Unleash Go SDK"
languages: ["go"]
import_pattern: "github.com/Unleash/unleash-client-go"
import_aliases: ["unleash"]
enabled: true
methods:
- name: "IsEnabled"
flag_key_index: 0
min_params: 1
- name: "GetVariant"
flag_key_index: 0
min_params: 1
TypeScript/JavaScript:
- name: "Unleash JS SDK"
languages: ["typescript", "javascript"]
import_pattern: "unleash-client"
import_aliases: ["Unleash", "unleash"]
enabled: true
methods:
- name: "isEnabled"
flag_key_index: 0
min_params: 1
- name: "getVariant"
flag_key_index: 0
min_params: 1
Python:
- name: "Unleash Python SDK"
languages: ["python"]
import_pattern: "UnleashClient"
import_aliases: ["unleash"]
enabled: true
methods:
- name: "is_enabled"
flag_key_index: 0
min_params: 1
- name: "get_variant"
flag_key_index: 0
min_params: 1
Split.io
Split.io uses "treatment" terminology instead of "variation." The flag key position varies by language.
Go:
- name: "Split.io Go SDK"
languages: ["go"]
import_pattern: "github.com/splitio/go-client"
import_aliases: ["split", "splitio"]
enabled: true
methods:
- name: "Treatment"
flag_key_index: 1 # Note: user key is index 0, flag key is index 1
min_params: 2
- name: "Treatments"
flag_key_index: 1
min_params: 2
- name: "TreatmentWithConfig"
flag_key_index: 1
min_params: 2
TypeScript/JavaScript:
- name: "Split.io JS SDK"
languages: ["typescript", "javascript"]
import_pattern: "@splitsoftware/splitio"
import_aliases: ["SplitFactory", "splitio"]
enabled: true
methods:
- name: "getTreatment"
flag_key_index: 0 # Note: JS SDK puts flag key first
min_params: 1
- name: "getTreatments"
flag_key_index: 0
min_params: 1
- name: "getTreatmentWithConfig"
flag_key_index: 0
min_params: 1
PostHog
PostHog combines analytics and feature flags. The flag methods take a flag key and a distinct user ID.
TypeScript/JavaScript:
- name: "PostHog JS SDK"
languages: ["typescript", "javascript"]
import_pattern: "posthog-node"
import_aliases: ["PostHog", "posthog"]
enabled: true
methods:
- name: "isFeatureEnabled"
flag_key_index: 0
min_params: 2
- name: "getFeatureFlag"
flag_key_index: 0
min_params: 2
- name: "getFeatureFlagPayload"
flag_key_index: 0
min_params: 2
- name: "getAllFlags"
flag_key_index: -1
min_params: 1
Python:
- name: "PostHog Python SDK"
languages: ["python"]
import_pattern: "posthog"
enabled: true
methods:
- name: "feature_enabled"
flag_key_index: 0
min_params: 2
- name: "get_feature_flag"
flag_key_index: 0
min_params: 2
- name: "get_feature_flag_payload"
flag_key_index: 0
min_params: 2
- name: "get_all_flags"
flag_key_index: -1
min_params: 1
Flipper (Ruby)
- name: "Flipper Ruby SDK"
languages: ["ruby"]
import_pattern: "flipper"
import_aliases: ["Flipper"]
enabled: true
methods:
- name: "enabled?"
flag_key_index: 0
min_params: 1
- name: "enable"
flag_key_index: 0
min_params: 1
- name: "disable"
flag_key_index: 0
min_params: 1
Configuring custom in-house providers
This is where .flagshark.yaml becomes essential. If your team has built a custom feature flag library -- whether it is a thin wrapper around an existing provider or a fully custom implementation -- you need to tell FlagShark how to detect it.
Step 1: Identify your SDK's method signatures
Start by finding how your flag library is used in code. Look for the import statement and the method calls.
Example custom Go library:
import "github.com/yourcompany/featureflags"
// Usage:
enabled := featureflags.IsEnabled(ctx, "new-dashboard")
value := featureflags.GetString(ctx, "banner-text", "default")
Example custom TypeScript library:
import { FeatureFlags } from '@yourcompany/feature-flags';
// Usage:
const enabled = await FeatureFlags.check('new-onboarding-flow');
const variant = await FeatureFlags.getVariant('pricing-experiment', 'control');
Step 2: Map the method signatures to YAML
For each method, identify:
- The exact method name (case-sensitive)
- Which argument position contains the flag key
- How many arguments the method requires at minimum
Go example:
- name: "YourCompany Feature Flags"
languages: ["go"]
import_pattern: "github.com/yourcompany/featureflags"
import_aliases: ["featureflags", "ff"]
description: "Internal feature flag library"
enabled: true
methods:
- name: "IsEnabled"
flag_key_index: 1 # ctx is index 0, flag key is index 1
context_index: 0
min_params: 2
examples:
- 'featureflags.IsEnabled(ctx, "new-dashboard")'
- name: "GetString"
flag_key_index: 1
context_index: 0
min_params: 3
examples:
- 'featureflags.GetString(ctx, "banner-text", "default")'
- name: "GetInt"
flag_key_index: 1
context_index: 0
min_params: 3
- name: "GetBool"
flag_key_index: 1
context_index: 0
min_params: 2
TypeScript example:
- name: "YourCompany Feature Flags"
languages: ["typescript", "javascript"]
import_pattern: "@yourcompany/feature-flags"
import_aliases: ["FeatureFlags", "flags", "ff"]
description: "Internal feature flag library"
enabled: true
methods:
- name: "check"
flag_key_index: 0
min_params: 1
examples:
- 'FeatureFlags.check("new-onboarding-flow")'
- name: "getVariant"
flag_key_index: 0
min_params: 2
examples:
- 'FeatureFlags.getVariant("pricing-experiment", "control")'
- name: "isEnabled"
flag_key_index: 0
min_params: 1
Step 3: Handle common custom patterns
Wrapper around an existing SDK:
If your custom library wraps LaunchDarkly or another provider, you likely want to configure both the wrapper and the underlying SDK. This ensures FlagShark detects flags whether they are called through your wrapper or directly through the underlying SDK.
providers:
# Your custom wrapper
- name: "Internal Flag Wrapper"
languages: ["go"]
import_pattern: "github.com/yourcompany/flags"
import_aliases: ["flags"]
enabled: true
methods:
- name: "IsEnabled"
flag_key_index: 0
min_params: 1
# The underlying LaunchDarkly SDK (in case anyone uses it directly)
- name: "LaunchDarkly Go SDK"
languages: ["go"]
import_pattern: "github.com/launchdarkly/go-server-sdk/v7"
import_aliases: ["ld"]
enabled: true
methods:
- name: "BoolVariation"
flag_key_index: 0
context_index: 1
min_params: 3
Environment variable-based flags:
Some teams use environment variables as feature flags. While FlagShark's tree-sitter parsing is optimized for method calls, you can still detect common patterns by configuring the environment variable access pattern:
- name: "Environment Variable Flags"
languages: ["go"]
import_pattern: "os"
import_aliases: ["os"]
enabled: true
methods:
- name: "Getenv"
flag_key_index: 0
min_params: 1
examples:
- 'os.Getenv("FEATURE_NEW_CHECKOUT")'
Note: This will detect all os.Getenv calls, not just flag-related ones. Use this only if your team has a clear naming convention for flag-related environment variables (e.g., prefixed with FEATURE_) and you are willing to filter results.
Multi-language internal SDKs:
If your internal flag library has implementations in multiple languages with the same package organization, you can either create separate provider entries per language or use a single entry without the languages field:
# Option A: Separate entries per language (recommended for different method signatures)
- name: "Internal Flags (Go)"
languages: ["go"]
import_pattern: "github.com/yourcompany/flags"
methods:
- name: "IsEnabled"
flag_key_index: 1
context_index: 0
min_params: 2
- name: "Internal Flags (Python)"
languages: ["python"]
import_pattern: "yourcompany.flags"
methods:
- name: "is_enabled"
flag_key_index: 0
min_params: 1
# Option B: Single entry (when method names are identical across languages)
- name: "Internal Flags"
# No "languages" field = applies to all languages
import_pattern: ""
methods:
- name: "isFeatureEnabled"
flag_key_index: 0
min_params: 1
Option A is recommended when your SDKs have language-idiomatic method names (e.g., IsEnabled in Go vs. is_enabled in Python). Option B works when the method names are identical across languages, but be aware that an empty import_pattern with common method names may produce false positives.
Advanced configuration
Multiple providers in one repository
Most real-world repositories use more than one flag provider. Perhaps you use LaunchDarkly for production flags and a custom library for internal tooling flags. Your .flagshark.yaml can include any number of providers:
version: "1.0"
global_settings:
enable_fallback_detection: true
strict_import_matching: false
providers:
- name: "LaunchDarkly Go SDK"
languages: ["go"]
import_pattern: "github.com/launchdarkly/go-server-sdk/v7"
import_aliases: ["ld"]
enabled: true
methods:
- name: "BoolVariation"
flag_key_index: 0
context_index: 1
min_params: 3
- name: "Internal Feature Flags"
languages: ["go"]
import_pattern: "github.com/yourcompany/flags"
import_aliases: ["flags"]
enabled: true
methods:
- name: "IsEnabled"
flag_key_index: 0
min_params: 1
- name: "PostHog JS SDK"
languages: ["typescript", "javascript"]
import_pattern: "posthog-node"
import_aliases: ["posthog"]
enabled: true
methods:
- name: "isFeatureEnabled"
flag_key_index: 0
min_params: 2
FlagShark evaluates each provider independently. A single file can match multiple providers if it imports multiple flag SDKs.
Temporarily disabling a provider
If you are migrating from one provider to another and want to stop tracking flags from the old provider without deleting the configuration, set enabled: false:
- name: "Legacy Flag System"
languages: ["go"]
import_pattern: "github.com/yourcompany/legacy-flags"
enabled: false # Temporarily disabled during migration
methods:
- name: "Check"
flag_key_index: 0
min_params: 1
The configuration is preserved for reference, but FlagShark ignores it during detection.
Language-specific overrides
Some providers have different SDKs for different languages with completely different APIs. Configure each as a separate provider entry with the appropriate languages field:
# The same provider has different APIs in different languages
- name: "ConfigCat Go SDK"
languages: ["go"]
import_pattern: "github.com/configcat/go-sdk/v7"
import_aliases: ["configcat"]
enabled: true
methods:
- name: "GetBoolValue"
flag_key_index: 0
min_params: 2
- name: "GetStringValue"
flag_key_index: 0
min_params: 2
- name: "ConfigCat CSharp SDK"
languages: ["csharp"]
import_pattern: "ConfigCat.Client"
enabled: true
methods:
- name: "GetValueAsync" # C# uses async pattern
flag_key_index: 0
min_params: 2
- name: "GetValueDetailsAsync"
flag_key_index: 0
min_params: 2
Testing your configuration
After creating or modifying your .flagshark.yaml, verify that it works correctly before relying on it for flag detection.
Manual verification
The simplest test is to open a pull request that adds a flag evaluation using your configured provider. FlagShark should detect the flag and comment on the PR with the detected flag key.
Create a test file with a clear flag evaluation:
package test
import "github.com/yourcompany/flags"
func TestFlagDetection() {
// FlagShark should detect this flag
enabled := flags.IsEnabled("test-detection-flag")
_ = enabled
}
Open a PR with this change and verify that FlagShark detects test-detection-flag. Then close the PR without merging.
Common configuration mistakes
| Symptom | Likely Cause | Fix |
|---|---|---|
| No flags detected | import_pattern does not match actual import path | Check the exact import path in your code |
| Wrong flag key extracted | flag_key_index points to wrong argument | Count arguments starting from 0 |
| False positives | Method name is too generic (e.g., get, check) | Add min_params constraint or enable strict_import_matching |
| Provider not matching | languages field excludes the file's language | Add the correct language or remove the languages field |
| Detection works in Go but not TypeScript | Different SDK has different method names | Create separate provider entries per language |
Verifying import_pattern
The import_pattern must match exactly what appears in your source code's import statement. Here is how to verify for each language:
Go -- look at the import block:
import (
ld "github.com/launchdarkly/go-server-sdk/v7"
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// This is your import_pattern
)
TypeScript/JavaScript -- look at the import or require:
import { LDClient } from '@launchdarkly/node-server-sdk';
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// This is your import_pattern
Python -- look at the import:
import ldclient
# ^^^^^^^^
# This is your import_pattern
from ldclient import LDClient
# ^^^^^^^^
# This is your import_pattern
A complete example configuration
Here is a full .flagshark.yaml for a team that uses LaunchDarkly for their Go backend, PostHog for their TypeScript frontend, and a custom in-house library for Python data pipelines:
version: "1.0"
global_settings:
enable_fallback_detection: true
strict_import_matching: false
providers:
# Production flag provider (Go backend)
- name: "LaunchDarkly Go SDK"
languages: ["go"]
import_pattern: "github.com/launchdarkly/go-server-sdk/v7"
import_aliases: ["ld", "ldclient"]
description: "Production feature flags for Go services"
enabled: true
methods:
- name: "BoolVariation"
flag_key_index: 0
context_index: 1
min_params: 3
examples:
- 'client.BoolVariation("enable-new-api", ctx, false)'
- name: "StringVariation"
flag_key_index: 0
context_index: 1
min_params: 3
- name: "IntVariation"
flag_key_index: 0
context_index: 1
min_params: 3
- name: "BoolVariationDetail"
flag_key_index: 0
context_index: 1
min_params: 3
# Frontend flag provider (TypeScript dashboard)
- name: "PostHog JS SDK"
languages: ["typescript", "javascript"]
import_pattern: "posthog-node"
import_aliases: ["posthog"]
description: "Feature flags and analytics for the frontend"
enabled: true
methods:
- name: "isFeatureEnabled"
flag_key_index: 0
min_params: 2
examples:
- 'posthog.isFeatureEnabled("new-dashboard-layout", userId)'
- name: "getFeatureFlag"
flag_key_index: 0
min_params: 2
# Internal flag library (Python data pipelines)
- name: "Internal Python Flags"
languages: ["python"]
import_pattern: "yourcompany.feature_flags"
description: "Custom feature flag library for data pipelines"
enabled: true
methods:
- name: "is_enabled"
flag_key_index: 0
min_params: 1
examples:
- 'flags.is_enabled("use-new-etl-pipeline")'
- name: "get_value"
flag_key_index: 0
min_params: 2
examples:
- 'flags.get_value("batch-size", 1000)'
This configuration ensures FlagShark detects flags across all three parts of the stack, each with different providers and different method signatures. When a developer opens a PR that touches Go backend code, TypeScript frontend code, and Python pipeline code, FlagShark will detect flags from all three providers and track them in a unified lifecycle view.
Troubleshooting
"FlagShark isn't detecting my flags"
Work through this checklist:
- Is
.flagshark.yamlin the repository root? It must be at the top level of the repository, not in a subdirectory. - Is the provider
enabled? Check thatenabledis not set tofalse. - Does
import_patternmatch exactly? Compare character by character with your actual import statement. - Does the
languagesfield include your file's language? A Go file will not match a provider withlanguages: ["typescript"]. - Is
min_paramstoo restrictive? If your method call has fewer arguments thanmin_params, it will not be detected. - Is
strict_import_matchingblocking detection? If set totrue, the import must be explicitly present in the file.
"FlagShark is detecting non-flag method calls"
This happens when method names are too generic. Solutions:
- Increase
min_paramsto filter out method calls with fewer arguments - Enable
strict_import_matchingto require the exact import path - Use a more specific method name if your SDK supports it (e.g.,
isFeatureEnabledinstead ofisEnabled)
"I changed .flagshark.yaml but nothing changed"
Configuration changes take effect on the next pull request that FlagShark processes. Existing PRs are not re-analyzed. Open a new PR or push a new commit to an existing PR to trigger re-detection.
The .flagshark.yaml configuration is what makes FlagShark work with any feature flag provider, not just the popular ones. Whether your team uses LaunchDarkly, Unleash, Split, PostHog, Flipper, ConfigCat, or a completely custom in-house library, a few minutes of YAML configuration gives you complete flag lifecycle tracking across your entire codebase.
Start with the ready-to-use configurations for your provider, add your custom libraries, and let FlagShark handle the rest. Every flag that gets detected is a flag that will not silently accumulate into technical debt.