Software Development with AI 10 min read Updated Jul 16, 2026

How to Design a Database Schema With AI

Four sentences of requirements go in, a confident schema comes out — and it quietly decided three things nobody decided. Here's how to turn real domain rules and access patterns into a reviewable database schema, with the modeling gaps named as open questions instead of guessed.

Build a Schema Design Prompt

Four sentences of requirements, a schema full of decisions nobody made

What you have is thin: "customers subscribe to a plan and get billed monthly," "show them their invoice history," "plans have a name and a price," "customers can cancel." What you need is a data model. So you paste the four sentences into an AI and ask it to design the database, and back comes a tidy set of tables with keys and types and a confident air about it. The trouble isn't that it's malformed — it parses, it would run. The trouble is that somewhere in that tidy little schema it decided a customer can only ever hold one subscription, that an invoice's amount can be read back from the plan's current price, and that "cancel" means deleting a row. Nobody decided any of that. The requirements never said. The model needed a column, so it picked one.

Those picks are the expensive kind. An ambiguous prompt gets rewritten in a minute; a wrong cardinality gets discovered in month seven, after it has data in it and three services reading it. So the job here isn't getting tables out of an AI — that part is easy, and that's the problem. The job is getting a candidate database schema you can actually review: every relationship carrying a direction and a cardinality, every key and constraint traceable to a rule someone stated, and every question the requirements didn't answer sitting in an open-questions list instead of quietly resolved into a column. The model can draft that from what you give it — your real domain rules, your access patterns, your sample records, the constraints you already live with. What it can't do is know your business. NewPrompt's reach here stops at the prompt: it never touches a database — no connection, no introspection of an existing schema, no DDL, no migration. The schema that comes back is a candidate with nothing promised about whether it's correct, whether it performs, or how it holds up at scale, and the cardinality, the constraints, the indexes and the lifecycle rules are all things a domain owner and a database engineer confirm before anything gets created.

Why "design my database" returns a textbook, not your domain

Ask a model to design a database from a short brief and it does something reasonable: it pattern-matches your nouns onto the shape of every similar system it has seen. That's genuinely useful for a first sketch and genuinely dangerous as a design, because the gap between "a normal subscription app" and *your* subscription app is exactly where the modeling decisions live. Five failures, in rough order of how much they cost to undo:

  • It resolves ambiguity silently. The requirements don't say whether a customer can hold two subscriptions at once, so the model picks one — usually by adding a unique constraint you never asked for — and now an assumption is a schema rule with no note saying it was ever a question.
  • It derives facts that should be frozen. An invoice's amount gets modeled as a lookup to the plan's price, which reads fine until someone raises the price and last year's invoices change. Historical facts need to be stored as they happened, not re-derived.
  • It models the happy path only. Cancel, expire, refund, downgrade, delete-my-account — the states that make a domain hard are the ones a four-sentence brief omits and a model won't invent unprompted.
  • It guesses cardinality from the noun. "Order has items" becomes one-to-many; "product has tags" becomes one-to-many too, when it's many-to-many and needs a join table. The word doesn't tell you the shape — the domain does.
  • It designs with no idea what you'll query. A schema is a set of trade-offs against read and write patterns, and a model given no access patterns optimizes for nothing in particular — which usually means it looks clean and reads badly.

Step 1: Bring the rules and the queries, not just the nouns

Two inputs decide whether the draft is worth reviewing, and briefs routinely carry neither. The first is the business rules in their real form, including the awkward ones: what makes a record valid, what must never happen, what changes over time and what must stay frozen once written, and what the lifecycle actually is — created, then what? The second is the access patterns: the reads and writes the application will really perform, roughly how often, and which ones have to be fast. "Show a customer their invoice history" is an access pattern; it tells you more about the shape of the invoice table than any noun in the brief does.

Then add the sample records and the constraints you already live with — an existing system's quirks, a compliance rule about retention, a reporting job that needs the data a certain way. Be equally explicit about what nobody has decided. If it's genuinely undecided whether canceling ends billing immediately or at period end, say so, in those words. That sounds like a small thing and it's the whole difference between a draft that flags the question and a draft that answers it for you. Every certainty you hand over comes back as a column — including the ones you didn't mean to express.

Step 2: Give every entity exactly one job

Before anything becomes a column, get the concepts named and bounded. Ask the model to list the entities with a one-line responsibility each, and then read that list adversarially: an entity whose description needs the word "and" is usually two entities. A `customer` that holds the billing address and the login credentials and the current plan is three ideas sharing a table, and it'll fight you the first time someone has two addresses.

