ffcraft is the project and module behind the ffcompile and ffcodegen commands. It helps teams author reusable feature flag definitions, validate them early, and generate consistent runtime config and typed code for supported targets.
This repository has two main entrypoints:
ffcompile: parse, validate, normalize, and compile authoring YAML into consistent runtime configurationffcodegen: generate application-facing typed code from the same authoring YAML
The pipeline is:
flowchart LR
A[Authoring YAML] --> B[Parse]
B --> C[Validate]
C --> D[Normalize]
D --> E[Normalized YAML]
E --> F[Target Compiler]
Supported today:
- authoring format
v1 - normalized YAML as an intermediate representation
- compiler targets:
flagd,gofeatureflag - reusable
variant_sets,rules, anddistributions - per-environment
serve,rules, anddefault_action scheduled_rolloutsprogressive_rolloutexperimentation- comparison, logical, collection, string, and semver operators
Current limitations:
matchesparses and validates, but does not compile forflagdorgofeatureflag- YAML aliases and anchors are not supported
- top-level environment
experimentationis not compiled forflagd
go install github.com/satorunooshie/ffcraft/cmd/ffcompile@latest
go install github.com/satorunooshie/ffcraft/cmd/ffcodegen@latestThe canonical schema lives in proto/ffcraft/v1/ffcraft.proto. A JSON Schema for editor and tooling integration lives in schema/developer-flags.schema.json. Generated Go code lives in gen/ffcraft/v1/ffcompile.pb.go.
- docs/authoring-format.md: authoring YAML syntax and semantics
- docs/compiler-targets.md: how compiled output differs between
flagdandgofeatureflag - docs/ffcodegen.md:
ffcodegencommands, defaults,ffcodegen.yaml, and generated API usage
Smallest end-to-end example:
# ffcompile.yaml
version: v1
variant_sets:
boolean:
on: true
off: false
flags:
- key: enable-new-home
variant_set: boolean
default_variant: off
environments:
prod:
default_action:
serve: ongo run ./cmd/ffcompile build flagd --in ffcompile.yaml --env prod --out prod.flagd.json
go run ./cmd/ffcodegen go --in ffcompile.yaml --out featureflags_gen.goevaluator := featureflags.New(client)
enabled, err := evaluator.EnableNewHome(ctx)client is the generated SDK-agnostic evaluator client interface. In practice, applications are expected to implement that interface in their infra layer, often as a thin adapter over OpenFeature or another flag runtime.
Compile runtime config for flagd:
go run ./cmd/ffcompile build flagd --in flags.yaml --env prod --out flagd.jsonCompile runtime config for GO Feature Flag:
go run ./cmd/ffcompile build gofeatureflag --in flags.yaml --env prod --out flags.goff.yamlGenerate typed Go accessors:
go run ./cmd/ffcompile build flagd --in ffcompile.yaml --env prod --out prod.flagd.json
go run ./cmd/ffcodegen go --in ffcompile.yaml --config ffcodegen.yaml --out featureflags_gen.goFor ffcodegen usage, defaults, and ffcodegen.yaml settings, see docs/ffcodegen.md.
Build flagd JSON from authoring YAML:
go run ./cmd/ffcompile build flagd --in flags.yaml --env prod --out flagd.jsonBuild GO Feature Flag YAML from authoring YAML:
go run ./cmd/ffcompile build gofeatureflag --in flags.yaml --env prod --out flags.goff.yamlNormalize first, then compile explicitly:
go run ./cmd/ffcompile normalize --in flags.yaml --out normalized.yaml
go run ./cmd/ffcompile compile flagd --in normalized.yaml --env prod --out flagd.json
go run ./cmd/ffcompile compile gofeatureflag --in normalized.yaml --env prod --out flags.goff.yamlInspect the normalized intermediate form while building:
go run ./cmd/ffcompile build flagd --in flags.yaml --env prod --dump -
go run ./cmd/ffcompile build gofeatureflag --in flags.yaml --env prod --dump normalized.yamlSkip flags that do not define the requested environment:
go run ./cmd/ffcompile build flagd --in flags.yaml --env prod --allow-missing-envbuild flagd: parse, validate, normalize, and compile toflagdJSONbuild gofeatureflag: parse, validate, normalize, and compile toGO Feature FlagYAMLnormalize: parse, validate, and emit normalized YAMLcompile flagd: compile normalized YAML toflagdJSONcompile gofeatureflag: compile normalized YAML toGO Feature FlagYAML
ffcodegen consumes authoring YAML or normalized YAML and emits application-linked generated code. The initial target is typed Go accessors over a small evaluator interface.
The generated Go code is intentionally runtime-SDK agnostic. It emits typed accessors plus a small Client interface and EvaluationContext type; consumer applications wire those to OpenFeature or another SDK through an adapter they own.
go run ./cmd/ffcodegen go --in ffcompile.yaml --config ffcodegen.yaml --out featureflags_gen.go
go run ./cmd/ffcodegen go --in ffcompile.yamlSee docs/ffcodegen.md for configuration and usage details.
version: v1
variant_sets:
boolean:
on: true
off: false
rules:
internal_ios:
all_of:
- eq:
- { var: user.type }
- internal
- eq:
- { var: device.platform }
- ios
flags:
- key: enable-new-home
variant_set: boolean
default_variant: off
environments:
prod:
rules:
- if:
rule: internal_ios
serve: on
default_action:
serve: offCompiled flagd output:
{
"$schema": "https://flagd.dev/schema/v0/flags.json",
"flags": {
"enable-new-home": {
"state": "ENABLED",
"variants": {
"off": false,
"on": true
},
"defaultVariant": "off",
"targeting": {
"if": [
{
"and": [
{
"==": [
{
"var": "user.type"
},
"internal"
]
},
{
"==": [
{
"var": "device.platform"
},
"ios"
]
}
]
},
"on",
"off"
]
}
}
}
}Compiled GO Feature Flag output:
enable-new-home:
variations:
off: false
on: true
defaultRule:
variation: off
targeting:
- query: (user.type eq "internal") AND (device.platform eq "ios")
variation: onffcompile has one authoring model, but the compiled semantics are not identical across targets. The practical differences are:
| Capability | flagd |
gofeatureflag |
|---|---|---|
| Fixed serve | native | native |
| Percentage rollout | fractional targeting |
native percentage |
| Progressive rollout | expanded at compile time into time-based steps | native progressiveRollout |
| Scheduled rollout | compiled into timestamp-ordered if chain |
native scheduledRollout |
| Step experimentation | temporary overlay during start <= now < end |
compiled to native rollout fields, not overlay-identical |
| Top-level environment experimentation | not supported | native experimentation |
| Mixed stickiness in one flag | allowed per action | rejected because bucketingKey is flag-scoped |
For the full target notes, see docs/compiler-targets.md.
Canonical paired examples use one authoring file and show both target outputs side by side:
basicrule-targetingscheduled-rolloutsprogressive-rolloutsexperimentation-rolloutsgo-codegenadapter implementation- examples/go-codegen/adapter/adapter.go
- examples/go-codegen/README.md
config and generated code- examples/go-codegen/basic/ffcompile.yaml
- examples/go-codegen/basic/ffcodegen.yaml
- examples/go-codegen/basic/gen/featureflags_gen.go
- examples/go-codegen/rollout/ffcompile.yaml
- examples/go-codegen/rollout/ffcodegen.yaml
- examples/go-codegen/withhooks/ffcompile.yaml
- examples/go-codegen/withhooks/ffcodegen.yaml
- examples/go-codegen/rollout/gen/featureflags_gen.go
- examples/go-codegen/withhooks/gen/featureflags_gen.go
flagd runtime examples- examples/go-codegen/basic/flagd/main.go
- examples/go-codegen/rollout/flagd/main.go
- examples/go-codegen/withhooks/flagd/main.go
gofeatureflag runtime examples- examples/go-codegen/basic/gofeatureflag/main.go
- examples/go-codegen/rollout/gofeatureflag/main.go
- examples/go-codegen/withhooks/gofeatureflag/main.go
Generate the sample outputs.
Canonical examples:
go run ./cmd/ffcompile build flagd --in examples/basic/ffcompile.yaml --env prod --out examples/basic/prod.flagd.json
go run ./cmd/ffcompile build gofeatureflag --in examples/basic/ffcompile.yaml --env prod --out examples/basic/prod.goff.yaml
go run ./cmd/ffcompile build flagd --in examples/rule-targeting/ffcompile.yaml --env prod --out examples/rule-targeting/prod.flagd.json
go run ./cmd/ffcompile build gofeatureflag --in examples/rule-targeting/ffcompile.yaml --env prod --out examples/rule-targeting/prod.goff.yaml
go run ./cmd/ffcompile build flagd --in examples/scheduled-rollouts/ffcompile.yaml --env prod --out examples/scheduled-rollouts/prod.flagd.json
go run ./cmd/ffcompile build gofeatureflag --in examples/scheduled-rollouts/ffcompile.yaml --env prod --out examples/scheduled-rollouts/prod.goff.yaml
go run ./cmd/ffcompile build flagd --in examples/progressive-rollouts/ffcompile.yaml --env prod --out examples/progressive-rollouts/prod.flagd.json
go run ./cmd/ffcompile build gofeatureflag --in examples/progressive-rollouts/ffcompile.yaml --env prod --out examples/progressive-rollouts/prod.goff.yaml
go run ./cmd/ffcompile build flagd --in examples/experimentation-rollouts/ffcompile.yaml --env prod --out examples/experimentation-rollouts/prod.flagd.json
go run ./cmd/ffcompile build gofeatureflag --in examples/experimentation-rollouts/ffcompile.yaml --env prod --out examples/experimentation-rollouts/prod.goff.yamlGo codegen examples:
make update-go-example- docs/authoring-format.md: authoring schema and evaluation model
- docs/compiler-targets.md: target-specific compilation behavior and gaps
- schema/README.md: JSON Schema and schema-directory notes
Reference fixtures: