askill
graphile-v5-plugins

graphile-v5-pluginsSafety 90Repository

Create custom PostGraphile v5 plugins using hooks. Use when asked to "create a plugin", "add custom field", "extend schema", "detect conflicts", "add metadata query", or when you need to extend PostGraphile's functionality.

0 stars
1.2k downloads
Updated 1/27/2026

Package Files

Loading files...
SKILL.md

PostGraphile v5 Plugins

Create custom plugins to extend PostGraphile's functionality using hooks.

Official Documentation

When to Apply

Use this skill when:

  • Adding custom query or mutation fields
  • Detecting naming conflicts in multi-schema setups
  • Adding metadata or introspection queries
  • Modifying the build process
  • Extending GraphQL types

Plugin Structure

import type { GraphileConfig } from 'graphile-config';

export const MyPlugin: GraphileConfig.Plugin = {
  name: 'MyPlugin',
  version: '1.0.0',
  description: 'What this plugin does',

  // Inflection overrides
  inflection: {
    replace: {
      // Override inflectors
    },
  },

  // Schema hooks
  schema: {
    hooks: {
      // Build-time hooks
    },
    entityBehavior: {
      // Behavior modifications
    },
  },
};

When Do Plugins Run?

Important: Plugins run at BUILD TIME, not on every request. The schema is built once and cached. This means:

  • Hooks only execute during schema generation
  • Logging in hooks only appears at startup
  • Changes require server restart

Schema Hooks

Available Hooks

HookWhen it runsUse case
initStart of buildCollect metadata, setup
buildAfter initAnalyze codecs, detect conflicts
GraphQLObjectType_fieldsWhen adding fields to typesAdd custom fields
GraphQLSchemaFinal schemaModify complete schema

Hook: init

Runs at the start of schema building. Good for collecting metadata:

schema: {
  hooks: {
    init(_, build) {
      const { pgRegistry } = build.input;
      
      // Access all tables/resources
      for (const resource of Object.values(pgRegistry.pgResources)) {
        console.log('Table:', resource.name);
      }
      
      return _;
    },
  },
},

Hook: build

Runs after init. Good for analyzing the schema:

schema: {
  hooks: {
    build(build) {
      // Access inflection
      const inflection = build.inflection;
      
      // Access codecs (types)
      for (const codec of Object.values(build.input.pgRegistry.pgCodecs)) {
        if (codec.attributes) {
          const typeName = inflection.tableType(codec);
          console.log('Type:', typeName);
        }
      }
      
      return build;
    },
  },
},

Hook: GraphQLObjectType_fields

Add or modify fields on GraphQL types:

schema: {
  hooks: {
    GraphQLObjectType_fields(fields, build, context) {
      const { Self } = context;
      
      // Only modify Query type
      if (Self.name !== 'Query') {
        return fields;
      }
      
      // Add a custom field
      return {
        ...fields,
        hello: {
          type: build.graphql.GraphQLString,
          resolve() {
            return 'world';
          },
        },
      };
    },
  },
},

Common Plugin Patterns

Conflict Detector Plugin

Detect naming conflicts between tables in different schemas:

import type { GraphileConfig } from 'graphile-config';

export const ConflictDetectorPlugin: GraphileConfig.Plugin = {
  name: 'ConflictDetectorPlugin',
  version: '1.0.0',

  schema: {
    hooks: {
      build(build) {
        const codecsByName = new Map<string, Array<{ schema: string; table: string }>>();

        for (const codec of Object.values(build.input.pgRegistry.pgCodecs)) {
          if (!codec.attributes || codec.isAnonymous) continue;

          const pgExtensions = codec.extensions?.pg as { schemaName?: string } | undefined;
          const schemaName = pgExtensions?.schemaName || 'unknown';
          const graphqlName = build.inflection.tableType(codec);

          if (!codecsByName.has(graphqlName)) {
            codecsByName.set(graphqlName, []);
          }
          codecsByName.get(graphqlName)!.push({
            schema: schemaName,
            table: codec.name,
          });
        }

        // Log conflicts
        for (const [graphqlName, tables] of codecsByName) {
          if (tables.length > 1) {
            const locations = tables.map(t => `${t.schema}.${t.table}`).join(', ');
            console.warn(`NAMING CONFLICT: "${graphqlName}" from: ${locations}`);
          }
        }

        return build;
      },
    },
  },
};

