Typebase.

Changelog

What changed in each release of typebase-io and typebase-io-cli.

Last updated on

typebase-io and typebase-io-cli are released together and always share a version number, so upgrade them as a pair:

npm install typebase-io@latest
npm install -D typebase-io-cli@latest

Run npx typebase-io-cli codegen after every upgrade. _generated/ is written by the version that generated it, so a folder left over from an older release can describe a context your server no longer provides.

0.1.23

2026-10-07.

Added

  • The CLI collects anonymous usage data: the command you ran, the options you passed, and your CLI, Node and OS versions. Option values are sent only when they come from a fixed list, such as --provider, so URLs, paths and secrets never leave your machine. The first command in a project prints a notice once. Opt out with "telemetry": { "enabled": false } in typebase.json, or with TYPEBASE_TELEMETRY_DISABLED=1 or DO_NOT_TRACK=1.

0.1.22

2026-10-05.

Changed

  • q.defineRelations requires every table your schema exports. A table added to schema.ts and left out of relations.ts is now a type error on the call that names it, such as Property 'events' is missing in type '{ todos: {}; }', instead of passing silently. Register a table with no relations as {}.

Fixed

  • auth generate writes a relations.ts that type-checks when your q.defineRelations callback takes no parameter. It wrote the auth relations as r.one.users(...) either way, so () => ({ todos: {} }) ended in Cannot find name 'r'. A callback without a parameter now gets r added, and one that names it differently keeps its name, which the auth relations are written through.

0.1.21

2026-10-02.

Added

  • start logs every request the local server answers, on one line once the response is sent: the method, the path, the status, and how long it took. Until now a local run printed nothing for a request that worked and only the bare error for one that didn't, so finding out what your frontend actually called meant adding console.log to your actions.
    • A request to an action names the action and the input it was called with, as the client sent it, so a request your schema rejected still shows what was rejected.
    • Auth and local storage requests are logged too, without their body, so the password a sign-in sends never reaches your terminal. Query strings are left out of every path, since links such as email verification carry their token there.
    • A 4xx is logged as a warning and a 5xx as an error, and the error an action threw is printed under its request line with its stack. An auth or storage handler that throws gets a line ending in failed.
    • Only start builds a server that logs. generate-server and deploy build the same server as before.

Fixed

  • An auth.ts that writes better-auth hooks with createAuthMiddleware and rejects requests with AuthError, both from typebase-io/server, builds a server that starts. The generated auth file dropped every typebase-io import while keeping the code that used them, so the server crashed at boot with createAuthMiddleware is not defined, and AuthError would have failed the first time a hook threw it. Both are now imported from better-auth/api, where they come from, and keep any local name you gave them.

Changed

  • typebase-io/server now exports only the API you write against. Action, filterActions, createPublisher, createStorage, and the ActionBuilder and GetDBBuilder types moved to typebase-io/internal, and typebase-io/server/local-storage is now typebase-io/internal/local-storage. They exist for the code the CLI generates and were never meant to be imported by hand, so your editor's autocomplete on typebase-io/server no longer offers them. Code you write is unaffected unless it imported one of them directly.
  • Run codegen after upgrading. A _generated/server.ts written by an older CLI still imports filterActions and the builder types from typebase-io/server, and stops type-checking until it is regenerated. deploy, start, and generate-server regenerate it before every build, so a server they build is never affected.

0.1.20

2026-09-30.

Fixed

  • The db publisher delivers events with the types they were published with. A Date in a payload used to reach subscribers, and your client, as an ISO string, because events are stored in a jsonb column and JSON has no date type; bigint, Set, Map, and URL values were flattened the same way. Payloads are now stored with the same serializer oRPC uses to send them to the client, so createdAt: z.date() arrives as a Date. Events stored before this release are still delivered exactly as they were, so a client resuming across the upgrade misses nothing.

Changed

  • Publishing a payload that holds a Blob or File now fails straight away, naming the event, instead of storing an empty object in its place. The events table only holds JSON: upload the file to storage and publish its key.