The reverse mistake is real too — splitting things that never move independently just because a diagram looks tidier. The test is whether the two parts have different lifecycles or different owners. A subscription and its invoices change on completely different schedules and one is immutable once issued, so they're separate. A customer's name and email change together and belong to the same party, so they're not. Work this out at the concept level, in plain language, while it's still cheap to be wrong.

Step 3: Give every relationship a direction and a cardinality

This is where the silent assumptions hide, so make them loud. Every association gets written as A —(1:1 / 1:N / N:M)— B, with the direction stated and the meaning spelled out in the domain's own words: not "customer has subscriptions" but "a customer may hold several subscriptions at once, one per product line." Force every many-to-many to resolve into an explicit join table, because a many-to-many left implicit is a table you'll discover you needed after the data is in.

Then interrogate the optionality, which is the part everyone skims. Can a subscription exist without a plan? Must an invoice always belong to a subscription, or can there be a one-off charge? Is the relationship required at creation, or only later? "Optional" and "required" are business rules wearing a nullability costume, and if the requirements don't settle one, that's an open question — not a default. The Schema Design & ERD Prompt is built around exactly this discipline: it asks for every relationship with its cardinality and direction, it refuses to leave a many-to-many implicit, and its standing rule is that where the requirements don't decide a modeling question, the question goes in Open Questions rather than getting quietly picked. It's a copy-paste prompt — you adapt it to your domain and run it in your own AI tool.

Step 4: Choose the keys and constraints that protect the data

Constraints are where a design stops being a diagram and starts being enforceable. Go through it deliberately: the primary key for each table, every foreign key with its target and its on-delete behavior, what's unique, what's genuinely required versus merely usually-present, and the check constraints that pin a status to a closed set instead of letting `cancelled`, `canceled`, and `CANCELLED` all coexist. On-delete deserves real thought rather than a default — `CASCADE` on a customer with billing history is a compliance incident waiting to happen, and `RESTRICT` is often the right call.

Ask the model to justify each constraint against a stated rule, and treat any it can't justify as suspect. This is the step where an invented rule does the most damage, because a constraint is a decision the database will enforce forever, silently, on everyone. A check constraint nobody asked for doesn't announce itself — it just makes a legitimate future case impossible, and nobody remembers why the line is there. If the evidence for a constraint is "it seemed sensible," it belongs in the open questions with the name of the person who can settle it.

Step 5: Run the queries and the edge cases against the schema

A database schema is a bet about how data gets read and written, so test the bet before you build on it. Walk each real access pattern through the candidate model: to show a customer's invoice history, how many joins is that, and does an index actually follow from it? Then walk the edge cases — the ones the brief never mentioned. What happens when a plan is discontinued but active subscriptions still point at it? When a customer cancels mid-period? When the same invoice needs reissuing? Do it concretely, with sample rows written out, because an edge case argued in the abstract always sounds survivable and a written row shows you the null you can't populate.

This is also where the normalization trade-off should become visible rather than implicit. Normalizing by default is a sound starting position, but there are places where a duplicated field is the correct answer — an invoice's charged amount must be stored, not derived, because an invoice is a historical fact and the plan's price is a current one. What matters is that the trade-off is written down with its reason, so the next person reads a decision instead of guessing at an accident. Ask for it explicitly, and make gaps carry through: a good candidate ends with an open-questions list, each one paired with the question that would settle it.

One structural check is worth doing mechanically. The whole premise here is that gaps come back flagged, which fails quietly if the model drops the open-questions section on its way out — and a long document is exactly where that goes unnoticed. Paste the output into the AI Output Validator, give it the sections you asked for, and it scans the headings in your browser and tells you which are missing or out of order, with a repair prompt when it finds something. Be clear about what that buys you: it confirms the section is there, not that the schema is right. Structure it can check; correctness is still yours.

Common mistakes

The habits that turn an AI-drafted database schema into a foundation you regret:

  • Accepting a constraint nobody asked for. A unique index on `customer_id` is a business rule the requirements never stated; make every constraint trace to a rule, and send the rest to open questions.
  • Treating a lifecycle state as a delete. Canceling isn't `DELETE`; it's a state with a timestamp. A row removed is billing history removed, and the requirement to keep it usually surfaces from finance, late.
  • Storing money as a float. It reads fine and rounds wrong, and a rounding bug in a billing table becomes an incident with customers in it. Money is a decimal or an integer count of minor units — a model will hand you `float` without blinking.
  • Bolting on soft-delete without touching the unique index. Add `deleted_at` to a table with `UNIQUE(email)` and a deleted row still occupies the slot, so the customer who leaves can never come back. The index needed to become partial at the same moment, and nobody notices until support files the ticket.
  • Picking the key type by vibe. UUID versus bigint changes index size, insert locality, and write cost at volume; it's a decision with reasons, not a default to inherit from whatever the model saw most.
  • Mistaking a candidate for a decision. It's a draft the domain owner and a database engineer review — NewPrompt doesn't read your database or check that the model is right.

