facebook/relay · error
Expected field to be defined on concrete type
Error message
Expected field to be defined on concrete type
What it means
This panic fires while collecting fragment dependencies of a @relay_resolver defined on an interface. For each object implementing the interface, the transform looks up the interface's field on the concrete object; if any implementing object lacks that field, the lookup returns None and the transform panics. Relay requires every implementing object to define the field so dependency fragments can be gathered per concrete type.
Source
Thrown at compiler/crates/relay-transforms/src/relay_resolvers/fragment_dependencies.rs:109
})
.and_then(|arg| arg.value.get_string_literal().map(FragmentDefinitionName));
if let Some(name) = name {
return vec![name];
}
let mut fragment_names = Vec::new();
// For a resolver field on an abstract type, we currently need to include rootFragments for all implementations,
// because compiling resolvers don't have incremental mode
if let Some(Type::Interface(interface_type)) = field.parent_type {
let interface = schema.interface(interface_type);
let implementing_objects = interface.recursively_implementing_objects(schema);
// Collect all fragment names from concrete implementations
for object_id in implementing_objects.iter() {
let concrete_field_id = schema
.named_field(Type::Object(*object_id), field.name.item)
.expect("Expected field to be defined on concrete type");
let concrete_field = schema.field(concrete_field_id);
if let Some(fragment_name) = concrete_field
.directives
.named(*RELAY_RESOLVER_DIRECTIVE_NAME)
.filter(|resolver_directive| {
let generated = resolver_directive
.arguments
.named(*GENERATED_FRAGMENT_ARGUMENT_NAME)
.and_then(|arg| arg.value.get_bool_literal())
.unwrap_or(false);
!generated
})
.and_then(|resolver_directive| {
resolver_directive
.arguments
.named(*FRAGMENT_KEY_ARGUMENT_NAME)
})View on GitHub (pinned to 668b1b85e0)
Solutions
- Add the missing @relay_resolver field to every type that implements the interface.
- Run graphql validation (fragment spread/type refinement validation) before the transform so mismatched interface fields are caught earlier.
- Regenerate concrete types from the updated interface definitions if types are generated.
- Temporarily narrow the interface (remove non-conforming implementors) until all implementors define the field.
Example fix
// before
interface Node { image_url: String @relay_resolver(import_path: "...", fragment_name: "...") }
// after
type User implements Node { image_url: String @relay_resolver(...) } // every implementor defines the field Defensive patterns
Strategy: validation
Validate before calling
// Before compiling, check every implementor defines the interface's resolver field:
for object_id in interface.recursively_implementing_objects(schema).iter() {
assert!(
schema.named_field(Type::Object(*object_id), field.name.item).is_some(),
"type {} does not define resolver field {}",
schema.object(*object_id).name.item.0, field.name.item.0
);
} Type guard
fn interface_field_defined_on_all_implementors(schema: &SDLSchema, interface: &Interface, field_name: &str) -> bool {
interface.recursively_implementing_objects(schema).iter().all(|id| {
schema.named_field(Type::Object(*id), field_name).is_some()
})
} Try / catch
// Panics abort the compiler process; validate ahead instead. If embedding the compiler, isolate it:
let output = std::panic::catch_unwind(|| run_relay_compiler(config));
match output {
Ok(result) => result,
Err(_) => Err("compiler panicked: interface resolver field missing on an implementor"),
} Prevention
- Whenever you add a @relay_resolver field to an interface, grep all 'implements <Interface>' types and add the field
- Add a CI lint that diffs interface fields against implementing object fields
- Regenerate concrete types after interface changes before compiling
- Keep fragment/type definitions in the same repo so renames are caught by the compiler
When it happens
Trigger: An interface (or object spreading an interface fragment) declares a @relay_resolver field, but one or more objects implementing that interface do not define that field in their own selection set / type definition — detected during visit_selections or get_ir_definition_references.
Common situations: Adding a @relay_resolver field to an interface and forgetting to add it to some implementing type; a new object type implementing the interface without updating its fields; codegen regenerating types after the interface changed.
Related errors
- Expected parent type
- Previous validation passes ensured this exists.
- has_root_fragment() returned true
- shadow_return_directive_fragment_name matched the directive
- BabelPluginRelay: Expected exactly one definition per graphq
AI-assisted analysis of facebook/relay@668b1b85e0 (2026-09-02).
Data as JSON: /api/errors/2a45325b35ae1d5f.
Report an issue: GitHub.