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

  1. 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
  2. Remove/replace nested storage-key-producing arguments inside lists or objects printed as constants
  3. Check for custom transforms that inject StorageKey primitives and emit the value directly instead
  4. 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

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


AI-assisted analysis of facebook/relay@668b1b85e0 (2026-09-02). Data as JSON: /api/errors/cc433f3c636efc96. Report an issue: GitHub.