PostgreSQL DESCRIBE TABLE: The psql \d Equivalent
Coming from MySQL you type DESCRIBE table and psql throws a syntax error. The psql equivalent is \d table_name. For Supabase, Drizzle, or any SQL client without backslash commands, use the information_schema.columns view.
If you came from MySQL, the first thing you try in PostgreSQL is DESCRIBE table_name; — and the answer is a syntax error:
ERROR: syntax error at or near "DESCRIBE"
LINE 1: DESCRIBE users;PostgreSQL does not have DESCRIBE TABLE. It is a MySQL/Oracle command. The canonical Stack Overflow thread (Q109325, ~1.7M views) gives the answer in one line: in psql, use \d table_name. Everything below is the "but I'm not in psql" half of the story.
The psql way — \d#
# \d users
Table "public.users"
Column | Type | Collation | Nullable | Default
------------+------------------------+-----------+----------+----------------------
id | integer | | not null | nextval('users_id_seq')
email | character varying(255) | | not null |
created_at | timestamp with time zone| | not null | now()
Indexes:
"users_pkey" PRIMARY KEY, btree (id)
"users_email_key" UNIQUE CONSTRAINT, btree (email)\d+ adds storage info, descriptions, and compression. \dt lists tables. \d with no argument lists everything in the schema. These are psql client commands — they are interpreted by the psql REPL, not by the PostgreSQL server, so they work only in a psql session.
The pure-SQL way — information_schema#
Every other client (Supabase SQL editor, Drizzle Studio, TablePlus, DataGrip, an ORM's raw query method) speaks SQL to the server, not psql. There, \d fails. Use the standard information schema instead:
SELECT column_name, data_type, is_nullable, column_default
FROM information_schema.columns
WHERE table_schema = 'public'
AND table_name = 'users'
ORDER BY ordinal_position;This works everywhere — it is plain SQL against a standard view that every PostgreSQL database exposes. In Supabase specifically, you can run it in the SQL editor or just open the Table Editor in the dashboard.
Constraints and indexes#
\d already shows these in psql. In pure SQL:
-- constraints (primary key, foreign keys, checks, unique)
SELECT constraint_name, constraint_type
FROM information_schema.table_constraints
WHERE table_schema = 'public' AND table_name = 'users';
-- indexes (name + definition)
SELECT indexname, indexdef
FROM pg_indexes
WHERE schemaname = 'public' AND tablename = 'users';Foreign keys in detail#
SELECT
kcu.column_name,
ccu.table_name AS foreign_table,
ccu.column_name AS foreign_column
FROM information_schema.table_constraints tc
JOIN information_schema.key_column_usage kcu
ON tc.constraint_name = kcu.constraint_name
JOIN information_schema.constraint_column_usage ccu
ON ccu.constraint_name = tc.constraint_name
WHERE tc.constraint_type = 'FOREIGN KEY'
AND tc.table_name = 'users';This is the query you reach for when debugging an RLS or foreign-key issue — pair it with the Supabase RLS debugging guide and the foreign-key constraint violation fix.
Common mistakes#
- Typing
DESCRIBEin Supabase — it is not a PostgreSQL command. Useinformation_schema.columnsor the Table Editor. - Running
\dfrom a JS/Python client — backslash commands are psql-only.supabase.from('...')andpg.query('\\d users')will both error. - Forgetting
table_schema— without it you get columns from every schema, includingpg_catalog, which floods the result. Filter topublic(or your target schema). - Expecting
information_schemato show storage details — it does not. For toast, fillfactor, or storage type, querypg_attribute/pg_classor usepsql \d+.
Official references: PostgreSQL — The Information Schema, psql — backslash commands (\d).
Getting the same info from Drizzle Kit or Prisma#
If the table already exists and you're setting up an ORM against it rather than typing
SQL by hand, both major ORMs have a dedicated introspection command that does the same
job \d does, then writes the result as code:
# Drizzle: reads the live database, writes drizzle/schema.ts
npx drizzle-kit introspect
# Prisma: reads the live database, writes/updates schema.prisma
npx prisma@7 db pullThe @7 matters while Prisma is not yet installed in the project: a bare npx prisma db pull would run npm's latest tag, which on 6 October 2026 is the 8.0.0-rc.20 release candidate (npm registry), and that CLI answers No command registered for `pull` .
Both read information_schema under the hood — the same view queried by hand above —
so they see exactly what \d sees: columns, types, nullability, primary keys, and
foreign keys. What they don't reliably introspect is everything RLS-adjacent: check
constraints with complex expressions and some index types round-trip imperfectly, so
treat the generated schema as a starting point to review, not a guaranteed mirror of the
database — a gap that shows up as puzzling type mismatches later, covered from the
migration side in drizzle-kit push: statement does not return data.
Neither command applies anything by itself — both are read-only, one-way snapshots of
what the database already looks like, the opposite direction from a migration. Drizzle
Kit additionally offers drizzle-kit check, which diffs the generated schema file
against the live database and reports drift without writing anything, useful right after
someone else ran a manual ALTER TABLE outside the normal migration flow and the schema
file in the repo is now quietly out of date.
Checking who can access the table, not just its structure#
\d and information_schema.columns both describe shape — column names and types —
but say nothing about access. A table can have exactly the schema you expect and
still reject every query, because the connecting role has no SELECT/INSERT grant on
it at all — a different failure from an RLS policy silently filtering rows, and one that
throws an explicit permission denied for table error instead of returning empty:
SELECT grantee, privilege_type
FROM information_schema.role_table_grants
WHERE table_schema = 'public' AND table_name = 'users';In Supabase specifically, this is the first thing to check when a client hits
permission denied for schema public or permission denied for schema auth — both
covered end to end in Supabase 42501: permission denied for schema public & auth
and Supabase 42501: permission denied for schema auth
— the grant is missing or scoped to the wrong role (anon vs authenticated) well
before RLS policies are ever evaluated. Supabase's default posture on a fresh project is
to grant nothing to anon/authenticated beyond what its own migrations set up, so a
brand-new table you create yourself needs an explicit GRANT SELECT ON your_table TO authenticated; before RLS policies even get a chance to run — RLS narrows an existing
grant, it does not create one. psql users get the same information natively with \dp table_name (or \z, its older alias) instead of querying the information schema by
hand.
Frequently Asked Questions
Still stuck? Paste your error.
Paste the error message or stack trace — we check it against every fix and tool on the site.
One email a month — the latest explained errors
What the site published that month — mostly Next.js, Supabase and Node errors, explained. You confirm by email; unsubscribe any time in one click.
Continue Reading
PostgreSQL SHOW TABLES / DESCRIBE TABLE (psql + Supabase)
Coming from MySQL you type `SHOW TABLES` or `DESCRIBE table` and PostgreSQL throws a syntax error — both are MySQL commands. The psql equivalents are `\dt` (list tables) and `\d table_name` (describe a table); the portable SQL equivalents are `information_schema.tables` and `information_schema.columns`. Here is exactly what to run in psql, the Supabase SQL editor, Drizzle, or any client, plus why your query returns zero rows.
Supabase Enums: ALTER TYPE ADD VALUE in Migrations
Step‑by‑step guide to adding a PostgreSQL enum type and column in Supabase, including verification and common pitfalls.
Fix Supabase CHARINDEX does not exist (SQLSTATE 42883)
Repair the email-to-username expression without adding columns or hiding trigger failures, with a PostgreSQL reproduction and signature checks.


