The Context Object
Whereas the Build object is the same for all hooks (except the build hook
which constructs it) within an individual build, the Context object changes
for each hook. Different hooks have different values available on the Context
object; use TypeScript auto-completion to explore them.
Common properties
All contexts include these properties:
type- a string indicating the hook being executed. Possible values arebuild,init,finalize,GraphQLSchema,GraphQLScalarType,GraphQLObjectType,GraphQLInterfaceType,GraphQLUnionType,GraphQLEnumType, andGraphQLInputObjectType.scope- a structured object that explains why the hook was called.
Scope
When an entity (type, field, arg, etc) is registered or created, a scope object is passed that provides details as to why that entity exists. Plugins can use this scope to ensure that they only apply changes to relevant entities.
All plugins are free to add more scope values (and are actively encouraged to do
so!), but it's highly recommended to use a prefix to avoid conflicts. For deeper
hooks (such as GraphQLObjectType_fields_field) the scope from shallower hooks
(such as GraphQLObjectType) are merged in; it's recommended to use additional
scope-depth prefixes to avoid collisions (e.g. field scopes should include the
term field in their scope names).
Scope names are derived from the hook name, by removing GraphQL and Type,
camel-casing the result, and prefixing Scope, e.g. the scope for the init
hook is ScopeInit and for the GraphQLObjectType_fields_field_args_arg hook
is ScopeObjectFieldsFieldArgsArg.
For ScopeObjectFieldsField, ScopeInterfaceFieldsField,
ScopeInputObjectFieldsField, and all their descendents: fieldName is
guaranteed to exist.
For ScopeObjectFieldsFieldArgsArg and ScopeInterfaceFieldsFieldArgsArg:
argName is guaranteed to exist.
For ScopeEnumValuesValue: valueName is guaranteed to exist.
Declaring scopes
To tell TypeScript about your custom scope values, declare them via declaration merging, e.g.:
declare global {
namespace GraphileBuild {
interface ScopeObject {
// Add your scope properties here:
myCompanyIsRelevantType?: boolean;
}
interface ScopeObjectFieldsField {
// Add your scope properties here:
myCompanyIsRelevantField?: boolean;
}
}
}
const MyCompanyPlugin: GraphileConfig.Plugin = {
name: "MyCompanyPlugin",
schema: {
hooks: {
init(_, build) {
for (let i = 0; i < 10; i++) {
build.registerObjectType(
`MyCompanyType${i}`,
// Indicate this is our type
{ myCompanyIsRelevantType: true },
() => ({ fields: {} }),
"Reason we're defining this object type",
);
}
return _;
},
GraphQLObjectType_fields(fields, build, context) {
// Only hook our own types
if (!context.scope.myCompanyIsRelevantType) return fields;
// Register a new field on this type
const fieldName = "myField";
return build.extend(
fields,
{
[fieldName]: context.fieldWithHooks(
{
fieldName, // Required
// Indicate this is our field
myCompanyIsRelevantField: true,
},
{ type: GraphQLString },
),
},
"MyCompany adding myField to __MyObject__",
);
},
GraphQLObjectType_fields_field(field, build, context) {
// Only hook our own fields
if (!context.scope.myCompanyIsRelevantField) return field;
field.description = "Yay, I found it!";
return field;
},
},
},
};
Context
The context object wraps the scope along with additional system-defined
details about the given entity.
Context names are derived from the hook name, by removing GraphQL and Type,
camel-casing the result, and prefixing Context, e.g. the context for the init
hook is ContextInit and for the GraphQLObjectType_fields_field_args_arg hook
is ContextObjectFieldsFieldArgsArg.
Contexts inherit from their parent contexts, so properties like Self and
fieldWithHooks remain available on deeper hooks.
build-ContextBuildinit-ContextInitfinalize-ContextFinalizeGraphQLSchema-ContextSchemaGraphQLSchema_types-ContextSchemaTypes(includesconfig)GraphQLScalarType-ContextScalarGraphQLObjectType-ContextObjectGraphQLObjectType_interfaces-ContextObjectInterfaces(includesSelf)GraphQLObjectType_fields-ContextObjectFields(includesSelfandfieldWithHooks)GraphQLObjectType_fields_field-ContextObjectFieldsFieldGraphQLObjectType_fields_field_args-ContextObjectFieldsFieldArgsGraphQLObjectType_fields_field_args_arg-ContextObjectFieldsFieldArgsArgGraphQLInputObjectType-ContextInputObjectGraphQLInputObjectType_fields-ContextInputObjectFields(includesSelfandfieldWithHooks)GraphQLInputObjectType_fields_field-ContextInputObjectFieldsField(includesSelf)GraphQLEnumType-ContextEnumGraphQLEnumType_values-ContextEnumValues(includesSelfwithname)GraphQLEnumType_values_value-ContextEnumValuesValueGraphQLUnionType-ContextUnionGraphQLUnionType_types-ContextUnionTypes(includesSelf)GraphQLInterfaceType-ContextInterfaceGraphQLInterfaceType_fields-ContextInterfaceFields(includesSelfandfieldWithHooks)GraphQLInterfaceType_fields_field-ContextInterfaceFieldsFieldGraphQLInterfaceType_fields_field_args-ContextInterfaceFieldsFieldArgsGraphQLInterfaceType_fields_field_args_arg-ContextInterfaceFieldsFieldArgsArgGraphQLInterfaceType_interfaces-ContextInterfaceInterfaces(includesSelf)
Self
The context.Self property is a reference to, where possible, the instance of
the GraphQL type. This is present for deferred hooks, and can be used to
determine whether or not to run the hooks logic.
fieldWithHooks(scope, spec)
Fields can be registered directly, but doing so doesn't give other plugins context as to whether the field should be augmented or not:
// Don't do this!
const DontDoThisPlugin: GraphileBuild.Plugin = {
name: "DontDoThisPlugin",
description: "Don't do this!",
schema: {
hooks: {
GraphQLObjectType_fields(fields, build, context) {
const isRelevant = determineIsRelevant(context);
if (!isRelevant) return fields;
// Don't do this, because other plugins can't easily hook it
fields.myNewField = {
description: "Special field from MyCompany",
type: build.graphql.GraphQLBoolean,
};
return fields;
},
},
},
};
Instead, use context.fieldWithHooks(scope, spec) so you can indicate additional scope information:
const DoThisInsteadPlugin: GraphileBuild.Plugin = {
name: "DoThisInsteadPlugin",
schema: {
hooks: {
GraphQLObjectType_fields(fields, build, context) {
const isRelevant = determineIsRelevant(context);
if (!isRelevant) return fields;
const fieldName = "myNewField";
return build.extend(
fields,
{
// Don't do this, because other plugins can't easily hook it
[fieldName]: context.fieldWithHooks(
{
fieldName, // Required
// Describe why this field exists; how might someone filter
// so they can hook it to e.g. deprecate it, add a description,
// add more args, etc?
isMyCompanySpecialField: true,
},
{
description: "Special field from MyCompany",
type: build.graphql.GraphQLBoolean,
},
),
},
"From DoThisInsteadPlugin",
);
},
},
},
};
declare global {
namespace GraphileBuild {
interface ScopeObjectFieldsField {
/** Documentation for this scope goes here */
isMyCompanySpecialField?: boolean;
}
}
}
Available on GraphQLObjectType_fields, GraphQLInputObjectType_fields, and
GraphQLInterfaceType_fields, this function registers scope for a field and
returns the generated field spec. If you do not call it, Graphile Build will
call it later on your behalf.
Examples
AddClientMutationIdDescriptionPlugin
const AddClientMutationIdDescriptionPlugin = {
name: "AddClientMutationIdDescriptionPlugin",
description: "Adds description to all clientMutationId mutation inputs",
version: "0.0.0",
schema: {
hooks: {
GraphQLInputObjectType_fields_field(
field,
{ extend },
{ scope: { isMutationInput, fieldName } },
) {
if (
!isMutationInput ||
fieldName !== "clientMutationId" ||
field.description != null
) {
return field;
}
return extend(field, {
description:
"An arbitrary string value with no semantic meaning. " +
"Will be included in the payload verbatim. " +
"May be used to track mutations by the client.",
});
},
},
},
};