A worked example: subscriptions and invoices

Four sentences of real requirements, the schema a thin ask returns, and the candidate you can actually put in front of someone.

Four sentences of requirements turned into a generic schema with three invented decisions, versus a candidate schema built on the rules the brief left out — explicit cardinality, constraints traced to stated rules, a duplication trade-off argued rather than hidden, and the undecided rules kept as open questions
RAW REQUIREMENTS (all you actually have):
  - "Customers subscribe to a plan and are billed monthly."
  - "Show a customer their invoice history."
  - "Plans have a name and a monthly price."
  - "Customers can cancel."

THE FOUR-SENTENCE ASK, AND WHAT IT PRODUCES:
  ask: "design my database for a subscription app"
  -> customers(id, name, email)
     plans(id, name, price)
     subscriptions(id, customer_id UNIQUE, plan_id, status TEXT)
     invoices(id, subscription_id, plan_id)   <- amount read from plans.price

  three decisions nobody made:
     * UNIQUE(customer_id) -> one subscription per customer, forever
     * invoice amount derived from plans.price -> last year's invoice
       changes the day you raise the price
     * status as free text -> 'cancelled' / 'canceled' / 'CANCELLED'

WHAT THE BRIEF LEAVES OUT (the rules you had to go and ask for):
  lifecycle   trialing -> active -> past_due -> canceled
  retention   a customer with billing history is never hard-deleted
  add-ons     sold as separate subscriptions on the same account
  billing     the charge is the plan price after proration, discount
              and tax — so it is not always the plan price
  access      "invoice history" is read per customer, newest first

A CANDIDATE SCHEMA (entities, cardinality, keys, constraints, gaps):
  ENTITIES (one responsibility each)
    customer      the billable party
    plan          a purchasable offer and its current list price
                    <- that "and" is a smell; see the open questions
    subscription  one customer's ongoing commitment to one plan
    invoice       one billing event, immutable once issued

  RELATIONSHIPS AND CARDINALITY
    customer     1-N subscription   (add-ons: several held at once)
    plan         1-N subscription   (a plan is held by many)
    subscription 1-N invoice        (one per billing period)

  KEYS AND CONSTRAINTS (each tied to a rule above, not to a hunch)
    subscription.customer_id  FK -> customer.id  ON DELETE RESTRICT
        (retention: a customer with billing history is not deletable)
    subscription.plan_id      FK -> plan.id      ON DELETE RESTRICT
        (a discontinued plan still has subscribers pointing at it)
    subscription.status  CHECK IN ('trialing','active','past_due','canceled')
        (the stated lifecycle, closed — not a free-text field)
    subscription.canceled_at  nullable ts  -- cancel is a state, not a DELETE
    invoice.amount_charged    NOT NULL     -- stored, NOT a read of plan.price
    invoice.currency          NOT NULL
    invoice.issued_at         NOT NULL
    UNIQUE (subscription_id, period_start) -- a period cannot be billed twice

  TRADE-OFF, STATED NOT HIDDEN
    amount_charged looks like a duplicate of plan.price and isn't.
    plan.price is today's list price; amount_charged is what this
    customer was actually charged, after proration, discount and tax.
    They match on the simple case and diverge the moment billing gets
    real — and deriving it would rewrite every past invoice on the
    next price change.

  OPEN QUESTIONS (named, not guessed)
    - is a plan's price one value, or one per currency?
        -> decides whether plan splits into plan + plan_price
    - does canceling stop billing immediately or at period end?
        -> decides whether canceled_at or ends_at drives invoicing
    - can a customer hold two active subscriptions to the SAME plan?
        -> add-ons say several plans; the same plan twice is undecided

REVIEW RESULT (what the two humans actually changed):
  domain owner: "same plan twice is real — that's how people buy a
      second seat." -> the last open question closes; no unique index
  database engineer: "invoice history is read per customer, and that's
      a join through subscription on every page." -> copy customer_id
      onto invoice and index (customer_id, issued_at) — the second
      deliberate duplication, bought for a stated access pattern

STILL YOUR JOB:
  the draft organized the domain and named its own gaps. the two
  decisions that actually mattered came from people who know the
  business and the queries — neither is in the requirements doc.

Where this fits in NewPrompt

