Skip to content


Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?


React Hooks library for local-first apps with end-to-end encrypted backup and sync using SQLite and CRDT.

Evolu is designed for privacy, ease of use, and no vendor lock-in.

  • The official SQLite Wasm in all browsers (React Native and Electron soon)
  • E2E encrypted sync and backup with CRDT (merging changes without conflicts)
  • Free Evolu server for testing (paid production-ready soon, or you can run your own)
  • Typed database schema with branded types (NonEmptyString1000, PositiveInt, etc.)
  • Reactive queries with React Suspense support
  • Real-time experience via revalidation on focus and network recovery
  • Schema evolving via filterMap ad-hoc migration
  • No signup/login, no email collection, only Bitcoin-like mnemonic (12 words)

Local-first apps

Local-first apps allow users to own their data. Evolu stores data in the user's device(s), so Evolu apps can work offline and without a specific server. How is it different from keeping files on disk? Files are not the right abstraction for apps and are complicated to synchronize among devices. That's why client-server architecture rules the world. But as with everything, it has trade-offs.

The trade-offs of the client-server architecture

Client-server architecture provides us with easy backup and synchronization, but all that depends on the ability of a server to fulfill its promises. Internet is offline, companies go bankrupt, users are banned, and errors occur. All those things happen all the time, and then what? Right, that's why the world needs local-first apps. But until now, writing local-first apps has been challenging because of the lack of libraries and design patterns. That's why I created Evolu.


  • TypeScript 4.9 or newer
  • The strict flag enabled in your tsconfig.json file
  • The exactOptionalPropertyTypes flag enabled in your tsconfig.json file
  // ...
  "compilerOptions": {
    // ...
    "strict": true,
    "exactOptionalPropertyTypes": true

Getting Started

npm install evolu @effect/schema @effect/data

The complete Next.js example is here.

Define Data

To start using Evolu, define schemas for your database and export React Hooks.

import * as Schema from "@effect/schema/Schema";
import * as Evolu from "evolu";

const TodoId ="Todo");
type TodoId = Schema.To<typeof TodoId>;

const TodoTable = Schema.struct({
  id: TodoId,
  title: Evolu.NonEmptyString1000,
  isCompleted: Evolu.SqliteBoolean,
type TodoTable = Schema.To<typeof TodoTable>;

const Database = Schema.struct({
  todo: TodoTable,

export const {
} = Evolu.create(Database);

Validate Data

Learn more about Schema.

import * as Schema from "@effect/schema/Schema";
import * as Evolu from "evolu";


Mutate Data

Mutation API is designed for local-first apps to ensure changes are always merged without conflicts.

const { create, update } = useMutation();

create("todo", { title, isCompleted: false });
update("todo", { id, isCompleted: true });

Query Data

Evolu uses type-safe TypeScript SQL query builder kysely, so autocompletion works out-of-the-box.

const { rows } = useQuery(
  (db) => db.selectFrom("todo").select(["id", "title"]).orderBy("updatedAt"),
  // (row) => row
  ({ title, }) => title && { title, }

Protect Data

Evolu encrypts data with Mnemonic, a safe autogenerated password based on bip39.

const owner = useOwner();


Delete Data

Leave no traces on a device.

const ownerActions = useOwnerActions();

if (confirm("Are you sure? It will delete all your local data."))

Restore Data

Restore data elsewhere. Encrypted data can only be restored with a Mnemonic.

const ownerActions = useOwnerActions();

ownerActions.restore(mnemonic).then((either) => {
  if (either._tag === "Left") alert(JSON.stringify(either.left, null, 2));

Handle Errors

Evolu useQuery and useMutation never fail, it's the advantage of local first apps, but Evolu, in rare cases, can.

const evoluError = useEvoluError();

useEffect(() => {
  // eslint-disable-next-line no-console
  if (evoluError) console.log(evoluError);
}, [evoluError]);

And that's all. Minimal API is the key to a great developer experience.


Evolu uses end-to-end encryption and generates strong and safe passwords for you. Evolu sync and backup server see only timestamps.


“There are no solutions. There are only trade-offs.” ― Thomas Sowell

Evolu is not P2P. For reliable syncing and backup, there needs to be a server. Evolu server is very minimal, and everyone can run their own. While it's theoretically possible to have P2P Evolu, I have yet to see a reliable solution. It's not only a technical problem; it's an economic problem. Someone has to be paid to keep your data safe. Evolu provides a free server for testing. Soon we will provide a paid server for production usage.

All table columns except for ID are nullable by default. It's not a bug; it's a feature. Local-first data are meant to last forever, but schemas evolve. This design decision was inspired by GraphQL nullability and versioning. Evolu provides a handy filterMap helper for queries.


The Evolu community is on GitHub Discussions, where you can ask questions and voice ideas.

To chat with other community members, you can join the Evolu Discord.

Twitter URL


Is Evolu ready for production?

It should be. The CRDT message format is stable.

What is the SQLite database size limit?

Storage quotas and eviction criteria

How can I check the current database filesize?

Use OPFS Explorer Chrome DevTools extension.


Evolu monorepo uses pnpm.

Install the dependencies with:

pnpm install

Build Evolu monorepo:

pnpm build

Start developing and watch for code changes:

pnpm dev

Run tests:

pnpm test

Describe changes for release log:

pnpm changeset