0.1.19

2026-09-28.

Fixed

  • deploy to Vercel serves your server again. Since 0.1.17 split the generated entry point into src/index.js and src/server.js, every Vercel deploy failed at boot with Invalid export found in module "/var/task/src/server.js". The default export must be a function or server. Vercel's Hono preset only accepts an entry file that imports hono by name, and checks src/index before src/server; src/index.js no longer imported it, so Vercel skipped it and loaded src/server.js, which has no default export. The Hono entry file now imports hono itself, so Vercel picks src/index.js. package.json's main didn't help, because Vercel only falls back to it when no file matches.
  • The generated entry point imports "dotenv/config" with double quotes, like every other import in the generated server.

0.1.18

2026-09-28.

Added

  • defineStorage: declare a storage provider and named buckets in typebase/storage.ts, and every action gets a typed storage. Until now, files meant leaving Typebase: picking a storage service, creating one bucket per environment by hand, copying credentials between dashboards, and wiring an SDK into every action that needed one.

    • The provider is vercel (Blob), cloudflare (R2), or filesystem, chosen independently of your server provider, so a Worker can keep its files in Vercel Blob and a Deno Deploy server can use either.
    • Each bucket is public or private. storage.bucket(name) only accepts a declared name, publicUrl exists only on public buckets and signedUrl only on private ones, so a misspelled bucket or a permanent link to a private file is a type error. Every bucket also has signedUploadUrl, for browser uploads that never pass through your server, and files-sdk's upload, download, head, exists, delete, copy, move, list, listAll, and search. Per-bucket prefix, plugins, and hooks are passed to files-sdk unchanged.
    • Every bucket exists once per target, named <project>-<bucket>-<target>. The project part is chosen on the first sync and frozen in typebase.json, so renaming your server project or switching server providers never points your code at a fresh set of empty buckets.
  • storage sync <target> creates every declared bucket missing for the target, adopts the ones that exist, warns about buckets you no longer declare, and writes their credentials to your project-root .env. It never deletes or empties a bucket, and it stops without changing anything when an existing bucket's access differs from the declaration. On Cloudflare, public buckets get their r2.dev domain and each target gets one R2-only token scoped to its buckets, widened in place as buckets are added.

  • A local run keeps a vercel or cloudflare storage in local storage: the same buckets, on disk in the server cache, with public, signed, and signed upload URLs served and verified by the local server, so browser upload flows work locally. --dev-storage and --prod-storage run against the real buckets instead, independently of the database flags.

  • generate-server --local-storage builds a server that uses local storage and serves its files, at /storage or, for an embedded server, at --storage-path (server.storagePath). Without the flag, a generated server reads the same storage variables a deployed one does.

  • init --with-storage scaffolds a storage file with a public avatars and a private documents bucket, plus getAvatarUploadUrl and getDocumentUrl actions that use them.

Changed

  • deploy runs bucket sync for its target before it builds, then sets the storage credentials on your server provider next to DATABASE_URL, so a deploy never ships against a bucket that doesn't exist. Your own Vercel or Cloudflare login token is never put on the server.
  • codegen now also has to be rerun when storage.ts is added or removed, the same as publisher.ts. Editing an existing one doesn't.
  • generate-server seeds the storage credentials into a standalone server's .env, preferring their _DEV values, the same way it does for DATABASE_URL.

0.1.17

2026-09-07.

