facebook/relay · error
Unexpected StorageKey
Error message
Unexpected StorageKey
What it means
write_constant_value converts a Primitive into the literal JS text of a generated artifact and only handles value kinds valid in a constant position (literals, enums, lists, objects, null). StorageKey primitives are handled elsewhere (storage key generation), so encountering one in a constant position means a StorageKey leaked into argument/value printing; the printer panics rather than emitting wrong code.
Source
Thrown at compiler/crates/relay-codegen/src/printer.rs:930
f.push('{');
for ObjectEntry { key: name, value } in obj {
write!(f, "\\\"{name}\\\":")?;
write_constant_value(f, builder, value)?;
f.push(',');
}
if !obj.is_empty() {
f.pop();
}
f.push('}');
Ok(())
}
}
}
Primitive::Null | Primitive::SkippableNull => {
f.push_str("null");
Ok(())
}
Primitive::StorageKey(_, _) => panic!("Unexpected StorageKey"),
Primitive::RawString(_) => panic!("Unexpected RawString"),
Primitive::GraphQLModuleDependency(_) => panic!("Unexpected GraphQLModuleDependency"),
Primitive::JSModuleDependency { .. } => panic!("Unexpected JSModuleDependency"),
Primitive::ResolverModuleReference { .. } => panic!("Unexpected ResolverModuleReference"),
Primitive::PropertyAccessor(_) => panic!("Unexpected PropertyAccessor"),
Primitive::DynamicImport { .. } => panic!("Unexpected DynamicImport"),
Primitive::RelayResolverModel { .. } => panic!("Unexpected RelayResolver"),
}
}
View on GitHub (pinned to 668b1b85e0)
Solutions
- Locate the argument producing the StorageKey (usually @static_arg / @arguments on a fragment spread) and move it to a supported position or use a plain constant value
- Remove/replace nested storage-key-producing arguments inside lists or objects printed as constants
- Check for custom transforms that inject StorageKey primitives and emit the value directly instead
- Update the Relay compiler to a version where StorageKey printing is routed through the storage-key path
Example fix
// before ...Frag @static_arg(key: "inner", value: [["nested"]]) // after ...Frag @static_arg(key: "inner", value: "nested")
Defensive patterns
Strategy: validation
Validate before calling
function ensureConstantArgs(doc) {
visit(doc, {
Argument(node) {
if (containsStorageKeyConstruct(node.value)) {
throw new Error(`Argument ${node.name.value} must be a plain constant`);
}
}
});
} Type guard
const isConstantPrimitive = (p) => ['string','number','boolean','enum','list','object','null'].includes(p.kind);
Try / catch
try {
generateArtifacts();
} catch (e) {
if (String(e).includes('Unexpected StorageKey')) {
// move the @static_arg/storage-key usage to a supported position
}
throw e;
} Prevention
- Use @static_arg only in its documented position on fragment spreads
- Never nest storage-key constructs inside lists/objects of other arguments
- Keep custom transforms free of StorageKey primitives in argument values
- Run codegen in CI so regressions surface early
When it happens
Trigger: Printing a fragment argument or embedded value where a Primitive::StorageKey was produced by the builder — e.g. a @static_arg/storage-key construct nested inside a position printed by write_argument_value → write_constant_value (lists, object fields) rather than the dedicated storage-key path.
Common situations: Combining @static_arg/@arguments constructs with positions the constant printer visits (nested lists/objects); custom transforms emitting StorageKey primitives in ordinary argument positions; compiler regression after upgrading Relay.
Related errors
- Expected an object key or null
- Unexpected RawString
- Unexpected GraphQLModuleDependency
- Unexpected JSModuleDependency
- Unexpected ResolverModuleReference
AI-assisted analysis of facebook/relay@668b1b85e0 (2026-09-02).
Data as JSON: /api/errors/cc433f3c636efc96.
Report an issue: GitHub.