[API reference](https://evolu.dev/docs/api-reference) › [@evolu/common](https://evolu.dev/docs/api-reference/common) › [local‑first/Evolu](https://evolu.dev/docs/api-reference/common/local-first/Evolu) › EvoluAppConfig

Defined in: [packages/common/src/local-first/Evolu.ts:1959](https://github.com/evoluhq/evolu/blob/aa223771feea747ffb1b0f5f3271574455633e37/packages/common/src/local-first/Evolu.ts#L1959)

Configuration for [createEvoluApp](https://evolu.dev/docs/api-reference/common/local-first/Evolu/functions/createEvoluApp). The other options apply to every
AppOwner's instance.

## Extends

- [`Omit`](https://www.typescriptlang.org/docs/handbook/utility-types.html#omittype-keys)\<[`EvoluConfig`](https://evolu.dev/docs/api-reference/common/local-first/Evolu/interfaces/EvoluConfig), `"appOwner"` \| `"memoryOnly"` \| `"onDatabaseDeleted"`\>

## Properties

<a id="appname"></a>

### appName

```ts
readonly appName: string & Brand<"UrlSafeString"> & Brand<"AppName">;
```

Defined in: [packages/common/src/local-first/Evolu.ts:227](https://github.com/evoluhq/evolu/blob/aa223771feea747ffb1b0f5f3271574455633e37/packages/common/src/local-first/Evolu.ts#L227)

An application name used in logs and local database names.

Evolu combines `appName` with the [AppOwner](https://evolu.dev/docs/api-reference/common/local-first/Owner/interfaces/AppOwner) identity to identify the
local database. Changing either opens a different database. Keep `appName`
stable across ordinary application updates.

Stored instances that share an `appName` and AppOwner open the same
database and must use the same schema. Evolu applies the schema of the
first instance and later instances join it. Each memory instance has its
own database; see [EvoluConfig.memoryOnly](https://evolu.dev/docs/api-reference/common/local-first/Evolu/interfaces/EvoluConfig#memoryonly).

A different app name lets you create a separate local replica for the same
AppOwner—for example, to test a different SQLite implementation or index
configuration, or rebuild a replica for debugging while preserving the
existing database.

Evolu supports running these databases concurrently. Their local separation
does not isolate synchronization: replicas synchronizing the same owners
through the same relay can still exchange changes. Changing `appName`
neither migrates existing local data nor isolates incompatible schemas.

### Example

```ts
import { AppName, assertEqual } from "@evolu/common";

const appName = AppName.orThrow("MyApp");
assertEqual(appName, "MyApp");
```

#### Inherited from

[`EvoluConfig`](https://evolu.dev/docs/api-reference/common/local-first/Evolu/interfaces/EvoluConfig).[`appName`](https://evolu.dev/docs/api-reference/common/local-first/Evolu/interfaces/EvoluConfig#appname)

---

<a id="auth"></a>

### auth

```ts
readonly auth: EvoluAppAuth;
```

Defined in: [packages/common/src/local-first/Evolu.ts:1962](https://github.com/evoluhq/evolu/blob/aa223771feea747ffb1b0f5f3271574455633e37/packages/common/src/local-first/Evolu.ts#L1962)

---

<a id="createfirstappowner"></a>

### createFirstAppOwner

```ts
readonly createFirstAppOwner: boolean;
```

Defined in: [packages/common/src/local-first/Evolu.ts:1972](https://github.com/evoluhq/evolu/blob/aa223771feea747ffb1b0f5f3271574455633e37/packages/common/src/local-first/Evolu.ts#L1972)

Whether the startup that creates the device database also creates the first
AppOwner, so the app starts with one. It never creates one in an existing
device database, even when the app deleted every AppOwner. An app that
starts with a choice, such as to create or restore an AppOwner, sets it to
`false`. To start over, add a new AppOwner first, then remove the old one
with [EvoluApp.remove](https://evolu.dev/docs/api-reference/common/local-first/Evolu/interfaces/EvoluApp#remove), so a crash between the two leaves two
AppOwners rather than none.

---

<a id="device"></a>

### device?

```ts
readonly device?: EvoluAppDeviceConfig<D>;
```

Defined in: [packages/common/src/local-first/Evolu.ts:1999](https://github.com/evoluhq/evolu/blob/aa223771feea747ffb1b0f5f3271574455633e37/packages/common/src/local-first/Evolu.ts#L1999)

The app's own tables in the device database; see [DeviceEvolu](https://evolu.dev/docs/api-reference/common/local-first/Evolu/interfaces/DeviceEvolu).

---

<a id="indexes"></a>

### indexes?

```ts
readonly indexes?: IndexesConfig;
```

Defined in: [packages/common/src/local-first/Evolu.ts:332](https://github.com/evoluhq/evolu/blob/aa223771feea747ffb1b0f5f3271574455633e37/packages/common/src/local-first/Evolu.ts#L332)

Use the `indexes` option to define SQLite indexes.

Unique indexes are rejected with an assertion because concurrent offline
writes can violate uniqueness during synchronization. See
[Uniqueness](https://www.evolu.dev/docs/schema#uniqueness).

Table and column names are not typed because Kysely doesn't support it.

https://medium.com/@JasonWyatt/squeezing-performance-from-sqlite-indexes-indexes-c4e175f3c346

### Example

```ts
import { createEvolu, id, testAppName, testAppOwner } from "@evolu/common";

const Schema = {
  todo: { id: id("Todo") },
  todoCategory: { id: id("TodoCategory") },
};

const _createTodoEvolu = createEvolu(Schema, {
  appName: testAppName,
  appOwner: testAppOwner,
  transports: [],
  indexes: (create) => [
    create("todoCreatedAt").on("todo").column("createdAt"),
    create("todoCategoryCreatedAt").on("todoCategory").column("createdAt"),
  ],
});
```

#### Inherited from

[`EvoluConfig`](https://evolu.dev/docs/api-reference/common/local-first/Evolu/interfaces/EvoluConfig).[`indexes`](https://evolu.dev/docs/api-reference/common/local-first/Evolu/interfaces/EvoluConfig#indexes)

---

<a id="onownerdeleted"></a>

### onOwnerDeleted?

```ts
readonly onOwnerDeleted?: (ownerId: string & Brand<"Id"> & Brand<"OwnerId">) => void;
```

Defined in: [packages/common/src/local-first/Evolu.ts:355](https://github.com/evoluhq/evolu/blob/aa223771feea747ffb1b0f5f3271574455633e37/packages/common/src/local-first/Evolu.ts#L355)

Called when [Evolu.deleteOwner](https://evolu.dev/docs/api-reference/common/local-first/Evolu/interfaces/Evolu#deleteowner) deletes an owner's rows from this
instance's local database, whether this or another instance in any tab
asked.

The owner no longer syncs in any instance of the database, so the app stops
showing it here, such as by leaving a screen of its data, whichever tab
deleted it. It runs once per deletion, so an owner deleted twice, such as
from two tabs at once, gets two calls.

#### Inherited from

[`EvoluConfig`](https://evolu.dev/docs/api-reference/common/local-first/Evolu/interfaces/EvoluConfig).[`onOwnerDeleted`](https://evolu.dev/docs/api-reference/common/local-first/Evolu/interfaces/EvoluConfig#onownerdeleted)

---

<a id="startappownerid"></a>

### startAppOwnerId?

```ts
readonly startAppOwnerId?:
  | string & Brand<"Id"> & Brand<"OwnerId">
  | null;
```

Defined in: [packages/common/src/local-first/Evolu.ts:1996](https://github.com/evoluhq/evolu/blob/aa223771feea747ffb1b0f5f3271574455633e37/packages/common/src/local-first/Evolu.ts#L1996)

The AppOwner this tab starts with:

| Value   | Start                                            |
| ------- | ------------------------------------------------ |
| omitted | the AppOwner opened most recently on this device |
| an ID   | that AppOwner if listed, otherwise none open     |
| `null`  | none open                                        |

Evolu reloads tabs on its own, such as when another build of the app
starts, so a tab that showed one AppOwner can come back with one another
tab opened since. Omitted, the tab starts with that one, while the ID of
the AppOwner it showed starts it where it was. With `null`, the tab opens
none, such as to let the user choose without opening and syncing an
AppOwner they didn't pick. An ID that isn't listed, such as of an AppOwner
another tab removed, opens none either, not the one opened most recently.
The start records no open time, so a later start without this option still
opens the one opened most recently.

Where the ID comes from is the app's business, such as a URL fragment,
which, unlike the path or query, no server sees.

---

<a id="transports"></a>

### transports?

```ts
readonly transports?: readonly OwnerWebSocketTransport[];
```

Defined in: [packages/common/src/local-first/Evolu.ts:448](https://github.com/evoluhq/evolu/blob/aa223771feea747ffb1b0f5f3271574455633e37/packages/common/src/local-first/Evolu.ts#L448)

Transport configuration for sync and backup.

If not specified, Evolu uses the default Evolu relay. Pass one or more
transports to override it with your own relays. They must be distinct, as
[Evolu.useOwner](https://evolu.dev/docs/api-reference/common/local-first/Evolu/interfaces/Evolu#useowner) requires. Pass an empty array to disable sync, which
is useful when sync should be configured later.

Empty transports start the instance without sync. In that case,
[Evolu.useOwner](https://evolu.dev/docs/api-reference/common/local-first/Evolu/interfaces/Evolu#useowner) must be called with explicit non-empty transports to
enable sync for any Owner, including the AppOwner.

**Redundancy:** The ideal setup uses at least two completely independent
relays - for example, a home relay and a geographically separate relay.
Data is sent to both relays simultaneously, providing true redundancy
similar to using two independent clouds. This eliminates vendor lock-in and
ensures your app continues working regardless of circumstances - whether
home relay hardware fails or disappears, or a remote relay provider shuts
down.

Currently supports:

- WebSocket: Real-time bidirectional communication with relay servers

Use [createOwnerWebSocketTransport](https://evolu.dev/docs/api-reference/common/local-first/Owner/functions/createOwnerWebSocketTransport) to create WebSocket transport
configurations with proper URL formatting and [OwnerId](https://evolu.dev/docs/api-reference/common/local-first/Owner/variables/OwnerId) inclusion. The
[OwnerId](https://evolu.dev/docs/api-reference/common/local-first/Owner/variables/OwnerId) in the URL lets a relay's `isOwnerAllowed` admit or reject
the connection, but an admitted connection can sync any owner, and anyone
who knows an OwnerId can name it, so a private relay needs access control
beyond the OwnerId, as
[Relay](https://www.evolu.dev/docs/relay#access-control) describes.

The default value is:

`[{ type: "WebSocket", url: "wss://free.evoluhq.com/" }]`.

It's a testing relay that admits anyone and may delete data, so set your
own relays before release, as
[Relay](https://www.evolu.dev/docs/relay#hosting-evolu-relay) describes.

### Example

```ts
import {
  assertEqual,
  createOwnerWebSocketTransport,
  RelayUrl,
  testAppOwner,
  WebSocketUrl,
  type OwnerTransport,
} from "@evolu/common";

// Use one relay.
const _singleRelay = [
  {
    type: "WebSocket",
    url: WebSocketUrl.orThrow("wss://relay1.example.com/"),
  },
] satisfies ReadonlyArray<OwnerTransport>;

// Use independent relays for redundancy.
const _redundantRelays = [
  {
    type: "WebSocket",
    url: WebSocketUrl.orThrow("wss://relay1.example.com/"),
  },
  {
    type: "WebSocket",
    url: WebSocketUrl.orThrow("wss://relay2.example.com/"),
  },
] satisfies ReadonlyArray<OwnerTransport>;

// Start local-only before authentication. After authentication, pass an
// owner-scoped transport to evolu.useOwner.
const _localOnlyBeforeAuthentication =
  [] satisfies ReadonlyArray<OwnerTransport>;

// Include the OwnerId when the relay admits owners by it.
const authenticatedRelay = [
  createOwnerWebSocketTransport({
    url: RelayUrl.orThrow("wss://relay.example.com/"),
    ownerId: testAppOwner.id,
  }),
];

assertEqual(
  authenticatedRelay[0]?.url,
  `wss://relay.example.com/?ownerId=${testAppOwner.id}`,
);
```

#### Inherited from

[`EvoluConfig`](https://evolu.dev/docs/api-reference/common/local-first/Evolu/interfaces/EvoluConfig).[`transports`](https://evolu.dev/docs/api-reference/common/local-first/Evolu/interfaces/EvoluConfig#transports)