Customizing the GraphQL Schema

ElasticGraph generates a complete GraphQL API from your schema definition, including filter inputs, aggregations, sort orders, connections, and more. This guide covers the customization options available to you to shape the generated schema to fit your project’s conventions.

The customizations primarily fall into two groups:

  • Naming options are passed to Local::RakeTasks in your Rakefile. They control the casing and spelling of generated names without changing schema structure.
  • Schema definition hooks let you customize generated types and fields; adding directives, documentation, or grouping fields under namespace types.

Casing of Generated Fields

Set schema_element_name_form to choose between :camelCase (the default) and :snake_case for every generated field name, argument name, and directive name in the SDL. Generated types like WidgetFilterInput are unaffected; only the elements within them.

ElasticGraph::Local::RakeTasks.new(
  local_config_yaml: "config/settings/local.yaml",
  path_to_schema: "config/schema.rb"
) do |tasks|
  tasks.schema_element_name_form = :snake_case
end

For example, this will cause ElasticGraph to generate StringFilterInput.equal_to_any_of rather than StringFilterInput.equalToAnyOf.

Note This option only impacts names originated by ElasticGraph. If you configure :snake_case but then define a homeCity field, ElasticGraph will use the name homeCity rather than home_city on the types it derives from your definition.

Renaming Generated Fields, Arguments, and Directives

Use schema_element_name_overrides to rename individual generated fields, arguments, or directives. For example, to spell out filter operators that ElasticGraph abbreviates by default:

ElasticGraph::Local::RakeTasks.new(
  local_config_yaml: "config/settings/local.yaml",
  path_to_schema: "config/schema.rb"
) do |tasks|
  tasks.schema_element_name_overrides = {
    gt: "greaterThan",
    gte: "greaterThanOrEqualTo",
    lt: "lessThan",
    lte: "lessThanOrEqualTo"
  }
end

To rename specific values within a generated enum (e.g. DayOfWeek.MONDAY to DayOfWeek.MON), use enum_value_overrides_by_type.

Naming Formats for Derived Types

For each type, ElasticGraph derives a number of other types. For example, if you define a Widget indexed type, ElasticGraph will derive types like WidgetFilterInput, WidgetAggregation, and WidgetSortOrder. These type naming patterns are configured by derived_type_name_formats.

For example, to drop the Input suffix from types like WidgetFilterInput across the entire schema:

ElasticGraph::Local::RakeTasks.new(
  local_config_yaml: "config/settings/local.yaml",
  path_to_schema: "config/schema.rb"
) do |tasks|
  tasks.derived_type_name_formats = {FilterInput: "%{base}Filter"}
end

The full set of naming formats is documented at SchemaElements::TypeNamer::DEFAULT_FORMATS. The %{base} placeholder is replaced with the source type’s name; format strings must preserve every placeholder the default uses, or schema generation fails with a config error.

Renaming Individual Types

When you need to rename a single type rather than changing a naming format used across the entire schema—for example, swapping ElasticGraph’s JsonSafeLong scalar for one with a name your team prefers—use type_name_overrides:

ElasticGraph::Local::RakeTasks.new(
  local_config_yaml: "config/settings/local.yaml",
  path_to_schema: "config/schema.rb"
) do |tasks|
  tasks.type_name_overrides = {JsonSafeLong: "BigInt"}
end

Warning The standard GraphQL scalars (Boolean, Float, ID, Int, String) and the root Query type cannot be renamed this way.

Customization Hooks

The schema definition API exposes hooks that let you customize generated types and fields. These hooks are commonly used to add directives like @deprecated. Hooks are available on individual fields, individual types, and on the schema itself.

Field-level Hooks

When you define a field, ElasticGraph generates corresponding fields on several derived types (filter input, aggregations, grouped-by, highlights, sub-aggregations) plus enum values on the sort order enum. on_each_generated_schema_element applies the same customization to all of them at once:

  schema.object_type "Transaction" do |t|
    t.field "id", "ID"
    t.field "currency", "String" do |f|
      f.on_each_generated_schema_element do |element|
        element.directive "deprecated"
      end
    end
    t.index "transactions"
  end

