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::RakeTasksin yourRakefile. 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:
customize_filter_fieldcustomize_aggregated_values_fieldcustomize_grouped_by_fieldcustomize_highlights_fieldcustomize_sub_aggregations_fieldcustomize_sort_order_enum_values
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.