facebook/relay · error

Expected client edge backing field to be transformed into ex

Error message

Expected client edge backing field to be transformed into exactly one primitive.

What it means

While building a client edge (Relay resolver-backed edge) in codegen, the compiler transforms the backing field selection into primitives and expects exactly one result. If the backing field expanded into zero or multiple primitives (e.g. a fragment with multiple selections or an empty selection), this panic fires, meaning the client-edge metadata is inconsistent with the actual document.

Source

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

                 })
            }
        })
    }

    // This function creates a node that is the UNION of the nodes that would be created for read time resolvers
    // and for exec time resolvers (so runtime has ALL the information it needs to run for both resolver modes.)
    // For C2C (client-to-client) edges, we emit ClientEdgeToClientObject with model resolvers.
    // For C2S (client-to-server) edges, we emit ClientEdgeToServerObject with the operation reference.
    fn build_client_edge_exec_and_read_time(
        &mut self,
        context: &mut ContextualMetadata,
        client_edge_metadata: ClientEdgeMetadata<'_>,
    ) -> Primitive {
        let backing_field_primitives =
            self.build_selections_from_selection(context, client_edge_metadata.backing_field);

        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. {:?}",

View on GitHub (pinned to 668b1b85e0)

Solutions

  1. Ensure the client edge backing field is a single scalar field (or single inlined resolver fragment resolving to one field) with no sibling selections.
  2. Regenerate the client schema/extension so backing-field metadata matches the current document.
  3. Update the relay compiler if a transform recently changed how backing fields are normalized.
  4. Inspect the document with graphql-js validation/dump to see the actual backing field shape before compiling.

Example fix

// before: backing fragment with multiple fields
extend type Query { viewer: Viewer @clientEdge(backing: ViewerEdgeFragment) }
fragment ViewerEdgeFragment on Query { viewer node(id: $id) }

// after: single backing resolver field
extend type Query { viewer: Viewer @clientEdge }
fragment ViewerEdgeFragment on Query { viewer }
Defensive patterns

Strategy: validation

Validate before calling

// Check client edge backing fragment has exactly one selection
function validateBackingFragment(fragment) {
  if (!fragment || fragment.selections.length !== 1) {
    throw new Error('Client edge backing fragment must contain exactly one selection, got ' +
      (fragment ? fragment.selections.length : 0));
  }
}

Type guard

function isSingleSelectionBacking(fragment) {
  return fragment != null &&
    Array.isArray(fragment.selections) &&
    fragment.selections.length === 1;
}

Try / catch

try {
  buildAst(context, clientEdgeMetadata);
} catch (e) {
  if (String(e).includes('exactly one primitive')) {
    console.error('Client edge backing field expanded to != 1 selection; inspect the backing fragment.');
  } else throw e;
}

Prevention

When it happens

Trigger: Compiling a @clientEdge/Relay resolver field whose backing_field selection builds to more or fewer than one primitive — e.g. the backing field is a fragment spread or inline fragment containing multiple selections instead of a single scalar/resolver field.

Common situations: Client schema extensions where the client edge backing field was edited to add extra fields; transforms that inlined fragments differently than expected; stale generated metadata after schema changes.

Related errors


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