English | 中文
Browse PostgreSQL heap pages, B-tree index pages, and WAL records in the browser. Connect locally, fetch raw blocks via pageinspect, or browse structured WAL records via pg_walinspect (PostgreSQL 15+).
Built for developers learning or debugging — not intended for public deployment.
- Structure diagram — 32 bytes/row: page header (with
pd_flagsbit strip), ItemId array, free space, tuples - Hex dump — same 32B/row layout, linked selection and scroll-to-offset
- Tuple decode — column values,
t_infomask/t_infomask2bit strips, HOT/ctid hints - Diff highlight — byte-level changes on Refresh (Tree on: click the currently displayed block again; Tree off: Refresh)
- Export PNG — chrome Export (or
Ctrl/Cmd+Shift+C) copies the full structure diagram (caption with relation and blkno; current theme; selection/diff colors included) to the clipboard and downloads a PNG. With the heap-peek overlay open, Export targets that heap page. Not in WAL mode. - Page nav — with Tree off, toolbar Prev / Next: heap uses the displayed
blkno ± 1; B-tree uses left/right siblings (btpo_prev/btpo_next). First/last (or leftmost/rightmost) pages disable the button instead of requesting an out-of-range block
-
Index browsing — pick a relation in the Tree catalog, or turn Tree off and choose a table or index from the toolbar; the index list shows access method, block count, and owning table; non-B-tree indexes are listed but marked unloadable; invalid indexes flagged (still loadable)
-
Page types — metapage (
btm_*incl.allequalimageon v4+), internal (child downlinks + level), leaf (heap TIDs); special spacebtpo_*with abtpo_flagsbit strip -
Hikey & posting lists — high-key mark on the first tuple of non-rightmost pages; dedup posting tuples (PG13+) with TID count and full scrollable list
-
Key value decoding — index tuple keys decoded per column type (int/bool/text/date/timestamp/timestamptz/uuid/numeric/float4/float8/bytea/domains) with NULL, include/↓/nulls-first badges and click-to-highlight column bytes; unsupported types (e.g. jsonb) or expression indexes degrade gracefully to hex-only
-
Block navigation — load siblings (
btpo_prev/btpo_next), root/fastroot, and child pages with one click; leaf heap TIDs jump straight to the owning table's block -
Tree panel — chrome Tree toggle (on = catalog visible). After connect it defaults on. On: two collapsible sections (
TABLE/INDEX, each capped at half the pane) and no Table/Index, blkno, Load, Refresh, Prev, or Next. Click a table to load heap blk 0, or a B-tree index to load metapage blk 0; nested block rows load that page, and clicking the currently displayed block refreshes it. Off: catalog hidden; the toolbar lets you pick a table or index (the index list is all user indexes, non-B-tree marked unloadable) and auto-loads blk 0, then blkno + Load. Names are unqualified (schema on hover). The INDEX section lists every user index; picking a table does not filter it. -
Guards — non-B-tree access methods (hash/gist/spgist/brin/gin) blocked in the UI and by the server (
INDEX_NOT_BTREE)
- Chrome switch Page | WAL; independent list UI (one wide metadata row per record)
- Query by start/end LSN; optional “Fill recent window” (~20 latest records; does not auto-load)
- FPI rows default collapsed (length/metadata only; no raw 8KB page render)
- v1 has no WAL raw-byte hex — structured
pg_walinspectfields only - Hard batch limits (fail, never truncate): ≤2000 records, ≤2 MiB JSON, ≤16 MiB LSN span
- Light / dark theme
- Deep links: the address bar mirrors the current view — copy, bookmark, or open it in a new tab to land on the same table/index page or WAL range (restored and auto-loaded once connected), e.g.
?mode=page&kind=table&table=<oid>&blkno=<n>
- Node.js 20+, pnpm 12+
- Page mode: a connect role that can enable
pageinspect(needsCREATEprivilege — usually superuser) and callget_raw_page; a missing extension is installed automatically on first Page request. Index browsing is B-tree only (PG13+ recommended for dedup posting lists) - WAL mode: PostgreSQL 15+; a connect role that can enable
pg_walinspect(CREATEprivilege) and callpg_get_wal_records_info/pg_current_wal_lsn(usually superuser); installed automatically when missing - Visibility map: the heap block's visibility map status (
all_visible/all_frozen/ page-headerpd_all_visible) is shown read-only viapg_visibility; a missing extension is installed automatically on first Page request (needsCREATEprivilege — usually superuser)
-- Manual fallback when automatic install fails (missing privilege / extension files)
CREATE EXTENSION pageinspect; -- Page mode
CREATE EXTENSION pg_walinspect; -- WAL mode (PG15+)
CREATE EXTENSION pg_visibility; -- visibility map infoMissing pageinspect / pg_walinspect / pg_visibility are installed automatically when a mode first needs them — the connect role needs CREATE privilege (usually superuser). Connect succeeds without these extensions; when automatic installation fails (insufficient privilege, missing extension files, …), the mode fails with the server's reason plus the manual steps above.
pnpm install
cp .env.example .env # optional: auto-connect on server start
# Foreground — two terminals
pnpm dev:server # http://127.0.0.1:8787
pnpm dev:web # http://127.0.0.1:5173
# Background — one terminal. Both processes must stay up: the UI on 5173
# proxies `/api` to 8787. Logs: /tmp/pageview-server.log, /tmp/pageview-web.log
pnpm dev:server >/tmp/pageview-server.log 2>&1 &
pnpm dev:web >/tmp/pageview-web.log 2>&1 &
# same shell: kill %1 %2Open http://127.0.0.1:5173 , connect (or rely on .env), then use Page (Tree on: pick a table or index in the catalog; Tree off: pick a table or index from the toolbar, then blkno + Load) or WAL (start/end LSN + Load).
Set credentials in .env (never commit secrets):
DATABASE_URL, orPGHOST/PGPORT/PGDATABASE/PGUSER/PGPASSWORDHOST(default127.0.0.1),PORT(default8787)
Passwords stay in the server process — not in the repo or browser storage.
| Path | Description |
|---|---|
packages/page-core |
Page parser, tuple decoder, structure field derivation |
packages/wal-core |
WAL record types, mapping, batch limit checks |
apps/server |
Fastify API proxy to PostgreSQL |
apps/web |
React UI |
pnpm test # all unit tests (page-core, wal-core, server, web)
pnpm -r typecheck
pnpm -r build
pnpm test:integration # needs .env; Page path L3 (heap + self-seeded B-tree oracle smoke)
pnpm test:wal # needs .env + PG 16+ with pg_walinspect; WAL path L3
pnpm exec playwright install chromium # once; bundled browser, no sudo
pnpm test:e2e # needs reachable PG + .env/DATABASE_URL; headless M1–M10CI runs two jobs on push and pull_request (see .github/workflows/ci.yml):
the unit job runs typecheck + tests + build without a database; the
integration job brings up a postgres:16 service with both extensions and
runs the two smoke scripts. Playwright (pnpm test:e2e) is local-only.
Fixture capture: see packages/page-core/fixtures/README.md.
- Page mode: heap user tables (
relkind = r) and B-tree indexes only - Standard 8 KB pages
- TOAST pointers shown; external toast pages not fetched
- No non-B-tree indexes, FSM, or system catalogs; the visibility map is shown read-only for heap blocks (via
pg_visibility) - WAL v1: structured records only; no raw WAL hex; no PG17+ block-info APIs
| Error | Fix |
|---|---|
PAGEINSPECT_MISSING |
Automatic install failed (e.g. missing privilege or extension files). Run CREATE EXTENSION pageinspect; as superuser — or install the extension files first — then retry Page |
WALINSPECT_MISSING |
Automatic install failed (e.g. missing privilege or extension files). Run CREATE EXTENSION pg_walinspect; as superuser — or install the extension files first — then retry WAL |
VISIBILITY_MISSING |
Automatic install failed (e.g. missing privilege or extension files). Run CREATE EXTENSION pg_visibility; as superuser — or install the extension files first — then retry Page (or use the Install button in the page toolbar) |
PG_VERSION_UNSUPPORTED |
Use PostgreSQL 15+ for WAL mode |
WAL_BATCH_TOO_LARGE |
Narrow the LSN range (≤2000 records / ≤2 MiB JSON / ≤16 MiB span) |
HTTP_500 / “Check the server is running on 127.0.0.1” |
The UI on 5173 is up but the API on 8787 is not. Start pnpm dev:server (foreground or background) and retry. 5173 only proxies /api. |
BLKNO_OUT_OF_RANGE |
Use blkno in 0 .. relpages-1 |
BAD_OID |
Use an oid (integer 1..4294967295) copied from the table/index list when building the URL |
BAD_URL_PARAM |
Fix or remove the invalid URL parameter shown in the message from the address bar, then reload — the app stays usable on the default view |
INDEX_NOT_BTREE |
Index pages support B-tree only; pick an index with access method btree, or browse its owning table |
get_raw_page / walinspect denied |
Use a privileged role |
