Database Schemas & AI
LumiDevKit schema format for AI
This is the complete specification for the JSON that LumiDevKit's Database Visualizer reads. It is written to be handed to an AI: paste the prompt below into ChatGPT or Claude along with a description of your app, and you get back a file that loads correctly the first time.
Read this before anything else. The parser never rejects a bad field type. Every unrecognised type string is silently accepted and turned into a meaningless plain value. There is no error, no warning, and no red text — you just get a diagram that is quietly wrong. One example makes the stakes clear: write list.user instead of list.custom.user and the diagram labels the field user[], which looks exactly right, while drawing no relationship line at all.
That is why the prompt below is so specific, and why this page spends more words on failure modes than on the happy path.
The prompt
Hand this to ChatGPT or Claude
Copy it, paste it into a new chat, replace the last line with a description of your app, then save the reply as my-app.json and upload it to the Database Visualizer.
Or point an AI straight at it — no copying:
The file
A LumiDevKit schema is a JSON object with exactly two top-level keys:
{
"user_types": {},
"option_sets": {}
}
That file is valid. It loads, and shows you an empty diagram containing only Bubble's built-in User.
user_typesholds your data types — the equivalent of tables.option_setsholds your fixed choice lists — the equivalent of enums.
Any other top-level key is ignored. That is deliberate, and it is why you can drop a complete Bubble application export straight onto the Database Visualizer: it simply picks out these two keys and discards pages, element_definitions, api, styles, settings and the rest.
Required and optional keys
| Key | Required | What happens if it's wrong |
|---|---|---|
user_types | Yes — must be an object, not null, not an array | Missing or null: file rejected, "No database types found." An array slips past that check and loads without an error |
option_sets | No — defaults to {} | Nothing; the diagram just has no option sets |
user_types.<key>.display | Yes in practice | The box renders labelled undefined. There is no fallback |
user_types.<key>.fields | No | The type appears with no fields |
option_sets.<key>.display | No | Falls back to the key, with _ turned into spaces |
option_sets.<key>.values | No | An empty option set, with no warning |
deleted: true | No | The item is hidden from the diagram by default |
The display asymmetry catches almost everyone: option sets fall back to their key, data types do not. A data type without display is not an error — it is a nameless box.
The complete value grammar
Every field is { "display": "...", "value": "..." }. The value string is matched against the list below, and the first match wins.
value | Means | Draws a link? | Treated as a list? |
|---|---|---|---|
text | Free text: names, emails, URLs, phone numbers, long text | No | No |
number | Integers, decimals, money, quantities, counts | No | No |
date | A date and time | No | No |
boolean | Bubble's yes / no | No | No |
image | An uploaded image | No | No |
file | An uploaded non-image file | No | No |
geographic_address | A physical address or map location | No | No |
list.text | A list of text values | No | Yes |
list.number | A list of numbers | No | Yes |
list.date | A list of dates | No | Yes |
list.boolean | A list of yes / no values | No | Yes |
list.image | A list of images | No | Yes |
list.file | A list of files | No | Yes |
user | One Bubble User | Yes, one-to-one | No |
custom.<type_key> | One record of another data type | Yes, one-to-one | No |
list.custom.<type_key> | Many records of another data type | Yes, one-to-many | Yes |
option.<option_set_key> | One option from an option set | Yes, one-to-one | No |
list.option.<option_set_key> | Many options from an option set | Yes, one-to-many | Yes |
That table is exhaustive. Anything not on it is accepted without complaint and rendered as a meaningless plain value.
Types LumiDevKit accepts but does not model
These are real strings you will find in genuine Bubble exports, or that LumiDevKit's own "add field" menu produces. They load without error, but the diagram cannot represent them:
| You write | The diagram shows | What actually happens |
|---|---|---|
list.user | user[] | No link, and not treated as a list. Use list.custom.user |
list.geographic_address | geographic_address[] | Not treated as a list. There is no list form of this type |
date_range | date_range | A plain value with no meaning. Use two date fields |
numeric_range | numeric_range | A plain value with no meaning. Use two number fields |
date_interval | date_interval | A plain value with no meaning. Use a number |
Text, varchar, uuid, jsonb | The word you wrote | A plain value. Type names are lowercase, from the table above |
list.user is worth repeating because it is both the most common mistake and the hardest to spot: the label on the diagram ends in [], so it reads as a working list of Users. It is not. Real Bubble exports contain list.user, so an AI trained on Bubble data will reach for it.
Referencing other types
custom.X and option.X are matched against the literal JSON key of the target — never against its display.
{
"user_types": {
"order_item": {
"display": "Order Item",
"fields": {
"product_custom_product": { "display": "Product", "value": "custom.product" }
}
},
"product": { "display": "Product", "fields": {} }
}
}
The link works because custom.product matches the key product. It would break with custom.Product, custom.products, or custom.Product Item.
The match is exact:
- Case-sensitive.
custom.Orderdoes not find the keyorder. - Not pluralised.
custom.ordersdoes not find the keyorder. - Whitespace-sensitive. Keys should never contain spaces at all.
- Dots are greedy. Everything after the first
custom.is the key, socustom.a.blooks for a key literally nameda.b. Do not put dots in keys.
A reference that points at a key you never defined does not raise an error. It draws a red [Missing: X] box on the diagram. Those red boxes are the fastest way to spot a broken schema, which is why the checklist below leads with them.
There is also no check that you used the right namespace: custom. pointing at an option set resolves happily, and so does option. pointing at a data type. Both produce a diagram that is wrong in a way nothing warns you about. Keep custom. for user_types and option. for option_sets.
Where this goes silently wrong
Every item here loads without an error message.
list.userinstead oflist.custom.user— labelleduser[], but no link and not a list.- A reference to a key that does not exist — a red
[Missing: X]box instead of a link. - Referencing the display instead of the key —
custom.Order Itemrather thancustom.order_item. Same red box. - A capital letter in a reference —
custom.Order. Same red box. - A pluralised reference —
custom.orderspointing at the keyorder. Same red box. - A missing
displayon a data type — a box labelledundefined. custom.pointing at an option set (oroption.at a data type) — a link that draws, styled as the wrong kind of thing."options"instead of"values"in an option set — LumiDevKit reads both, but Bubble's own paste format expectsvalues, so prefer it.deleted: trueanywhere — the item vanishes from the diagram, because deleted items are hidden by default. Turn on Show deleted to see them.{"user_types": {}}— loads "successfully" and shows one lone User node.{"user_types": []}does the same, because the check istypeof user_types === 'object'and an array passes it.- SQL type names —
varchar,int,uuid,timestamp,jsonb. All render as their literal string. date_range/numeric_range/date_interval— real Bubble types, meaningless here.list.geographic_address— not a list.- Bubble's automatic fields —
id,created_at,modified_date,creator,slug. Bubble adds these itself; including them clutters the diagram and, if you later paste into Bubble, collides with the built-ins. - A key reused in both
user_typesandoption_sets— the two share one namespace on the diagram, so one silently overwrites the other.
Did it work
Check the diagram, not the JSON. Six things:
- No red boxes. Red means
[Missing: …], which means a reference typo. - Every box has a real name. A box reading
undefinedmeans a missingdisplay. - Every list-of-records field shows a link. If a field you meant as a list has no line running out of it, the type string degraded — check for
list.user. - The colours are right. Blue is a data type, orange an option set, gold the built-in User.
- The count matches. If you asked for twelve types and see nine, three collided on a duplicate key.
- Toggle Show deleted. If items appear, something was marked
deleted: true.
If any check fails, paste the specific problem back to the AI — "the watchers field on Order has no link" — and re-upload. That loop is usually one round.
What the parser does not do
Worth stating plainly, because it explains why the checklist above is necessary:
- No schema validation. There is no JSON Schema, no zod, no ajv.
- No type checking beyond the grammar table.
- No cross-reference checking. Dangling references become placeholder boxes.
- No duplicate detection.
- No required-field checking, apart from
user_typesexisting.
There is exactly one check: is user_types an object? Everything else is best-effort rendering.
Getting the file into LumiDevKit
Save the AI's output as a .json file and upload it to the Database Schema card on the Database Visualizer.
- Accepted extensions:
.json,.bubble,.sql - Maximum size: 50 MB
You can also paste JSON rather than uploading a file — but note that the paste box on that screen sits under a panel headed Postgres / SQL. Despite the label it accepts Bubble JSON too; the format is detected from the content, not the heading.
If the upload fails outright, the error decoder lists every message and its cause.
From LumiDevKit into Bubble
Once the diagram looks right, you can push the whole schema into a real Bubble app — every data type, every field, every option set — using the LumiDevKit extension. See Push a schema into Bubble.