Added

  • generate-server --embedded generates a server for your existing application to mount, rather than one that owns a process of its own. Until now the only way into an existing application was to open the generated entry point, copy the handler construction out of it, and delete the rest — a fork that went stale on the next build, and in watch mode on every keystroke. An embedded build writes what you mount and regenerates it with everything else. { "server": { "embedded": true } } in typebase.json makes it the default; omitting it, or false, keeps the standalone server you already run, which behaves exactly as before.

    • Every adapter exports the same three names — typebaseHandler, router, and auth where the project has auth — so switching adapters doesn't touch your integration code. Only the mount's shape follows the adapter: a Fastify plugin, a Hono sub-application, a Connect-style Node middleware that calls next() when the request wasn't Typebase's, and a fetch handler for Bun, Deno, and Cloudflare. The page carries one example each.
    • It emits loose source — no package.json, tsconfig.json, or .env — because a nested package inside your application means a second copy of drizzle-orm, better-auth, or fastify loaded in one process. Your application owns the dependencies, the environment, and the cross-origin policy; the first build reports what it needs: an install command for your package manager listing only what your package.json lacks, warnings where your versions disagree with the generated ones, the environment keys the server reads, that CORS is now yours to configure, and the snippet that mounts it. Watch rebuilds stay quiet.
    • TypeScript output uses .js import specifiers for its own relative imports, so it compiles under an ordinary application tsconfig.json under nodenext and bundler alike. --output esm and --output cjs are still available, but an embedded build ships no manifest, so its .js files are interpreted using the host's "type" — match the format to the application you are mounting into.
    • An embedded Fastify build registers its catch-all content type parser inside the plugin, and Fastify scopes parsers to the plugin they belong to, so mounting Typebase leaves your other routes' body parsing alone — a webhook that verifies a signature over the raw body keeps working. A standalone Fastify server still registers it on the root instance, as it always has; it owns the process, so there is nothing else to affect.
    • The default output directory is _handler rather than _server, so a fragment meant for a host application is never confused with a runnable server. --out-dir and server.outDir still override it.
  • --actions-path and --auth-path, also server.actionsPath and server.authPath, move where an embedded server serves your actions and your auth. They are independent of each other, and default to /rpc and /api/auth, so a build that sets neither is unchanged. The auth path is written into the generated auth config as better-auth's basePath too, so the framework and the route can't disagree — and a basePath you set in your own auth.ts wins, with a warning saying so. A custom actions path changes the URL your client points at. Both are embedded-only: passing either flag to a standalone build is an error, while a value inherited from typebase.json is ignored, so one configuration file still serves every command.

Changed

  • Every generated server now splits its entry point in two: src/server.ts builds the router, the RPC handler, the auth wiring, and the mount, and src/index.ts starts it listening. A standalone build emits both and behaves exactly as before, including the package.json entry point and start script. An embedded build emits only the first, which is why an embedded server can't drift from the runnable one — the runnable one is built on top of it.
  • Both modes now write a typebase-server.json marker next to the generated source, recording the adapter, the mode, the CLI version, the required dependencies with their versions, and the environment keys the server reads. The safety check that refuses to replace a directory it doesn't recognize reads it, which is what makes an embedded output directory inside your own source tree safe to regenerate. Directories generated before this release are still recognized by their @typebase-io/server package.json, so upgrading doesn't make your next build refuse to run. The same file is a machine-readable dependency list you can script an install from.
  • generate-server --port and --embedded now conflict, in either order, and --port is also rejected when server.embedded selected an embedded server: nothing would listen on it. A server.port inherited from typebase.json is ignored instead. start always builds a standalone server and ignores server.embedded entirely.

0.1.16

2026-09-02.

