What comes after InstantDB?

Instant has announced that its team is joining OpenAI. New signups are closed, and existing applications must migrate before Instant Cloud shuts down on August 31, 2027. The code remains open source, and self-hosting is an option.

Instant brings relational queries, optimistic updates, caching and offline behaviour into a client designed for interactive applications. If your application depends on that combination, a migration needs to account for all of it, including authentication, permissions and the way pending writes reach the server.

Choosing a migration path

For an existing application that needs the fewest changes, self-hosting Instant is the most direct path. You keep the query model, SDKs, authentication, permissions, presence and dashboard.

Consider Datahike if you also want queryable history, branches for proposed changes, or more choice about where the database runs. An editor can query a draft before accepting it; a reporting job can use the data as it stood last Tuesday. The same database engine can read those values in a browser, an application process or a dedicated server, using storage you control.

Moving from Instant to Datahike involves adapting queries, transactions and application integration. The starting point depends on what your application needs:

Application needStarting point
Keep an existing Instant application runningSelf-host Instant
Query a server from an online web applicationdatahike/remote with Datahike Server
Query locally, read offline and render optimistic updatesdatahike/kabel with Datahike Server
Own the database inside a browser, Node or JVM applicationEmbedded Datahike
Store personal data directly in a bucket from the browserdatahike/s3, with temporary scoped credentials

The three browser client modes share core operations such as connect, transact, q and pull. You choose an npm entry point and configure its storage and connection. The thin client exposes the operations that can run remotely; the embedded engine and replica also support local database operations.

Where queries run and data lives

For an online web application that can query over the network, start with the thin client. It sends queries and writes to Datahike Server and keeps handles to the server’s database values. The page carries a small HTTP client, and the server owns storage and query execution.

A replica is useful when the interface makes frequent queries, needs to read while disconnected, or should render edits before the server responds. It keeps a local database in memory and IndexedDB, queries that database, and sends writes through the server. The server applies each transaction and streams the changed storage back.

Embedded Datahike gives the application direct ownership of the database. The ordinary browser build supports an in-memory database, as in the example below; Node can also use a file store. Direct browser access to S3 uses the separate datahike/s3 entry and a local memory tier.

Browser modeQueries runWrites goJavaScript download
Thin client, datahike/remoteOn the serverTo the serverAbout 5 KB
Replica, datahike/kabelAgainst the local replicaTo the server, then replicate backAbout 613 KB
Embedded, datahikeLocallyThrough the application’s configured storeAbout 422 KB

Sizes are gzipped JavaScript from the published Datahike 0.8.1881 npm package, measured with gzip -9 on September 6, 2026, rounded to decimal KB. They exclude application code and database contents. The direct-S3 build is about 504 KB.

Thin clients can subscribe to a stream of committed changes and re-query when the application needs fresh results. Reads against an immutable snapshot can also be cached: small query arguments travel in a GET URL identifying the snapshot, while larger requests use POST. The TypeScript client caches snapshot results in memory, and the server’s cache configuration controls HTTP caching. The thin client does not provide Datahike’s optimistic overlay, which requires a local database.

How the same index travels

Datahike stores its persistent indexes as immutable, content-addressed blocks. A transaction writes changed portions of the index and produces a new database value that shares unchanged structure with its predecessor. A stored database value identifies the roots of those indexes.

A browser replica receives these blocks and queries them with the same engine that runs on the server. Synchronization therefore preserves the database’s index representation across stores and processes. The thin client leaves that engine on the server; an embedded application runs it in its own process.

The same storage structure supports history and branching. An older retained root identifies an earlier database value, and a new branch starts with roots over shared blocks. This lets the application query a past state or develop a proposal without copying the entire database. The writer coordinates updates to the accepted state, while the storage backend determines how blocks are persisted and shared.

Queries and application services

Datalog supports joins, aggregates, multiple database values as query inputs and recursive rules. Those rules are useful for provenance chains, threaded comments, dependency graphs and bill-of-materials rollups. The playground has a runnable example.

Instant’s schema feeds its TypeScript API. Datahike leaves query-result typing to the caller and validates query text at runtime. Adopting Datalog gives the application more control over queries, but it also means rewriting the InstaQL queries it uses today.

CapabilityInstantDatahike
Query languageInstaQLDatalog, including recursive rules and aggregates
Schema-derived query typesGenerated from schemaQuery results typed by the caller
Historical database valuesNot centralQueryable historical values
BranchingNot providedStable API with shared storage
Optimistic writesAvailable in the clientBeta overlay over a local database
Pending writes survive reloadDurable queueIn-memory queue
AuthenticationUsers and login flows includedExternal identity provider; JWT validation in Datahike Server
PermissionsObject and field rulesPer-database roles in Datahike Server; custom server policy
File uploads and presenceBuilt inApplication integration required
Framework integrationFramework SDKs and reactive queriesJS/TS and Clojure APIs; application owns UI integration

