<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Varun Krishnan</title>
    <description>The latest articles on DEV Community by Varun Krishnan (@not_varunkv).</description>
    <link>https://dev.to/not_varunkv</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3608714%2F3d7fbcf6-347c-4905-b0ec-2b7ec91677b4.jpg</url>
      <title>DEV Community: Varun Krishnan</title>
      <link>https://dev.to/not_varunkv</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/not_varunkv"/>
    <language>en</language>
    <item>
      <title>How to Format SQL in PostgreSQL: Styles, Tools, and Team Standards</title>
      <dc:creator>Varun Krishnan</dc:creator>
      <pubDate>Thu, 08 Oct 2026 14:30:00 +0000</pubDate>
      <link>https://dev.to/not_varunkv/how-to-format-sql-in-postgresql-styles-tools-and-team-standards-55c7</link>
      <guid>https://dev.to/not_varunkv/how-to-format-sql-in-postgresql-styles-tools-and-team-standards-55c7</guid>
      <description>&lt;h2&gt;
  
  
  The short version
&lt;/h2&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Unformatted SQL — a 400-character single line from your logs, an ORM dump with random indentation — slows down everyone who reads it. Formatting is a solved problem: paste it into a formatter, pick a style, move on.

[dbdiagramr's free SQL formatter](https://www.dbdiagramr.space/sql-formatter) formats PostgreSQL in your browser. Uppercase or lowercase keywords, 2 or 4 space indent, copy or download. Nothing is uploaded.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  What "formatted" actually means
&lt;/h2&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Take this real-world log line:
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;post_count&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="k"&gt;left&lt;/span&gt; &lt;span class="k"&gt;join&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;interval&lt;/span&gt; &lt;span class="s1"&gt;'30 days'&lt;/span&gt; &lt;span class="k"&gt;group&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="k"&gt;order&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="n"&gt;post_count&lt;/span&gt; &lt;span class="k"&gt;desc&lt;/span&gt; &lt;span class="k"&gt;limit&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Formatted:
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
  &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;post_count&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;
  &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;
  &lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt;
  &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;interval&lt;/span&gt; &lt;span class="s1"&gt;'30 days'&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt;
  &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt;
  &lt;span class="n"&gt;post_count&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;
&lt;span class="k"&gt;LIMIT&lt;/span&gt;
  &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Same query. The second version shows the misplaced JOIN or wrong GROUP BY in seconds; the first hides it.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Uppercase vs lowercase keywords
&lt;/h2&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;There is no technical difference — Postgres parses both identically. It is purely culture:


  - **UPPERCASE** — traditional DBA style, SQL Server and Oracle shops, certification materials. Keywords visually pop against table names.
  - **lowercase** — modern analytics style (dbt, Postgres-first teams). Less shouting, easier to type.

Pick the one that matches your codebase and enforce it with a formatter, not a style doc nobody reads.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Where formatting pays off most
&lt;/h2&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  - **Migration PRs** — formatted DDL diffs review in minutes; single-line dumps review never.
  - **Log triage** — paste the slow query from pg_stat_statements or your app logs and read it.
  - **Onboarding** — new hires can read the schema migrations as documentation.
  - **Before diagramming** — clean SQL is easier to sanity-check before you [visualize it as an ER diagram](https://www.dbdiagramr.space/visualize).
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;**Does formatting change what my query does?**No. A formatter only changes whitespace and keyword casing. Same semantics, better manners.

**Can I format SELECT queries or only DDL?**Both — SELECTs, CTEs, JOINs, migrations, anything valid PostgreSQL.

**Is my SQL sent to a server?**Not with dbdiagramr's formatter — it runs entirely in your browser.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>postgres</category>
      <category>database</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Database Schema Diagram Tool: 5 Ways to Visualize PostgreSQL</title>
      <dc:creator>Varun Krishnan</dc:creator>
      <pubDate>Tue, 06 Oct 2026 14:30:00 +0000</pubDate>
      <link>https://dev.to/not_varunkv/database-schema-diagram-tool-5-ways-to-visualize-postgresql-9l2</link>
      <guid>https://dev.to/not_varunkv/database-schema-diagram-tool-5-ways-to-visualize-postgresql-9l2</guid>
      <description>&lt;h2&gt;
  
  
  The short version
&lt;/h2&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You need a database schema diagram tool that works with PostgreSQL. There are five practical options, each making a different tradeoff between speed, depth, and how current the diagram stays.

For most developers, the fastest path to a usable diagram is [dbdiagramr](https://www.dbdiagramr.space/visualize) -- paste SQL or connect to a live database, get an interactive ER diagram in seconds. No signup, no install, no limits.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  5 database schema diagram tools compared
&lt;/h2&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    | `psql` + `pg_catalog` | ZeroYesAlways liveQuick inspection |
    | pgAdmin ERD | Already installedYesSnapshotOne-off look |
    | dbdiagram.io | Account requiredLimitedNo (DBML)Designing new schema |
    | DrawSQL | Account requiredLimitedYes (paid)Team collaboration |
    | dbdiagramr | NoneYesYes (free)Understanding real DB |
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  1. psql: the zero-setup baseline
&lt;/h2&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;If you have PostgreSQL installed, you already have `psql`. It's not a diagram tool, but it shows schema structure:
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="err"&gt;\\&lt;/span&gt;&lt;span class="n"&gt;dt&lt;/span&gt;              &lt;span class="c1"&gt;-- list all tables&lt;/span&gt;
&lt;span class="err"&gt;\\&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="k"&gt;table_name&lt;/span&gt;    &lt;span class="c1"&gt;-- show one table's structure&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;For relationships, query `information_schema` directly. The output is text, not a picture. Past a dozen tables you're reconstructing the graph in your head.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  2. pgAdmin ERD: free and built in
&lt;/h2&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pgAdmin 4 includes an ER diagram tool. Right-click your database, select *ERD For Database*, and it generates a diagram from the live schema.

**Pros:** Free, already installed, connects to live database.

**Cons:** Auto-layout struggles past a few dozen tables. No SVG export. Not shareable without screenshots.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  3. dbdiagram.io: the popular choice
&lt;/h2&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dbdiagram.io is the most well-known database diagram tool. You write schema definitions in DBML (a custom markup language) and it renders an interactive diagram.

**Pros:** Clean UI, good for designing new schemas, shareable links.

**Cons:** Must learn DBML. Free tier limited to 10 tables. No live database connection -- the diagram is always a manual copy.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  4. DrawSQL: the desktop option
&lt;/h2&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DrawSQL is a desktop app that connects to live databases and generates ER diagrams. Supports MySQL, PostgreSQL, SQL Server, and MariaDB.

**Pros:** Desktop app, exports, team collaboration (paid).

**Cons:** Paid for full features. Free tier limited to 10 diagrams.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  5. dbdiagramr: paste SQL, get a diagram
&lt;/h2&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[dbdiagramr](https://www.dbdiagramr.space/visualize) takes a different approach: paste SQL or a PostgreSQL connection string, get an interactive ER diagram. No account, no install, no table limits.

Because it introspects the live database, the diagram is always current. Change a migration, paste the same connection string again, up-to-date in under 10 seconds.

**Pros:** Free, no signup, no limits, live connection, SVG/PNG export, search tables/columns.

**Cons:** PostgreSQL only (for now).
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Which tool should you choose?
&lt;/h2&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  - **Just need a quick look?** `psql` or pgAdmin. Fast, local, no sharing.
  - **Designing a new schema?** dbdiagram.io. Great for planning before you build.
  - **Need to understand an existing database?** dbdiagramr. Paste SQL or connect, get a diagram in seconds.
  - **Team collaboration?** DrawSQL paid plan. Real-time editing with multiple cursors.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Why "stays current" matters
&lt;/h2&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Every tool except dbdiagramr shares the same failure mode: the diagram is a snapshot. The day someone runs a migration, the diagram is wrong. A wrong diagram is worse than none -- it's confidently wrong.

The test that settles it: **how much work does it take to make this picture true again?** If the answer is "re-export" or "drag boxes by hand," humans will stop doing it and the diagram will lie to you.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;**What's the best database schema diagram tool for PostgreSQL?**For understanding an existing database, dbdiagramr is the fastest -- paste SQL or connect to a live database, no signup required.

**Can I use dbdiagram.io without learning DBML?**Not really. DBML is the input format. If you prefer raw SQL, dbdiagramr accepts it directly.

**Is pgAdmin's ERD tool good enough?**For small schemas and quick inspection, yes. For anything you need to share or keep current, use a dedicated tool.

**Does DrawSQL support live PostgreSQL connections?**Yes, but only on paid plans. The free tier is limited to 10 diagrams.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>postgres</category>
      <category>database</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>SQL to Schema Diagram Online: Convert SQL to ERD in Seconds</title>
      <dc:creator>Varun Krishnan</dc:creator>
      <pubDate>Thu, 01 Oct 2026 14:30:00 +0000</pubDate>
      <link>https://dev.to/not_varunkv/sql-to-schema-diagram-online-convert-sql-to-erd-in-seconds-fhk</link>
      <guid>https://dev.to/not_varunkv/sql-to-schema-diagram-online-convert-sql-to-erd-in-seconds-fhk</guid>
      <description>&lt;h2&gt;
  
  
  The short version
&lt;/h2&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You have SQL. You need a diagram. The fastest path from `CREATE TABLE` to a visual schema is a tool that parses SQL and renders an ER diagram on the spot.

[dbdiagramr](https://www.dbdiagramr.space/visualize) does exactly that -- paste your SQL, get an interactive diagram in seconds. No signup, no install, no export limits.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Why convert SQL to a schema diagram?
&lt;/h2&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SQL is precise. Diagrams are comprehensible. When you're onboarding to a new codebase or explaining a schema to a non-technical stakeholder, a visual representation does something SQL can't: it shows relationships at a glance.

Reading 200 lines of `CREATE TABLE` statements, you'll find the foreign keys eventually. Seeing them drawn as lines between boxes takes half a second.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  How to convert SQL to a schema diagram online
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Step 1: Get your SQL
&lt;/h3&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Grab your schema from one of these sources:


  - `pg_dump --schema-only yourdb`
  - Migration files (Prisma, Drizzle, Rails, Django)
  - Your IDE's schema export
  - A SQL file you already have
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  Step 2: Paste into dbdiagramr
&lt;/h3&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Go to [dbdiagramr.com/visualize](https://www.dbdiagramr.space/visualize) and paste your SQL in the editor. The tool parses CREATE TABLE statements, detects primary keys, and maps foreign key relationships automatically.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  Step 3: Explore and export
&lt;/h3&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Your ER diagram appears instantly. Pan, zoom, drag tables around to arrange them. When you're happy, export as SVG or PNG with one click.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  What SQL syntax works?
&lt;/h2&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dbdiagramr understands standard PostgreSQL syntax:


  - `CREATE TABLE` with columns and types
  - `PRIMARY KEY` constraints
  - `REFERENCES` for foreign keys
  - `DEFAULT` values
  - `NOT NULL` and `UNIQUE` constraints
  - `ON DELETE` and `ON UPDATE` actions

If your SQL is valid PostgreSQL, dbdiagramr will parse it.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Example: converting a Laravel migration
&lt;/h2&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;If you're using Laravel, your migrations live in `database/migrations/`. Export them to SQL and paste the result:
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Laravel migration exported to SQL&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="n"&gt;BIGSERIAL&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;UNIQUE&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;password&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;remember_token&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="n"&gt;TIMESTAMPTZ&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;updated_at&lt;/span&gt; &lt;span class="n"&gt;TIMESTAMPTZ&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="n"&gt;BIGSERIAL&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="nb"&gt;BIGINT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;DELETE&lt;/span&gt; &lt;span class="k"&gt;CASCADE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;slug&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;UNIQUE&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;published&lt;/span&gt; &lt;span class="nb"&gt;BOOLEAN&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="k"&gt;FALSE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="n"&gt;TIMESTAMPTZ&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;updated_at&lt;/span&gt; &lt;span class="n"&gt;TIMESTAMPTZ&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dbdiagramr renders the `users` and `posts` tables with the foreign key relationship drawn between them. Done in seconds.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Alternative methods (and why they're slower)
&lt;/h2&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    | pgAdmin ERD tool | Already installedMediumSnapshot, manual re-run |
    | Draw.io + manual | ShortSlowHand-draw every table |
    | dbdiagram.io | Account requiredFastMust learn DBML syntax |
    | dbdiagramr | NoneInstantPostgreSQL only (for now) |


dbdiagramr is the only option that requires zero setup and accepts raw SQL directly.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;**Can I convert MySQL SQL to a diagram?**Not yet. dbdiagramr currently supports PostgreSQL syntax only. MySQL support is planned.

**Does it work with Prisma schema files?**Not directly. Export your Prisma schema to SQL first with `prisma db pull` or `pg_dump`, then paste the SQL.

**How large a schema can it handle?**dbdiagramr handles schemas with dozens of tables comfortably. Very large schemas (100+ tables) may need some manual arrangement, but the parsing works fine.

**Is there a table limit?**No. Paste as many tables as you want. No signup, no limits.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>postgres</category>
      <category>database</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Free Online Database Diagram Tool: Visualize Your Schema in Seconds</title>
      <dc:creator>Varun Krishnan</dc:creator>
      <pubDate>Tue, 29 Sep 2026 14:30:00 +0000</pubDate>
      <link>https://dev.to/not_varunkv/free-online-database-diagram-tool-visualize-your-schema-in-seconds-4al7</link>
      <guid>https://dev.to/not_varunkv/free-online-database-diagram-tool-visualize-your-schema-in-seconds-4al7</guid>
      <description>&lt;h2&gt;
  
  
  The short version
&lt;/h2&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You need a database diagram. You don't want to pay for it, sign up for another account, or install anything. A free online database diagram tool should let you paste SQL or connect to your database and get a visual schema in seconds.

Most tools claim to be free, then hit you with table limits, export restrictions, or require an email before you can do anything. The one that actually works the way you expect is [dbdiagramr](https://www.dbdiagramr.space) -- paste SQL or a PostgreSQL connection string, get an interactive ER diagram, export SVG/PNG. No signup, no limits, no catch.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  What a good free database diagram tool should do
&lt;/h2&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    | Paste SQL directly | You already have the schema -- just show it |
    | Live database connection | See what's actually deployed, not what you think is deployed |
    | Interactive diagram | Pan, zoom, drag tables around |
    | Export SVG/PNG | Share with your team or drop in docs |
    | Search tables/columns | Find anything in a large schema fast |
    | No signup required | You're diagramming, not buying enterprise software |
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  How to use dbdiagramr as your free database diagram tool
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Option 1: Paste SQL
&lt;/h3&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;If you have a SQL dump or migration file, paste it directly into dbdiagramr and see your ER diagram appear instantly. Works with PostgreSQL CREATE TABLE statements, foreign keys, indexes -- the whole schema.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  Option 2: Connect to a live database
&lt;/h3&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;If you have a PostgreSQL connection string, paste it into dbdiagramr. It queries `information_schema` for tables, columns, primary keys, and foreign keys, then renders an interactive ER diagram you can pan, zoom, drag, and export as SVG or PNG. Your SQL never leaves your browser -- the connection happens client-side.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  Option 3: Upload a file
&lt;/h3&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Drag and drop a `.sql` file or use the file picker. Same result -- instant diagram.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Why most "free" database diagram tools aren't actually free
&lt;/h2&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    | dbdiagram.io | 10 tables on free plan, export requires paid plan |
    | DrawSQL | Limited to 10 diagrams, no collaboration on free plan |
    | Lucidchart | 3 editable documents, 60 shapes per document |
    | QuickDBD | Limited diagrams, watermark on exports |


dbdiagramr has none of these limits. Paste SQL, connect to your database, export as much as you want. The tool is open-source -- you can even self-host it.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Real example: visualizing a Supabase schema
&lt;/h2&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Supabase projects come with auth, storage, and realtime tables out of the box. Understanding how they connect requires a diagram.

Paste this SQL into dbdiagramr:
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="n"&gt;UUID&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;gen_random_uuid&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;UNIQUE&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="n"&gt;TIMESTAMPTZ&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="n"&gt;UUID&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;gen_random_uuid&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="n"&gt;UUID&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;DELETE&lt;/span&gt; &lt;span class="k"&gt;CASCADE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;published&lt;/span&gt; &lt;span class="nb"&gt;BOOLEAN&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="k"&gt;FALSE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="n"&gt;TIMESTAMPTZ&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;comments&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="n"&gt;UUID&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;gen_random_uuid&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="n"&gt;post_id&lt;/span&gt; &lt;span class="n"&gt;UUID&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;DELETE&lt;/span&gt; &lt;span class="k"&gt;CASCADE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="n"&gt;UUID&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;DELETE&lt;/span&gt; &lt;span class="k"&gt;CASCADE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="n"&gt;TIMESTAMPTZ&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;In two seconds you see: `users` -- `posts` -- `comments` with all foreign key relationships drawn. Drag the boxes around, zoom in, export as PNG for your README.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  When to use a free tool vs. a paid one
&lt;/h2&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;**Use a free tool when:**


  - You're exploring an existing schema
  - You need a quick diagram for documentation
  - You're a solo developer or small team
  - You want to understand relationships before writing queries

**Consider paid when:**


  - You need real-time collaboration (Lucidchart, DrawSQL paid)
  - You need version control integration (some enterprise tools)
  - You're designing a schema from scratch with a large team

For 90% of database diagramming tasks, a free tool is enough.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;**What's the best free online database diagram tool?**dbdiagramr is the best free option -- no table limits, no signup, open-source. Paste SQL or connect to a live PostgreSQL database.

**Can I create a database diagram without signing up?**Yes. dbdiagramr requires no account. Go to the site, paste SQL, get your diagram.

**Does it work with PostgreSQL only?**Currently yes. PostgreSQL is the most common target for ER diagrams, and dbdiagramr specializes in it. SQL paste works with any PostgreSQL-compatible syntax.

**Can I export the diagram?**Yes. Export as SVG or PNG with one click. No paid plan required.

**Is my database connection secure?**Yes. dbdiagramr connects to your database client-side. Your connection string never leaves your browser. No data is sent to any server.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>postgres</category>
      <category>database</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>dbdiagram.io vs dbdiagramr vs DrawSQL: Honest Comparison</title>
      <dc:creator>Varun Krishnan</dc:creator>
      <pubDate>Fri, 25 Sep 2026 14:30:00 +0000</pubDate>
      <link>https://dev.to/not_varunkv/dbdiagramio-vs-dbdiagramr-vs-drawsql-honest-comparison-4794</link>
      <guid>https://dev.to/not_varunkv/dbdiagramio-vs-dbdiagramr-vs-drawsql-honest-comparison-4794</guid>
      <description>&lt;h2&gt;
  
  
  The short version
&lt;/h2&gt;

&lt;p&gt;dbdiagram.io is the original -- great for writing schemas in code (DBML), but no live database connection. DrawSQL is a paid GUI tool with good auto-generation but requires a desktop app. dbdiagramr is free, runs in the browser, connects directly to Supabase/Neon/Railway, and generates diagrams from your live database in seconds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Feature comparison
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;dbdiagram.io&lt;/th&gt;
&lt;th&gt;dbdiagramr&lt;/th&gt;
&lt;th&gt;DrawSQL&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Pricing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Free (1 diagram), $9/mo for more&lt;/td&gt;
&lt;td&gt;Free (unlimited)&lt;/td&gt;
&lt;td&gt;$8/mo (individual), $15/mo (team)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Live database connection&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes (Supabase, Neon, Railway, any Postgres)&lt;/td&gt;
&lt;td&gt;Yes (MySQL, Postgres, SQLite)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;DBML support&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes (native)&lt;/td&gt;
&lt;td&gt;No (generates from DB)&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Auto-generate from DB&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes (paste connection string)&lt;/td&gt;
&lt;td&gt;Yes (desktop app)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Supabase integration&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes (direct connection)&lt;/td&gt;
&lt;td&gt;No (generic Postgres)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Neon integration&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Railway integration&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Browser-based&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No (desktop app)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Schema export&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;DBML, SQL&lt;/td&gt;
&lt;td&gt;SQL, PNG&lt;/td&gt;
&lt;td&gt;SQL, PNG, PDF&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Collaboration&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes (paid)&lt;/td&gt;
&lt;td&gt;No (yet)&lt;/td&gt;
&lt;td&gt;Yes (paid)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;API access&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes (paid)&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;AI context (llms.txt)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  dbdiagram.io
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Best for&lt;/strong&gt;: Teams that want to design schemas in code (DBML) before creating them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;DBML is a great way to define schemas as code.&lt;/li&gt;
&lt;li&gt;Clean, fast editor.&lt;/li&gt;
&lt;li&gt;Good for greenfield projects where you're designing from scratch.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No live database connection -- you can't diagram an existing database.&lt;/li&gt;
&lt;li&gt;Free tier is limited to 1 diagram.&lt;/li&gt;
&lt;li&gt;Can't generate from Supabase, Neon, or Railway directly.&lt;/li&gt;
&lt;li&gt;Requires learning DBML syntax.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Pricing&lt;/strong&gt;: Free (1 diagram), $9/mo (10 diagrams), $17/mo (unlimited).&lt;/p&gt;

&lt;h2&gt;
  
  
  dbdiagramr
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Best for&lt;/strong&gt;: Developers who want to see their existing database schema in seconds.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Free and unlimited.&lt;/li&gt;
&lt;li&gt;Paste a connection string, get a diagram -- no signup, no install.&lt;/li&gt;
&lt;li&gt;Direct Supabase, Neon, Railway integration.&lt;/li&gt;
&lt;li&gt;Browser-based, works on any device.&lt;/li&gt;
&lt;li&gt;Generates AI context (llms.txt) for your schema.&lt;/li&gt;
&lt;li&gt;Open source.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Read-only (can't edit schemas in the UI, yet).&lt;/li&gt;
&lt;li&gt;No collaboration features (yet).&lt;/li&gt;
&lt;li&gt;No DBML support.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Pricing&lt;/strong&gt;: Free.&lt;/p&gt;

&lt;h2&gt;
  
  
  DrawSQL
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Best for&lt;/strong&gt;: Teams that want a desktop GUI for database exploration and documentation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Good auto-generation from live databases.&lt;/li&gt;
&lt;li&gt;Clean UI with export options (PNG, PDF, SQL).&lt;/li&gt;
&lt;li&gt;Supports MySQL, Postgres, SQLite.&lt;/li&gt;
&lt;li&gt;Collaboration features for teams.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Desktop app -- not browser-based.&lt;/li&gt;
&lt;li&gt;Paid (no free tier for most features).&lt;/li&gt;
&lt;li&gt;No direct Supabase integration (generic Postgres only).&lt;/li&gt;
&lt;li&gt;No AI context generation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Pricing&lt;/strong&gt;: $8/mo (individual), $15/mo (team), $25/mo (organization).&lt;/p&gt;

&lt;h2&gt;
  
  
  Which one should you use?
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;If you...&lt;/th&gt;
&lt;th&gt;Use...&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Want to diagram an existing Supabase/Neon database&lt;/td&gt;
&lt;td&gt;dbdiagramr&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Want to design a schema from scratch in code&lt;/td&gt;
&lt;td&gt;dbdiagram.io&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Need a desktop GUI with export options&lt;/td&gt;
&lt;td&gt;DrawSQL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Want free, unlimited diagrams&lt;/td&gt;
&lt;td&gt;dbdiagramr&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Need team collaboration&lt;/td&gt;
&lt;td&gt;dbdiagram.io or DrawSQL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Want AI context for your schema&lt;/td&gt;
&lt;td&gt;dbdiagramr&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Are on a tight budget&lt;/td&gt;
&lt;td&gt;dbdiagramr&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  The real question
&lt;/h2&gt;

&lt;p&gt;The tools aren't competitors -- they serve different workflows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Design first&lt;/strong&gt; (dbdiagram.io): You write DBML, generate migrations, deploy to production.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Existing database&lt;/strong&gt; (dbdiagramr): You already have a database, you want to see what it looks like.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GUI exploration&lt;/strong&gt; (DrawSQL): You want a desktop app to browse, query, and export your schema.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most developers use dbdiagramr to understand what they have, then dbdiagram.io if they want to redesign it.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Can I import a dbdiagram.io diagram into dbdiagramr?
&lt;/h3&gt;

&lt;p&gt;Not directly. dbdiagram.io uses DBML format. dbdiagramr connects to live databases. You'd need to deploy your DBML schema to a database first, then connect dbdiagramr to it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does dbdiagramr support MySQL?
&lt;/h3&gt;

&lt;p&gt;Not yet. Currently supports PostgreSQL only. MySQL support is planned.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is DrawSQL worth the money?
&lt;/h3&gt;

&lt;p&gt;If you need a desktop GUI with export options and team collaboration, yes. If you just need to see your schema, dbdiagramr does it for free in the browser.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I use dbdiagramr with Prisma?
&lt;/h3&gt;

&lt;p&gt;Yes. Prisma connects to PostgreSQL. Use your Prisma connection string (with &lt;code&gt;?pgbouncer=true&lt;/code&gt; if using Supabase pooler) and dbdiagramr will diagram your entire schema.&lt;/p&gt;

</description>
      <category>database</category>
      <category>tutorial</category>
      <category>postgres</category>
      <category>tools</category>
    </item>
    <item>
      <title>Supabase Connection String: IPv6, ENOTFOUND, and the Transaction Pooler Fix</title>
      <dc:creator>Varun Krishnan</dc:creator>
      <pubDate>Thu, 24 Sep 2026 14:30:00 +0000</pubDate>
      <link>https://dev.to/not_varunkv/supabase-connection-string-ipv6-enotfound-and-the-transaction-pooler-fix-17pk</link>
      <guid>https://dev.to/not_varunkv/supabase-connection-string-ipv6-enotfound-and-the-transaction-pooler-fix-17pk</guid>
      <description>&lt;h2&gt;
  
  
  The short version
&lt;/h2&gt;

&lt;p&gt;Supabase uses IPv6 by default. Most local dev environments and many hosting providers don't support IPv6, so DNS resolution fails with &lt;code&gt;ENOTFOUND&lt;/code&gt; or hangs with &lt;code&gt;ETIMEDOUT&lt;/code&gt;. The fix: use the transaction pooler connection string (port &lt;code&gt;6543&lt;/code&gt;) instead of the direct connection (port &lt;code&gt;5432&lt;/code&gt;). It routes through Supabase's IPv4-compatible proxy.&lt;/p&gt;

&lt;h2&gt;
  
  
  The error
&lt;/h2&gt;

&lt;p&gt;You're running your app locally and you see:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Error: getaddrinfo ENOTFOUND db.xxxxxxxxx.supabase.co
    at GetAddrInfoReq.onlookup [as oncomplete] (node:dns:1107:26)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Error: connect ETIMEDOUT 2600:1ff:f000:e000::1:5432
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first one means DNS can't resolve the hostname. The second means DNS resolved it to an IPv6 address, but the connection timed out because your network doesn't route IPv6.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why it happens
&lt;/h2&gt;

&lt;p&gt;Supabase assigns each project a hostname like &lt;code&gt;db.xxxxxxxxx.supabase.co&lt;/code&gt;. This hostname resolves to an IPv6 address by default. If your machine or hosting provider doesn't support IPv6, the connection fails.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Environment&lt;/th&gt;
&lt;th&gt;IPv6 support&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;macOS (most setups)&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Linux (most setups)&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Windows (most setups)&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Vercel&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Railway&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Render&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cloudflare Workers&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Local Docker&lt;/td&gt;
&lt;td&gt;Depends on config&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  The three connection strings
&lt;/h2&gt;

&lt;p&gt;Supabase gives you three connection strings in the dashboard (Settings → Database):&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Direct connection (port 5432)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;postgresql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="n"&gt;postgres&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="n"&gt;YOUR&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;PASSWORD&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;xxxxxxxxx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;supabase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;co&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;5432&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;postgres&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Full Postgres protocol support&lt;/li&gt;
&lt;li&gt;Supports prepared statements, &lt;code&gt;SET&lt;/code&gt; commands, &lt;code&gt;LISTEN/NOTIFY&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Uses IPv6 -- may fail locally or on some hosts&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Transaction pooler (port 6543)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;postgresql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="n"&gt;postgres&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="n"&gt;YOUR&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;PROJECT&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="k"&gt;REF&lt;/span&gt;&lt;span class="p"&gt;]:[&lt;/span&gt;&lt;span class="n"&gt;YOUR&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;PASSWORD&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;aws&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;pooler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;supabase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;6543&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;postgres&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Routes through Supabase's connection pooler (PgBouncer)&lt;/li&gt;
&lt;li&gt;IPv4-compatible -- works everywhere&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Does NOT support&lt;/strong&gt; prepared statements or &lt;code&gt;SET&lt;/code&gt; commands&lt;/li&gt;
&lt;li&gt;Best for most web apps&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Session pooler (port 6543, mode=session)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;postgresql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="n"&gt;postgres&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="n"&gt;YOUR&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;PROJECT&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="k"&gt;REF&lt;/span&gt;&lt;span class="p"&gt;]:[&lt;/span&gt;&lt;span class="n"&gt;YOUR&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;PASSWORD&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;aws&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;pooler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;supabase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;6543&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;postgres&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="n"&gt;pgbouncer&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;session_mode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Same as transaction pooler but preserves session state&lt;/li&gt;
&lt;li&gt;Supports &lt;code&gt;SET&lt;/code&gt; commands but not prepared statements&lt;/li&gt;
&lt;li&gt;Use this if you need &lt;code&gt;SET search_path&lt;/code&gt; or similar&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Which one to use
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Use case&lt;/th&gt;
&lt;th&gt;Connection string&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Local dev (Node.js, Python, etc.)&lt;/td&gt;
&lt;td&gt;Transaction pooler (6543)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Vercel / Railway / Render&lt;/td&gt;
&lt;td&gt;Transaction pooler (6543)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cloudflare Workers&lt;/td&gt;
&lt;td&gt;Transaction pooler (6543)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Prisma ORM&lt;/td&gt;
&lt;td&gt;Transaction pooler (6543)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Drizzle ORM&lt;/td&gt;
&lt;td&gt;Transaction pooler (6543)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Django&lt;/td&gt;
&lt;td&gt;Session pooler (6543 + session_mode)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Laravel&lt;/td&gt;
&lt;td&gt;Transaction pooler (6543)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;pg_dump / pg_restore&lt;/td&gt;
&lt;td&gt;Direct connection (5432)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Migrations (Prisma, Knex)&lt;/td&gt;
&lt;td&gt;Direct connection (5432)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  The Prisma gotcha
&lt;/h2&gt;

&lt;p&gt;Prisma uses prepared statements by default. The transaction pooler doesn't support prepared statements, so you'll get:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Error: prepared statement "stmt_1" does not exist
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Fix&lt;/strong&gt;: Add &lt;code&gt;?pgbouncer=true&lt;/code&gt; to your connection string, or use the direct connection for migrations and the pooler for runtime queries.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# .env&lt;/span&gt;
&lt;span class="nv"&gt;DATABASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"postgresql://postgres.xxx:password@aws-0-us-east-1.pooler.supabase.com:6543/postgres?pgbouncer=true"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Quick diagnosis
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Test if IPv6 works
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# If this returns an address, IPv6 works&lt;/span&gt;
dig AAAA db.xxxxxxxxx.supabase.co

&lt;span class="c"&gt;# If this returns an address, IPv4 works&lt;/span&gt;
dig A db.xxxxxxxxx.supabase.co
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Test the connection
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Direct connection (may fail with IPv6)&lt;/span&gt;
psql &lt;span class="s2"&gt;"postgresql://postgres:password@db.xxxxxxxxx.supabase.co:5432/postgres"&lt;/span&gt;

&lt;span class="c"&gt;# Transaction pooler (should always work)&lt;/span&gt;
psql &lt;span class="s2"&gt;"postgresql://postgres.xxx:password@aws-0-us-east-1.pooler.supabase.com:6543/postgres"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Why does my app work on Vercel but not locally?
&lt;/h3&gt;

&lt;p&gt;Vercel supports IPv6. Your local machine might not. Use the transaction pooler (6543) locally and you'll get the same behavior as production.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I force IPv4 on the direct connection?
&lt;/h3&gt;

&lt;p&gt;Yes. Add &lt;code&gt;?sslmode=require&amp;amp;ip=4&lt;/code&gt; to the direct connection string, but this isn't officially supported and may break. Use the pooler instead.&lt;/p&gt;

&lt;h3&gt;
  
  
  What's the difference between transaction and session pooler?
&lt;/h3&gt;

&lt;p&gt;Transaction pooler resets the connection after each transaction (faster, more scalable). Session pooler preserves session state (needed for &lt;code&gt;SET&lt;/code&gt; commands, &lt;code&gt;LISTEN/NOTIFY&lt;/code&gt;). Most apps should use transaction pooler.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does the pooler affect performance?
&lt;/h3&gt;

&lt;p&gt;Minimal. The pooler adds ~1-2ms of latency per query. For most web apps, this is negligible compared to network latency and query execution time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I use both pooler and direct connections?
&lt;/h3&gt;

&lt;p&gt;Yes. Use the pooler for runtime queries (better scalability) and the direct connection for migrations and admin tasks (full protocol support).&lt;/p&gt;

</description>
      <category>supabase</category>
      <category>postgres</category>
      <category>tutorial</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Why Schema Diagrams Go Stale (and the Fix)</title>
      <dc:creator>Varun Krishnan</dc:creator>
      <pubDate>Tue, 22 Sep 2026 02:30:00 +0000</pubDate>
      <link>https://dev.to/not_varunkv/why-schema-diagrams-go-stale-and-the-fix-3ib3</link>
      <guid>https://dev.to/not_varunkv/why-schema-diagrams-go-stale-and-the-fix-3ib3</guid>
      <description>&lt;h2&gt;
  
  
  The short version
&lt;/h2&gt;

&lt;p&gt;Schema diagrams go stale because they're static snapshots of a moving target. A migration adds a column, someone renames a table, and suddenly your beautiful diagram is wrong. The fix: generate diagrams from the live database, not from a file. Run it on every deploy or PR.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why it happens
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Reason 1: Migrations don't update diagrams
&lt;/h3&gt;

&lt;p&gt;You run &lt;code&gt;ALTER TABLE orders ADD COLUMN shipping_cost_cents integer;&lt;/code&gt; and the migration succeeds. But your ER diagram still shows the old schema. Nobody thinks to update the diagram because the migration "just works."&lt;/p&gt;

&lt;h3&gt;
  
  
  Reason 2: Diagrams live in the wrong place
&lt;/h3&gt;

&lt;p&gt;If your diagram is in a Confluence page, a Figma file, or a static image in your repo, it's disconnected from the code. The code changes, the diagram doesn't.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reason 3: Nobody owns it
&lt;/h3&gt;

&lt;p&gt;Diagram maintenance falls between "frontend" and "backend" and "DevOps." Everyone assumes someone else will update it. Nobody does.&lt;/p&gt;

&lt;h2&gt;
  
  
  The three fixes
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Fix 1: Generate from the live database
&lt;/h3&gt;

&lt;p&gt;Instead of maintaining a static diagram, generate it from the actual database state:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Using dbdiagramr CLI (or paste your connection string at dbdiagramr.space)&lt;/span&gt;
&lt;span class="c"&gt;# Your schema is always current -- no manual updates needed.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the nuclear option. Your diagram is always 100% accurate because it's generated from the source of truth.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fix 2: Generate in CI/CD
&lt;/h3&gt;

&lt;p&gt;Add a schema diagram step to your CI pipeline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# GitHub Actions example&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Generate schema diagram&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;pg_dump --schema-only $DATABASE_URL &amp;gt; schema.sql&lt;/span&gt;
    &lt;span class="s"&gt;# Generate diagram from schema.sql&lt;/span&gt;
    &lt;span class="s"&gt;# Upload as artifact or commit to repo&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run it on every push to &lt;code&gt;main&lt;/code&gt;. The diagram is always one commit behind, but it's close enough.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fix 3: One-page SCHEMA.md
&lt;/h3&gt;

&lt;p&gt;Maintain a single markdown file with the schema overview. Update it in the same PR that adds or changes a column:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# SCHEMA.md&lt;/span&gt;

&lt;span class="gu"&gt;## orders&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="sb"&gt;`id`&lt;/span&gt; (uuid, PK)
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="sb"&gt;`user_id`&lt;/span&gt; (uuid, FK → users)
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="sb"&gt;`status`&lt;/span&gt; (text) -- pending | confirmed | shipped | delivered
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="sb"&gt;`total_amount_cents`&lt;/span&gt; (integer) -- added 2026-09-15
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="sb"&gt;`shipping_cost_cents`&lt;/span&gt; (integer) -- added 2026-09-20
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Put the update in your PR checklist:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;## PR checklist&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; [ ] Tests pass
&lt;span class="p"&gt;-&lt;/span&gt; [ ] SCHEMA.md updated (if schema changed)
&lt;span class="p"&gt;-&lt;/span&gt; [ ] Migration is reversible
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What doesn't work
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Manual updates to Figma/Confluence
&lt;/h3&gt;

&lt;p&gt;Nobody does it. The diagram is always 3 months out of date.&lt;/p&gt;

&lt;h3&gt;
  
  
  Screenshots of pgAdmin
&lt;/h3&gt;

&lt;p&gt;The moment you take the screenshot, it's stale. And you can't diff a screenshot.&lt;/p&gt;

&lt;h3&gt;
  
  
  Auto-generated docs from migration files
&lt;/h3&gt;

&lt;p&gt;Migration files show the history of changes, not the current state. You'd need to replay all migrations to get the current schema, which is slow and error-prone.&lt;/p&gt;

&lt;h2&gt;
  
  
  The self-healing diagram
&lt;/h2&gt;

&lt;p&gt;The ideal setup:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Source of truth&lt;/strong&gt;: the live database.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Diagram generation&lt;/strong&gt;: runs on every deploy (or on-demand).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Storage&lt;/strong&gt;: committed to the repo as an SVG or markdown.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enforcement&lt;/strong&gt;: CI fails if the diagram doesn't match the schema.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is overkill for most projects. But if you've ever debugging a "why does the diagram show a column that doesn't exist?" issue, it's worth it.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  How often should I update my schema diagram?
&lt;/h3&gt;

&lt;p&gt;Every time you add, remove, or rename a column. If you're using a tool like dbdiagramr that generates from the live database, it's always current.&lt;/p&gt;

&lt;h3&gt;
  
  
  Should I commit the diagram to git?
&lt;/h3&gt;

&lt;p&gt;Yes. Commit it as an SVG or markdown file. That way it's versioned with your code and you can see when it changed.&lt;/p&gt;

&lt;h3&gt;
  
  
  What's the minimum viable schema documentation?
&lt;/h3&gt;

&lt;p&gt;A one-page &lt;code&gt;SCHEMA.md&lt;/code&gt; file in your repo with table names, key columns, and relationships. Update it in the same PR that changes the schema.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I auto-generate schema docs from Prisma/Drizzle schema files?
&lt;/h3&gt;

&lt;p&gt;Partially. Prisma's &lt;code&gt;prisma-docs-generator&lt;/code&gt; and Drizzle's &lt;code&gt;drizzle-kit generate&lt;/code&gt; can create basic docs. But they miss business rules, enum values, and deletion policies. Use them as a starting point, not the final doc.&lt;/p&gt;

</description>
      <category>database</category>
      <category>documentation</category>
      <category>tutorial</category>
      <category>postgres</category>
    </item>
    <item>
      <title>How to Document Your Database Schema for a Team</title>
      <dc:creator>Varun Krishnan</dc:creator>
      <pubDate>Thu, 17 Sep 2026 14:30:00 +0000</pubDate>
      <link>https://dev.to/not_varunkv/how-to-document-your-database-schema-for-a-team-b8n</link>
      <guid>https://dev.to/not_varunkv/how-to-document-your-database-schema-for-a-team-b8n</guid>
      <description>&lt;h2&gt;
  
  
  The short version
&lt;/h2&gt;

&lt;p&gt;Document your schema in three layers: &lt;br&gt;
(1) name things well so they're self-documenting, &lt;br&gt;
(2) add inline column comments for the non-obvious stuff, &lt;br&gt;
(3) generate a one-page visual diagram that shows the relationships. Skip the 40-page Confluence page nobody reads it.&lt;/p&gt;
&lt;h2&gt;
  
  
  Layer 1: Name things well
&lt;/h2&gt;

&lt;p&gt;The fastest documentation is a good name. If your column names are clear, you barely need comments.&lt;/p&gt;
&lt;h3&gt;
  
  
  Tables
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Use &lt;strong&gt;plural nouns&lt;/strong&gt;: &lt;code&gt;users&lt;/code&gt;, &lt;code&gt;orders&lt;/code&gt;, &lt;code&gt;order_items&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Use &lt;strong&gt;snake_case&lt;/strong&gt;: &lt;code&gt;order_items&lt;/code&gt;, not &lt;code&gt;OrderItems&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Be specific: &lt;code&gt;payment_methods&lt;/code&gt;, not &lt;code&gt;methods&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Columns
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;created_at&lt;/code&gt; and &lt;code&gt;updated_at&lt;/code&gt; for timestamps.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;{table}_id&lt;/code&gt; for foreign keys: &lt;code&gt;user_id&lt;/code&gt;, not &lt;code&gt;userId&lt;/code&gt; or &lt;code&gt;user&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Prefix booleans with &lt;code&gt;is_&lt;/code&gt; or &lt;code&gt;has_&lt;/code&gt;: &lt;code&gt;is_active&lt;/code&gt;, &lt;code&gt;has_paid&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Avoid abbreviations: &lt;code&gt;customer_id&lt;/code&gt;, not &lt;code&gt;cust_id&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Foreign keys
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Name them after the parent table: &lt;code&gt;orders.user_id&lt;/code&gt; → &lt;code&gt;users.id&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Don't use generic names like &lt;code&gt;ref_id&lt;/code&gt; or &lt;code&gt;parent_id&lt;/code&gt; (unless it's a tree structure).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Layer 2: Inline comments
&lt;/h2&gt;

&lt;p&gt;PostgreSQL supports column-level comments. Use them for anything that isn't obvious from the name:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;COMMENT&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;COLUMN&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt;
  &lt;span class="s1"&gt;'pending | confirmed | shipped | delivered | cancelled. Never delete orders set status to cancelled instead.'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;COMMENT&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;COLUMN&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;phone_number&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt;
  &lt;span class="s1"&gt;'E.164 format (+1234567890). Required for SMS auth. May be null if user signed up via OAuth.'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;COMMENT&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;COLUMN&lt;/span&gt; &lt;span class="n"&gt;products&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sku&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt;
  &lt;span class="s1"&gt;'Stock Keeping Unit. Format: CATEGORY-NNNN (e.g., ELEC-0042). Must be unique across all products.'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  What to comment
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Enum values&lt;/strong&gt;: What are the possible values for &lt;code&gt;status&lt;/code&gt;?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Format requirements&lt;/strong&gt;: What format does &lt;code&gt;phone_number&lt;/code&gt; expect?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Business rules&lt;/strong&gt;: Why is &lt;code&gt;email&lt;/code&gt; nullable? (OAuth users may not share it.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deletion policy&lt;/strong&gt;: Do you delete rows or soft-delete?&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What NOT to comment
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;id&lt;/code&gt; - everyone knows what &lt;code&gt;id&lt;/code&gt; is.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;created_at&lt;/code&gt; / &lt;code&gt;updated_at&lt;/code&gt; - self-explanatory.&lt;/li&gt;
&lt;li&gt;Foreign keys named after their parent table - &lt;code&gt;user_id&lt;/code&gt; is obvious.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Layer 3: Visual diagram
&lt;/h2&gt;

&lt;p&gt;A one-page ER diagram replaces 40 pages of documentation. Generate it automatically:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.dbdiagramr.space" rel="noopener noreferrer"&gt;dbdiagramr&lt;/a&gt;&lt;/strong&gt; - paste your connection string, get a visual schema in seconds. No signup required.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;pgAdmin&lt;/strong&gt; - built-in ERD tool for PostgreSQL.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DBeaver&lt;/strong&gt; - database tool with auto-generated ER diagrams.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  What to show
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Table names and primary keys.&lt;/li&gt;
&lt;li&gt;Foreign key relationships (the lines between tables).&lt;/li&gt;
&lt;li&gt;Column types for non-obvious fields (JSONB, arrays, enums).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What to skip
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Every single column - just show the important ones.&lt;/li&gt;
&lt;li&gt;Indexes - they're implementation details.&lt;/li&gt;
&lt;li&gt;Default values - put those in comments instead.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The one-page cheat sheet
&lt;/h2&gt;

&lt;p&gt;Create a single markdown file in your repo called &lt;code&gt;SCHEMA.md&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# Database Schema&lt;/span&gt;

&lt;span class="gu"&gt;## Overview&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; 12 tables, 3 schemas (public, auth, storage)
&lt;span class="p"&gt;-&lt;/span&gt; Last updated: 2026-09-01

&lt;span class="gu"&gt;## Tables&lt;/span&gt;

&lt;span class="gu"&gt;### users&lt;/span&gt;
Core user table. Created by Supabase Auth.
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="sb"&gt;`id`&lt;/span&gt; (uuid, PK) - Auth user ID
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="sb"&gt;`email`&lt;/span&gt; (text) - May be null for phone auth
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="sb"&gt;`raw_user_meta_data`&lt;/span&gt; (jsonb) - Profile data from OAuth

&lt;span class="gu"&gt;### orders&lt;/span&gt;
Customer orders. Never delete - use status instead.
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="sb"&gt;`id`&lt;/span&gt; (uuid, PK)
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="sb"&gt;`user_id`&lt;/span&gt; (uuid, FK → users) - Customer
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="sb"&gt;`status`&lt;/span&gt; (text) - pending | confirmed | shipped | delivered | cancelled
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="sb"&gt;`total_amount_cents`&lt;/span&gt; (integer) - Price in cents to avoid float rounding

...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  ER Diagram
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fl10t584ocv71vqc8qhjf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fl10t584ocv71vqc8qhjf.png" alt="Schema from dbdiagram" width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Naming conventions by team size
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Team size&lt;/th&gt;
&lt;th&gt;What works&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1 person&lt;/td&gt;
&lt;td&gt;Whatever you remember. Add comments for future-you.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2-5 people&lt;/td&gt;
&lt;td&gt;Naming conventions + &lt;code&gt;SCHEMA.md&lt;/code&gt; cheat sheet.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5-15 people&lt;/td&gt;
&lt;td&gt;Naming conventions + comments + auto-generated docs (dbdiagramr, pgAdmin).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;15+ people&lt;/td&gt;
&lt;td&gt;All of the above + dedicated data docs (DataHub, Atlan, or dbt docs).&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Should I document every table?
&lt;/h3&gt;

&lt;p&gt;No. Document the tables that matter: the ones new engineers will touch, the ones with complex business logic, and the ones with non-obvious schemas. Skip internal Django/Laravel/NextAuth tables framework docs cover those.&lt;/p&gt;

&lt;h3&gt;
  
  
  How often should I update the docs?
&lt;/h3&gt;

&lt;p&gt;When you add or change a column. The best way to enforce this: put the &lt;code&gt;SCHEMA.md&lt;/code&gt; update in your PR template as a checklist item.&lt;/p&gt;

&lt;h3&gt;
  
  
  What's the best tool for auto-generating schema docs?
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.dbdiagramr.space" rel="noopener noreferrer"&gt;dbdiagramr&lt;/a&gt; for visual diagrams. For text-based docs, use dbt's &lt;code&gt;docs generate&lt;/code&gt; if you're already on dbt. For everything else, a markdown file in your repo beats any SaaS tool.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I generate schema docs from migration files?
&lt;/h3&gt;

&lt;p&gt;Partially. Migration files show the changes, not the current state. You'd need to run &lt;code&gt;pg_dump --schema-only&lt;/code&gt; or query &lt;code&gt;information_schema&lt;/code&gt; to get the actual current schema, then generate docs from that.&lt;/p&gt;

</description>
      <category>database</category>
      <category>documentation</category>
      <category>tutorial</category>
      <category>postgres</category>
    </item>
    <item>
      <title>information_schema vs pg_catalog: Which Should You Query?</title>
      <dc:creator>Varun Krishnan</dc:creator>
      <pubDate>Tue, 15 Sep 2026 14:30:00 +0000</pubDate>
      <link>https://dev.to/not_varunkv/informationschema-vs-pgcatalog-which-should-you-query-4e07</link>
      <guid>https://dev.to/not_varunkv/informationschema-vs-pgcatalog-which-should-you-query-4e07</guid>
      <description>&lt;h2&gt;
  
  
  The short version
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;information_schema&lt;/code&gt; is the SQL-standard way to query metadata portable across databases, slower, limited to what the standard defines. &lt;code&gt;pg_catalog&lt;/code&gt; is PostgreSQL-specific faster, more detailed, has everything. Use &lt;code&gt;information_schema&lt;/code&gt; for simple queries you might run on MySQL too. Use &lt;code&gt;pg_catalog&lt;/code&gt; when you need Postgres-specific details or performance.&lt;/p&gt;

&lt;h2&gt;
  
  
  What each one is
&lt;/h2&gt;

&lt;h3&gt;
  
  
  information_schema
&lt;/h3&gt;

&lt;p&gt;A set of views defined by the SQL standard. Every relational database (Postgres, MySQL, SQL Server) has the same views with the same column names.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;table_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;column_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data_type&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;information_schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;columns&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;table_schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'public'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;: Portable. Readable. Standard.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;: Slower. Missing Postgres-specific types (arrays, JSONB, ranges). Can't see internal system tables.&lt;/p&gt;

&lt;h3&gt;
  
  
  pg_catalog
&lt;/h3&gt;

&lt;p&gt;PostgreSQL's internal catalog. It's a schema that's automatically search path and contains tables, views, and functions that describe every object in the database.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relname&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="k"&gt;table_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;attname&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="k"&gt;column_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;pg_catalog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format_type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;atttypid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;atttypmod&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;data_type&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;pg_catalog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pg_class&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;pg_catalog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pg_attribute&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;attrelid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;oid&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;pg_catalog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pg_namespace&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;oid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relnamespace&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nspname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'public'&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;attnum&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;attisdropped&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;attnum&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;: Faster. More detailed. Has Postgres-specific types. Can see system tables.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;: Not portable. Harder to read. Syntax is verbose.&lt;/p&gt;

&lt;h2&gt;
  
  
  Head-to-head comparison
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Query&lt;/th&gt;
&lt;th&gt;information_schema&lt;/th&gt;
&lt;th&gt;pg_catalog&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;List tables&lt;/td&gt;
&lt;td&gt;&lt;code&gt;SELECT table_name FROM information_schema.tables&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;SELECT relname FROM pg_class WHERE relkind = 'r'&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;List columns&lt;/td&gt;
&lt;td&gt;&lt;code&gt;SELECT column_name, data_type FROM information_schema.columns&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;SELECT attname, format_type(...) FROM pg_attribute&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;List indexes&lt;/td&gt;
&lt;td&gt;&lt;code&gt;SELECT indexname FROM information_schema.statistics&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;SELECT indexrelname FROM pg_stat_user_indexes&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;List constraints&lt;/td&gt;
&lt;td&gt;&lt;code&gt;SELECT constraint_name FROM information_schema.table_constraints&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;SELECT conname FROM pg_constraint&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;List foreign keys&lt;/td&gt;
&lt;td&gt;&lt;code&gt;SELECT ... FROM information_schema.key_column_usage&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;SELECT ... FROM pg_constraint WHERE contype = 'f'&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Table size&lt;/td&gt;
&lt;td&gt;Not available&lt;/td&gt;
&lt;td&gt;&lt;code&gt;SELECT pg_total_relation_size(oid)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Table owner&lt;/td&gt;
&lt;td&gt;Not available&lt;/td&gt;
&lt;td&gt;&lt;code&gt;SELECT pg_catalog.get_owner(c.oid)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;View definitions&lt;/td&gt;
&lt;td&gt;Not available&lt;/td&gt;
&lt;td&gt;&lt;code&gt;SELECT definition FROM pg_views&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  When to use which
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Use information_schema when:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;You need &lt;strong&gt;portable SQL&lt;/strong&gt; (might run on MySQL/SQLite too).&lt;/li&gt;
&lt;li&gt;You're writing a &lt;strong&gt;simple query&lt;/strong&gt; and don't care about performance.&lt;/li&gt;
&lt;li&gt;You want &lt;strong&gt;readable, standard syntax&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;You're building a tool that works across databases.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Simple: list all tables in the public schema&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;table_name&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;information_schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tables&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;table_schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'public'&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;table_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'BASE TABLE'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Use pg_catalog when:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;You need &lt;strong&gt;Postgres-specific info&lt;/strong&gt; (table owners, indexes, sizes, permissions).&lt;/li&gt;
&lt;li&gt;You're &lt;strong&gt;querying large databases&lt;/strong&gt; pg_catalog is faster.&lt;/li&gt;
&lt;li&gt;You need &lt;strong&gt;internal system tables&lt;/strong&gt; (pg_stat_activity, pg_locks, etc.).&lt;/li&gt;
&lt;li&gt;You're building a &lt;strong&gt;Postgres-only tool&lt;/strong&gt;.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- See active queries with full details&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;usename&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;application_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;state&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;pg_catalog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pg_stat_activity&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="k"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'active'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Common queries
&lt;/h2&gt;

&lt;h3&gt;
  
  
  List all columns with types (information_schema)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;table_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;column_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;is_nullable&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;information_schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;columns&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;table_schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'public'&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="k"&gt;table_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ordinal_position&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  List all columns with types (pg_catalog)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relname&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="k"&gt;table_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;attname&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="k"&gt;column_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;pg_catalog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format_type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;atttypid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;atttypmod&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;data_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;attnotnull&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="k"&gt;nullable&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;pg_catalog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pg_class&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;pg_catalog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pg_attribute&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;attrelid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;oid&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;pg_catalog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pg_namespace&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;oid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relnamespace&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nspname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'public'&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relkind&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'r'&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;attnum&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;attisdropped&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;attnum&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  List foreign keys
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- information_schema&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt;
  &lt;span class="n"&gt;kcu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;table_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;kcu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;column_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;ccu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;table_name&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;foreign_table&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;ccu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;column_name&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;foreign_column&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;information_schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;key_column_usage&lt;/span&gt; &lt;span class="n"&gt;kcu&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;information_schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;constraint_column_usage&lt;/span&gt; &lt;span class="n"&gt;ccu&lt;/span&gt;
  &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;kcu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;constraint_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ccu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;constraint_name&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;kcu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;constraint_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'FOREIGN KEY'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Show table sizes
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- pg_catalog only&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt;
  &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relname&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="k"&gt;table_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;pg_catalog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pg_size_pretty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pg_catalog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pg_total_relation_size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;oid&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;total_size&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;pg_catalog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pg_class&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;pg_catalog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pg_namespace&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;oid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relnamespace&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relkind&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'r'&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nspname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'public'&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;pg_catalog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pg_total_relation_size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;oid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  See who's connected right now
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- pg_catalog only&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;usename&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;application_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;client_addr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;pg_catalog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pg_stat_activity&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;backend_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'client backend'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Performance note
&lt;/h2&gt;

&lt;p&gt;For simple metadata queries on small databases, the performance difference doesn't matter. But on large databases (thousands of tables, millions of rows), &lt;code&gt;pg_catalog&lt;/code&gt; can be 2-10x faster because it's indexed internally.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Can I query pg_catalog from MySQL?
&lt;/h3&gt;

&lt;p&gt;No. pg_catalog is PostgreSQL-specific. If you need cross-database portability, stick with &lt;code&gt;information_schema&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Which one does pg_dump use?
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;pg_catalog&lt;/code&gt;. It needs Postgres-specific details like table OIDs, ACLs, and storage parameters that &lt;code&gt;information_schema&lt;/code&gt; doesn't expose.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do tools like Prisma or Drizzle use one over the other?
&lt;/h3&gt;

&lt;p&gt;They use both. Prisma uses &lt;code&gt;information_schema&lt;/code&gt; for schema introspection. Drizzle uses &lt;code&gt;pg_catalog&lt;/code&gt; for more detailed Postgres metadata. Both fall back to the other when one doesn't have the info they need.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I see system tables (pg_stat_activity, pg_locks) from information_schema?
&lt;/h3&gt;

&lt;p&gt;No. System tables are only in &lt;code&gt;pg_catalog&lt;/code&gt;. Use &lt;code&gt;pg_catalog.pg_stat_activity&lt;/code&gt; to see active queries, &lt;code&gt;pg_catalog.pg_locks&lt;/code&gt; to see locks, etc.&lt;/p&gt;

</description>
      <category>postgres</category>
      <category>database</category>
      <category>tutorial</category>
      <category>advanced</category>
    </item>
    <item>
      <title>Django Auth Tables and Permissions Explained</title>
      <dc:creator>Varun Krishnan</dc:creator>
      <pubDate>Thu, 10 Sep 2026 14:34:00 +0000</pubDate>
      <link>https://dev.to/not_varunkv/django-auth-tables-and-permissions-explained-44hd</link>
      <guid>https://dev.to/not_varunkv/django-auth-tables-and-permissions-explained-44hd</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0h5fwft8rsrpxchgjafe.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0h5fwft8rsrpxchgjafe.png" alt="DJANGO AUTH Schema Diagram built on DBDiagramr" width="800" height="558"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Django creates 5 core auth tables: &lt;code&gt;auth_user&lt;/code&gt;, &lt;code&gt;auth_group&lt;/code&gt;, &lt;code&gt;auth_permission&lt;/code&gt;, &lt;code&gt;django_content_type&lt;/code&gt;, and two join tables (&lt;code&gt;auth_user_groups&lt;/code&gt;, &lt;code&gt;auth_group_permissions&lt;/code&gt;). The permission system is built on content types each model gets a default &lt;code&gt;add&lt;/code&gt;, &lt;code&gt;change&lt;/code&gt;, &lt;code&gt;delete&lt;/code&gt;, and &lt;code&gt;view&lt;/code&gt; permission, and you can create custom ones.&lt;/p&gt;

&lt;h2&gt;
  
  
  The core tables
&lt;/h2&gt;

&lt;h3&gt;
  
  
  auth_user
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Column&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;What it means&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;int (PK)&lt;/td&gt;
&lt;td&gt;Auto-incrementing primary key.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;password&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;varchar(128)&lt;/td&gt;
&lt;td&gt;Hashed password (PBKDF2 by default).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;last_login&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;datetime&lt;/td&gt;
&lt;td&gt;When the user last logged in. Null if never.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;is_superuser&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;bool&lt;/td&gt;
&lt;td&gt;Bypasses all permission checks.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;username&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;varchar(150)&lt;/td&gt;
&lt;td&gt;Unique username.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;first_name&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;varchar(150)&lt;/td&gt;
&lt;td&gt;User's first name.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;last_name&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;varchar(150)&lt;/td&gt;
&lt;td&gt;User's last name.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;email&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;varchar(254)&lt;/td&gt;
&lt;td&gt;Email address. Not necessarily unique.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;is_staff&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;bool&lt;/td&gt;
&lt;td&gt;Can access the Django admin.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;is_active&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;bool&lt;/td&gt;
&lt;td&gt;Set to False instead of deleting users.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;date_joined&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;datetime&lt;/td&gt;
&lt;td&gt;Registration time.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  auth_group
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Column&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;What it means&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;int (PK)&lt;/td&gt;
&lt;td&gt;Auto-incrementing ID.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;name&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;varchar(150)&lt;/td&gt;
&lt;td&gt;Unique group name (e.g., "Editors", "Moderators").&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Groups are role containers. Assign permissions to groups, then add users to groups.&lt;/p&gt;

&lt;h3&gt;
  
  
  auth_permission
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Column&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;What it means&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;int (PK)&lt;/td&gt;
&lt;td&gt;Auto-incrementing ID.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;name&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;varchar(255)&lt;/td&gt;
&lt;td&gt;Human-readable name (e.g., "Can add post").&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;content_type_id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;int (FK)&lt;/td&gt;
&lt;td&gt;Which model this permission applies to.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;codename&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;varchar(100)&lt;/td&gt;
&lt;td&gt;Code identifier (e.g., &lt;code&gt;add_post&lt;/code&gt;).&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Each model gets 4 default permissions: &lt;code&gt;add_modelname&lt;/code&gt;, &lt;code&gt;change_modelname&lt;/code&gt;, &lt;code&gt;delete_modelname&lt;/code&gt;, &lt;code&gt;view_modelname&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  django_content_type
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Column&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;What it means&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;int (PK)&lt;/td&gt;
&lt;td&gt;Auto-incrementing ID.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;app_label&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;varchar(100)&lt;/td&gt;
&lt;td&gt;The Django app (e.g., &lt;code&gt;blog&lt;/code&gt;).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;model&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;varchar(100)&lt;/td&gt;
&lt;td&gt;The model name (e.g., &lt;code&gt;post&lt;/code&gt;).&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This table maps every model in your project to an ID. The permission system uses it to know which model a permission applies to.&lt;/p&gt;

&lt;h3&gt;
  
  
  auth_user_groups
&lt;/h3&gt;

&lt;p&gt;Join table for many-to-many: users ↔ groups.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Column&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;What it means&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;int (PK)&lt;/td&gt;
&lt;td&gt;Auto-incrementing ID.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;user_id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;int (FK)&lt;/td&gt;
&lt;td&gt;→ &lt;code&gt;auth_user.id&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;group_id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;int (FK)&lt;/td&gt;
&lt;td&gt;→ &lt;code&gt;auth_group.id&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  auth_group_permissions
&lt;/h3&gt;

&lt;p&gt;Join table for many-to-many: groups ↔ permissions.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Column&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;What it means&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;int (PK)&lt;/td&gt;
&lt;td&gt;Auto-incrementing ID.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;group_id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;int (FK)&lt;/td&gt;
&lt;td&gt;→ &lt;code&gt;auth_group.id&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;permission_id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;int (FK)&lt;/td&gt;
&lt;td&gt;→ &lt;code&gt;auth_permission.id&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  How permissions work
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User ──M──M── Group ──M──M── Permission ──M──1── ContentType
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;A &lt;strong&gt;permission&lt;/strong&gt; is tied to a &lt;strong&gt;content type&lt;/strong&gt; (a specific model).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Groups&lt;/strong&gt; collect permissions (e.g., "Editors" get &lt;code&gt;add_post&lt;/code&gt;, &lt;code&gt;change_post&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Users&lt;/strong&gt; are added to groups to inherit their permissions.&lt;/li&gt;
&lt;li&gt;You can also assign permissions directly to users (bypass groups).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Check permission
&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has_perm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;blog.add_post&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Add user to group
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.contrib.auth.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Group&lt;/span&gt;
&lt;span class="n"&gt;editors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Editors&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;groups&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;editors&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Create custom permission
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.contrib.auth.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Permission&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.contrib.contenttypes.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ContentType&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;blog.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Post&lt;/span&gt;
&lt;span class="n"&gt;content_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ContentType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_for_model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;custom_perm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Permission&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;codename&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;publish_post&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Can publish post&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;content_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;content_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What to change
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Add a &lt;code&gt;role&lt;/code&gt; field&lt;/strong&gt; to &lt;code&gt;auth_user&lt;/code&gt; if you need simple role-based access (or use groups).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add an &lt;code&gt;avatar&lt;/code&gt; field&lt;/strong&gt; for profile pictures.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Create custom permissions&lt;/strong&gt; for fine-grained access control (e.g., &lt;code&gt;publish_post&lt;/code&gt;, &lt;code&gt;archive_post&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add &lt;code&gt;unique=True&lt;/code&gt;&lt;/strong&gt; to &lt;code&gt;email&lt;/code&gt; if you want unique emails.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What to leave alone
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Don't modify &lt;code&gt;django_content_type&lt;/code&gt; it's managed by Django's migration framework.&lt;/li&gt;
&lt;li&gt;Don't change &lt;code&gt;auth_permission.codename&lt;/code&gt; it's referenced by &lt;code&gt;has_perm()&lt;/code&gt; and decorators.&lt;/li&gt;
&lt;li&gt;Don't delete &lt;code&gt;auth_user.is_active&lt;/code&gt; use it instead of deleting users (preserves foreign keys).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Does Django create all these tables automatically?
&lt;/h3&gt;

&lt;p&gt;Yes. Running &lt;code&gt;python manage.py migrate&lt;/code&gt; creates all auth tables. They're part of Django's built-in &lt;code&gt;django.contrib.auth&lt;/code&gt; app.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I use Django without the permission system?
&lt;/h3&gt;

&lt;p&gt;Yes. Remove &lt;code&gt;django.contrib.auth&lt;/code&gt; from &lt;code&gt;INSTALLED_APPS&lt;/code&gt; and you lose the permission tables but keep the &lt;code&gt;auth_user&lt;/code&gt; model (or replace it entirely).&lt;/p&gt;

&lt;h3&gt;
  
  
  What's the difference between &lt;code&gt;is_superuser&lt;/code&gt; and &lt;code&gt;is_staff&lt;/code&gt;?
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;is_superuser&lt;/code&gt; bypasses all permission checks. &lt;code&gt;is_staff&lt;/code&gt; only controls access to the Django admin. A superuser automatically has &lt;code&gt;is_staff=True&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I see my Django auth tables?
&lt;/h3&gt;

&lt;p&gt;Use &lt;a href="https://www.dbdiagramr.space" rel="noopener noreferrer"&gt;dbdiagramr&lt;/a&gt; paste your connection string and get a visual schema of your Django auth tables.&lt;/p&gt;

</description>
      <category>django</category>
      <category>database</category>
      <category>tutorial</category>
      <category>python</category>
    </item>
    <item>
      <title>NextAuth / Auth.js Database Schema Explained</title>
      <dc:creator>Varun Krishnan</dc:creator>
      <pubDate>Mon, 07 Sep 2026 18:30:00 +0000</pubDate>
      <link>https://dev.to/not_varunkv/nextauth-authjs-database-schema-explained-13d2</link>
      <guid>https://dev.to/not_varunkv/nextauth-authjs-database-schema-explained-13d2</guid>
      <description>&lt;h2&gt;
  
  
  The short version
&lt;/h2&gt;

&lt;p&gt;NextAuth (now Auth.js) creates 4 tables in your database: &lt;code&gt;users&lt;/code&gt;, &lt;code&gt;accounts&lt;/code&gt;, &lt;code&gt;sessions&lt;/code&gt;, and &lt;code&gt;verification_tokens&lt;/code&gt;. The &lt;code&gt;users&lt;/code&gt; and &lt;code&gt;accounts&lt;/code&gt; tables have a one-to-one relationship via &lt;code&gt;accounts.user_id&lt;/code&gt;. Sessions link to users via &lt;code&gt;sessions.user_id&lt;/code&gt;. Verification tokens are short-lived and self-cleaning.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 4 tables
&lt;/h2&gt;

&lt;h3&gt;
  
  
  users
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Column&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;What it means&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;text / UUID&lt;/td&gt;
&lt;td&gt;Primary key. Generated by NextAuth.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;name&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;text&lt;/td&gt;
&lt;td&gt;Display name from the OAuth provider (Google, GitHub, etc.)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;email&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;text&lt;/td&gt;
&lt;td&gt;User's email. May be null if the provider doesn't share it.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;email_verified&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;timestamp&lt;/td&gt;
&lt;td&gt;When the email was verified. Null if never verified.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;image&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;text&lt;/td&gt;
&lt;td&gt;Profile picture URL from the provider.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;created_at&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;timestamp&lt;/td&gt;
&lt;td&gt;When the user first signed in.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;updated_at&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;timestamp&lt;/td&gt;
&lt;td&gt;Last profile sync from the provider.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  accounts
&lt;/h3&gt;

&lt;p&gt;This table links a user to an OAuth provider. One user can have multiple accounts (e.g., Google + GitHub).&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Column&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;What it means&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;text / UUID&lt;/td&gt;
&lt;td&gt;Primary key.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;user_id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;text&lt;/td&gt;
&lt;td&gt;Foreign key → &lt;code&gt;users.id&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;type&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;text&lt;/td&gt;
&lt;td&gt;Always &lt;code&gt;"oauth"&lt;/code&gt; or &lt;code&gt;"oidc"&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;provider&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;text&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;"google"&lt;/code&gt;, &lt;code&gt;"github"&lt;/code&gt;, &lt;code&gt;"discord"&lt;/code&gt;, etc.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;provider_account_id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;text&lt;/td&gt;
&lt;td&gt;The provider's unique ID for this user.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;refresh_token&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;text&lt;/td&gt;
&lt;td&gt;OAuth refresh token (encrypted in production).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;access_token&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;text&lt;/td&gt;
&lt;td&gt;OAuth access token (encrypted in production).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;expires_at&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;integer&lt;/td&gt;
&lt;td&gt;When the access token expires (Unix timestamp).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;token_type&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;text&lt;/td&gt;
&lt;td&gt;Usually &lt;code&gt;"Bearer"&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;scope&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;text&lt;/td&gt;
&lt;td&gt;Permissions granted by the provider.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;id_token&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;text&lt;/td&gt;
&lt;td&gt;OIDC ID token (if using OIDC).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;session_state&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;text&lt;/td&gt;
&lt;td&gt;Provider-specific session state.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  sessions
&lt;/h3&gt;

&lt;p&gt;Active sessions for each user. NextAuth creates a new row here on every sign-in.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Column&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;What it means&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;text / UUID&lt;/td&gt;
&lt;td&gt;Primary key.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;session_token&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;text&lt;/td&gt;
&lt;td&gt;The session token stored in the user's cookie.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;user_id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;text&lt;/td&gt;
&lt;td&gt;Foreign key → &lt;code&gt;users.id&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;expires&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;timestamp&lt;/td&gt;
&lt;td&gt;When this session expires.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  verification_tokens
&lt;/h3&gt;

&lt;p&gt;Short-lived tokens for email verification, password reset, etc. Self-cleaning old tokens are deleted automatically.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Column&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;What it means&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;identifier&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;text&lt;/td&gt;
&lt;td&gt;Email or user ID the token is for.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;token&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;text&lt;/td&gt;
&lt;td&gt;The actual token value.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;expires&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;timestamp&lt;/td&gt;
&lt;td&gt;When this token expires.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  How they connect
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;users ──1──1── accounts
   │
   1
   │
   ∞
sessions

users ──1──∞── verification_tokens (via identifier)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;One user → one or more accounts (Google, GitHub, etc.)&lt;/li&gt;
&lt;li&gt;One user → many sessions (different devices/browsers)&lt;/li&gt;
&lt;li&gt;Verification tokens are temporary and don't have a foreign key&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What to change
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Add a &lt;code&gt;role&lt;/code&gt; column&lt;/strong&gt; to &lt;code&gt;users&lt;/code&gt; if you need role-based access control.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add a &lt;code&gt;phone_number&lt;/code&gt; column&lt;/strong&gt; to &lt;code&gt;users&lt;/code&gt; if you're using SMS auth.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Encrypt &lt;code&gt;access_token&lt;/code&gt; and &lt;code&gt;refresh_token&lt;/code&gt;&lt;/strong&gt; in production NextAuth doesn't do this by default.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What to leave alone
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Don't modify the &lt;code&gt;verification_tokens&lt;/code&gt; table it's managed automatically.&lt;/li&gt;
&lt;li&gt;Don't change the &lt;code&gt;session_token&lt;/code&gt; format it's a signed JWT.&lt;/li&gt;
&lt;li&gt;Don't add indexes to &lt;code&gt;provider_account_id&lt;/code&gt; unless you're querying it directly (it's already unique).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Does NextAuth store passwords?
&lt;/h3&gt;

&lt;p&gt;No. NextAuth is an OAuth-first library. It doesn't handle passwords. If you need email/password auth, use &lt;code&gt;next-auth/providers/credentials&lt;/code&gt; with bcrypt, or use a service like Clerk or Lucia.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I see what's in my NextAuth tables?
&lt;/h3&gt;

&lt;p&gt;Use &lt;a href="https://www.dbdiagramr.space" rel="noopener noreferrer"&gt;dbdiagramr&lt;/a&gt; paste your connection string and get a visual schema of your NextAuth tables in seconds.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I add custom fields to the users table?
&lt;/h3&gt;

&lt;p&gt;Yes. Add columns to the &lt;code&gt;users&lt;/code&gt; table directly. NextAuth will ignore columns it doesn't know about, so you can safely add &lt;code&gt;role&lt;/code&gt;, &lt;code&gt;phone_number&lt;/code&gt;, &lt;code&gt;preferences&lt;/code&gt;, etc.&lt;/p&gt;

&lt;h3&gt;
  
  
  What happens when a user deletes their account?
&lt;/h3&gt;

&lt;p&gt;NextAuth doesn't cascade deletes by default. You need to manually delete from &lt;code&gt;users&lt;/code&gt;, &lt;code&gt;accounts&lt;/code&gt;, and &lt;code&gt;sessions&lt;/code&gt;. Or add &lt;code&gt;ON DELETE CASCADE&lt;/code&gt; to your foreign key constraints.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is Auth.js the same as NextAuth?
&lt;/h3&gt;

&lt;p&gt;Yes. Auth.js is the rebranded version of NextAuth. The database schema is identical. If you're on NextAuth v4, you're using the same tables.&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>auth</category>
      <category>database</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Laravel Default Database Tables Explained</title>
      <dc:creator>Varun Krishnan</dc:creator>
      <pubDate>Thu, 03 Sep 2026 17:00:00 +0000</pubDate>
      <link>https://dev.to/not_varunkv/laravel-default-database-tables-explained-341n</link>
      <guid>https://dev.to/not_varunkv/laravel-default-database-tables-explained-341n</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fl09jga0j5lfykqnq4btj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fl09jga0j5lfykqnq4btj.png" alt="Laravel Schema Diagram" width="799" height="499"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A fresh Laravel install creates 7-8 tables depending on your packages. The core ones are &lt;code&gt;users&lt;/code&gt;, &lt;code&gt;password_resets&lt;/code&gt;, &lt;code&gt;failed_jobs&lt;/code&gt;, and &lt;code&gt;personal_access_tokens&lt;/code&gt;. The &lt;code&gt;cache&lt;/code&gt;, &lt;code&gt;sessions&lt;/code&gt;, &lt;code&gt;jobs&lt;/code&gt;, and &lt;code&gt;batches&lt;/code&gt; tables are only created if you run the corresponding Artisan commands. None of them are sacred -- you can rename, extend, or replace any of them.&lt;/p&gt;

&lt;h2&gt;
  
  
  The core tables
&lt;/h2&gt;

&lt;h3&gt;
  
  
  users
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Column&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;What it means&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;bigint (PK)&lt;/td&gt;
&lt;td&gt;Auto-incrementing primary key.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;name&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;varchar(255)&lt;/td&gt;
&lt;td&gt;User's display name.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;email&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;varchar(255)&lt;/td&gt;
&lt;td&gt;Unique email address.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;email_verified_at&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;timestamp&lt;/td&gt;
&lt;td&gt;When email was verified. Null if unverified.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;password&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;varchar(255)&lt;/td&gt;
&lt;td&gt;Hashed password (bcrypt). Never store plain text.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;remember_token&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;varchar(100)&lt;/td&gt;
&lt;td&gt;Token for "remember me" functionality.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;created_at&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;timestamp&lt;/td&gt;
&lt;td&gt;Registration time.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;updated_at&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;timestamp&lt;/td&gt;
&lt;td&gt;Last profile update.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  password_resets
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Column&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;What it means&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;email&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;varchar(255)&lt;/td&gt;
&lt;td&gt;The email requesting a reset.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;token&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;varchar(255)&lt;/td&gt;
&lt;td&gt;The reset token (hashed).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;created_at&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;timestamp&lt;/td&gt;
&lt;td&gt;When the token was generated.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This table is self-cleaning -- old tokens are garbage collected.&lt;/p&gt;

&lt;h3&gt;
  
  
  failed_jobs
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Column&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;What it means&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;bigint (PK)&lt;/td&gt;
&lt;td&gt;Auto-incrementing ID.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;uuid&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;varchar(255)&lt;/td&gt;
&lt;td&gt;Unique identifier for the job.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;connection&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;text&lt;/td&gt;
&lt;td&gt;Queue connection that failed.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;queue&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;queue name&lt;/td&gt;
&lt;td&gt;Which queue the job was on.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;payload&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;longText&lt;/td&gt;
&lt;td&gt;The job's serialized data.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;exception&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;longText&lt;/td&gt;
&lt;td&gt;The full exception stack trace.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;failed_at&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;timestamp&lt;/td&gt;
&lt;td&gt;When it failed.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Use this table to debug failed queue jobs. The &lt;code&gt;exception&lt;/code&gt; column has the full stack trace.&lt;/p&gt;

&lt;h3&gt;
  
  
  personal_access_tokens
&lt;/h3&gt;

&lt;p&gt;Created by Laravel Sanctum for API token authentication.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Column&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;What it means&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;bigint (PK)&lt;/td&gt;
&lt;td&gt;Auto-incrementing ID.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;tokenable_type&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;varchar(255)&lt;/td&gt;
&lt;td&gt;The model this token belongs to (e.g., &lt;code&gt;App\Models\User&lt;/code&gt;).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;tokenable_id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;bigint&lt;/td&gt;
&lt;td&gt;The ID of that model.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;name&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;varchar(255)&lt;/td&gt;
&lt;td&gt;Token name (e.g., "Mobile App").&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;token&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;varchar(60)&lt;/td&gt;
&lt;td&gt;The hashed token value.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;abilities&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;text&lt;/td&gt;
&lt;td&gt;JSON array of allowed abilities (e.g., &lt;code&gt;["*"]&lt;/code&gt;).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;last_used_at&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;timestamp&lt;/td&gt;
&lt;td&gt;When the token was last used.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;expires_at&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;timestamp&lt;/td&gt;
&lt;td&gt;When the token expires (null = never).&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Optional tables
&lt;/h2&gt;

&lt;h3&gt;
  
  
  sessions
&lt;/h3&gt;

&lt;p&gt;Created by &lt;code&gt;php artisan session:table&lt;/code&gt;. Stores HTTP session data in the database instead of files.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Column&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;What it means&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;varchar(255) (PK)&lt;/td&gt;
&lt;td&gt;Session ID (from the cookie).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;user_id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;bigint (FK)&lt;/td&gt;
&lt;td&gt;The user this session belongs to. Null for guests.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ip_address&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;varchar(45)&lt;/td&gt;
&lt;td&gt;Client IP.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;user_agent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;text&lt;/td&gt;
&lt;td&gt;Browser user agent string.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;payload&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;longText&lt;/td&gt;
&lt;td&gt;Serialized session data.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;last_activity&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;integer&lt;/td&gt;
&lt;td&gt;Unix timestamp of last activity.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  cache
&lt;/h3&gt;

&lt;p&gt;Created by &lt;code&gt;php artisan cache:table&lt;/code&gt;. Stores key-value cache entries in the database.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Column&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;What it means&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;key&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;varchar(255) (PK)&lt;/td&gt;
&lt;td&gt;Cache key.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;value&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;longText&lt;/td&gt;
&lt;td&gt;Serialized cache value.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;expiration&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;integer&lt;/td&gt;
&lt;td&gt;Unix timestamp when it expires.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  jobs
&lt;/h3&gt;

&lt;p&gt;Created by &lt;code&gt;php artisan queue:table&lt;/code&gt;. Stores pending queue jobs.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Column&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;What it means&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;bigint (PK)&lt;/td&gt;
&lt;td&gt;Auto-incrementing ID.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;queue&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;varchar(255)&lt;/td&gt;
&lt;td&gt;Queue name.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;payload&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;longText&lt;/td&gt;
&lt;td&gt;Serialized job data.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;attempts&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;integer&lt;/td&gt;
&lt;td&gt;How many times it's been tried.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;reserved_at&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;integer&lt;/td&gt;
&lt;td&gt;When it was reserved by a worker.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;available_at&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;integer&lt;/td&gt;
&lt;td&gt;When it can be picked up.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;created_at&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;integer&lt;/td&gt;
&lt;td&gt;When it was dispatched.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  batches
&lt;/h3&gt;

&lt;p&gt;Created by &lt;code&gt;php artisan queue:batches-table&lt;/code&gt;. Tracks batch progress for parallel job processing.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Column&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;What it means&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;bigint (PK)&lt;/td&gt;
&lt;td&gt;Auto-incrementing ID.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;name&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;varchar(255)&lt;/td&gt;
&lt;td&gt;Batch name.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;total_jobs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;integer&lt;/td&gt;
&lt;td&gt;Total jobs in the batch.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pending_jobs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;integer&lt;/td&gt;
&lt;td&gt;Jobs still to run.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;failed_jobs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;integer&lt;/td&gt;
&lt;td&gt;Jobs that failed.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;failed_job_ids&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;longText&lt;/td&gt;
&lt;td&gt;JSON array of failed job IDs.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;options&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;longText&lt;/td&gt;
&lt;td&gt;Serialized batch options.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cancelled_at&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;timestamp&lt;/td&gt;
&lt;td&gt;When cancelled (null if not).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;created_at&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;timestamp&lt;/td&gt;
&lt;td&gt;When the batch was created.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;finished_at&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;timestamp&lt;/td&gt;
&lt;td&gt;When the batch completed.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  What to change
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Add a &lt;code&gt;role&lt;/code&gt; column&lt;/strong&gt; to &lt;code&gt;users&lt;/code&gt; for role-based access.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add a &lt;code&gt;phone_number&lt;/code&gt; column&lt;/strong&gt; for SMS auth.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add an &lt;code&gt;avatar_url&lt;/code&gt; column&lt;/strong&gt; for profile pictures.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rename &lt;code&gt;password_resets&lt;/code&gt;&lt;/strong&gt; to &lt;code&gt;password_reset_tokens&lt;/code&gt; (Laravel 8+ convention).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What to leave alone
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Don't modify the &lt;code&gt;failed_jobs.exception&lt;/code&gt; column -- it needs to hold full stack traces.&lt;/li&gt;
&lt;li&gt;Don't remove &lt;code&gt;personal_access_tokens.tokenable_type&lt;/code&gt; -- it's a polymorphic relation.&lt;/li&gt;
&lt;li&gt;Don't add indexes to &lt;code&gt;cache.key&lt;/code&gt; -- it's already the primary key.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Does Laravel create all these tables automatically?
&lt;/h3&gt;

&lt;p&gt;No. Only &lt;code&gt;users&lt;/code&gt;, &lt;code&gt;password_resets&lt;/code&gt;, and &lt;code&gt;failed_jobs&lt;/code&gt; are created by &lt;code&gt;php artisan migrate&lt;/code&gt;. The others (&lt;code&gt;sessions&lt;/code&gt;, &lt;code&gt;cache&lt;/code&gt;, &lt;code&gt;jobs&lt;/code&gt;, &lt;code&gt;batches&lt;/code&gt;) require separate Artisan commands.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I use Redis instead of the database tables?
&lt;/h3&gt;

&lt;p&gt;Yes. Laravel supports Redis for sessions, cache, and queues out of the box. Switch your &lt;code&gt;.env&lt;/code&gt; driver to &lt;code&gt;redis&lt;/code&gt; and the database tables become unnecessary.&lt;/p&gt;

&lt;h3&gt;
  
  
  What's the difference between &lt;code&gt;password_resets&lt;/code&gt; and &lt;code&gt;password_reset_tokens&lt;/code&gt;?
&lt;/h3&gt;

&lt;p&gt;Same table, different names. Laravel 8+ renamed it to &lt;code&gt;password_reset_tokens&lt;/code&gt;. If you're on an older version, it's still &lt;code&gt;password_resets&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I visualize my Laravel schema?
&lt;/h3&gt;

&lt;p&gt;Use &lt;a href="https://www.dbdiagramr.space" rel="noopener noreferrer"&gt;dbdiagramr&lt;/a&gt; -- paste your connection string and get a visual schema of all your Laravel tables.&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>database</category>
      <category>tutorial</category>
      <category>php</category>
    </item>
  </channel>
</rss>