Added

  • start: one command that runs your Typebase server on your own machine. It builds the server, installs its dependencies, brings your database in step with your schema, starts it, and then rebuilds, re-syncs and restarts on every change inside typebase/. No directory change, no manual install, no separate database step. It replaces the four-step generate-server sequence for local work; generate-server is unchanged and remains the command for a server you want to open, read, or self-host.

    • The database is chosen explicitly and there is no fallback chain: no flag reads DATABASE_URL_LOCAL, --dev-database reads DATABASE_URL_DEV, --prod-database reads DATABASE_URL, and --database-url takes one directly. A project with a schema and an empty key stops immediately naming that key, rather than starting a server that dies seconds later on environment validation. A project with no schema skips the database step.
    • Push mode pushes and migrations mode applies pending migrations, both on every change under typebase/db/. The destructive-change prompt is preserved, with --skip-schema-changes-confirmation to answer it in advance. Drift warns and continues rather than refusing, matching db <target> migrate, so the loop keeps working while you are still deciding what the migration should say.
    • The generated server is built into a server cache outside your project, so a local run never adds a file to review, commit, or ignore, and typebase/_server/ is left untouched. It persists between runs, which is what keeps the loop fast, and prunes itself when the project that owns it is deleted.
    • The install and the database step are skipped on a rebuild when the content that feeds them has not changed, so editing an action never opens a database connection or pays for an install. Both checks live in memory, so restarting the command redoes everything.
    • The output format is chosen by asking your Node whether it can execute TypeScript: Node 22.18 or newer runs it directly and skips transpiling. --output overrides it. Other options: --port, --command, --install-command.
    • TYPEBASE_APP_URL_LOCAL is written to your project-root .env on every run, so a client reading TYPEBASE_APP_URL_LOCAL || TYPEBASE_APP_URL_DEV || TYPEBASE_APP_URL reaches a local run whenever one is going. Your database URLs are read and never written. An auth secret is generated and saved where a later deploy will find it.
    • A project with an auth.ts gets the local URL written into the generated server's auth config as baseURL, following --port, so better-auth can build callbacks and email links without the Base URL could not be determined warning. A baseURL you set in typebase/auth.ts yourself is left alone.
  • Migrations: an opt-in alternative to pushing, where schema changes are recorded as SQL files you read, edit, commit and review, and reach a database only when applied. A project is in migrations mode when typebase/db/migrations/ exists.

    • db migrations generate diffs your schema files against the last recorded migration and writes the difference. It is offline and contacts no database. --name names it, --custom gives you an empty file to write SQL into yourself, and --ignore-conflicts overrides the forked-history check.
    • db <target> migrate applies pending migrations to dev, prod, or, with db local migrate --url, a Postgres you run yourself. Each migration runs exactly once per target.
    • db migrations init adopts migrations on a project whose databases were built by push, recording a baseline and marking the databases that already have those tables as having applied it. It never provisions a target that has no database.
    • init --with-migrations starts a new project in migrations mode, with the scaffolded schema recorded as its first migration.
    • auth generate records a migration for the auth tables it adds.
    • Migrations ship with generate-server output and the deployed bundle, and the generated drizzle config points at them.
  • generate-server fills the generated server's .env from your project-root .env, so a server you run locally starts with the values the CLI already collected instead of you copying them across. Only the keys the server validates are copied — DATABASE_URL, BETTER_AUTH_SECRET, and whatever you declared in env.ts — and DATABASE_URL comes from DATABASE_URL_DEV when there is one, so a local server reaches the dev branch rather than production. Keys already in the file are never overwritten. Provider tokens are not copied, and deploy is unchanged: it syncs variables to the provider and never puts a .env in the bundle.

  • generate-server --command "<command>" runs a command inside the generated server once it has been generated. With --watch it restarts that command on every rebuild, so what is running is always the latest build: npx typebase-io-cli generate-server --watch --command "pnpm start". Stopping the watcher stops the command, including the server a package script started rather than just the script itself. A build that fails leaves the running command alone, and a command that exits does not stop the watcher.

  • --skip-confirmation on db <target> push and --skip-schema-changes-confirmation on deploy answer the destructive-change prompt in advance, for pipelines with nobody at the keyboard. The warnings are still printed either way, so the log records what was dropped.

Changed

  • db <target> push refuses to run in migrations mode and names the migrate command for that target. Projects that do not opt into migrations are unaffected.
  • deploy applies pending migrations instead of pushing when a project is in migrations mode, and refuses to deploy when your schema files hold changes no migration records. Note that migrations are applied before the new server ships.
  • db pull refuses in migrations mode without --force, and offers to adopt migrations in a project that has none.
  • The drizzle config written into a generated server points out at ./src/db/migrations rather than an unused ./drizzle directory.
  • generate-server writes a pnpm-workspace.yaml next to the generated package.json for pnpm projects, making the generated server a workspace root in its own right. Without it, installing inside a generated server that sits under a pnpm workspace walked up to your workspace root, installed your own projects instead, and left the generated server with no dependencies at all — while still reporting success. .yarnrc.yml for yarn berry and bunfig.toml for bun are written the same way; npm and yarn classic need no extra file.

