facebook/relay · error

Expected Client Edge backing field to be a Relay Resolver. {

Error message

Expected Client Edge backing field to be a Relay Resolver. {:?}

What it means

When reading directives of a client edge backing field, the compiler only accepts ScalarField or InlineFragment selections (both can carry Relay Resolver metadata). Any other selection kind (LinkedField, FragmentSpread at this stage, etc.) reaching this match means the backing field is not a Relay Resolver-backed field, so it panics with the selection dumped.

Source

Thrown at compiler/crates/relay-codegen/src/build_ast.rs:2061

        if backing_field_primitives.len() != 1 {
            panic!(
                "Expected client edge backing field to be transformed into exactly one primitive."
            )
        }
        let backing_field = backing_field_primitives.into_iter().next().unwrap();

        let selections_item = self.build_linked_field(context, client_edge_metadata.linked_field);

        match &client_edge_metadata.metadata_directive {
            ClientEdgeMetadataDirective::ClientObject {
                model_resolvers, ..
            } => {
                let field_directives = match &client_edge_metadata.backing_field {
                    Selection::ScalarField(field) => Some(&field.directives),
                    // Although the reader checks for FragmentSpreads on the backing field, the normalization
                    // transforms inline the fragment spread so we match an InlineFragment here
                    Selection::InlineFragment(inline_frag) => Some(&inline_frag.directives),
                    _ => panic!(
                        "Expected Client Edge backing field to be a Relay Resolver. {:?}",
                        client_edge_metadata.backing_field
                    ),
                };
                let client_edge_model_resolvers = field_directives.and_then(|field_directives| {
                    let resolver_metadata = RelayResolverMetadata::find(field_directives).unwrap();
                    let is_weak_resolver = matches!(
                        resolver_metadata.output_type_info,
                        ResolverOutputTypeInfo::Composite(_)
                    );
                    let model_resolver_primitives = if !is_weak_resolver {
                        self.build_client_edge_model_resolvers(model_resolvers, resolver_metadata)
                    } else {
                        vec![]
                    };
                    if model_resolver_primitives.is_empty() {
                        None
                    } else {

View on GitHub (pinned to 668b1b85e0)

Solutions

  1. Make the client edge backing field a Relay Resolver scalar field (or an inline fragment that normalizes to one).
  2. Confirm the normalization transforms that inline fragment spreads ran before codegen; align compiler version with your transform config.
  3. Check RelayResolverMetadata is attached via @relay_resolver / RescriptRelay resolver directives on the backing field.
  4. Dump the normalized IR to verify which selection kind the backing field became.

Example fix

// before: backing field is a linked field
extend type Query { items: [Item] @clientEdge }

// after: backing field is a resolver scalar
extend type Query { itemResolver: Item @relay_resolver(root_fragment_name: "ItemResolverFragment") @clientEdge }
Defensive patterns

Strategy: type-guard

Validate before calling

function validateClientEdgeBacking(selection) {
  const ok = selection.kind === 'ScalarField' || selection.kind === 'InlineFragment';
  if (!ok) {
    throw new Error('Client edge backing field must be a scalar or inline fragment, got ' + selection.kind);
  }
}

Type guard

function isResolverBackableSelection(s) {
  return s != null && (s.kind === 'ScalarField' || s.kind === 'InlineFragment');
}

Try / catch

try {
  compile();
} catch (e) {
  if (String(e).includes('Client Edge backing field to be a Relay Resolver')) {
    console.error('Backing field selection kind not resolver-compatible:', e.message);
  } else throw e;
}

Prevention

When it happens

Trigger: Compiling a client edge whose backing field is a Selection::LinkedField or any selection other than a scalar field / inline fragment after normalization transforms.

Common situations: Defining a client edge over a linked (object) field instead of a resolver scalar; transform pipeline differences where the backing fragment spread was not inlined to an InlineFragment; hand-edited client schema extensions.

Related errors


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