hasura/graphql-engine · error · Error

{} is defined on a non-composite type: {type_name}

Error message

{} is defined on a non-composite type: {type_name}

What it means

Thrown when a fragment (named or inline) declares an `on` target that is not a composite type (object, interface, or union) — e.g. `fragment F on Int` or an inline fragment on a scalar. Raised from collect.rs (collect_fields_internal / around line 299-323) during field collection; the error names either 'inline fragment' or 'fragment <name>' plus the offending type name. GraphQL only allows fragment type conditions on composite types.

Source

Thrown at v3/crates/graphql/lang-graphql/src/validation/error.rs:16

use thiserror::Error;

use crate::ast::{common as ast, spanning};

pub type Result<T> = core::result::Result<T, Error>;

#[derive(Error, Debug, Clone)]
pub enum Error {
    #[error("fragment cycle detected through: {0:?}")]
    CycleDetected(Vec<ast::Name>),
    // TODO, this error isn't thrown yet
    #[error("unused fragment: {0}")]
    FragmentNotUsed(ast::Name),
    #[error("fragment not defined in the document: {0}")]
    UnknownFragment(ast::Name),
    #[error("{} is defined on a non-composite type: {type_name}", match fragment_name { None => "inline fragment".to_owned(), Some(fragment_name) => format!("fragment {fragment_name}")})]
    FragmentOnNonCompositeType {
        fragment_name: Option<ast::Name>,
        type_name: ast::TypeName,
    },
    #[error("fragment of type {fragment_type} cannot be spread on type {selection_type}")]
    FragmentCannotBeSpread {
        selection_type: ast::TypeName,
        fragment_type: ast::TypeName,
    },
    // TODO, this error isn't thrown yet
    #[error(
        "a selection set is specified on field '{field_name}' of non-composite type: {type_name}"
    )]
    SelectionOnNonCompositeType {
        field_name: ast::Name,
        type_name: ast::TypeName,
    },
    #[error("no such field on type {type_name}: {field_name}")]

View on GitHub (pinned to 724551b9ae)

Solutions

  1. Change the fragment's type condition to a composite type (object/interface/union) that exists in the schema
  2. If the parent field is a scalar/enum, remove the fragment/selection set entirely and select the scalar leaf directly
  3. Regenerate client queries against the current schema after schema changes

Example fix

// before
fragment F on Int { ... }
# after
fragment F on User { id }
Defensive patterns

Strategy: validation

Validate before calling

let t = schema.lookup(&fragment.type_condition); assert!(t.map(|t| t.is_composite()).unwrap_or(false), "type condition must be composite");

Type guard

fn is_composite(t: &Type) -> bool { matches!(t, Type::Object(_) | Type::Interface(_) | Type::Union(_)) }

Try / catch

match validate(doc) { Err(Error::FragmentOnNonCompositeType { fragment_name, type_name }) => fix_type_condition(fragment_name, type_name), r => r }

Prevention

When it happens

Trigger: Writing `fragment F on String` (or any scalar/enum type) in a document; using an inline fragment `{ ... on MyEnum { ... } }` on a non-composite parent; applying fragments generated against a schema where the target used to be an object but is now a scalar/enum.

Common situations: Schema evolution changed a type from object to scalar or enum while stale queries still spread fragments on it; hand-written queries misunderstand that type conditions must be composite; codegen emitted fragments against the wrong schema version.

Related errors


AI-assisted analysis of hasura/graphql-engine@724551b9ae (2026-08-28). Data as JSON: /api/errors/3f1721178c4c68d7. Report an issue: GitHub.