Extending the GraphQL-Ruby Type Definition System

While integrating GraphQL into your app, you can customize the definition DSL. For example, you might:

This guide describes various options for extending the class-based definition API. Keep in mind that these approaches may change as the API matures. If you’re having trouble, consider opening an issue on GitHub to get help.

Note: This document describes best practice with GraphQL-Ruby 1.10+. For customizing schemas on older versions, use GitHub to browse older versions of this page.

Customization Overview

In general, the schema definition process goes like this:

Customizing type definitions

In your custom classes, you can add class-level instance variables that hold configuration. For example:

class Types::BaseObject < GraphQL::Schema::Object
  # Call this method in an Object class to get or set the permission level:
  def self.required_permission(permission_level = nil)
    if permission_level.nil?
      # return the configured value
      @required_permission
    else
      @required_permission = permission_level
    end
  end
end

# Then, in concrete classes
class Dossier < BaseObject
  # The Dossier object type will have `.metadata[:required_permission] # => :admin`
  required_permission :admin
end

# Now, the type responds to that method:
Dossier.required_permission
# => :admin

Now, any runtime code which calls type.required_permission will get the configured value.

Customizing fields

Fields are generated in a different way. Instead of using classes, they are generated with instances of GraphQL::Schema::Field (or a subclass). In short, the definition process works like this:

# This is what happens under the hood, roughly:
# In an object class:
field :name, String, null: false
# ...
# Leads to:
field_config = GraphQL::Schema::Field.new(:name, String, null: false)

So, you can customize this process by:

For example, you can create a custom class which accepts a new parameter to initialize:

class Types::AuthorizedField < GraphQL::Schema::Field
  # Override #initialize to take a new argument:
  def initialize(*args, required_permission: nil, **kwargs, &block)
    @required_permission = required_permission
    # Pass on the default args:
    super(*args, **kwargs, &block)
  end

  attr_reader :required_permission
end

Then, pass the field class as field_class(...) wherever it should be used:

class Types::BaseObject < GraphQL::Schema::Object
  # Use this class for defining fields
  field_class AuthorizedField
end

# And/Or
class Types::BaseInterface < GraphQL::Schema::Interface
  field_class AuthorizedField
end

Now, AuthorizedField.new(*args, &block) will be used to create GraphQL::Schema::Fields on those types. At runtime field.required_permission will return the configured value.

Customizing Connections

Connections may be customized in a similar way to Fields.

For example, you can create a custom connection:

class Types::MyCustomConnection < GraphQL::Types::Relay::BaseConnection
  field :total_count, Integer, null: false

  def total_count
    object.nodes.size
  end
end

Then, pass the field class as connection_type_class(...) wherever it should be used:

module Types
  class Types::BaseObject < GraphQL::Schema::Object
    # Use this class for defining connections
    connection_type_class MyCustomConnection
  end
end

Now, all type classes that extend BaseObject will have a connection_type with the additional field totalCount.

Customizing Edges

Edges may be customized in a similar way to Connections.

Customizing Arguments

Arguments may be customized in a similar way to Fields.

Then, in your custom argument class, you can use #initialize(name, type, desc = nil, **kwargs) to take input from the DSL.

Customizing Enum Values

Enum values may be customized in a similar way to Fields.

Then, in your custom argument class, you can use #initialize(name, desc = nil, **kwargs) to take input from the DSL.