GraphQL Interfaces â Shared Field Contracts Across Types
In this tutorial, you will learn about Graphql Interfaces. We cover key concepts, practical examples, and best practices to help you master this topic.
GraphQL interfaces define a contract of shared fields that multiple types can implement, enabling polymorphic queries where a single field returns different object types.
What You'll Learn
You will learn how to define interfaces in SDL, implement them on object types, query interface fields with inline fragments, and design effective interface hierarchies.
Why Interfaces Matter
Without interfaces, each type defines its own fields even when they share common patterns. Every Device, Threat, and User has an id, createdAt, and updatedAt â but without an interface, each defines them separately. Interfaces enforce that every implementing type includes these fields, making schemas more predictable. DodaTech's Durga Antivirus Pro uses a Node interface for all timestamped entities and a Event interface for all notification events, ensuring every event type has id, timestamp, and severity.
flowchart TB
A["interface Node {\n id: ID!\n createdAt: DateTime!\n updatedAt: DateTime!\n}"] --> B["type Device implements Node"]
A --> C["type Threat implements Node"]
A --> D["type User implements Node"]
A --> E["type Scan implements Node"]
B --> F["Query { search: [Node!]! }"]
style A fill:#dbeafe,stroke:#2563eb
style B fill:#fef3c7,stroke:#d97706
style C fill:#fef3c7,stroke:#d97706
style D fill:#fef3c7,stroke:#d97706
style E fill:#fef3c7,stroke:#d97706
Prerequisites: GraphQL types and schema design. Understanding of object-oriented polymorphism is helpful.
Defining an Interface
interface Node {
id: ID!
createdAt: DateTime!
updatedAt: DateTime!
}
interface Event {
id: ID!
timestamp: DateTime!
severity: Severity!
message: String!
}
Interface fields must be implemented exactly as defined â same name, same type, same nullability.
Implementing an Interface
type Device implements Node {
id: ID!
createdAt: DateTime!
updatedAt: DateTime!
name: String!
os: String!
user: User!
}
type Threat implements Node & Event {
id: ID!
createdAt: DateTime!
updatedAt: DateTime!
timestamp: DateTime!
severity: Severity!
message: String!
name: String!
threatType: ThreatType!
}
type Scan implements Node & Event {
id: ID!
createdAt: DateTime!
updatedAt: DateTime!
timestamp: DateTime!
severity: Severity!
message: String!
status: ScanStatus!
duration: Int!
}
A type can implement multiple interfaces. It must include all required fields from each interface.
Querying Interface Fields
interface Searchable {
id: ID!
score: Float!
}
type Query {
search(query: String!): [Searchable!]!
recentEvents: [Event!]!
}
# Query that returns an interface type
query RecentAlerts {
recentEvents {
id
timestamp
severity
message
# Type-specific fields need inline fragments
... on Threat {
name
threatType
}
... on Scan {
status
duration
}
}
}
# Expected response
{
"data": {
"recentEvents": [
{
"id": "evt-001",
"timestamp": "2026-06-28T10:00:00Z",
"severity": "CRITICAL",
"message": "Emotet detected on Office-PC",
"name": "Emotet",
"threatType": "RANSOMWARE"
},
{
"id": "evt-002",
"timestamp": "2026-06-28T10:05:00Z",
"severity": "INFO",
"message": "Full scan completed on Dev-Macbook",
"status": "CLEAN",
"duration": 45000
}
]
}
}
Interface Resolvers
When a field returns an interface type, you need __resolveType to tell GraphQL which concrete type to use:
const resolvers = {
Event: {
__resolveType(event) {
// Determine type based on a discriminator field
if (event.threatType) return 'Threat';
if (event.status) return 'Scan';
if (event.channel) return 'Notification';
return null; // Unknown type â GraphQL will error
},
},
Query: {
recentEvents: () => {
return [
{ id: 'evt-001', timestamp: new Date(), severity: 'CRITICAL',
message: 'Threat detected', threatType: 'RANSOMWARE', name: 'Emotet',
createdAt: new Date(), updatedAt: new Date() },
{ id: 'evt-002', timestamp: new Date(), severity: 'INFO',
message: 'Scan complete', status: 'CLEAN', duration: 45000,
createdAt: new Date(), updatedAt: new Date() },
];
},
},
};
Interface vs Union
# Interface â types share common fields
interface Event {
id: ID!
timestamp: DateTime!
severity: Severity!
}
# Union â types may have no common fields
union SearchResult = Device | Threat | User
# Query
type Query {
recentEvents: [Event!]! # Guaranteed id, timestamp, severity on all
search(query: String!): [SearchResult!]! # No guaranteed common fields
}
Use interfaces when types share fields. Use unions when types are completely different but can appear in the same context.
Common Mistakes
1. Forgetting __resolveType
Without __resolveType, GraphQL cannot determine which concrete type to return for an interface field. Every interface must have a type resolver.
2. Omitting Interface Fields in Implementing Types
Every field declared in the interface must be present in the implementing type with the exact same signature (name, type, nullability). Missing fields cause schema errors.
3. Not Using Inline Fragments in Queries
Interface queries return only interface-level fields. To access type-specific fields, use ... on Threat { ... } or ... on Scan { ... }.
4. Overusing Interfaces for Unrelated Types
If types share no meaningful common fields, use a union instead. Forcing unrelated types under an interface creates a misleading contract.
5. Deep Interface Hierarchies
Interfaces implementing interfaces creates complex resolution chains. Limit to one level of interface inheritance unless absolutely necessary.
Practice Questions
- What is the difference between an interface and a union?
- What is __resolveType and when is it needed?
- Can a type implement multiple interfaces?
- What happens if an implementing type omits an interface field?
- How do clients access type-specific fields from an interface query?
Answers:
- Interfaces define shared fields that all implementing types must include. Unions define a set of possible types with no guaranteed common fields.
__resolveTypeis a resolver function on the interface that returns the name of the concrete type for a given value. It's required for every interface.- Yes â
type Threat implements Node & Eventimplements both interfaces. The type must include fields from both. - The schema is invalid and won't load. Every interface field must be present in the implementing type with the exact same signature.
- Using inline fragments:
... on Threat { threatType }. The client checks__typenameor uses fragments to access type-specific fields.
Challenge: Design an interface hierarchy for DodaTech's event system. Create a base Event interface with id, timestamp, severity, and source. Implement SecurityEvent (threatType, affectedDevice), SystemEvent (serviceName, status), and UserEvent (userId, action). Write a query that fetches recent events and displays type-specific details. Implement __resolveType for the interface.
FAQ
Mini Project
Create a GraphQL schema for DodaTech's notification system with an Notification interface implemented by EmailNotification, SMSNotification, PushNotification, and WebhookNotification. Implement a notifications query that returns all types and uses inline fragments for type-specific rendering.
What's Next
| Topic | Description |
|---|---|
| Union Types | Polymorphic types without shared fields |
| Enums in Depth | Fixed value sets for type safety |
| Input Types | Structured mutation arguments |
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro