Model domain types in TypeScript with discriminated unions
Redesign your TypeScript domain types so impossible states do not compile: discriminated unions, branded IDs, exhaustive switches and validation at system boundaries. Designed for ChatGPT, Claude, Gemini and Microsoft Copilot. It includes 3 customizable variables and you can copy it for free in one click.
Advanced · Code · Pending editorial review
Prompt
Act as a TypeScript and type-driven design expert who values clarity over cleverness.
Context:
- Domain: {{domain}}
- Current types (if any): {{current_types}}
- Where they are used: {{usage}}
If it is unclear which state transitions are valid or which fields exist in each state, ask me before designing.
Deliverables:
1. A diagnosis of the current types: which impossible combinations they allow (for example, a "shipped" order with no ship date) and what bugs that can cause.
2. The new model: unions discriminated by a literal field, fields present only in the state they belong to, `readonly` where it makes sense and branded types for IDs that must not be mixed up.
3. Typed transition functions between states that only accept a valid source state.
4. An exhaustive `switch` with a `never` check, plus an example of the compile error that appears when a new state is added but not handled.
5. Validation at system boundaries (API, forms, storage) with a schema the type is derived from, so the type and the validation cannot drift apart.
6. Type tests with `@ts-expect-error` or `expectTypeOf` proving that impossible states do not compile.
7. When this model is overkill and something simpler would do.
Constraints:
- Prefer literal unions over `enum`, unless you explain why an enum is better here.
- No complex conditional types or generics unless they clearly pay off; a mid-level developer must be able to read the code.
- Do not invent business rules: mark the ones I must confirm with [FILL IN].
Output format: diagnosis, code blocks labeled with file names and a table of states and transitions.How to use it
- Describe your entity’s real states and what data each one has. 2. Paste the current types if you have them. 3. Replace the types gradually and let the compiler point out the usages you need to update.
Usage example
Domain: online store orders with pending, paid, shipped, delivered and cancelled states · Current types: an Order interface with every field optional · Usage: UI state and API responses.
Sample output
Diagnosis
status: string plus optional fields allows impossible combinations: a 'shipped' order with no trackingNumber, or a 'cancelled' one with paidAt and no reason. Every component ends up sprinkling ?. everywhere.
order.ts
export type OrderId = string & { readonly __brand: 'OrderId' };
type Base = { readonly id: OrderId };
export type Order =
| (Base & { status: 'pending' })
| (Base & { status: 'paid'; paidAt: Date })
| (Base & { status: 'shipped'; paidAt: Date; trackingNumber: string })
| (Base & { status: 'delivered'; paidAt: Date; trackingNumber: string })
| (Base & { status: 'cancelled'; cancelReason: string });
type InStatus<S extends Order['status']> = Extract<Order, { status: S }>;
export function ship(order: InStatus<'paid'>, trackingNumber: string): InStatus<'shipped'> {
return { ...order, status: 'shipped', trackingNumber };
}
export function label(order: Order): string {
switch (order.status) {
case 'pending': return 'Awaiting payment';
case 'paid': return 'Paid';
case 'shipped': return `Shipped (${order.trackingNumber})`;
case 'delivered': return 'Delivered';
case 'cancelled': return `Cancelled: ${order.cancelReason}`;
default: {
const exhaustive: never = order;
return exhaustive;
}
}
}
If you add 'returned' tomorrow and don't handle it, the never line stops compiling. To confirm: can a paid order be cancelled? [FILL IN]…
Key facts
- Category
- Programming & development · TypeScript
- Output type
- Code
- Level
- Advanced
- Designed for
- ChatGPT, Claude, Gemini and Microsoft Copilot
- Verified in
- Pending editorial verification
- Variables
- Domain, Current types and Where they are used
- Language
- English
- Price
- Free, no sign-up
- Updated
- Nov 7, 2026
Frequently asked questions
What is a discriminated union?
A type made of several variants that share a literal field, such as status: "paid" or "shipped". TypeScript uses that field to know which other fields exist in each variant.
What are branded types for IDs good for?
They make the compiler reject a CustomerId where an OrderId is expected, even though both are strings. They prevent silent bugs that are hard to catch in code review.
What is this prompt for?
Redesign your TypeScript domain types so impossible states do not compile: discriminated unions, branded IDs, exhaustive switches and validation at system boundaries.
Which AI tools does it work with?
It is designed for ChatGPT, Claude, Gemini and Microsoft Copilot. We have not verified it in these tools yet, so results may vary between them.
How do I customize the prompt?
Click “Customize” and fill in the variables (Domain, Current types and Where they are used). The text updates instantly and you can then copy it in one click. Values are stored only in your browser.
Do I need experience to use it?
It is advanced: it is meant for people with experience in the topic who can evaluate and adjust the result.
Is this prompt free?
Yes. Every HomeCasa Prompts prompt is free, with no sign-up or subscription. You can also use them in professional projects.
Can I trust the AI output?
Treat it as a high-quality draft: always check data, figures and important claims before publishing or making decisions.