facebook/relay · error
Unexpected custom directives
Error message
Unexpected custom directives: {:#?} What it means
When building an inline fragment in codegen, the compiler only supports inline fragments with no directives, @client, @defer/@stream-style metadata, @match, @module, or @catch. If an inline fragment carries other custom directives after client directives were handled, the compiler does not know how to represent them and panics, printing the full directive list.
Solutions
- Remove unsupported custom directives from the inline fragment or move the directive to a supported position (e.g. the parent field or a named fragment).
- Give the inline fragment a type condition so Relay can emit it (TODO T63559346: anonymous inline fragments without directives are unsupported here).
- Implement @include/@skip semantics via variables handled by transforms, or restructure the query so those directives apply to fields/spreads Relay supports.
- If the directive is your own, strip it with a custom compiler transform before codegen.
Example fix
// before
... on User @myDirective { name }
// after
... on User { name } Defensive patterns
Strategy: validation
Validate before calling
const SUPPORTED = new Set(['client', 'defer', 'stream', 'match', 'module', 'catch', 'required', 'relay']);
function validateInlineFragmentDirectives(fragment) {
for (const d of fragment.directives || []) {
if (!SUPPORTED.has(d.name.value)) {
throw new Error('Unsupported directive @' + d.name.value + ' on inline fragment');
}
}
if (!fragment.typeCondition && !(fragment.directives || []).length) {
console.warn('Anonymous inline fragment without directives is unsupported by relay codegen');
}
} Type guard
function hasOnlySupportedDirectives(fragment) {
const allowed = new Set(['client', 'defer', 'stream', 'match', 'module', 'catch', 'required', 'relay']);
return (fragment.directives || []).every(d => allowed.has(d.name.value));
} Try / catch
try {
relayCompiler.compile();
} catch (e) {
if (String(e).includes('Unexpected custom directives')) {
console.error('Remove unsupported directives from inline fragments:', e.message);
} else throw e;
} Prevention
- Do not put custom/server directives on inline fragments
- Always give inline fragments a type condition
- Strip custom directives with a compiler transform before codegen
- Document allowed directives for your GraphQL authors
When it happens
Trigger: Compiling a document with an anonymous inline fragment carrying custom (non-Relay) directives, e.g. `... on Type @myDirective` or `... @include(if: $x)` in a position Relay's codegen does not support.
Common situations: Using server-only directives like @include/@skip on inline fragments in reader codegen paths; custom schema directives left on inline fragments; anonymous inline fragments (`... ` with no type condition) that Relay cannot type.
Related errors
- Expect the module import inline fragment to have a type
- Cannot have @module selections at multiple paths unless the…
- Expected a scalar field.
- Expected filters_arg to have been previously validated.
- expected to find a supported argument as checked before
AI-assisted analysis of facebook/relay@668b1b85e0 (2026-09-02).
Data as JSON: /api/errors/5807e0750529cc2f.
Report an issue: GitHub.
Appendix: source
Thrown at compiler/crates/relay-codegen/src/build_ast.rs:2367
selections: selections,
type_: Primitive::SkippableNull,
abstract_key: Primitive::SkippableNull,
}));
let aliased_fragment = Primitive::Key(self.object(object! {
fragment: primitive,
kind: Primitive::String(CODEGEN_CONSTANTS.aliased_inline_fragment_spread),
name: Primitive::String(fragment_alias_metadata.alias.item),
}));
if let Some(catch_metadata) =
CatchMetadataDirective::find(&inline_frag.directives)
{
self.build_catch_node(catch_metadata, aliased_fragment)
} else {
aliased_fragment
}
} else {
// TODO(T63559346): Handle anonymous inline fragments with no directives
panic!(
"Unexpected custom directives: {:#?}",
inline_frag.directives
);
}
}
Some(type_condition) => {
if self.variant == CodegenVariant::Normalization {
let is_abstract_inline_fragment = type_condition.is_abstract_type();
if is_abstract_inline_fragment {
// Maintain a few invariants:
// - InlineFragment (and `selections` arrays generally) cannot be empty
// - Don't emit a TypeDiscriminator under an InlineFragment unless it has
// a different abstractKey
// This means we have to handle two cases:
// - The inline fragment only contains a TypeDiscriminator with the same
// abstractKey: replace the Fragment w the Discriminator
// - The inline fragment contains other selections: return all the selections
// minus any Discriminators w the same keyView on GitHub (pinned to 668b1b85e0)