# 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. [[prompt]] ## The file A LumiDevKit schema is a JSON object with exactly two top-level keys: ```json { "user_types": {}, "option_sets": {} } ``` That file is valid. It loads, and shows you an empty diagram containing only Bubble's built-in User. - `user_types` holds your data types — the equivalent of tables. - `option_sets` holds 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..display` | **Yes in practice** | The box renders labelled **`undefined`**. There is no fallback | | `user_types..fields` | No | The type appears with no fields | | `option_sets..display` | No | Falls back to the key, with `_` turned into spaces | | `option_sets..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.` | One record of another data type | Yes, one-to-one | No | | `list.custom.` | Many records of another data type | Yes, one-to-many | Yes | | `option.` | One option from an option set | Yes, one-to-one | No | | `list.option.` | 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`. ```json { "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.Order` does not find the key `order`. - **Not pluralised.** `custom.orders` does not find the key `order`. - **Whitespace-sensitive.** Keys should never contain spaces at all. - **Dots are greedy.** Everything after the first `custom.` is the key, so `custom.a.b` looks for a key literally named `a.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. 1. **`list.user` instead of `list.custom.user`** — labelled `user[]`, but no link and not a list. 2. **A reference to a key that does not exist** — a red `[Missing: X]` box instead of a link. 3. **Referencing the display instead of the key** — `custom.Order Item` rather than `custom.order_item`. Same red box. 4. **A capital letter in a reference** — `custom.Order`. Same red box. 5. **A pluralised reference** — `custom.orders` pointing at the key `order`. Same red box. 6. **A missing `display` on a data type** — a box labelled `undefined`. 7. **`custom.` pointing at an option set** (or `option.` at a data type) — a link that draws, styled as the wrong kind of thing. 8. **`"options"` instead of `"values"`** in an option set — LumiDevKit reads both, but Bubble's own paste format expects `values`, so prefer it. 9. **`deleted: true`** anywhere — the item vanishes from the diagram, because deleted items are hidden by default. Turn on *Show deleted* to see them. 10. **`{"user_types": {}}`** — loads "successfully" and shows one lone User node. `{"user_types": []}` does the same, because the check is `typeof user_types === 'object'` and an array passes it. 11. **SQL type names** — `varchar`, `int`, `uuid`, `timestamp`, `jsonb`. All render as their literal string. 12. **`date_range` / `numeric_range` / `date_interval`** — real Bubble types, meaningless here. 13. **`list.geographic_address`** — not a list. 14. **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. 15. **A key reused in both `user_types` and `option_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: 1. **No red boxes.** Red means `[Missing: …]`, which means a reference typo. 2. **Every box has a real name.** A box reading `undefined` means a missing `display`. 3. **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`. 4. **The colours are right.** Blue is a data type, orange an option set, gold the built-in User. 5. **The count matches.** If you asked for twelve types and see nine, three collided on a duplicate key. 6. **Toggle Show deleted.** If items appear, something was marked `deleted: true`. [[img:database-visualizer/03-missing-reference.png|Check 1, failing. Two red boxes from two reference typos: custom.products where the key is product, and option.orderStatus where the key is order_status. The file loaded without a single error.]] 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_types` existing. 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](/docs/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](/docs/troubleshooting-uploads) 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](/docs/copy-for-bubble).