Plugins

Extend pgfence with custom rules and policy checks. Plugins are JavaScript modules that export a PgfencePlugin object.

Usage

bash
pgfence analyze --plugin ./my-rules.js migrations/*.sql

# Multiple plugins
pgfence analyze --plugin ./team-rules.js ./compliance-rules.js migrations/*.sql

Writing a Plugin

A plugin exports a default object with a name and optional rules and policies arrays:

typescript
// my-rules.js
export default {
  name: 'my-team-rules',

  rules: [
    {
      ruleId: 'plugin:no-drop-in-peak-hours',
      check(stmt, config) {
        // stmt contains the parsed statement
        // Return CheckResult[] for any findings
        return [];
      },
    },
  ],

  policies: [
    {
      ruleId: 'plugin:require-migration-comment',
      check(stmts, config) {
        // stmts is the full list of parsed statements
        // Return PolicyViolation[] for any findings
        return [];
      },
    },
  ],
};

Plugin API

Rules

A rule's check function receives a single parsed statement and returns an array of CheckResult objects:

typescript
interface PgfencePluginRule {
  ruleId: string;
  check(stmt: ParsedStatement, config: PgfenceConfig): CheckResult[];
}

Each rule is called once per statement in the migration. Return an empty array for statements your rule does not apply to.

Policies

A policy's check function receives all parsed statements and returns an array of PolicyViolation objects:

typescript
interface PgfencePluginPolicy {
  ruleId: string;
  check(stmts: ParsedStatement[], config: PgfenceConfig): PolicyViolation[];
}

Policies run once per file after all statements are analyzed. Use them for cross-statement checks (e.g., "migration must contain a comment" or "no more than N dangerous statements per file").

Namespacing

Plugin rule IDs and policy IDs must already be prefixed with plugin:. pgfence rejects plugin entries that are not explicitly namespaced, so plugin:no-drop-in-peak-hours is valid and no-drop-in-peak-hours is not.

Supported File Types

Plugins can be .js, .mjs, or .cjs files. If you write plugins in TypeScript, compile them to JavaScript before loading them in pgfence. They are loaded via dynamic import().

Security

Plugin paths are resolved relative to the current working directory and must resolve to a real file within the project directory. Symlink escapes and paths outside the project root are rejected.

Via Config File

Plugins can also be specified in your .pgfence.toml or .pgfence.json config file:

toml
# .pgfence.toml
plugins = ["./rules/team-rules.js", "./rules/compliance.js"]