Turso is joining Supabase to give every agent its own database Read the news →

Databases as Variables: What Unlimited Databases Actually Changes

What changes when a database is free to create and costs only storage while idle.

Cover image for Databases as Variables: What Unlimited Databases Actually Changes

Every SaaS data model I've seen starts with the same bit of arithmetic. You have 10,000 users. You know a database per tenant would give you cleaner isolation and a shorter compliance conversation, and that deleting an account would mean deleting one database. You also know that 10,000 databases means 10,000 things to provision, monitor, migrate, and pay for. So you add a tenant_id column to every table, scope every query to the current tenant, and move on.

For a lot of products, that's still the right call. But if cost is what decided it, check whether that cost still exists.

So change one input. A database is free to create and costs only storage while idle, so 10,000 of them cost about what 10,000 small files cost. The arithmetic flips, and a many-database architecture starts to look like a sensible default for many products.

This post covers how Turso Cloud pricing works, which patterns become practical, and which constraints are still there. If you're weighing this for a multi-tenant SaaS product specifically, our post on the many-database architecture is the full decision guide.

#No limit on active databases

Paid Turso Cloud plans have included unlimited databases for a while. The catch was a limit on active databases, meaning any database that received at least one query in a billing cycle. We added that metric because some of our infrastructure costs scaled with active databases, but it produced exactly the question we never wanted anyone asking: "should I add a database here?" As the announcement post in May put it, we couldn't live with ourselves.

The scenario we heard most was that a team would have thousands of tenants who barely touched their data, yet each one ran just enough queries in a month to count as active. Paying for all of them as active databases made the per-tenant model expensive at exactly the scale where it should pay off. That's the trade-off Turso exists to remove.

So we removed the limit: every paid plan now includes unlimited databases with no active-database accounting. The Developer plan starts at $4.99/month and includes 9 GB of storage, 2.5 billion rows read, and 25 million rows written. Database count isn't metered at all, and idle databases cost only storage.

This works because of how Turso Cloud stores data. A Turso database is a file, not a long-running server process. In Turso Cloud, that file lives as a set of 128 kB segments in object storage (the What is Turso page explains the storage layer in more detail), so an idle database has nothing running: nothing to scale to zero, and no cold start when a query finally shows up. A small tenant database of a few hundred kilobytes barely registers against a 9 GB allowance, so you can fit tens of thousands of them before storage becomes a concern.

#Thinking in databases the way you think in variables

