Import a Postgres schema

The Database Visualizer is not Bubble-only. Paste in a Postgres schema from Supabase, a migration file or a pg_dump, and you get the same diagram.

What is supported

Postgres only. MySQL, SQL Server and SQLite dialects are not parsed.

Upload a .sql file, or paste the SQL into the Postgres / SQL panel on the Database Visualizer. Uploads are routed by extension: .sql goes to the SQL parser, .json and .bubble go to the Bubble JSON parser, and any other extension is refused before the file is read. Pasted text has no filename, so it is routed by what is in it instead.

The Postgres / SQL panel, below the four upload cards. It is the only paste box on the page, and it takes Bubble JSON as happily as it takes SQL.

What gets read

ConstructBecomes
CREATE TABLEA data type
CREATE TYPE … AS ENUMAn option set
REFERENCES on a columnA one-to-one relationship
ALTER TABLE … ADD CONSTRAINT … FOREIGN KEYA one-to-one relationship
CHECK (col IN ('a','b'))An option set named <table>_<column>
A <name>_options table with value and label columnsAn option set
NOT NULL, UNIQUEKept for the round trip back out to SQL
type[]A list field
INSERT INTO … VALUESOption set values

Column types are mapped onto Bubble's vocabulary: booleans become boolean, timestamps and dates become date, all numeric types become number, and everything else including uuid, jsonb and varchar becomes text.

id, created_at and updated_at columns are suppressed as fields, because Bubble creates its own equivalents.

Three ways an option set is recognised

This trips people up, so it is worth stating explicitly. A fixed set of choices in Postgres can be modelled at least three ways, and LumiDevKit understands all three:

  1. A native CREATE TYPE … AS ENUM.
  2. An inline CHECK (status IN ('draft','published')).
  3. A lookup table whose name ends in _options and which has both a value and a label column.

The lumidevkit: markers

Some intent cannot be recovered from SQL alone, so LumiDevKit's own SQL export writes comment markers that it reads back on import:

COMMENT ON TABLE priority_options IS 'lumidevkit:option_set';
COMMENT ON TABLE tasks_watchers   IS 'lumidevkit:join';
COMMENT ON TABLE profiles         IS 'lumidevkit:user';
COMMENT ON COLUMN tasks.owner_id  IS 'lumidevkit:display=Owner';

The join marker is the significant one. A junction table is folded back into a list field on its owner only when it is explicitly marked. Unmarked two-foreign-key tables are kept as data types on purpose: a table like votes or memberships with its own surrogate id is indistinguishable from a pure join table once it has been parsed, and keeping it is the lossless choice.

What is dropped by design

Row-level security policies, triggers, functions, indexes, views, sequences, extensions, DEFAULT values, numeric precision, ON DELETE behaviour, composite primary keys and schema qualification are all discarded. The diagram models entities and relationships, not a full database.

Import notes

Where the parser makes a judgement call, it tells you underneath the drop zone:

  • Treated "X" as an option set (looks like a lookup table).
  • Folded join table "X" into a list field on "Y".
  • Skipped join table "X" (needs two foreign keys).
  • Skipped join table "X" (owner "Y" not found).

Read them. They are the difference between a diagram you can trust and one you assume is right.

If it fails

You get "Could not parse SQL: …" with the underlying parser's message and a line number. The usual causes are a non-Postgres dialect, or a file that is a data dump rather than a schema.

Going the other way

See export your schema for turning a Bubble schema into Postgres.

View this page as plain markdown