Core Directives
@resolver
Marks fields or types that require custom resolution logic. This is the primary mechanism for implementing data fetching in Viaduct.FIELD_DEFINITION, OBJECT
Purpose:
- Fields that fetch data from external services or databases
- Fields that require custom business logic beyond simple property access
- Object types that need node resolution for Global ID support
@resolver to a field, Viaduct generates an abstract resolver class that you must implement:
Node, generates a node resolver:
@backingData
Specifies the backing data class for a field, enabling type-safe data access in resolvers.FIELD_DEFINITION
Arguments:
Fully qualified name of the backing data class (Kotlin class name)
- Mapping GraphQL types to internal data models
- Providing type information for fields derived from backing data
- Enabling Viaduct to automatically resolve fields from backing data without custom resolvers
- Resolver fetches data: The resolver for
User.profilereturns aUserProfileDatainstance - Viaduct maps fields: Viaduct automatically maps
UserProfilefields toUserProfileDataproperties - No field resolvers needed: Individual fields like
bio,avatarUrlare resolved automatically
- Separates transformation logic from data retrieval
- Reduces boilerplate - no need to write resolvers for every field
- Type-safe mapping between GraphQL and internal models
- Centralizes data shaping logic
@scope
Controls field and type visibility across different schema scopes. This is a repeatable directive that enables multi-tenant or multi-variant schemas.OBJECT, INTERFACE, UNION, ENUM, INPUT_OBJECT, FIELD_DEFINITION, ENUM_VALUE
Arguments:
List of scope names where this element is visible. Use
["*"] for universal visibility.- Creating public vs. internal API variants from the same codebase
- Feature flagging schema elements
- Multi-tenant schema visibility
- Gradual rollout of new features
- Permission-based field visibility
- Schema filtering: Elements are included/excluded at schema compilation time
- Access granted if ANY scope matches: If a user has scope
internal, they can see elements marked@scope(to: ["internal", "admin"]) - Introspection: Non-matching fields are omitted from introspection queries
- Planning: Unauthorized elements are not planned or executed
- Define a
defaultscope for general availability - Keep scopes orthogonal - avoid overlapping responsibilities
- Apply
@scopeexplicitly to sensitive fields - Log active scopes per request for auditability
- Complement with application-level authorization for data-level controls
@idOf
Declares that a field represents a Global ID for a specific GraphQL type. This enables type-safe ID handling.FIELD_DEFINITION, INPUT_FIELD_DEFINITION, ARGUMENT_DEFINITION
Arguments:
Name of the GraphQL type this ID references (must implement
Node)- Type-safe Global ID handling in resolvers
- Node interface implementations
- Cross-type references with compile-time validation
- Preventing ID type confusion
@idOf:
@idOf:
- Type Safety: Prevents passing a
PostID where aUserID is expected - Compile-Time Validation: Catches ID type mismatches before runtime
- Self-Documenting: Clear what type each ID references
- Relay Compatibility: Works seamlessly with Relay’s Global Object Identification spec
Pagination Directives
@connection
Marks an object type as a Relay Connection type for pagination support.OBJECT
Purpose:
- Build-time validation of connection type structure
- Integration with Viaduct’s pagination utilities
- Clear schema documentation of pagination patterns
@connection must:
- Have a name ending in
Connection - Have an
edgesfield returning a list of edge types (marked with@edge) - Have a
pageInfo: PageInfo!field
@edge
Marks an object type as a Relay Edge type within connections.OBJECT
Purpose:
- Build-time validation of edge type structure
- Integration with Viaduct’s pagination utilities
- Clear schema documentation of pagination patterns
@edge must:
- Have a
nodefield (any output type except list) - Have a
cursor: String!field (non-nullable String)
Directive Summary
| Directive | Locations | Purpose | Generated Code Impact |
|---|---|---|---|
@resolver | FIELD_DEFINITION, OBJECT | Marks fields/types requiring custom resolution | Generates abstract resolver classes |
@backingData(class: String!) | FIELD_DEFINITION | Specifies backing data class | Enables automatic field resolution |
@scope(to: [String!]!) | OBJECT, INTERFACE, UNION, ENUM, INPUT_OBJECT, FIELD_DEFINITION, ENUM_VALUE | Controls visibility by scope (repeatable) | Affects schema filtering |
@idOf(type: String!) | FIELD_DEFINITION, INPUT_FIELD_DEFINITION, ARGUMENT_DEFINITION | Declares Global ID type | Uses GlobalID<T> instead of String |
@connection | OBJECT | Marks Relay Connection types | Enables pagination validation |
@edge | OBJECT | Marks Relay Edge types | Enables pagination validation |
Best Practices
Do
- Apply
@resolverto fields fetching external data - This is how Viaduct knows which fields need custom logic - Use
@idOffor type-safe IDs - Leverage compile-time validation for ID references - Apply
@scopeexplicitly to sensitive fields - Don’t rely on implicit visibility - Use
@backingDatato separate concerns - Keep transformation logic separate from retrieval - Follow Relay conventions with
@connectionand@edge- Use standard pagination patterns
Don’t
- Don’t override core directives -
@resolver,@backingData,@scope,@idOf,@connection, and@edgeare framework-provided - Don’t forget to extend root types - Always use
extend type Query, nottype Query - Don’t use
@scopeas the only authorization mechanism - Complement with application-level checks - Don’t apply
@resolverto every field - Use backing data classes for simple mappings
See Also
- Built-in Types - Node interface and Connection types
- Scalars - Custom scalar types
- Tenant API - Implementing resolvers
- Module Plugin - Code generation from directives