You don't hold a design review before allocating a hash map. You create it where you need it and drop it when you're done (at least, I hope that's what you do!). One of our goals has been to make databases as easy to create as variables in your code, and removing the active limit is what makes that comparison hold up in practice.

Most of the change is in where isolation comes from. In a shared database, everyone's rows live together and separation is something you enforce, either with a WHERE tenant_id = ? in every query or with row-level security policies that apply it for you. Done well, that works. When databases are cheap, you get another starting point: begin from separation, then decide what actually needs to be shared. A query against tenant A's database can't return tenant B's rows, because tenant B's rows aren't in it.

Compute went through something similar. Before functions-as-a-service, you thought hard about how many servers to run and how to pack work onto them. Once an idle function cost nothing, people stopped rationing them. Databases are heading the same way, and the useful design question becomes "what's the natural unit of isolation in this system?"

#Patterns that stop being expensive

The most direct one is a database per tenant, at whatever scale you reach. Backup, point-in-time restore, and deletion all operate on one customer without touching anyone else. When a prospect asks where their data lives, you can answer "in its own database, reachable only with a token scoped to it," and what's left to verify is the small routing layer that picks the right database for each request. And the 8,000 tenants who log in once a month cost their storage footprint and nothing more.

AI agents follow the same logic on a shorter timescale. An agent gets a fresh database at the start of a session for its memory, tool call results, and intermediate state. Branching is instant, so an agent can also fork its state, try something, and roll back if it fails. When the session ends, you archive the database or delete it. Under the old model, every session database counted as active in the month it ran, so a busy agent platform paid for thousands of short-lived databases. Now each one costs only its storage and the reads and writes it actually does.

Experiments are a good fit too. If an A/B test needs a different data shape for the treatment group, give the experiment its own database instead of adding nullable columns to the production schema. When the test wraps up, there's nothing to migrate out of the main tables. These databases get heavy use for a few weeks and then go quiet, which is the usage profile that active-database pricing used to penalize.

Regulated products get a more structural approach. A therapy platform or a legal document service can keep each user's records in a separate database, which keeps every user's data isolated for compliance even when most users are rarely active. Separate databases are easier to walk an auditor through than a set of policy definitions. You still need proper access controls and agreements in place, but the isolation itself no longer has to be proven query by query.

Preview environments are the last one. Each pull request can get its own database, and because branching in Turso Cloud is a metadata-only, copy-on-write operation, a new branch is effectively instant. Branch from a sanitized copy of your data rather than production itself, and throw the branch away when the PR merges (branching doesn't add usage cost).

#The provisioning layer

The new code in a database-per-whatever design is small: a function that creates a database when a new entity appears and returns credentials for it. With the Platform API and the @tursodatabase/api client, that's two calls. Seeding from an empty template database means every new tenant starts with your current schema already applied.

import { createClient } from "@tursodatabase/api";

// Use a token scoped to the "tenants" group, not your whole org
const turso = createClient({
  org: process.env.TURSO_ORG!,
  token: process.env.TURSO_API_TOKEN!,
});

// Called when a tenant signs up
export async function provisionTenant(tenantId: string) {
  const db = await turso.databases.create(`tenant-${tenantId}`, {
    group: "tenants",
    seed: { type: "database", name: "tenant-template" },
  });
  const { jwt } = await turso.databases.createToken(db.name);
  return { url: `https://${db.hostname}`, authToken: jwt };
}

Store the URL and token, encrypted, alongside the tenant record. Your request handler looks up the tenant, opens a connection with @tursodatabase/serverless, and everything below that point is ordinary single-tenant code:

import { connect } from "@tursodatabase/serverless";

export async function listInvoices(tenant: {
  dbUrl: string;
  dbToken: string;
}) {
  const conn = connect({
    url: tenant.dbUrl,
    authToken: tenant.dbToken,
  });
  const stmt = conn.prepare(
    "SELECT id, total, status FROM invoices ORDER BY created_at DESC"
  );
  return await stmt.all();
}

There's no tenant_id filter in that query, and nothing to forget. Our many-database architecture post covers the rest of this layer, including tokens issued by your auth provider and which driver to use with an ORM.

#When a shared schema is still the right call

Cheaper databases change the cost argument, but not every argument. Here's the short version of what I usually recommend:

  • Start with a shared schema if you're early, your customers aren't regulated, and nobody has asked about isolation yet. It's also a strong fit when you have thousands of tenants with small, similar workloads, when cross-tenant analytics are central to the product, or when documented logical isolation satisfies your customers' compliance requirements. If you already run Postgres well and your row-level security policies are tested, that's a perfectly good place to be.
  • Take database-per-tenant seriously from day one if you sell into healthcare, finance, or legal, if enterprise deals hinge on the security review, or if per-tenant backup, restore, export, or data residency are part of the product.
  • Or mix the two. A common middle path keeps the long tail of small tenants on a shared schema and gives enterprise or regulated accounts their own databases, especially as it's now much cheaper with an idle database costing only storage.

My post on the many-database architecture walks through each of these in detail.

#What's still hard

Running many databases, even cheap ones, still takes work.

Cross-tenant queries need their own path. Platform analytics and billing rollups both want to look across databases, and at fleet scale that means a pipeline that reads from each database and lands the data in a central store. Change data capture can make that pipeline incremental, so it only picks up what changed. It's available on Turso Cloud databases that run on the Turso Database engine.

Migrations become a fleet operation when tenants share a schema. In a single shared database you run ALTER TABLE once. With 50,000 databases you need a runner that lists databases through the Platform API, applies the change to each one, records which succeeded, and retries the stragglers. You only have to build it once, but build it before you have 50,000 tenants. Backward-compatible migrations help a lot here, since some databases will be a version ahead of others for a while. If each tenant defines its own schema, there's no fleet-wide migration to run: each database's schema changes on its own.

Some products never had a shared schema to begin with. In a CRM, each customer defines its own objects and fields. In a no-code builder, users design their own tables. An AI agent may create tables as it works. Squeezing all of that into one shared schema usually ends in generic key-value tables or JSON columns that are hard to query and harder to index. A database per tenant lets each one have exactly the schema it needs.

Routing and monitoring change shape too. Your application needs a small layer that maps each request to the right database, and when something breaks, it may break for exactly one tenant.

Finally, unlimited databases start with the Developer plan; the Free plan includes 100. And unlimited databases doesn't mean unlimited storage or rows. A fleet full of real data costs what its data costs, so storage is the number to watch as you grow.

#Where this leaves tenant_id

Most of us reached for tenant_id because a database per tenant cost too much to justify. On Turso Cloud, that reason is gone. Plenty of other good reasons to share a schema remain, and for many products they'll win. But if cost was the only thing holding you back, isolation by construction is easier to reason about than isolation you enforce in every query, and when construction is a single API call, it's easy to try.

Plan details are on the pricing page, and the Platform API docs cover provisioning, tokens, and groups. You can try Turso Cloud free with up to 100 databases. If you build something on a database-per-agent or database-per-user design, come show us in the Turso Discord!