Explain query plan
pg_explainRetrieve PostgreSQL query plans for SQL statements, optionally running EXPLAIN ANALYZE, and inspect buffers, planner settings, memory, or hypothetical indexes to diagnose performance issues.
Instructions
Get the query plan for a SQL statement. By default, this uses plain EXPLAIN (no execution). Set analyze: true to run the query with EXPLAIN ANALYZE - for non-SELECT statements, ALLOW_WRITES=1 is required (since ANALYZE actually executes the statement). Writes executed during EXPLAIN ANALYZE are rolled back, so you can inspect a plan for an INSERT/UPDATE/DELETE without persisting the rows -- but only what Postgres rolls back is undone: a sequence the statement advanced (nextval, a serial or identity column) stays advanced, and any side effect of a function the statement called that lands outside table data (pg_terminate_backend, an advisory lock) persists. Format is text (default) or json. Pass the raw SQL (not an EXPLAIN-prefixed statement). Planner options (all optional): buffers reports shared/local/temp block hits and is the fastest way to tell a bad plan from a cold cache - it defaults to TRUE whenever analyze is true (matching PostgreSQL 18, which turns it on for you), pass buffers: false to suppress it; requesting it WITHOUT analyze needs PostgreSQL 13+. verbose adds output columns and schema-qualified names. settings (PostgreSQL 12+) lists planner GUCs set away from their defaults - the usual explanation for a plan that looks impossible. wal (PostgreSQL 13+) reports WAL generated and serialize (none|text|binary, PostgreSQL 17+) charges the cost of building the result rows; both require analyze. memory (PostgreSQL 17+) reports memory used by the PLANNER, so it works with or without analyze - use it alone to ask why planning a statement is expensive. generic_plan (PostgreSQL 16+) plans a parameterized statement WITHOUT values for its $1/$2 placeholders and cannot be combined with analyze or params. costs and timing default to true (as in postgres); set either to false to drop those columns, and note timing only applies with analyze. Options that need a newer server than the one connected are rejected with an explicit error naming the required version instead of a confusing parse failure. Set hypothetical_indexes to a list of {table, columns, using?} to ask the planner 'what would the plan be if these indexes existed?' -- requires the HypoPG extension (CREATE EXTENSION hypopg). The hypothetical indexes are torn down at the end of the call, never touching real disk.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| sql | Yes | The SQL statement to explain. Do NOT prefix with EXPLAIN. | |
| wal | No | Report WAL generated by the statement. Requires `analyze` (PostgreSQL 13+). | |
| costs | No | Include estimated cost/rows/width. Set false for a terser plan. | |
| format | No | Output format. | text |
| memory | No | Report memory used by the planner (PostgreSQL 17+). Works with or without `analyze`, since planning happens either way. | |
| params | No | Positional parameters referenced as $1, $2, ... in the SQL. | |
| timing | No | Include per-node actual timing. Setting it to false REQUIRES `analyze: true` (it is rejected otherwise, not silently ignored); false lowers measurement overhead. | |
| analyze | No | Run EXPLAIN ANALYZE (actually executes the query). | |
| buffers | No | Report buffer hits/reads/dirtied. Defaults to TRUE when `analyze` is true (PostgreSQL 18 does the same); pass false to suppress. Requesting it without `analyze` requires PostgreSQL 13+. | |
| verbose | No | Include output columns, schema-qualified names, and triggers. | |
| settings | No | Report planner GUCs set away from their defaults - explains a weird plan (PostgreSQL 12+). | |
| serialize | No | Charge the cost of serializing result rows (network-bound queries hide it otherwise). Requires `analyze` (PostgreSQL 17+). | |
| generic_plan | No | Plan the statement with UNKNOWN values for its $1/$2 placeholders - the plan a prepared statement would get. Cannot be combined with `analyze` or `params` (PostgreSQL 16+). | |
| hypothetical_indexes | No | List of indexes the planner should pretend exist for this EXPLAIN. Requires the HypoPG extension. Indexes are session-scoped and reset at the end of the call. |
Output Schema
| Name | Required | Description | Default |
|---|---|---|---|
| plan | Yes | Newline-joined plan text for `format: "text"` (with a trailing truncation marker when POSTGRES_MAX_ROWS chopped it), or the parsed plan array for `format: "json"`. |