Custom Mutation Plugin

Add a custom mutation:

import type { GraphileConfig } from 'graphile-config';
import {
  GraphQLObjectType,
  GraphQLNonNull,
  GraphQLString,
  GraphQLInputObjectType,
} from 'graphql';

export const CustomMutationPlugin: GraphileConfig.Plugin = {
  name: 'CustomMutationPlugin',
  version: '1.0.0',

  schema: {
    hooks: {
      GraphQLObjectType_fields(fields, build, context) {
        const { Self } = context;
        if (Self.name !== 'Mutation') return fields;

        const SendEmailInput = new GraphQLInputObjectType({
          name: 'SendEmailInput',
          fields: {
            to: { type: new GraphQLNonNull(GraphQLString) },
            subject: { type: new GraphQLNonNull(GraphQLString) },
            body: { type: new GraphQLNonNull(GraphQLString) },
          },
        });

        const SendEmailPayload = new GraphQLObjectType({
          name: 'SendEmailPayload',
          fields: {
            success: { type: new GraphQLNonNull(build.graphql.GraphQLBoolean) },
            messageId: { type: GraphQLString },
          },
        });

        return {
          ...fields,
          sendEmail: {
            type: SendEmailPayload,
            args: {
              input: { type: new GraphQLNonNull(SendEmailInput) },
            },
            async resolve(_parent, args, context) {
              const { to, subject, body } = args.input;
              // Implement email sending logic
              return { success: true, messageId: 'msg-123' };
            },
          },
        };
      },
    },
  },
};

Combining Inflection and Hooks

export const CompletePlugin: GraphileConfig.Plugin = {
  name: 'CompletePlugin',
  version: '1.0.0',

  // Inflection overrides
  inflection: {
    replace: {
      _schemaPrefix(_previous, _options, _details) {
        return '';
      },
    },
  },

  // Schema hooks
  schema: {
    hooks: {
      build(build) {
        console.log('Schema building...');
        return build;
      },
    },
    
    // Behavior modifications
    entityBehavior: {
      pgResourceUnique: {
        override: {
          provides: ['myBehavior'],
          callback(behavior, [_resource, unique]) {
            if (!unique.isPrimary) {
              return [behavior, '-single'];
            }
            return behavior;
          },
        },
      },
    },
  },
};

Creating a Preset from Plugins

export const MyPluginPreset: GraphileConfig.Preset = {
  plugins: [
    ConflictDetectorPlugin,
    MetaSchemaPlugin,
    CustomMutationPlugin,
  ],
};

// Usage
const preset: GraphileConfig.Preset = {
  extends: [
    PostGraphileAmberPreset,
    MyPluginPreset,
  ],
};

Troubleshooting

IssueSolution
Hook not calledCheck plugin is in preset's plugins array
Changes not visibleRestart server (schema is cached)
Type errorsImport types from graphile-config
Can't access build propertiesCheck hook signature and available properties
Conflict with other pluginsCheck plugin order in plugins array

Source Code References

References

Install

Download ZIP
Requires askill CLI v1.0+

AI Quality Score

95/100Analyzed 2/10/2026

An excellent, comprehensive guide for creating PostGraphile v5 plugins. It includes clear triggers, detailed hook explanations with code examples, and practical patterns like conflict detection and custom mutations.

90
95
90
95
98

Metadata

Licenseunknown
Version-
Updated1/27/2026
Publisherconstructive-io

Tags

ci-cdgithubgraphqlobservability