diff --git a/docs/howto/engine-plugins.md b/docs/howto/engine-plugins.md
new file mode 100644
index 0000000000..b2c79b3b5c
--- /dev/null
+++ b/docs/howto/engine-plugins.md
@@ -0,0 +1,251 @@
+# Database Engine Plugins
+
+sqlc supports adding custom database backends through engine plugins. This allows you to use sqlc with databases that aren't natively supported (like MyDB, CockroachDB, or other SQL-compatible databases).
+
+## Overview
+
+Engine plugins are external programs that implement the sqlc engine interface:
+- **Process plugins** (Go): Communicate via **Protocol Buffers** over stdin/stdout
+- **WASM plugins** (any language): Communicate via **JSON** over stdin/stdout
+
+## Compatibility Guarantee
+
+For Go process plugins, compatibility is guaranteed at **compile time**:
+
+```go
+import "github.com/sqlc-dev/sqlc/pkg/engine"
+```
+
+When you import this package:
+- If your plugin compiles successfully → it's compatible with this version of sqlc
+- If types change incompatibly → your plugin won't compile until you update it
+
+The Protocol Buffer schema ensures binary compatibility. No version negotiation needed.
+
+## Configuration
+
+### sqlc.yaml
+
+```yaml
+version: "2"
+
+# Define engine plugins
+engines:
+ - name: mydb
+ process:
+ cmd: sqlc-engine-mydb
+ env:
+ - MYDB_CONNECTION_STRING
+
+sql:
+ - engine: mydb # Use the MyDB engine
+ schema: "schema.sql"
+ queries: "queries.sql"
+ gen:
+ go:
+ package: db
+ out: db
+```
+
+### Configuration Options
+
+| Field | Description |
+|-------|-------------|
+| `name` | Unique name for the engine (used in `sql[].engine`) |
+| `process.cmd` | Command to run (must be in PATH or absolute path) |
+| `wasm.url` | URL to download WASM module (`file://` or `https://`) |
+| `wasm.sha256` | SHA256 checksum of the WASM module |
+| `env` | Environment variables to pass to the plugin |
+
+## Creating a Go Engine Plugin
+
+### 1. Import the SDK
+
+```go
+import "github.com/sqlc-dev/sqlc/pkg/engine"
+```
+
+### 2. Implement the Handler
+
+```go
+package main
+
+import (
+ "github.com/sqlc-dev/sqlc/pkg/engine"
+)
+
+func main() {
+ engine.Run(engine.Handler{
+ PluginName: "mydb",
+ PluginVersion: "1.0.0",
+ Parse: handleParse,
+ GetCatalog: handleGetCatalog,
+ IsReservedKeyword: handleIsReservedKeyword,
+ GetCommentSyntax: handleGetCommentSyntax,
+ GetDialect: handleGetDialect,
+ })
+}
+```
+
+### 3. Implement Methods
+
+#### Parse
+
+Parses SQL text into statements with AST.
+
+```go
+func handleParse(req *engine.ParseRequest) (*engine.ParseResponse, error) {
+ sql := req.GetSql()
+ // Parse SQL using your database's parser
+
+ return &engine.ParseResponse{
+ Statements: []*engine.Statement{
+ {
+ RawSql: sql,
+ StmtLocation: 0,
+ StmtLen: int32(len(sql)),
+ AstJson: astJSON, // AST encoded as JSON bytes
+ },
+ },
+ }, nil
+}
+```
+
+#### GetCatalog
+
+Returns the initial catalog with built-in types and functions.
+
+```go
+func handleGetCatalog(req *engine.GetCatalogRequest) (*engine.GetCatalogResponse, error) {
+ return &engine.GetCatalogResponse{
+ Catalog: &engine.Catalog{
+ DefaultSchema: "public",
+ Name: "mydb",
+ Schemas: []*engine.Schema{
+ {
+ Name: "public",
+ Functions: []*engine.Function{
+ {Name: "now", ReturnType: &engine.DataType{Name: "timestamp"}},
+ },
+ },
+ },
+ },
+ }, nil
+}
+```
+
+#### IsReservedKeyword
+
+Checks if a string is a reserved keyword.
+
+```go
+func handleIsReservedKeyword(req *engine.IsReservedKeywordRequest) (*engine.IsReservedKeywordResponse, error) {
+ reserved := map[string]bool{
+ "select": true, "from": true, "where": true,
+ }
+ return &engine.IsReservedKeywordResponse{
+ IsReserved: reserved[strings.ToLower(req.GetKeyword())],
+ }, nil
+}
+```
+
+#### GetCommentSyntax
+
+Returns supported SQL comment syntax.
+
+```go
+func handleGetCommentSyntax(req *engine.GetCommentSyntaxRequest) (*engine.GetCommentSyntaxResponse, error) {
+ return &engine.GetCommentSyntaxResponse{
+ Dash: true, // -- comment
+ SlashStar: true, // /* comment */
+ Hash: false, // # comment
+ }, nil
+}
+```
+
+#### GetDialect
+
+Returns SQL dialect information for formatting.
+
+```go
+func handleGetDialect(req *engine.GetDialectRequest) (*engine.GetDialectResponse, error) {
+ return &engine.GetDialectResponse{
+ QuoteChar: "`", // Identifier quoting character
+ ParamStyle: "dollar", // $1, $2, ...
+ ParamPrefix: "$", // Parameter prefix
+ CastSyntax: "cast_function", // CAST(x AS type) or "double_colon" for ::
+ }, nil
+}
+```
+
+### 4. Build and Install
+
+```bash
+go build -o sqlc-engine-mydb .
+mv sqlc-engine-mydb /usr/local/bin/
+```
+
+## Protocol
+
+### Process Plugins (Go)
+
+Process plugins use **Protocol Buffers** for serialization:
+
+```
+sqlc → stdin (protobuf) → plugin → stdout (protobuf) → sqlc
+```
+
+The proto schema is published at `buf.build/sqlc/sqlc` in `engine/engine.proto`.
+
+Methods are invoked as command-line arguments:
+```bash
+sqlc-engine-mydb parse # stdin: ParseRequest, stdout: ParseResponse
+sqlc-engine-mydb get_catalog # stdin: GetCatalogRequest, stdout: GetCatalogResponse
+```
+
+### WASM Plugins
+
+WASM plugins use **JSON** for broader language compatibility:
+
+```
+sqlc → stdin (JSON) → wasm module → stdout (JSON) → sqlc
+```
+
+## Full Example
+
+See `examples/plugin-based-codegen/` for a complete engine plugin implementation.
+
+## Architecture
+
+```
+┌─────────────────────────────────────────────────────────────────┐
+│ sqlc generate │
+│ │
+│ 1. Read sqlc.yaml │
+│ 2. Find engine: mydb → look up in engines[] │
+│ 3. Run: sqlc-engine-mydb parse < schema.sql │
+│ 4. Get AST via protobuf on stdout │
+│ 5. Generate Go code │
+└─────────────────────────────────────────────────────────────────┘
+
+Process Plugin Communication (Protobuf):
+
+ sqlc sqlc-engine-mydb
+ ──── ────────────────
+ │ │
+ │──── spawn process ─────────────► │
+ │ args: ["parse"] │
+ │ │
+ │──── protobuf on stdin ─────────► │
+ │ ParseRequest{sql: "..."} │
+ │ │
+ │◄─── protobuf on stdout ───────── │
+ │ ParseResponse{statements} │
+ │ │
+```
+
+## See Also
+
+- [Codegen Plugins](plugins.md) - For custom code generators
+- [Configuration Reference](../reference/config.md)
+- Proto schema: `protos/engine/engine.proto`
diff --git a/examples/plugin-based-codegen/README.md b/examples/plugin-based-codegen/README.md
new file mode 100644
index 0000000000..5f59c39951
--- /dev/null
+++ b/examples/plugin-based-codegen/README.md
@@ -0,0 +1,184 @@
+# Plugin-Based Code Generation Example
+
+This example demonstrates how to use **custom database engine plugins** and **custom code generation plugins** with sqlc.
+
+## Overview
+
+```
+┌─────────────────────────────────────────────────────────────────┐
+│ sqlc generate │
+│ │
+│ 1. Read schema.sql & queries.sql │
+│ 2. Send to sqlc-engine-sqlite3 (custom DB engine) │
+│ 3. Get AST & catalog │
+│ 4. Send to sqlc-gen-rust (custom codegen) │
+│ 5. Get generated Rust code │
+└─────────────────────────────────────────────────────────────────┘
+```
+
+## Structure
+
+```
+plugin-based-codegen/
+├── go.mod # This module depends on sqlc
+├── sqlc.yaml # Configuration
+├── schema.sql # Database schema (SQLite3)
+├── queries.sql # SQL queries
+├── plugin_test.go # Integration test
+├── plugins/
+│ ├── sqlc-engine-sqlite3/ # Custom database engine plugin
+│ │ └── main.go
+│ └── sqlc-gen-rust/ # Custom code generator plugin
+│ └── main.go
+└── gen/
+ └── rust/
+ └── queries.rs # ✅ Generated Rust code
+```
+
+## Quick Start
+
+### 1. Build the plugins
+
+```bash
+cd plugins/sqlc-engine-sqlite3 && go build -o sqlc-engine-sqlite3 .
+cd ../sqlc-gen-rust && go build -o sqlc-gen-rust .
+cd ../..
+```
+
+### 2. Run tests
+
+```bash
+go test -v ./...
+```
+
+### 3. Generate code (requires sqlc with plugin support)
+
+```bash
+SQLCDEBUG=processplugins=1 sqlc generate
+```
+
+## How It Works
+
+### Database Engine Plugin (`sqlc-engine-sqlite3`)
+
+The engine plugin implements the `pkg/engine.Handler` interface:
+
+```go
+import "github.com/sqlc-dev/sqlc/pkg/engine"
+
+func main() {
+ engine.Run(engine.Handler{
+ Parse: handleParse, // Parse SQL
+ GetCatalog: handleGetCatalog, // Return initial catalog
+ IsReservedKeyword: handleIsReservedKeyword,
+ GetCommentSyntax: handleGetCommentSyntax,
+ GetDialect: handleGetDialect,
+ })
+}
+```
+
+Communication: **Protobuf over stdin/stdout**
+
+### Code Generation Plugin (`sqlc-gen-rust`)
+
+The codegen plugin uses the `pkg/plugin.Run` helper:
+
+```go
+import "github.com/sqlc-dev/sqlc/pkg/plugin"
+
+func main() {
+ plugin.Run(func(req *plugin.GenerateRequest) (*plugin.GenerateResponse, error) {
+ // Generate Rust code from req.Queries and req.Catalog
+ return &plugin.GenerateResponse{
+ Files: []*plugin.File{{Name: "queries.rs", Contents: rustCode}},
+ }, nil
+ })
+}
+```
+
+Communication: **Protobuf over stdin/stdout**
+
+## Compatibility
+
+Both plugins import public packages from sqlc:
+
+- `github.com/sqlc-dev/sqlc/pkg/engine` - Engine plugin SDK
+- `github.com/sqlc-dev/sqlc/pkg/plugin` - Codegen plugin SDK
+
+**Compile-time compatibility**: If the plugin compiles, it's compatible with this version of sqlc.
+
+## Configuration
+
+```yaml
+version: "2"
+
+engines:
+ - name: sqlite3
+ process:
+ cmd: ./plugins/sqlc-engine-sqlite3/sqlc-engine-sqlite3
+
+plugins:
+ - name: rust
+ process:
+ cmd: ./plugins/sqlc-gen-rust/sqlc-gen-rust
+
+sql:
+ - engine: sqlite3 # Use custom engine
+ schema: "schema.sql"
+ queries: "queries.sql"
+ codegen:
+ - plugin: rust # Use custom codegen
+ out: gen/rust
+```
+
+## Generated Code Example
+
+The `sqlc-gen-rust` plugin generates type-safe Rust code from SQL:
+
+**Input (`queries.sql`):**
+```sql
+-- name: GetUser :one
+SELECT * FROM users WHERE id = ?;
+
+-- name: CreateUser :exec
+INSERT INTO users (id, name, email) VALUES (?, ?, ?);
+```
+
+**Output (`gen/rust/queries.rs`):**
+```rust
+use sqlx::{FromRow, SqlitePool};
+use anyhow::Result;
+
+#[derive(Debug, FromRow)]
+pub struct Users {
+ pub id: i32,
+ pub name: String,
+ pub email: String,
+}
+
+pub async fn get_user(pool: &SqlitePool, id: i32) -> Result