1"use strict";(globalThis.webpackChunk_localrepo_graphile_build_website=globalThis.webpackChunk_localrepo_graphile_build_website||[]).push([[7190],{7661(e,n,i){i.r(n),i.d(n,{assets:()=>a,contentTitle:()=>l,default:()=>c,frontMatter:()=>r,metadata:()=>s,toc:()=>d});const s=JSON.parse('{"id":"plugins","title":"Plugins","description":"The plugin system Graphile Build uses is provided by the graphile-config","source":"@site/graphile-build/plugins.md","sourceDirName":".","slug":"/plugins","permalink":"/graphile-build/next/plugins","draft":false,"unlisted":false,"editUrl":"https://github.com/graphile/crystal/tree/main/graphile-build/website/graphile-build/plugins.md","tags":[],"version":"current","lastUpdatedAt":1777377761000,"sidebarPosition":2,"frontMatter":{"layout":"page","path":"/graphile-build/plugins/","title":"Plugins","sidebar_position":2},"sidebar":"tutorialSidebar","previous":{"title":"Graphile Build","permalink":"/graphile-build/next/"},"next":{"title":"Hooks","permalink":"/graphile-build/next/hooks"}}');var t=i(1085),o=i(1184);const r={layout:"page",path:"/graphile-build/plugins/",title:"Plugins",sidebar_position:2},l=void 0,a={},d=[{value:"Presets",id:"presets",level:2},{value:"Writing Plugins",id:"writing-plugins",level:2},{value:"<code>inflection</code> scope",id:"inflection-scope",level:3},{value:"<code>gather</code> scope",id:"gather-scope",level:3},{value:"<code>schema</code> scope",id:"schema-scope",level:3},{value:"Example 1",id:"example-1",level:4},{value:"Example 2",id:"example-2",level:4}];function h(e){const n={a:"a",admonition:"admonition",code:"code",em:"em",h2:"h2",h3:"h3",h4:"h4",li:"li",p:"p",pre:"pre",ul:"ul",...(0,o.R)(),...e.components};return(0,t.jsxs)(t.Fragment,{children:[(0,t.jsxs)(n.p,{children:["The plugin system Graphile Build uses is provided by the ",(0,t.jsx)(n.code,{children:"graphile-config"}),"\nmodule. This module isn't specific to any project (it's used by Graphile Build,\nPostGraphile, Gra",(0,t.jsx)(n.em,{children:"fast"}),', Grafserv, and more), and it handles the common\nrequirements of a plugin and preset system (composing plugins, ordering them,\nhandling dependencies, disabling plugins, etc) without worrying about the\ndetails specific to any one use case. The specifics are handled by named\n"scopes", which are root-level keys in the plugin object.']}),"\n",(0,t.jsxs)(n.p,{children:["Graphile Build plugins are ",(0,t.jsx)(n.code,{children:"graphile-config"})," plugins that have one or more of\nthe following scopes:"]}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsx)(n.li,{children:(0,t.jsx)(n.code,{children:"inflection"})}),"\n",(0,t.jsx)(n.li,{children:(0,t.jsx)(n.code,{children:"gather"})}),"\n",(0,t.jsx)(n.li,{children:(0,t.jsx)(n.code,{children:"schema"})}),"\n"]}),"\n",(0,t.jsx)(n.admonition,{type:"info",children:(0,t.jsx)(n.p,{children:"Plugins with none of the scopes listed above can still be included in a\nGraphile Build preset, they will just have no effect. This allows users\nto share a single configuration across many different projects."})}),"\n",(0,t.jsx)(n.h2,{id:"presets",children:"Presets"}),"\n",(0,t.jsxs)(n.p,{children:[(0,t.jsx)(n.code,{children:"graphile-config"})," also provides the preset system used by Graphile Build. A\npreset is simply a collection of other presets, plugins, and configuration\noptions to use. Presets can \"extend from\" other presets, and it's very common\nto start your preset off by extending Graphile Build's ",(0,t.jsx)(n.code,{children:"defaultPreset"}),":"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-js",metastring:'title="graphile.config.mjs"',children:'import { defaultPreset } from "graphile-build";\n\nexport default {\n extends: [defaultPreset],\n};\n'})}),"\n",(0,t.jsxs)(n.p,{children:["Once you have a preset, you can feed it into the relevant Graphile Build methods, such as ",(0,t.jsx)(n.code,{children:"buildSchema"}),":"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-js",metastring:"{3, 5}",children:'import { buildSchema } from "graphile-build";\nimport { printSchema } from "graphql";\nimport preset from "./graphile.config.mjs";\n\nconst schema = await buildSchema(preset);\nconsole.log(printSchema(schema));\n'})}),"\n",(0,t.jsx)(n.h2,{id:"writing-plugins",children:"Writing Plugins"}),"\n",(0,t.jsxs)(n.p,{children:["A Graphile Build plugin is a simple object with a ",(0,t.jsx)(n.code,{children:"name"}),", ",(0,t.jsx)(n.code,{children:"description"}),",\n",(0,t.jsx)(n.code,{children:"version"})," and entries for any of the scopes it wishes to implement."]}),"\n",(0,t.jsx)(n.p,{children:"Here's a plugin that does nothing:"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-js",children:'const NoopPlugin = {\n name: "NoopPlugin",\n version: "0.0.0",\n description: "Does nothing",\n};\n'})}),"\n",(0,t.jsxs)(n.p,{children:["The scopes that a plugin might implement relate to the phases of schema\nbuilding. Schema building starts with the ",(0,t.jsx)(n.code,{children:"inflection"})," phase, where the various\ninflectors which control the naming of things are registered and customized.\nNext comes the ",(0,t.jsx)(n.code,{children:"gather"})," phase, where data is collected (for example by\nperforming introspection against your database). Finally we have the ",(0,t.jsx)(n.code,{children:"schema"}),"\nphase where the behaviors of all the entities from the gather phase are\ndetermined and then the schema is generated."]}),"\n",(0,t.jsxs)(n.h3,{id:"inflection-scope",children:[(0,t.jsx)(n.code,{children:"inflection"})," scope"]}),"\n",(0,t.jsxs)(n.p,{children:["If a plugin wants to name things, or change how things are named, it would\nimplement the ",(0,t.jsx)(n.code,{children:"inflection"})," scope."]}),"\n",(0,t.jsxs)(n.p,{children:["This plugin replaces the ",(0,t.jsx)(n.code,{children:"builtin"})," inflector so that the root types ",(0,t.jsx)(n.code,{children:"Query"}),",\n",(0,t.jsx)(n.code,{children:"Mutation"})," and ",(0,t.jsx)(n.code,{children:"Subscription"})," are renamed to ",(0,t.jsx)(n.code,{children:"RootQuery"}),", ",(0,t.jsx)(n.code,{children:"RootMutation"})," and\n",(0,t.jsx)(n.code,{children:"RootSubscription"}),":"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-js",children:'const RootNamingPlugin = {\n name: "RootNamingPlugin",\n version: "0.0.0",\n description: "Prefixes \'Root\' to the root operation types",\n\n inflection: {\n replace: {\n builtin(previous, options, text) {\n if (["Query", "Mutation", "Subscription"].includes(text)) {\n return `Root${text}`;\n } else {\n return previous(text);\n }\n },\n },\n },\n};\n'})}),"\n",(0,t.jsxs)(n.h3,{id:"gather-scope",children:[(0,t.jsx)(n.code,{children:"gather"})," scope"]}),"\n",(0,t.jsxs)(n.p,{children:["If a plugin needs to do something asynchronous, such as gather data from a\nremote source, or read it from a file, then that work should be done via the\n",(0,t.jsx)(n.code,{children:"gather"})," scope."]}),"\n",(0,t.jsxs)(n.h3,{id:"schema-scope",children:[(0,t.jsx)(n.code,{children:"schema"})," scope"]}),"\n",(0,t.jsxs)(n.p,{children:["Most commonly, Graphile Build plugins will want to implement the ",(0,t.jsx)(n.code,{children:"schema"})," scope\nin order to affect the GraphQL schema that is being built. The GraphQL schema\nis built by hooking the various configuration objects that are passed to the\nconstructors in GraphQL.js (such as ",(0,t.jsx)(n.code,{children:"GraphQLObjectType"}),",\n",(0,t.jsx)(n.code,{children:"GraphQLInputObjectType"}),", ",(0,t.jsx)(n.code,{children:"GraphQLUnionType"}),", etc) and some of their\nconfiguration fields (such as ",(0,t.jsx)(n.code,{children:"GraphQLObjectType"}),"'s ",(0,t.jsx)(n.code,{children:"fields"})," or ",(0,t.jsx)(n.code,{children:"interfaces"}),"\nconfiguration fields), and sometimes even the fields within that, or deeper\nstill. The deepest hook is ",(0,t.jsx)(n.code,{children:"GraphQLObjectType_fields_field_args_arg"})," which is\nused to manipulate a specific argument from the list of arguments from a\nspecific field from a list of fields on a specific GraphQL object type."]}),"\n",(0,t.jsxs)(n.p,{children:["Every hook is passed three parameters, the first parameter is the entity\nconfiguration being manipulated, the second is the ",(0,t.jsx)(n.a,{href:"./build-object",children:"build object"}),"\nwhich is common to all hooks, and the third is the ",(0,t.jsx)(n.a,{href:"./context-object",children:"context object"}),"\nspecific to that hook, which gives a description (via the ",(0,t.jsx)(n.code,{children:"scope"}),") of what is\nactually being hooked."]}),"\n",(0,t.jsx)(n.h4,{id:"example-1",children:"Example 1"}),"\n",(0,t.jsxs)(n.p,{children:["For example this plugin adds a field to the root\nquery type by hooking ",(0,t.jsx)(n.code,{children:"GraphQLObjectType_fields"}),", and looking at the\n",(0,t.jsx)(n.code,{children:"context.scope.isRootQuery"})," to determine if this is the object that we want to\nmodify:"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-js",children:'import { constant } from "grafast";\n\nconst RootQueryFieldPlugin = {\n name: "RootQueryFieldPlugin",\n version: "0.0.0",\n description: "Adds a field to the root Query type",\n\n schema: {\n hooks: {\n GraphQLObjectType_fields(fields, build, context) {\n // Only add the field to the root query type\n if (!context.scope.isRootQuery) return fields;\n\n // Add a field called `meaningOfLife`\n fields.meaningOfLife = {\n // It\'s an integer\n type: build.graphql.GraphQLInt,\n\n // When you call the field, you should always return the number \'42\'\n plan() {\n return constant(42);\n },\n };\n\n return fields;\n },\n },\n },\n};\n'})}),"\n",(0,t.jsx)(n.h4,{id:"example-2",children:"Example 2"}),"\n",(0,t.jsxs)(n.p,{children:["This plugin will add a field ",(0,t.jsx)(n.code,{children:"random(sides: Int)"})," to every GraphQLObjectType\nthat is generated:"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-js",children:'// No imports required!\n\nconst MyRandomFieldPlugin = {\n name: "MyRandomFieldPlugin",\n version: "0.0.0",\n\n schema: {\n GraphQLObjectType_fields(fields, build, context) {\n const {\n extend,\n graphql: { GraphQLInt },\n options: { myDefaultMin = 1, myDefaultMax = 100 },\n } = build;\n return extend(fields, {\n random: {\n type: GraphQLInt,\n args: {\n sides: {\n type: GraphQLInt,\n },\n },\n plan(_, fieldArgs) {\n const $sides = fieldArgs.getRaw("sides");\n return lambda(\n $sides,\n (sides) =>\n Math.floor(\n Math.random() * ((sides ?? myDefaultMax) - myDefaultMin + 1),\n ) + myDefaultMin,\n );\n },\n },\n });\n },\n },\n};\n'})}),"\n",(0,t.jsxs)(n.p,{children:["First it registers a hook on ",(0,t.jsx)(n.code,{children:"GraphQLObjectType_fields"})," which will be called for\nthe ",(0,t.jsx)(n.code,{children:"fields"})," property of every ",(0,t.jsx)(n.code,{children:"GraphQLObjectType"})," that is constructed."]}),"\n",(0,t.jsxs)(n.p,{children:["The callback to this ",(0,t.jsx)(n.a,{href:"./hooks",children:"hook"})," is passed the three standard\noptions:"]}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:["input object, ",(0,t.jsx)(n.code,{children:"fields"}),", which is basically a\n",(0,t.jsxs)(n.a,{href:"http://graphql.org/graphql-js/type/#graphqlobjecttype",children:[(0,t.jsx)(n.code,{children:"GraphQLFieldConfigMap"})," from graphql-js"]}),"."]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsxs)(n.a,{href:"./build-object",children:[(0,t.jsx)(n.code,{children:"Build"})," object"]})," (from which we're using\n",(0,t.jsx)(n.code,{children:"extend"})," and ",(0,t.jsx)(n.code,{children:"graphql.GraphQLInt"})]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsxs)(n.a,{href:"./context-object",children:[(0,t.jsx)(n.code,{children:"Context"})," object"]})," which it is ignoring; but\nif we wanted to filter which objects get the ",(0,t.jsx)(n.code,{children:"random"})," field added this would\nbe what we'd use"]}),"\n"]}),"\n",(0,t.jsxs)(n.p,{children:["Finally we're returning a derivative of the ",(0,t.jsx)(n.code,{children:"fields"})," that were input by adding\nan additonal property ",(0,t.jsx)(n.code,{children:"field"})," which is a GraphQL field config\n",(0,t.jsx)(n.code,{children:"GraphQLFieldConfig"})," (see the ",(0,t.jsx)(n.a,{href:"http://graphql.org/graphql-js/type/#graphqlobjecttype",children:"GraphQL-js\ndocumentation"}),") but with\nGra",(0,t.jsx)(n.em,{children:"fast"})," features mixed in - most notably the ",(0,t.jsx)(n.code,{children:"plan"})," (rather than a ",(0,t.jsx)(n.code,{children:"resolve"}),"\nmethod)."]}),"\n",(0,t.jsxs)(n.p,{children:[(0,t.jsx)(n.a,{href:"./hooks",children:"Read more about hooks"}),"."]})]})}function c(e={}){const{wrapper:n}={...(0,o.R)(),...e.components};return n?(0,t.jsx)(n,{...e,children:(0,t.jsx)(h,{...e})}):h(e)}},1184(e,n,i){i.d(n,{R:()=>r,x:()=>l});var s=i(4041);const t={},o=s.createContext(t);function r(e){const n=s.useContext(o);return s.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function l(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(t):e.components||t:r(e.components),s.createElement(o.Provider,{value:n},e.children)}}}]);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.