Like Supabase,
but code your
AI can read.
Add a typebase/ folder to your repo: database, server functions, auth, and file storage, all in TypeScript. No dashboard settings, no SQL policies. Every rule is code your AI can see.
npx typebase-io-cli initSERVER · typebase/actions/queries/todos.ts
import { action } from '../../_generated/server';export const getMany = action .handler(async ({ db }) => { return db.query.todos.findMany(); });FRONTEND · src/app/page.tsx
import { client } from '@/lib/typebase/client';export default async function Page() { const todos = await client.queries.todos.getMany(); // ^? { id: number; value: string; completed: boolean }[] return todos.map((t) => ( <li key={t.id}>{t.value}</li> ));}tl;dr: three files, database to UI
import { p } from 'typebase-io/db';export const todos = p.pgTable('todos', { id: p.integer().primaryKey().generatedAlwaysAsIdentity(), value: p.text().notNull(), completed: p.boolean().notNull().default(false),});Register this table in db/relations.ts to query it in your actions.
import { action } from '../../_generated/server';export const getMany = action .handler(async ({ db }) => { return db.query.todos.findMany(); });Your database credentials and server logic stay on the server.
import { client } from '@/lib/typebase/client';export default async function Page() { const todos = await client.queries.todos.getMany(); // ^? { id: number; value: string; completed: boolean }[] return todos.map((todo) => ( <div key={todo.id}>{todo.value}</div> ));}The client guide covers creating and connecting your typed client.
Give your data a home.
Define your Postgres tables in TypeScript. Typebase gives your server functions a typed database client.
Explore the database guideWrite the function. That’s your API.
An action is a function that runs on your server. Typebase handles the endpoint and carries its types to your frontend.
Explore the actions guideCall it like a local function.
Your frontend gets autocomplete and type checking, all the way back to your database. The call travels over HTTP; Typebase handles that part.
Explore the client guideYour agent’s
kind of backend.
Your schema, auth, and server functions live in the repo your agent is already working in. It can follow the code from a database table all the way to your frontend.
- Familiar tools. TypeScript, Drizzle, better-auth, and oRPC, with their own docs for your agent to read.
- Useful feedback. The type checker catches mismatches as your agent makes changes.
- A skill for the specifics. File conventions, commands, and setup instructions, ready for your agent to read.
Add a Typebase backend to this project. Read the project first. Install the Typebase skill with: npx skills add typebase-io/monorepo Read the skill before writing code. Ask what I’m building, what data I need, and whether users should sign in. Then install Typebase, scaffold the backend, and connect it to my existing frontend. Follow the skill and keep the project’s conventions. For private data, verify the session and check ownership in server actions. Validate inputs and accept only the fields each action needs. Run the project’s type checks and test the permission checks. Summarize your changes and stop before deploying. I’ll handle the first deployment and provider login.
Paste into Claude Code, Cursor, Codex, or your preferred coding agent.
Batteries included.
Wiring removed.
Built on Drizzle, better-auth, oRPC, and files-sdk. Familiar tools, working together in one folder.
Meet the toolkitA real Postgres database.
Define tables with Drizzle. Query them in your server functions. Your data lives in your own database.
db/schema.tsLogin, already accounted for.
Email and password, social sign-in, sessions. Configure better-auth in one file and use it in your actions.
auth.tsFiles, in buckets you declared.
Public and private buckets on Vercel Blob or Cloudflare R2, created for dev and prod by the CLI. Upload and hand out signed URLs from your actions.
storage.tsTypes that go the whole way.
From your database to your server to your frontend. Change a return value and your editor knows about it.
actions/RLS policies.
Plain TypeScript checks.
Alice should see Alice’s todos. Signed in is not the same as owner, and that gap is easy to miss in a policy, because nothing in your editor is checking it. Here is the same rule, written twice.
Other providers
trueauth.role() = 'authenticated'user_id = auth.uid()Policy 2 passes for anyone signed in. Bob can update Alice’s row, in every column, plus any column you add later. No type checker reads this.
Typebase.
import { authedAction } from '../custom-actions';export const getMine = authedAction .handler(async ({ db, user }) => { return db.query.todos.findMany({ where: { userId: user.id }, }); });authedAction checks the session, and the highlighted line narrows the rows to that user. Change the shape and the type checker tells you.
The DX of Convex.
The openness of Supabase.
A dashboard setting never shows up in a pull request. There’s no diff to review and no commit to revert, so your repo and your backend drift apart. A toggle someone flips on Tuesday can break production without a line of code changing. Your agent can’t see it either; it only reads the repo. In Typebase your schema, auth, and permission checks are files, reviewed like the rest of your app.
| Your choices | Typebase | Supabase | Convex |
|---|---|---|---|
| Backend logic | TypeScript actions | SQL functions and Edge Functions | Queries, mutations, and actions |
| Database | Postgres | Postgres | Proprietary |
| Type safety | End-to-end, always in sync | Generated, can drift | End to end |
| Realtime | Streaming actions over SSE | Realtime subscriptions | Reactive queries |
| Auth | better-auth, configured in one file | Built in, configured in the dashboard | Third-party providers |
| Storage | Vercel Blob or Cloudflare R2, declared in one file | Built in, managed in the dashboard | Built in |
| Hosting | Your providers, or a generated server | Supabase Cloud or self-hosted | Convex Cloud or self-hosted |
Explore Typebase. This is the whole backend.
Each file has one job. db/ defines your tables, anything you export from actions/ becomes an endpoint your frontend can call, and auth.ts gives those functions a session to check. One command regenerates the types and deploys the folder as a server.
- typebase/
- actions/
- mutations/
- queries/
- db/
import { z } from 'zod';import { ServerError } from 'typebase-io/server';import { action } from '../../_generated/server';import { todos } from '../../db/schema';export const create = action .input(z.object({ value: z.string().min(1) })) .handler(async ({ db, input, publisher }) => { const [todo] = await db.insert(todos).values(input).returning(); if (!todo) throw new ServerError('INTERNAL_SERVER_ERROR'); await publisher.publish('todo.created', todo); return todo; });Validate input, insert a todo, and publish a typed event.
import { z } from 'zod';import { action } from '../../_generated/server';export const getByStatus = action .input(z.object({ completed: z.boolean() })) .handler(async ({ db, input }) => { return db.query.todos.findMany({ where: { completed: input.completed }, }); });Validate the input, filter the read, and the return type reaches your frontend.
import { q } from 'typebase-io/db';import * as schema from './schema';export const relations = q.defineRelations(schema, (r) => ({ todos: { user: r.one.users({ from: r.todos.userId, to: r.users.id, }), }, users: { todos: r.many.todos(), },}));Register each table, and describe how they join for typed queries.
import { p } from 'typebase-io/db';export const users = p.pgTable('users', { id: p.integer().primaryKey().generatedAlwaysAsIdentity(), name: p.varchar({ length: 255 }).notNull(), email: p.varchar({ length: 255 }).notNull().unique(),});export const todos = p.pgTable('todos', { id: p.integer().primaryKey().generatedAlwaysAsIdentity(), value: p.varchar({ length: 255 }).notNull(), completed: p.boolean().notNull(), userId: p.integer().notNull().references(() => users.id),});Every table lives in one file. references() is what creates the foreign key in Postgres.
import { defineAuth } from 'typebase-io/server';export const auth = defineAuth({ trustedOrigins: ['http://localhost:3000'], emailAndPassword: { enabled: true },});Configure better-auth. Use an authenticated action for private data.
import { defineEnv } from 'typebase-io/server';import { z } from 'zod';export const env = defineEnv({ EXAMPLE_API_KEY: z.string().min(1),});Validate the environment variables your actions need.
import { definePublisher } from 'typebase-io/server';import { z } from 'zod';export const publisher = definePublisher({ provider: 'db', events: { 'todo.created': z.object({ id: z.number(), value: z.string(), completed: z.boolean(), }), },});Declare typed events to publish from mutations and consume in streams.
import { defineStorage } from 'typebase-io/server';export const storage = defineStorage({ provider: 'vercel', buckets: { avatars: { access: 'public' }, documents: { access: 'private' }, },});Declare public and private buckets. The CLI creates them for dev and prod, and bucket names are type-checked.
Real backend.
Imaginary fan club.
None of these people exist. Their taste in backend tooling is excellent tho.
I deleted 40,000 lines of REST plumbing last quarter. My tech lead cried. I think they were happy tears. I have stopped asking.
Before Typebase I had three acronyms in my pipeline: REST, gRPC, and WHY. Now I have one: fn(). I have never been happier and my Oura ring agrees.
I’ve told four separate therapists about Typebase. Two stopped taking me as a client. The other two are now shipping an app with it.
My co-founder asked where the auth lives. I said “a file called auth.ts.” He hasn’t spoken to me since. I assume he’s impressed.
We replaced 14 microservices with one folder. The DevOps team threw me a party. The party was a meeting. The meeting was about layoffs.
10/10 would make my backend a folder again.
* The people are made up. The code above isn’t.
Fair questions,
straight answers.
Watch Typebase in 100 secondsYes. Install Typebase in your existing JavaScript or TypeScript project. Your frontend stays where it is; the backend goes in a typebase/ folder. Start with the getting started guide.
Typebase is a library and CLI. Your server runs in your own hosting account, and the database and files are yours too. The CLI deploys to Vercel, Cloudflare Workers, or Deno Deploy with a Neon database, and keeps files in Vercel Blob or Cloudflare R2. You can also generate a server to host yourself.
Typebase is free and open source under the MIT license. You pay your hosting and database providers for their usage. Typebase doesn’t add a platform fee.
Typebase keeps everything in one place. Your tables, your server functions, and your auth are files in the repo you already have, not settings spread across a dashboard and a handful of separate services. There is no new paradigm to learn either: where Convex asks you to take on its document model, its query API, and its reactive defaults, Typebase is TypeScript over ordinary Postgres, with Drizzle and better-auth doing the work you already know them for. The goes row by row.
Yes. The start command builds your server and restarts it as you edit. If you use a database, connect your own local Postgres or a dev database. Follow the local development guide.
Good idea.
Now give it a backend.
Start with one function. Build the rest as you go.
npm i typebase-io && npm i -D typebase-io-cli