Datahike Server can validate JSON Web Tokens issued by your application or an identity provider. Login screens, account management and token issuance remain with that service. With a system database configured, the server also keeps a permission graph: server administrators provision databases, and each database has owners, writers and readers. These relationships govern HTTP calls, Kabel writes and replica subscriptions, and can be managed over the HTTP API.

Permissions apply to a whole database, including its branches. A database per tenant or per user works well when that matches who may see the data. A browser replica receives the database it is permitted to read; a local query filter cannot conceal facts already on the device. Applications needing finer read access can expose domain operations that return only authorized results. A custom server policy can also inspect transaction content and reject writes.

You can run Datahike Server as a container or embed its HTTP routes in your own service. An application hosting the Kabel listener can register named domain functions, such as shop/place-order, that receive the caller’s identity. This gives the application a place to enforce its business rules and coordinate work across databases. The browser replica guide covers server configuration, tokens, permissions and remote functions.

The server provides HTTP and Kabel WebSocket access, with an optional beta PostgreSQL listener. PostgreSQL has its own authentication settings and does not yet map users to the HTTP/Kabel permission graph.

React applications connect the ordered snapshot stream from optimisticListen to useSyncExternalStore themselves. optimisticListen returns an unsubscribe function. Simmis uses the overlay with server-side domain operations in its collaborative editor; its source shows that application integration.

Pending, branched and historical database values

Instant described the synchronization challenge years ago in A Graph-Based Firebase: optimistic changes need ordering, undo and a policy for dependent writes when an earlier change fails.

Optimistic writes

Datahike applies pending transactions through its ordinary transaction machinery, including schema validation, uniqueness, entity resolution and cardinality checks.

npm install datahike

The example runs in the browser and remains editable. It was tested with Datahike 0.8.1865.

Optimistic updates · runs in your browser
Show codeHide code
const config = {
  store: {
    backend: ':memory',
    id: d.randomUuid()
  },
  'schema-flexibility': ':write'
};

await d.createDatabase(config);
const conn = await d.connect(config);

await d.transact(conn, [
  {
    'db/ident': ':task/id',
    'db/valueType': ':db.type/uuid',
    'db/cardinality': ':db.cardinality/one',
    'db/unique': ':db.unique/identity'
  },
  {
    'db/ident': ':task/title',
    'db/valueType': ':db.type/string',
    'db/cardinality': ':db.cardinality/one'
  }
]);

const overlay = d.openOptimistic(conn, {});
const taskId = d.randomUuid();

const titles = (db) =>
  d.q('[:find ?t :where [?e :task/title ?t]]', db);

d.optimisticListen(overlay, (event) => {
  console.log(
    'revision', event.revision,
    '| cause', event.cause.type,
    '| changes', event.changes ? 'exact' : 'invalidate'
  );
});

console.log(
  'Before:',
  await titles(d.optimisticDb(overlay))
);

// Returns immediately.
// The durable write proceeds separately.
const handle = d.optimisticTransact(overlay, [
  {
    'task/id': taskId,
    'task/title': 'Rendered instantly'
  }
]);

// The snapshot lookup is synchronous. The query returns a Promise.
// The overlay published this local result before durable dispatch began.
console.log(
  'Immediately after:',
  await titles(d.optimisticDb(overlay))
);

// Eventually the writer tells us what happened.
const result = await handle.result;

console.log(
  'Writer said:',
  result.status
);

// Invalid transactions are predicted using the same
// transaction semantics, and rejected writes disappear.
const bad = d.optimisticTransact(overlay, [
  { 'task/title': 42 }
]);

console.log(
  'Rejected write:',
  (await bad.result).status
);

d.closeOptimistic(overlay);

Immediately after optimisticTransact, d.optimisticDb(overlay) returns the database value with the pending transaction applied. The snapshot lookup is synchronous, although the JavaScript query API still returns a Promise. The overlay publishes this local result before durable dispatch begins.

The durable writer may produce a different result if its base has advanced. The local prediction still follows the same schema, uniqueness, entity-resolution and cardinality rules.

Branches

Some useful states should remain separate from the current database: an agent’s proposal, a draft of a shared workspace or the database before yesterday’s import. Immutable database values let each of these start from a known state and develop independently.

New states structurally share storage with old ones, so forking takes a few writes rather than a full copy. The Git Model for Databases explains the sharing, while Branches as Values, Merges as Queries covers the versioning API and merges.

A bulk reorganization can run against a branch, allowing the application to display a preview using its existing queries. Historical asOf databases use the same query interface, so the application can run a query against the present or an earlier value.

