Skip to main content
Interfaces define a set of fields that multiple object types must implement. They enable polymorphic field resolution and are perfect for representing shared characteristics across different object types.

Basic Interface Definition

Define interfaces in files or inline within your schema:
# app/graphql/interfaces/person.rb
module GraphQL
  class Person < GraphQL::Interface
    field :email, :string, null: false
    field :name, :string
  end
end
The resulting GraphQL type:
interface Person {
  email: String!
  name: String
}

Real-World Example

The Star Wars schema demonstrates interfaces for character polymorphism:
Star Wars Character Interface
interface 'Character' do
  desc 'A character in the Star Wars Trilogy'
  
  field :id, null: false,
    desc: 'The id of the character'
    
  field :name,
    desc: 'The name of the character'
    
  field :friends, 'Character', array: true,
    desc: 'The friends of the character, or an empty list if they have none'
    
  field :appears_in, 'Episode', array: true,
    desc: 'Which movies they appear in'
    
  field :secret_backstory,
    desc: 'All secrets about their past'
end

Implementing Interfaces

Objects implement interfaces to inherit fields and validation:

Importing Fields (Default)

By default, interface fields are automatically imported as proxies:
object 'Human' do
  implements 'Character'
  # All Character fields are automatically available
  
  # Add Human-specific fields
  field :home_planet, :string
end

object 'Droid' do
  implements 'Character'
  # All Character fields are automatically available
  
  # Add Droid-specific fields
  field :primary_function, :string
end
Proxy fields delegate resolution to the interface instance, perfect for sharing resolution logic across objects.

Not Importing Fields

For validation-only interfaces, prevent field import:
# Option 1: Abstract interface
interface 'Person' do
  self.abstract = true  # Never instantiated
  field :email
end

# Option 2: Per-implementation control
object 'User' do
  # Define all fields first
  field :email
  field :name
  
  # Then declare implementation without importing
  implements 'Person', import_fields: false
end

Type Resolution

Interfaces require type resolution to determine which object type to use for a given value:

Default Resolution

The default type_for method checks each implementing type:
interface 'Character' do
  # Default implementation:
  def self.type_for(value, request)
    all_types&.reverse_each&.find { |t| t.valid_member?(value) }
  end
end

Custom Resolution

Override for specific logic, like ActiveRecord Single Table Inheritance:
interface 'Person' do
  field :email
  field :name
  
  def self.type_for(value, request)
    # Use ActiveRecord's type column
    request.find_type(value.type)
  end
end
Always use request.find_type for type lookups - it’s cached and respects namespaces.

Field Validation

When an object implements an interface, all fields are validated for equivalency:
interface 'Character' do
  field :id, :id, null: false
  field :name, :string
end

object 'Human' do
  implements 'Character'
  
  # ✓ Valid: matches interface signature
  field :id, :id, null: false
  field :name, :string
  
  # ✗ Invalid: different nullability
  # field :id, :id, null: true  # Would raise ArgumentError
end
Fields are checked by equivalency. Mismatched fields raise an ArgumentError.

Usage in Queries

Use interfaces as field return types with spreads for type-specific fields:
query_fields do
  field :hero, 'Character' do
    argument :episode, 'Episode'
  end
end
Query with Spreads
query {
  hero(episode: EMPIRE) {
    __typename
    id
    name
    
    ... on Human {
      homePlanet
    }
    
    ... on Droid {
      primaryFunction
    }
  }
}
Response
{
  "data": {
    "hero": {
      "__typename": "Droid",
      "id": "2001",
      "name": "R2-D2",
      "primaryFunction": "Astromech"
    }
  }
}

Common Patterns

Single Table Inheritance

interface 'Person' do
  self.assigned_to = 'Person'
  
  field :id
  field :email
  
  def self.type_for(value, request)
    request.find_type(value.type)
  end
end

object 'User' do
  self.assigned_to = 'User'
  implements 'Person'
end

object 'Admin' do
  self.assigned_to = 'Admin'
  implements 'Person'
  field :role
end

Shared Resolution Logic

interface 'Searchable' do
  field :search_rank, :float
  
  def search_rank
    current_value.calculate_search_score
  end
end

object 'Post' do
  implements 'Searchable'  # Inherits search_rank field and resolution
end

object 'Comment' do
  implements 'Searchable'  # Inherits search_rank field and resolution
end

Multiple Interfaces

object 'User' do
  implements 'Person', 'Node', 'Timestamped'
  # Imports fields from all three interfaces
end

Descriptions

Document interfaces for better API understanding:
interface 'Character' do
  desc 'A character in the Star Wars Trilogy'
  
  field :id, null: false,
    desc: 'The id of the character'
end

Best Practices

Interfaces are ideal for ActiveRecord Single Table Inheritance. The shared parent model maps perfectly to the interface.

Use interfaces for

  • Shared fields across types
  • Polymorphic associations
  • Common resolution logic
  • STI models

Use unions instead for

  • Grouping unrelated types
  • No shared fields
  • Polymorphic belongs_to

Build docs developers (and LLMs) love