0.1.15

2026-08-26.

Added

  • InferStreamEvent from typebase-io/server: give it the RouterOutputs entry of a streaming action and it gives you the type of one event, so you no longer have to unwrap the async iterable by hand to type the state a stream feeds.
  • The CLI checks itself against the installed typebase-io before running a command and warns when the two versions differ, since they are released together and a mismatched pair can generate code your server doesn't understand. npx typebase-io-cli --version prints the CLI's own version.

Changed

  • A stream declared without .output() now reports the same type as one declared with it. Before, its RouterOutputs entry was the generator type of the handler that happened to implement it — AsyncGenerator<Event, void, unknown> — so adding an output schema, or rewriting the handler as something other than a generator function, silently changed the action's public type. Both now describe the same event iterator, matching the value an oRPC client actually hands back. Nothing changes at runtime, but code that named AsyncGenerator explicitly to type a stream needs updating; InferStreamEvent is the replacement.

0.1.14

2026-08-24.

Added

  • Streaming actions: swap .handler() for .stream() and an action becomes an async generator that pushes events to the client over SSE for as long as it's listening. .output() describes one event rather than the whole response, and the generator gets signal and lastEventId on top of the usual handler context. The compiler enforces that a stream yields at least once, that events are yielded rather than returned, and that each one matches .output().
  • definePublisher: declare the events your actions publish in typebase/publisher.ts, and every action gets a typed publisher on its context. A mutation publishes an event, a stream subscribed to it forwards the event to every client watching. Event names and payloads are checked at compile time, and publish(name, payload, { tx }) joins a transaction so a rollback publishes nothing.
  • The db publisher provider keeps events in an events table in your own database, polls it on a loop shared by every subscriber on the instance, and tags each event with its row id, so a client that reconnects resumes from the last event it saw instead of missing whatever was published while it was away. The table is declared in your own db/schema.ts like any other.
  • withEventMeta and getEventMeta from typebase-io/server, for attaching an id or retry to an event and reading it back.
  • init --with-db-publisher: scaffolds publisher.ts, the events table, its relation, and example actions that publish and stream. Like --with-auth, it can't be combined with --skip-example.
  • consumeStream from typebase-io/client, for reading a stream with callbacks instead of for await. Create a client first, then pass the stream call: client.path.to.stream() for createRouterClient, or client.path.to.stream.call() for createTanstackQueryClient. It starts immediately and returns an unsubscribe function, so UI code must start it from the component's mount lifecycle and unsubscribe during cleanup rather than calling it during render.
  • typebase-io/client/plugins re-exports every oRPC client plugin, so ClientRetryPlugin and the rest work without adding a dependency. Streams don't reconnect on their own, so this is what you reach for to make a dropped connection resume.

Changed

  • codegen now also has to be rerun when publisher.ts is added or removed, the same as auth.ts and env.ts. Editing an existing one doesn't need it.

0.1.13

2026-08-19.

Added

  • db pull: read an existing PostgreSQL database and write it to db/schema.ts and db/relations.ts, then regenerate the types. Column types, defaults, primary keys, unique constraints, indexes, foreign keys, enums and views all come across, every table gets registered in relations (Drizzle only writes the ones that have a foreign key), and the result is written in the shorthand a Typebase schema is normally written in. It asks before replacing existing files unless you pass --force.

0.1.12

2026-08-17.

Added

  • config: prints the typebase.json JSON Schema with every option annotated with the value in effect and whether it came from a default. Read-only, and the one command that doesn't need typebase-io installed, so tooling can read a project's setup without reimplementing the schema or its defaults.
  • init now creates a typebase.json holding a $schema reference, so your editor autocompletes the config from the start. Values already in an existing file are kept.

0.1.11

2026-08-02.

Added

  • generate-server --watch keeps the command running and rebuilds typebase/_server/ whenever anything inside typebase/ changes. See Work locally.

