---
title: Getting Started
description: "Scaffold an offline-first app with create-kora-app, define a schema, and get local persistence, reactive queries, and multi-device sync in about ten minutes."
---

# Getting Started

Get from zero to a working offline-first app in under 5 minutes.

## Quick Start

Scaffold a new project with a single command:

```bash
npx create-kora-app my-app
```

You will be prompted to choose a template and package manager:

```
Kora.js - Offline-first application framework

? Platform:
  > Web (browser)
    Desktop (Tauri - native SQLite)

? UI framework:
  > React

? Use Tailwind CSS? Yes

? Enable multi-device sync? Yes

? Package manager:
  > pnpm
    npm
    yarn
    bun
```

Selecting **Desktop (Tauri)** scaffolds a native desktop app with native SQLite and sync enabled. See the [Tauri Desktop guide](/guide/tauri-desktop) for details.

You can also skip the prompts entirely:

```bash
npx create-kora-app my-app --yes  # Uses recommended defaults
```

Once scaffolding completes:

```bash
cd my-app
pnpm install
pnpm dev
```

Your app is running. Everything works offline out of the box.

## Manual Setup

If you prefer to add Kora to an existing project:

```bash
pnpm add korajs @korajs/react
```

## Project Structure

A scaffolded Kora project looks like this:

```
my-app/
  src/
    schema.ts         # Schema entry point
    app.ts            # Kora app instance
    main.tsx          # React entry point
    components/       # Your UI components
  kora.config.ts      # Optional: sync and DevTools config
  package.json
```

## Define Your Schema

The schema is the single source of truth for your data model. Create `src/schema.ts`:

```typescript
import { defineSchema, t } from 'korajs'

export default defineSchema({
  version: 1,

  collections: {
    todos: {
      fields: {
        title: t.string(),
        completed: t.boolean().default(false),
        createdAt: t.timestamp().auto(),
      },
      indexes: ['completed', 'createdAt'],
    },
  },
})
```

Key points:

- **`defineSchema`** validates your schema and generates TypeScript types.
- **`t.string()`**, **`t.boolean()`**, etc. are field type builders that support chaining (`.default()`, `.optional()`, `.auto()`).
- **`indexes`** improve query performance on the listed fields.
- **`version`** tracks schema changes for migrations.

For larger apps, keep `src/schema.ts` as the entry point and split collections by feature or domain
module:

```text
src/
  modules/
    todos/
      todo.schema.ts
      todo.queries.ts
      todo.mutations.ts
      useTodos.ts
      components/
  schema.ts
```

```typescript
// src/modules/todos/todo.schema.ts
import { t } from 'korajs'

export const todos = {
  fields: {
    title: t.string(),
    completed: t.boolean().default(false),
    createdAt: t.timestamp().auto(),
  },
  indexes: ['completed', 'createdAt'],
}
```

```typescript
// src/schema.ts
import { defineSchema } from 'korajs'
import { todos } from './modules/todos/todo.schema'

export default defineSchema({
  version: 1,
  collections: { todos },
})
```

The CLI and runtime still read one schema export, but your collection definitions can live close to
the code that owns them:

- `todo.schema.ts` defines the data shape.
- `todo.queries.ts` contains reads only.
- `todo.mutations.ts` contains writes: inserts, updates, deletes, and transactions.
- `useTodos.ts` is the React binding that connects those reads and writes to components.
- `components/` contains UI for the feature.

The schema, query, and mutation files are framework-agnostic and apply across web, desktop, and
mobile. The binding file is framework-specific; React templates use `useTodos.ts`. Kora does not
require controllers, services, or a routing convention, so use the router and app structure that fits
your framework.

## Create the App

Create `src/app.ts`:

```typescript
import { createApp } from 'korajs'
import schema from './schema'

export const app = createApp({ schema })
```

That is the entire setup for a local-only app. No database configuration, no storage boilerplate. Kora uses SQLite WASM with OPFS persistence under the hood, running in a Web Worker so your UI never blocks. If OPFS cannot be acquired at runtime, `createApp()` falls back to durable IndexedDB and emits `store:storage-fallback`; `store:opfs-unavailable` is reserved for the last-resort case where the app is running in non-persistent memory. See the [Multi-runtime Storage](/guide/multi-runtime-storage) guide.

## CRUD Operations

With your app instance, you can immediately perform operations on your collections:

