Skip to main content

One Schema, Ten Formats: Understanding TalkingSchema's Export System

· 4 min read
Karan Patel
Founder, CEO

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:

FormatUse case
PostgreSQLDirect DDL for Postgres, Supabase, Neon
MySQLDDL for MySQL 8+ or PlanetScale
SQLiteEmbedded/local databases, mobile apps
DBMLDatabase Markup Language — human-readable, shareable
JSONStructured representation for tooling or APIs
PrismaNode.js/TypeScript ORM for Vercel, Railway, etc.
DrizzleTypeScript-first ORM with SQL-like syntax
TypeScript + ZodType-safe schema for frontend validation
OpenAPIREST API specification with schema components
GraphQLType 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.