Explain query plan
pg_explainInspect PostgreSQL query plans to diagnose and optimize SQL performance. Use ANALYZE for real execution details, buffer stats, planner settings, and hypothetical indexes to test improvements.
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 always rolled back, so you can inspect a plan for an INSERT/UPDATE/DELETE without persisting the mutation. 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"`. |