Designing the model is one move in a longer sequence, and the AI Database Design Workflow is that sequence: model the entities and relationships, set the constraints that protect integrity, plan indexes around real queries, then document the schema and the migration. This guide covers the first three of those subjects; the workflow carries on into the documentation and migration step this guide stops before. It supplies the prompts and the order, and you run them in your own AI tool with every modeling call staying yours. Three pieces do the concrete work here: the Schema Design & ERD Prompt is the copy-paste prompt that produces an ERD-ready spec — a textual model, not a drawn diagram — and the Markdown Output Builder is the tool that builds that prompt, with the schema sections and the open-questions list already in its shape. When you want the model reasoning about trade-offs and boundaries rather than reaching for a table, the Software Architect Role Prompt is a role prompt aimed at pressure-testing a design before it gets built.

"Schema" is an overloaded word around here, so be explicit about which one this is. An extraction schema defines the fields you pull *out of* documents. A JSON schema defines the shape of a model's *response*. This is neither — it's the entities, relationships, and keys that hold your domain in a database. It also stops short of two neighbours on purpose: writing the migration that gets an existing, populated database from its current shape to a new one is a different problem with different risks, and tuning a slow query against a schema that already exists is a third. Design first, on paper, while changing your mind is free.

If the schema is for a new backend, the Build an API Backend with AI project is the fuller path this stage sits inside — its define-scope stage pins the requirements first, then its design stages run architecture, API contract, data model, and access control before the endpoints exist, and its data-model stage points at the same workflow and prompt this guide uses. The project frames the order; it doesn't write or ship the backend for you. That order is also a live argument worth knowing about: schema-first gives your API something real to sit on, contract-first pins what clients depend on. Both are defensible, both have their advocates, and the answer depends on which one your team will regret getting wrong.

Every modeling decision here carries a price tag, and the only variable is when you read it. The unique constraint nobody asked for costs a five-minute conversation this week, or a migration with downtime in month seven. The invoice amount read from the plan's current price costs one line now, or a year of quietly wrong invoices later. That asymmetry is the whole argument for writing the candidate down and fighting about it while it's still a document — and it's why the most valuable part of a good draft isn't the tables. It's the open-questions list, because that's the part that says out loud what your requirements never decided and who has to decide it. Get those arguments to happen now, on a document, and not in month seven, on a table with data in it.

Tools for this guide

Each generates the prompt described above — you run it in your own AI assistant.

Ready-made resources

Reusable prompts and templates for the exact steps in this guide.

Take it further

When this task is one step inside a larger workflow or build.

FAQ

Does NewPrompt connect to my database or read my existing schema?

No. NewPrompt builds a schema-design prompt in your browser — it doesn't connect to a database or a database server, introspect an existing schema, run DDL, create tables, or apply a migration. You paste your own requirements, business rules, sample records, and access patterns into your own AI tool and run the prompt there; the candidate model comes back to you as text. Creating anything, and deciding whether the design is right first, happens in your environment and with your team.

Who should review the candidate schema, and what is each person checking?

Two people, and neither can do the other's job. The domain owner checks the rules: can a customer really hold two subscriptions, does canceling stop billing today or at period end, is that status list the actual lifecycle. Those are business facts, and a wrong one makes the schema wrong no matter how clean it looks. The database engineer checks what the domain owner can't see: whether an index genuinely serves a query or just adds cost on every write, how the migration behaves at your real row count, whether a constraint is enforceable at that volume, what the on-delete rules do under load. Give them the same draft and they'll return different findings — that's the point. The draft's job is to be specific enough that both have something concrete to push back on.

What should the AI do when my requirements don't decide something?

Name it, not answer it. The rule to put in the prompt is that where the requirements don't settle a modeling question, it goes in an open-questions list rather than getting quietly picked — and each one should carry the question that would resolve it, so it's actionable rather than just flagged. The failure this prevents is specific and expensive: nothing in a four-sentence brief says whether a customer can hold two subscriptions at once, so an unprompted model adds a unique constraint, and an assumption becomes a rule the database enforces on everyone with no note explaining why. An open question costs a five-minute conversation. A wrong constraint costs a migration.

How is this different from designing a JSON or extraction schema?

Different artifact, different failure mode. A JSON output schema pins the shape of a single AI response so your parser doesn't break. An extraction schema defines the fields you pull out of a document and what to do when one is missing. A database schema models a domain that has to stay correct across every future write — which is why this guide spends its time on cardinality, keys, referential integrity, and access patterns, and why the mistakes are the kind you discover months later rather than on the next run. The habit they share is refusing to let the model invent: state what's known, and flag what isn't.