Skip to content

Plugin Development

A plugin turns a contract into files: Helm charts, Terraform, Kubernetes manifests, a README. It is a separate program, written in any language. pacto generate <name> sends it the contract as JSON on stdin, and the plugin answers with a list of files on stdout. The Bash example below is a complete, working plugin.


Official plugins

Plugin Description
pacto-plugin-schema-infer Infers a JSON Schema from sample configuration files (JSON, YAML, TOML)
pacto-plugin-openapi-infer Extracts OpenAPI 3.1 specs from source code (FastAPI, Huma)

Both work in the other direction: they read files you already have and write contract inputs, so you do not hand-write a schema. No deployment-artifact plugin ships with Pacto. pacto generate helm is a plugin you write or install, not one Pacto provides. Without a pacto-plugin-helm on your PATH it exits 1 with plugin "helm" not found (looked for pacto-plugin-helm in $PATH and ~/.config/pacto/plugins/).

See Installation for how to install them. Refer to each plugin's README for usage:


How it works

Pacto loads the contract, finds the plugin program (pacto-plugin-<name>), writes a request to its stdin, reads the response from its stdout and writes the files. A plugin gets a copy of the contract and cannot change it. Pacto stops a plugin after 60 seconds or 64 MB of output.


Plugin discovery

Pacto searches for pacto-plugin-<name> in $PATH, then ~/.config/pacto/plugins/. Not found is an error: pacto generate helm without pacto-plugin-helm exits 1.


Protocol (v1)

Request (stdin)

Pacto writes a JSON object to the plugin's stdin:

{
  "protocolVersion": "1",
  "contract": {
    "pactoVersion": "2.0",
    "service": {
      "name": "my-service",
      "version": "1.0.0"
    },
    "interfaces": [...],
    "workload": "service",
    "state": {...},
    ...
  },
  "bundleDir": "my-service",
  "outputDir": "helm-output",
  "options": {
    "namespace": "production"
  }
}
Field Type Description
protocolVersion string Always "1"
contract object The full parsed contract
bundleDir string Path to the bundle (may be relative)
outputDir string Path for output files (may be relative)
options object Optional. User-provided key-value options from --option key=value

Response (stdout)

The plugin writes a JSON object to stdout:

{
  "files": [
    {
      "path": "deployment.yaml",
      "content": "apiVersion: apps/v1\nkind: Deployment\n..."
    },
    {
      "path": "service.yaml",
      "content": "apiVersion: v1\nkind: Service\n..."
    }
  ],
  "message": "Generated Kubernetes manifests for my-service"
}
Field Type Description
files array List of generated files
files[].path string Relative path within output directory
files[].content string File content
message string Optional message

files[].path must be relative. Absolute paths and .. are rejected. On error, write to stderr and exit non-zero.


Example: Minimal plugin in Bash

#!/usr/bin/env bash
# pacto-plugin-readme — Generates a README from a Pacto contract

set -euo pipefail

# Read the full JSON request from stdin
REQUEST=$(cat)

# Extract fields using jq
NAME=$(echo "$REQUEST" | jq -r '.contract.service.name')
VERSION=$(echo "$REQUEST" | jq -r '.contract.service.version')
WORKLOAD=$(echo "$REQUEST" | jq -r '.contract.workload // "n/a"')
STATE=$(echo "$REQUEST" | jq -r '.contract.state.type // "n/a"')

# Generate a README
CONTENT="# ${NAME}

**Version:** ${VERSION}
**Workload:** ${WORKLOAD}
**State:** ${STATE}

This file was auto-generated by pacto-plugin-readme.
"

# Write the response JSON to stdout
jq -n \
  --arg path "README.md" \
  --arg content "$CONTENT" \
  --arg msg "Generated README for ${NAME}" \
  '{files: [{path: $path, content: $content}], message: $msg}'

Make it executable and place it in your $PATH:

chmod +x pacto-plugin-readme
mv pacto-plugin-readme /usr/local/bin/
pacto generate readme my-service

Guidelines

  • Read only from bundleDir. Do not access files outside the bundle.
  • Write only to stdout. Do not write files directly; return them in the response. Pacto handles file creation.
  • Follow the protocol. Return clean relative paths, write errors to stderr and exit non-zero on failure.
  • Be deterministic. Given the same input, produce the same output.
  • Handle missing optional fields. Only pactoVersion and service are required — not all contracts have workload, state, configurations, dependencies or capabilities.