# Get started with local-first

This guide will help you get started with the Evolu local-first platform.

> Upgrading from Evolu 7? Evolu 8 uses a different local storage format and does
> not migrate existing Evolu 7 databases yet. Automatic migration is planned.
> Until then, existing Evolu 7 data is preserved but is not shown in Evolu 8.

> Requirements: TypeScript 7 or newer with `exactOptionalPropertyTypes` enabled.

## Installation

Evolu offers SDKs for a variety of frameworks, including React, Svelte, React Native, Expo, and others. Below, you can see how to install the SDKs for each framework.

## Define schema

First, define your app database schema—tables, columns, and types.

Evolu uses [Type](https://evolu.dev/docs/api-reference/common/Type) for data modeling. Instead of plain JS types like string or number, we recommend using branded types to enforce domain rules.

```ts

// Primary keys are branded types, preventing accidental use of IDs across
// different tables (e.g., a TodoId can't be used where a UserId is expected).
const TodoId = Evolu.id("Todo");
type TodoId = typeof TodoId.Output;

// Schema defines the database structure and mutation/query types.
// Validate untrusted input before insert/update/upsert.
const Schema = {
  todo: {
    id: TodoId,
    // Branded type ensuring titles are non-empty and ≤100 chars.
    title: Evolu.NonEmptyTrimmedString100,
    // SQLite doesn't support the boolean type; it uses 0 and 1 instead.
    isCompleted: Evolu.nullOr(Evolu.SqliteBoolean),
  },
};
```

> Evolu automatically adds [system
> columns](https://evolu.dev/docs/api-reference/common/local-first/Schema/variables/SystemColumns-1):
> `createdAt`, `updatedAt`, `isDeleted`, and `ownerId`.

## Create Evolu

After defining the schema, create an Evolu instance for your environment. Every instance needs an `appOwner`. Create one on the first run and persist its secret material securely, for example in Expo SecureStore or WebAuthn-backed storage, then restore the same owner on later runs:

```ts
import {
  createAppOwner,
  createOwnerSecret,
  createRandomBytes,
} from "@evolu/common";

// First run only. Persist the secret and restore the owner on later runs.
const appOwner = createAppOwner(
  createOwnerSecret({ randomBytes: createRandomBytes() }),
);
```

The following snippets continue from the schema and `appOwner` above.

Create one shared Run for your application's Evolu instances. Omit `transports` to use the default relay, provide your own relay URLs, or pass `transports: []` to disable synchronization.

);

export const App = ({ children }: { children: ReactNode }) => (
  
  </Suspense>
);

// Call useEvolu() and useQuery() in components inside EvoluProvider.
```

```ts {{ title: 'React Native', language: 'tsx' }}

const run = createRun(createEvoluDeps());
const evoluPromise = run.ok(
  createEvolu(Schema, {
    appName: AppName.orThrow("your-app-name"),
    appOwner,
  }),
);

const { EvoluContext, useEvolu, useQuery } = createEvoluBinding<typeof Schema>();

const EvoluProvider = ({ children }: { children: ReactNode }) => (
  
);

export const App = ({ children }: { children: ReactNode }) => (
  
  </Suspense>
);

// Call useEvolu() and useQuery() in components inside EvoluProvider.
```

```ts {{ title: 'Expo', language: 'tsx' }}

const run = createRun(createEvoluDeps());
const evoluPromise = run.ok(
  createEvolu(Schema, {
    appName: AppName.orThrow("your-app-name"),
    appOwner,
  }),
);

const { EvoluContext, useEvolu, useQuery } = createEvoluBinding<typeof Schema>();

const EvoluProvider = ({ children }: { children: ReactNode }) => (
  
);

export const App = ({ children }: { children: ReactNode }) => (
  
  </Suspense>
);

// Call useEvolu() and useQuery() in components inside EvoluProvider.
```

```ts {{ title: 'Svelte' }}

const run = createRun(evoluSvelteDeps);
const evolu = await run.ok(
  createEvolu(Schema, {
    appName: AppName.orThrow("your-app-name"),
    appOwner,
  }),
);
```

```ts {{ title: 'Vue' }}

const run = createRun(createEvoluDeps());
const evolu = await run.ok(
  createEvolu(Schema, {
    appName: AppName.orThrow("your-app-name"),
    appOwner,
  }),
);

export default defineComponent({
  setup() {
    provideEvolu(evolu);
    const { insert, update } = evolu;
    return { insert, update };
  },
});
```

```ts {{ title: 'Vanilla JS' }}

const run = createRun(createEvoluDeps());
const evolu = await run.ok(
  createEvolu(Schema, {
    appName: AppName.orThrow("your-app-name"),
    appOwner,
  }),
);
```

</SinglePlatformCodeGroup>

## Mutate data

## Query data

Evolu uses type-safe TypeScript SQL query builder [Kysely](https://github.com/koskimas/kysely), so autocompletion works out-of-the-box.

Let's start with a simple `Query`.

```ts
const createQuery = Evolu.createQueryBuilder(Schema);
const allTodos = createQuery((db) => db.selectFrom("todo").selectAll());
```

Once we have a query, we can load or subscribe to it.

## Delete data

To delete a row, set `isDeleted` to `sqliteTrue` (1). Synced tables use **soft deletes**. Tables prefixed with `_` delete the row permanently and have no sync history. In the examples below, `todoId` is the ID of the row to delete.

When querying, filter out deleted rows:

```ts
const activeTodos = createQuery((db) =>
  db
    .selectFrom("todo")
    .selectAll()
    // Filter out deleted rows
    .where("isDeleted", "is not", Evolu.sqliteTrue)
    .orderBy("createdAt"),
);
```

## Protect data

**Privacy is essential for Evolu**, so all data are **encrypted** with an encryption key derived from a cryptographically strong secret (which can be represented as a [mnemonic](https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki)) or provided by an external hardware device.

`evolu.appOwner` is the owner passed to `createEvolu`; it is available synchronously. For the owner created from a secret in this guide, `mnemonic` can be shown in your recovery UI.

## Purge data

> `Evolu.deleteDatabase` and `Evolu.deleteOwner` are declared but **not
> implemented yet**. Both currently throw. Implementation is planned soon.

`deleteDatabase` is intended to remove an instance's local database. `deleteOwner` is intended to remove an owner's local rows and history and stop using that owner across tabs. Neither currently provides a working purge operation. The Evolu 7 `resetAppOwner` method is no longer available.

## Restore data

The Evolu 7 `restoreAppOwner` method is no longer available. To restore synced data, validate the user's mnemonic with `Mnemonic`, convert it with `mnemonicToOwnerSecret`, and pass the resulting secret to `createAppOwner`. Then pass that owner to `createEvolu` and connect to the relays holding its data.

Use the original `appName` to reopen the same local database on a device that already has it. Your app currently manages owner persistence, instance switching, and UI updates. Built-in account management is still being developed. Rows in `_`-prefixed tables cannot be restored from a relay because they never sync.

To learn more about Evolu, explore our [playgrounds](https://evolu.dev/docs/playgrounds) and [examples](https://evolu.dev/docs/examples).