One Schema, Ten Formats: Understanding TalkingSchema's Export System
A common frustration in backend development: you've designed a schema in one context (a diagramming tool, a migration file, a Prisma schema), and now you need it in a different format for a different purpose. You end up maintaining multiple representations of the same model — and keeping them in sync manually.
TalkingSchema takes a different approach. The schema is defined once. Any export format is generated from that single source of truth, on demand, without touching the original.
The ten formats
Here's a quick reference for when you'd reach for each one:
| Format | Use case |
|---|---|
| PostgreSQL | Direct DDL for Postgres, Supabase, Neon |
| MySQL | DDL for MySQL 8+ or PlanetScale |
| SQLite | Embedded/local databases, mobile apps |
| DBML | Database Markup Language — human-readable, shareable |
| JSON | Structured representation for tooling or APIs |
| Prisma | Node.js/TypeScript ORM for Vercel, Railway, etc. |
| Drizzle | TypeScript-first ORM with SQL-like syntax |
| TypeScript + Zod | Type-safe schema for frontend validation |
| OpenAPI | REST API specification with schema components |
| GraphQL | Type definitions for GraphQL servers |
Same schema, different outputs
Here's a minimal example — a users table with an email and a created_at field — rendered in four different formats to show what the same model looks like across contexts.
PostgreSQL:
CREATE TABLE users (
id BIGSERIAL PRIMARY KEY,
email TEXT NOT NULL UNIQUE,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
Prisma:
model User {
id Int @id @default(autoincrement())
email String @unique
createdAt DateTime @default(now())
@@map("users")
}
Drizzle:
import { pgTable, bigserial, text, timestamp } from "drizzle-orm/pg-core";
export const users = pgTable("users", {
id: bigserial("id", { mode: "number" }).primaryKey(),
email: text("email").notNull().unique(),
createdAt: timestamp("created_at", { withTimezone: true })
.notNull()
.defaultNow(),
});
TypeScript + Zod:
import { z } from "zod";
export const UserSchema = z.object({
id: z.number().int().positive(),
email: z.string().email(),
createdAt: z.coerce.date(),
});
export type User = z.infer<typeof UserSchema>;
DBML: the shareable format
DBML (Database Markup Language) deserves a special mention. Unlike SQL dialects, DBML is designed to be readable by non-engineers — you can paste it into a doc, a PR description, or a Slack message and it reads naturally:
Table users {
id bigint [pk, increment]
email text [not null, unique]
created_at timestamptz [not null, default: `now()`]
}
Table orders {
id bigint [pk, increment]
user_id bigint [not null, ref: > users.id]
total_cents int [not null]
status text [not null, default: 'pending']
}
The ref: > users.id notation makes the relationship direction explicit at a glance. DBML is also the format TalkingSchema uses internally — which means it's always up to date with whatever you've built in the canvas.
When to export
There's no single right time to export. A few common workflows:
- Start of a project: Design in TalkingSchema, export SQL to seed your migration tool.
- During a review: Export DBML to include in your PR description for reviewers who don't want to open a diagram.
- Onboarding a new service: Export Prisma or Drizzle to bootstrap a new TypeScript service against an existing schema.
- API documentation: Export OpenAPI to generate server stubs or client SDKs.
- Frontend validation: Export TypeScript + Zod to keep form validation aligned with your database constraints.
The key property is that any export reflects the current state of the schema — not a stale snapshot from six months ago.
All export formats are available from the Export button on any thread. If there's a format you need that isn't here yet, let us know.