In this case, a @deprecated directive would be added to Transaction.currency, as well as all the schema elements derived from Transaction.currency including TransactionFilterInput.currency, TransactionGroupedBy.currency, and several others.

To target a single derived form, use the more specific hooks:

Type-level Hooks

To customize the derived types generated from an object or interface type as a whole, use customize_derived_types. Pass :all to customize every derived type. For example, to mark every derived type for Campaign with @deprecated:

  schema.object_type "Campaign" do |t|
    t.field "id", "ID"
    t.customize_derived_types :all do |dt|
      dt.directive "deprecated"
    end
    t.index "campaigns"
  end

Or pass one or more specific type names to target just those derived types:

  schema.object_type "Order" do |t|
    t.field "id", "ID"
    t.customize_derived_types "OrderFilterInput", "OrderSortOrderInput" do |dt|
      dt.directive "deprecated"
    end
    t.index "orders"
  end

To customize specific fields on a derived type, use customize_derived_type_fields. For example, to deprecate pageInfo and totalEdgeCount on ProductConnection:

  schema.object_type "Product" do |t|
    t.field "id", "ID"
    t.customize_derived_type_fields "ProductConnection", "pageInfo", "totalEdgeCount" do |f|
      f.directive "deprecated"
    end
    t.index "products"
  end

Built-in Types

ElasticGraph generates several built-in types you don’t define directly (Query, PageInfo, AggregationCountDetail, and others). on_built_in_types runs your block on each one as it’s generated:

  schema.on_built_in_types do |type|
    type.append_to_documentation "This is a built-in ElasticGraph type."
  end

Customizing the Root Query Type

on_root_query_type is a specialization of on_built_in_types that fires only for the root Query type. Use it to add ad hoc fields to Query, append documentation, or apply directives:

  schema.on_root_query_type do |t|
    t.append_to_documentation "Generated by ElasticGraph."
  end

You can also use this hook to add custom Query fields that use a custom GraphQL resolver.

Namespace Types

By default, the root query fields ElasticGraph generates for an indexed type are added directly to Query. For example, an indexed Widget type produces Query.widgets and Query.widgetAggregations.

When ElasticGraph is composed into a federated supergraph alongside other subgraphs, Query can become crowded and fields from different subgraphs can collide. A namespace type lets you group ElasticGraph’s root query fields under a nested path; for example, Query.olap.widgets instead of Query.widgets. This can improve discoverability and isolation of domain-specific types.

A namespace is an object type declared with namespace_type. You can route an indexed type’s root fields to the namespace by passing on: to root_query_fields, then expose the namespace type as a field on Query.

ElasticGraph.define_schema do |schema|
  schema.namespace_type "OlapQuery"

  schema.on_root_query_type do |t|
    t.field "olap", "OlapQuery"
  end

  schema.object_type "Widget" do |t|
    t.field "id", "ID"
    t.field "name", "String"
    t.index "widgets"
    t.root_query_fields plural: "widgets", on: "OlapQuery"
  end
end

The namespace type is named OlapQuery and is exposed as the olap field on Query. This produces a GraphQL API where Widgets are queried through olap:

query QueryWidgets {
  olap {
    widgets(first: 10) {
      nodes {
        id
        name
      }
    }
    widgetAggregations {
      nodes {
        count
      }
    }
  }
}

You don’t need to wire up a resolver for Query.olap. ElasticGraph auto-resolves any no-argument field whose return type is a namespace type.

Nested Namespaces

Namespace types can be nested inside other namespace types. The same auto-resolution applies, so you don’t have to configure a resolver for any intermediate field.

ElasticGraph.define_schema do |schema|
  schema.namespace_type "OlapQuery" do |t|
    t.field "domain", "DomainQuery"
  end

  schema.namespace_type "DomainQuery"

  schema.on_root_query_type do |t|
    t.field "olap", "OlapQuery"
  end

  schema.object_type "Widget" do |t|
    t.field "id", "ID"
    t.index "widgets"
    t.root_query_fields plural: "widgets", on: "DomainQuery"
  end
end

Widgets are now queried at Query.olap.domain.widgets.