```typescript
import { app } from './app'

// Insert a record
const todo = await app.collections.todos.insert({
  title: 'Ship Kora v1',
  // completed defaults to false
  // createdAt is set automatically
})
// => { id: '01905e5a-...', title: 'Ship Kora v1', completed: false, createdAt: 1712188800000 }

// Find by ID
const found = await app.collections.todos.findById(todo.id)

// Update (partial: only the fields you pass)
await app.collections.todos.update(todo.id, { completed: true })

// Query with filters
const active = await app.collections.todos
  .where({ completed: false })
  .orderBy('createdAt', 'desc')
  .limit(10)
  .exec()

// Count
const count = await app.collections.todos.where({ completed: false }).count()

// Delete
await app.collections.todos.delete(todo.id)
```

`app.collections` is collision-free and works for every wire collection name. Direct access such as
`app.todos` remains available when the name does not overlap a framework member. A schema with an
`events` collection keeps its wire name and uses `app.collections.events`; framework events remain
available through `app.events` or `app.on(type, listener)`.

Every operation works offline. Data is persisted to the local store immediately.

## Use with React

Wrap your app in `KoraProvider` and use hooks to access data reactively:

```tsx
import { KoraProvider, useQuery, useMutation } from '@korajs/react'
import { app } from './app'

function App() {
  return (
    <KoraProvider app={app}>
      <TodoList />
    </KoraProvider>
  )
}

function TodoList() {
  // Reactive query: re-renders when data changes
  const todos = useQuery(
    app.todos.where({ completed: false }).orderBy('createdAt')
  )

  const addTodo = useMutation(app.todos.insert)

  return (
    <div>
      <button onClick={() => addTodo({ title: 'New todo' })}>
        Add Todo
      </button>
      <ul>
        {todos.map((todo) => (
          <li key={todo.id}>{todo.title}</li>
        ))}
      </ul>
    </div>
  )
}
```

`useQuery` returns data synchronously from the local store. There are no loading spinners for local data because the data is always available.

## Enable Sync

To sync data across devices, add a `sync` property to your app config:

```typescript
import { createApp } from 'korajs'
import schema from './schema'

export const app = createApp({
  schema,
  sync: {
    url: 'wss://my-server.com/kora',
  },
})

await app.ready
await app.sync?.connect()
```

Kora handles connection management, conflict resolution, and operation syncing after `connect()` is called. When the device is offline, operations queue locally and sync when connectivity returns.

For details on running the sync server, see [Deployment](/guide/deployment).

## Deploy Your App

Ready to share your app with the world? One command deploys to a cloud platform:

```bash
kora deploy
```

See the [Deployment guide](/guide/deployment) for a full step-by-step walkthrough, including Fly.io setup and troubleshooting.

## What's Next

- [Deployment](/guide/deployment): Deploy your app to Fly.io or Railway in 10 minutes
- [Schema Design](/guide/schema-design): Field types, relations, state machines, and versioning
- [State Machines](/guide/state-machines): Constrained enum transitions for workflows
- [Offline Patterns](/guide/offline-patterns): Building UIs that embrace offline-first
- [Conflict Resolution](/guide/conflict-resolution): How Kora handles concurrent edits
- [React Hooks](/guide/react-hooks): Full reference for all React bindings
- [Kora for AI Agents](/guide/ai-agents): Rules and the `kora agents-md` command so coding agents build Kora apps correctly
- [Presence & Awareness](/guide/presence): Real-time collaborative presence
- [Sync Configuration](/guide/sync-configuration): Transports, encryption, diagnostics, and reconnection
- [Server-side Validation](/guide/server-side-validation): Adjudicate untrusted client operations before they become authoritative
- [Production Server](/guide/production-server): Background-job data access, size and rate limits, and central blob storage
- [Sync Encryption](/guide/sync-encryption): End-to-end encryption for sync
- [Authentication](/guide/authentication): Sessions, MFA, organizations, RBAC, and passkeys
- [Storage Configuration](/guide/storage-configuration): Client and server storage backends
- [Multi-runtime Storage](/guide/multi-runtime-storage): Running more than one runtime on one origin, and storage diagnostics
- [Backup and Restore](/guide/backup-restore): Local app backups and sync server backups
- [Testing](/guide/testing): Test harness for offline-first apps
- [Tauri Desktop Apps](/guide/tauri-desktop): Build native desktop apps with native SQLite
- [DevTools](/guide/devtools): Debugging with the Kora browser extension
- [API Reference](/api/): Complete reference for all packages