An agent can write proposed edits to a branch while a person continues working with the current database:

current database

       ├──── human continues here

       └──── agent branch ──▶ proposed database

The application can display the proposal and compare it with the original. If a person approves the changes, the application supplies the transaction data for a merge. An optimistic value follows the outcome of its pending transaction; a branch remains available for further work until the application removes it.

Connection recovery and pending writes

The browser replica API and optimistic overlay are Beta. The thin client and its change stream are newer; the changelog labels them Experimental. The branch and merge APIs are marked stable in the API specification.

Kabel can maintain a peer connection with backoff and refresh its token from an application-supplied function. After a transport reconnection it authenticates again and announces remote functions. The application still needs to reconnect the database to restore its store subscription. Each tab currently has its own replica and connection, without coordination across tabs or Web Workers.

An open replica can answer local queries while disconnected. Writes waiting for a connection stay in memory, so reloading the page loses that queue even when committed data is stored in IndexedDB. Durable pending-write recovery would also need to resolve transactions whose server outcome is unknown: blindly retrying a non-idempotent write can apply it twice. Instant includes this recovery protocol; it remains future work for Datahike.

Optimistic event handling

Validation arrives asynchronously as a tagged result. Pending transaction data must be deterministic because a new durable base can replay it. When the base advances, event.changes is null; incremental consumers must invalidate derived state and use the authoritative event['db-after'] snapshot.

While the overlay remains open, an in-flight writer transaction stays visible until it resolves. A slow response cannot retract the pending value while the transaction may still commit. Closing the overlay is the explicit exception: it retracts the local view but does not claim to cancel a durable operation already in flight. The design document specifies the lifecycle, event ordering, invalidation rules and lower-level prediction API.

Operating costs

Instant publishes VPS, AWS and migration guides for its open-source backend. Its estimates cover a bundle with authentication, permissions, file storage, presence and a dashboard. Datahike’s costs depend on where the database runs, its storage backend and how often each database is opened.

DeploymentWhat you operateCost basisFits when
Instant, self-hosted VPSPostgreSQL, sync server, dashboard, MinIO and CaddyInstant estimates about $30/monthYou want to retain the integrated backend
Instant, self-hosted AWSMultiple backend instances, Aurora, S3 and a dashboardInstant estimates at least $600/monthYou need its higher-availability deployment
Datahike ServerA server container and storageYour compute and storageClients need HTTP, Kabel or the optional PostgreSQL listener
Browser against a bucketObject storage and credential issuanceStorage, requests and any transfer chargesThe browser may access the entire database
One application process, many tenantsYour app and a bounded pool of tenant databasesShared compute, storage and trafficTenant databases share application infrastructure
Ephemeral computeFunctions and durable storageInvocations, execution time and storageDatabases are accessed infrequently

The SaaS starter cost model estimates about $98/month on AWS or $48/month for a Hetzner VM with a Tigris bucket for 1,000 small tenant databases. It uses July 2026 list prices and assumes 20,000 application reads and 2,000 commits per tenant per month, a bounded pool of open databases, and 20 KB average responses. These are estimates for that workload and configuration, including compute, object storage and user traffic, rather than a price for a complete application backend.

Function-based deployments can cost less for quiet databases, but cold starts and execution time become more significant as activity grows. The serverless measurements distinguish local Lambda-emulator measurements from field results. Use them to understand the sources of cost and latency before measuring your own workload.

Direct bucket access also changes where authorization happens: anyone holding credentials to read a database can read its contents. Use a server to mediate access when users should receive only selected results.

A shared database lineage

Instant and Datahike draw on the same database lineage. Instant’s server is written in Clojure, with triples and Datalog underneath InstaQL. Nikita Prokopov, the author of DataScript, joined Instant in December 2024 to work on databases in the browser. Datahike began as a fork of DataScript and added persistent indexes and storage (why).

Both projects draw on Datomic and Rich Hickey’s model of a database at a point in time as a value. In Datahike, that value can represent committed history, a pending edit or work on a branch, and the same query engine can inspect each one.

Instant applies these ideas in an integrated backend with a query syntax and SDKs designed for JavaScript applications. Datahike applies them to portable database values that a browser, a Node process or a server can hold, query and store in infrastructure you control.

A deployed Datahike version reads its documented database format directly from storage. Existing data remains accessible without calling an endpoint operated by the maintainers.

To try Datahike with a server, follow the thin-client guide or the browser replica setup. The JavaScript API documentation covers embedded use and the core operations.

If you are deciding whether to self-host Instant or rebuild around Datahike, send us a description of the workload. We can map its requirements to the relevant deployment and integration work.

For direct storage access, continue with A database that is just a bucket.