0.1.10

2026-08-01.

Fixed

  • Simplified the env type, so editors show the keys you declared instead of an expanded internal type.

0.1.9

2026-08-01.

Added

  • defineEnv: declare the variables your server needs in typebase/env.ts and read them from the handler context as env, each one a plain string. The generated server validates the whole schema before it serves a single request, so a missing variable is a startup error instead of an undefined surfacing deep inside a handler later. DATABASE_URL and BETTER_AUTH_SECRET are added for you.
  • The generated server carries its own env module, and @t3-oss/env-core is added to its dependencies only when the project needs one.

Changed

  • Empty database and relation types are never rather than {}, so a project without a schema no longer offers a db you can't use.
  • A project with an auth.ts but no db/schema.ts is now refused instead of building a server whose auth could never work.
  • Removed skipLoadEnv.

Fixed

  • Types are generated before they are validated, so the first run type-checks against fresh types.
  • Type errors inside _generated/ are no longer reported as yours.

0.1.8

2026-07-29.

Added

  • logs <dev|prod>: stream runtime logs from Vercel, Cloudflare Workers or Deno Deploy until you stop it.
  • deploy <target> --logs tails the server as soon as the deploy goes live.

Fixed

  • proxyToTypebase strips x-forwarded-* headers from the proxied request, which some providers rejected.

0.1.7

2026-07-20.

Added

  • server.port is part of the config schema, so editors validate it in typebase.json.

Changed

  • generate-server removes the files an earlier run left behind instead of writing over them.

Fixed

  • init --with-auth writes the correct relations for the example.
  • Clearer failures instead of silent ones: an unreadable auth.ts, code that cannot be parsed, and relations that auth generate cannot update now stop with an explanation.

0.1.6

2026-07-03.

Added

  • RouterInputs and RouterOutputs in the generated server types, plus InferRouterInputs and InferRouterOutputs from typebase-io, so you can name an action's input or output type without redeclaring its shape. See Generated Code.

0.1.5

2026-07-03.

Fixed

  • auth generate adds the plugin import to the generated auth file when a plugin needs one.
  • A formatting error in generated files.

0.1.4

2026-07-01.

Changed

  • The CLI got a test suite, and CI now runs it on every change.

0.1.3

2026-06-10.

Changed

  • env <target> add takes --no-encrypted instead of --encrypted, so values are encrypted unless you opt out.

Fixed

  • Writing the project .env keeps the variables already in it.
  • Router generation handles nested action files correctly.
  • Type validation ignores _generated/.
  • A command run in a project without typebase-io installed says so, and failures exit with a non-zero code.
  • defineAuth warns when it cannot read trustedOrigins instead of carrying on with an empty list.

0.1.2

2026-06-08.

Added

  • Every built-in better-auth plugin is re-exported through typebase-io/server/auth-plugins and typebase-io/client/auth-plugins, so you don't have to depend on better-auth directly. See Auth.
  • init --skip-example still writes base db/schema.ts and db/relations.ts files.

Changed

  • deploy regenerates types as part of every deploy.

0.1.1

2026-06-03.

Fixed

  • defineAuth infers its options correctly, so plugin and provider config reaches the client types.
  • The example relations file generated with --with-auth registers the auth tables.

0.1.0

2026-05-13.

Added

  • The AI Skill: a single SKILL.md that teaches a coding agent the file layout, CLI commands and conventions.
  • generate-server --port, with a new default port.
  • Dev deploys save their URL to .env as TYPEBASE_APP_URL_DEV, so the dev and prod URLs coexist.
  • The generated server records a packageManager field and installs with the package manager your project uses.

Fixed

  • Vercel: a placeholder is deployed on a first dev deploy when there is no production deployment yet.
  • Neon: the CLI waits for a new dev branch to be ready, and no longer copies data into it.
  • Deno Deploy: fixed the deploy flow and a terminal read error.
  • Only actions end up on the router; helpers exported alongside them are ignored.

On this page