A named dimension along which the product differs: one lever, a closed set of values.
A configuration axis is a named dimension along which the product's compositionCompositionWorkspaceA published arrangement of graph entities that has an identity of its own: a gallery, a board, a deck. Membership can be derived by running its query, position is authored by a person, and both are stored because both are real.View reference → differs. The stored graph is the union of a configuration family; an axis names one dimension of that family, and a projection picks one value on it. SurfacesSurfaceExperience DesignA place inside a screen, the features that occupy it, and the rule that decides who wins itView reference → appear, disappear, or are replaced by different surfaces with different occupants, and the axis is what lets the model say so instead of quietly describing one configuration as though it were the product.
FeatureFeatureProduct SpecificationA product capability or featureView reference → toggles gave teams a way to ship code that is present but inactive. The useful distinction in Martin Fowler's account is between the short-lived releaseReleaseProduct SpecificationA shipped version of the productView reference → toggle, which exists to be removed, and the long-lived permission or ops toggle, which becomes part of the product's shape and never goes away. Plan tiers, permission levels and beta programmes reached the same shape from other directions: each is a lever that changes what a customer's product actually contains.
Software product line engineering had already named the general problem. A family of related products is better modelled through explicit variation points than through a branch per variantVariantGrowthA variant in an A/B testView reference →, because the branch-per-variant approach makes every shared fact a thing you maintain several times. What none of these supplied was a place to write the family down that a product model could read. A toggle lives in the deploymentDeploymentEngineeringA deployment eventView reference → system, a tier lives in billing, and a product model showing one configuration inherits an unstated condition from both.
The configuration axis lifts the lever into the graph, one axis per semantic decisionDecisionStrategyA recorded decision with context, rationale, and consequencesView reference → rather than per mechanism. The consequence worth stating is the zero-migration guarantee: a graph that declares no axis describes a product with one configuration, which is exactly what every graph written before the type existed already meant. Silence still means true everywhere.
A team is halfway through a navigation change. Half the product model describes the new sidebar, half still describes the old one, and neither half says which is which. A new joiner reads the graph and builds against a navigation that only a third of accounts see. The usual fix is a note in a description field, and the usual outcomeOutcomeStrategyA desired business or user outcomeView reference → is a note that is true for six weeks.
Modelled as an axis, the lever gets a name, a closed set of values, and a link to the flag that drives it. The two navigations both stay in the graph and each declares which values it is present_under. Nothing about the product changes that day. What changes is that an unqualified read now returns the union honestly, and a reader who wants one configuration asks for it by name instead of inferring it from which half of the model somebody wrote most recently.
The rule that saves the most trouble is one axis per semantic lever, not one per code flag. Two flags that cannot be set independently, where one forces the other on, are one axis with two values. Modelling them as two axes asserts a four-configuration family of which three members do not exist, and the arithmetic compounds: five independent axes claim thirty-two configurations. Link each driving flag with Feature FlagdrivesConfiguration Axiscross-domain. The mechanism lives in engineering; the lever lives here.feature_flag_drives_configuration_axis
In the Unified Product Graph, Configuration AxisProduct SpecificationA named dimension along which the product's composition differs: a feature flag, a plan tier, a permission level, a beta programme. The stored graph is the union of the family; one configuration is a projection of it. sits in the Product Spec domain and hangs off the product that defines it through configuration_axisProductdefinesConfiguration Axishierarchy. Inbound, product_defines_configuration_axisFeature FlagdrivesConfiguration Axiscross-domain names the mechanism and feature_flag_drives_configuration_axisSurfacevaries byConfiguration Axiscross-domain names what changes along it. Structural relationships are edges, per the P14 principle, so the axis holds no list of the things that vary; it holds only its own values and its kind.surface_varies_by_configuration_axis
Three marking mechanisms hang off it, and choosing between them is most of the work. present_under is for a thing that only exists under some values. active_when is for a relationship that changes while both of its ends persist. Surfacealternates withSurfacesemantic is for a one-of-these-depending pair, declared from the surface present under the default value. surface_alternates_with_surfacevalidate_graph enforces the closed-values rule under its configuration_drift scope, which is the check that keeps a projection meaningful: an axis whose values are open cannot support one, because nothing bounds the family being projected from.
Type-specific fields on BaseNode
valuesstring[]The closed set of values this axis can take. Required: an axis with no values selects nothing and cannot be projected along. Every `present_under` and `active_when.values` entry must name one of them.
Two values is the common case and three is not unusual (a plan ladder). Order is not significant: the axis is categorical, not ordinal. Where the ordering does matter (an entitlement ladder in which each tier includes the one below), that is a classification question, and `classification_axis` with `axis_kind: 'ordinal'` is the instrument for it.
default_valuestringThe value this axis is understood to sit at when nobody says otherwise. Must be a member of `values`. NOTHING APPLIES IT AUTOMATICALLY: an unqualified read returns the union, not this projection.
The union is the honest answer to an unqualified question, because it is every configuration at once; silently substituting one of them would hide the others from a reader who did not know to ask. What the field does carry is the declaration convention for `surface_alternates_with_surface` (declare the edge from the surface present under the default) and a documented anchor for tools that later want to offer a starting configuration. It is a claim about the model, not about deployment: it says which value most of the graph was written against, not which configuration most customers are on.
kindenumWhat kind of lever this is. Names the mechanism family.
idstringrequiredUnique identifier (UUID)
typeNodeTyperequiredDiscriminator for the entity type
titlestringrequiredDisplay name
descriptionstringOptional detailed description
statusstringLifecycle status
tagsstring[]Freeform tags for filtering
3 edge types connected to this entity.
product_defines_configuration_axisfeature_flag_drives_configuration_axissurface_varies_by_configuration_axis