facebook/relay · error

Expected field to be defined on concrete type

Error message

Expected field to be defined on concrete type

What it means

In concrete_types_have_different_implementations, when deciding whether to fan out an interface field's selections per concrete object type, the code looks up the selection's field on each implementing object via schema.named_field(...) and expects it to exist on every concrete type. This assumes schema validation previously proved the field is defined on all implementing objects; if an implementing object lacks the field, the expect panics.

Source

Thrown at compiler/crates/relay-transforms/src/relay_resolvers_abstract_types.rs:157

            .iter()
            .find(|directive| directive.name.0 == RELAY_RESOLVER_DIRECTIVE_NAME.0)
            && resolver_directive
                .arguments
                .named(ArgumentName(*ROOT_FRAGMENT_FIELD))
                .is_some()
        {
            return true;
        }
        // Any of the implementing objects' corresponding field is a resolver field
        let selection_name = interface_field.name.item;
        let implementing_objects =
            interface.recursively_implementing_objects(Arc::as_ref(&self.program.schema));
        implementing_objects.iter().any(|object_id| {
            let concrete_field_id = self
                .program
                .schema
                .named_field(Type::Object(*object_id), selection_name)
                .expect("Expected field to be defined on concrete type");
            let concrete_field = self.program.schema.field(concrete_field_id);
            // A field is a "different implementation" if it's either an explicit
            // resolver or any extension field (e.g. a synthetic ID field on a
            // client model type that has no @relay_resolver directive but is still
            // client-only and must be fanned out per concrete type).
            concrete_field.is_extension
                || concrete_field
                    .directives
                    .iter()
                    .any(|directive| directive.name.0 == RELAY_RESOLVER_DIRECTIVE_NAME.0)
        })
    }

    fn create_inline_fragment_selections_for_interface(
        &self,
        interface_id: InterfaceID,
        selections: &[Selection],
    ) -> Vec<Selection> {

View on GitHub (pinned to 668b1b85e0)

Solutions

  1. Fix the schema so every object implementing the interface defines the selected field (interface field implementations are mandatory).
  2. Run/enable the schema validation passes before relay_resolvers_abstract_types.
  3. Check custom client-extension generation for object types missing the field.
  4. If the field genuinely is absent, guard with if let Some(...) instead of expect in a local fork and skip the object.

Example fix

// before
let concrete_field_id = self.program.schema
    .named_field(Type::Object(*object_id), selection_name)
    .expect("Expected field to be defined on concrete type");
// after
let Some(concrete_field_id) = self.program.schema
    .named_field(Type::Object(*object_id), selection_name) else { return false; };
Defensive patterns

Strategy: type-guard

Validate before calling

for object_id in interface.recursively_implementing_objects(schema) {
    assert!(schema.named_field(Type::Object(object_id), field_name).is_some(),
        "{field_name} must be defined on implementing object");
}

Type guard

fn field_on_all_impls(schema: &Schema, iface: InterfaceId, name: &str) -> bool {
    iface.recursively_implementing_objects(schema).iter().all(|o| schema.named_field(Type::Object(*o), name).is_some())
}

Try / catch

let Some(field_id) = schema.named_field(Type::Object(*object_id), selection_name) else { return false; };

Prevention

When it happens

Trigger: An interface field selection whose name is not defined on one of the interface's recursively-implementing objects reaches should_copy_selection — e.g. the schema was built with a field defined on the interface but a concrete object misses it, bypassing the normal validation pass.

Common situations: Hand-constructed or extended schemas (client extensions / synthetic ID fields) where an implementing object type omits the interface field; running the abstract-types transform before schema validation; schema stitching that dropped a field.

Related errors


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