Docs Getting started
Quick start
Provision Kizuna into the Supabase application you already ship, so your app reads and writes offline through the client libraries. Run kizunasync from your app project's root, or pass --workdir.
kizunasync installs the SQL pack into your own Supabase project. It records the synced tables in that project's own configuration tables. The sync rules and buckets there decide which rows reach a device.
Before you begin#
| Requirement | Notes |
|---|---|
| A Supabase project | Hosted on Supabase Cloud, or local through the Supabase CLI in your app repository |
| Your application repository | The tree that contains (or will contain) supabase/config.toml, migrations, and app source |
| A primary key on every table you sync | One column or several, each uuid, text, character varying, smallint, integer, or bigint. The wizard lists a table without such a key as unavailable, and Row keys explains the rule |
| Supabase access | Linked project, local stack, Postgres URL, or account login. The wizard discovers what is available |
Node.js ^20.19.0 or >=22.12.0 | Required by the kizunasync npm launcher |
| Docker (optional) | Only if you run Supabase locally inside your app repository |
1. Verify kizunasync#
From any directory:
npx kizunasync --helppnpm dlx kizunasync --helpyarn dlx kizunasync --helpbunx kizunasync --helpYou should now see the command list (init, sync, status, doctor, lint, and the rest), which CLI documents one by one.
2. Open your app project#
cd /path/to/your-supabase-appThe CLI resolves the working directory (or --workdir) for supabase/ and the generated migrations.
3. Run kizunasync init#
From your app tree, in a real terminal:
npx kizunasync initpnpm dlx kizunasync inityarn dlx kizunasync initbunx kizunasync initThe wizard walks you through four stages, and nothing is written until you accept the plan. kizunasync init documents every flag it accepts.
- Connection: linked Supabase project,
.envdatabase URL, local stack, or a project from your Supabase account - Schema and tables: proposes synced tables from your Row Level Security policies, which keep deciding every write after provisioning
- Review: shows the pack and config migrations before anything is written
- Confirm: applies when you accept
You should now have the kizunasync schema, or an updated provision ledger on a hosted-only path, with one kizunasync._config row per table you kept. On the local migration path you also get timestamped files under supabase/migrations/. Those behave like any other schema migration in your repository. What is installed itemizes the objects the pack creates.
Scripted or CI#
For pipelines and one-shot runs, pass explicit flags with --yes. Install carries the full matrix: --dry-run, --db-url, --project-ref, and the hosted Management API path, which Kizuna uses to apply the same pack without a direct Postgres connection.
4. Confirm project shape#
Still from your app tree:
npx kizunasync doctor
npx kizunasync statuspnpm dlx kizunasync doctor
pnpm dlx kizunasync statusyarn dlx kizunasync doctor
yarn dlx kizunasync statusbunx kizunasync doctor
bunx kizunasync statusOn a TTY, kizunasync status renders the same read-only report the wizard uses elsewhere. kizunasync doctor reads kizunasync._config, checks the local project files, and can probe the Data API when credentials are available, which is how it catches a kizunasync schema that is not exposed.
Once Kizuna is installed, kizunasync with no command in a terminal opens a control panel that runs these checks and the other commands from one menu, listed in Interactive mode.
5. Wire the client in your app#
After provisioning, add the offline client for your UI:
| UI | Next page | Reference |
|---|---|---|
| React | React | React |
| Vue | Vue | Vue |
| Expo / React Native | Expo / React Native | Expo |
| Vite (browser) | Vite | JavaScript |
| Other JavaScript | Vanilla JavaScript | JavaScript |
| Swift or Kotlin (no JS UI) | Swift and Kotlin | Swift, Kotlin |
Every JavaScript guide follows the same shape. One module creates the app client once, at module scope, and every other file imports it. In a browser app that module is src/kizunasync.ts, next to the src/supabase-client.ts file that exports your supabase-js client:
// src/kizunasync.ts
import { byOwner, defineConfig } from 'kizunasync'
import { createSupabaseKizunaSync } from 'kizunasync/supabase'
import { createWebWorkerDriver } from 'kizunasync/web'
import { supabase } from './supabase-client'
const config = defineConfig({
tables: { todos: { sync: 'read-write', bucket: byOwner('user_id') } },
})
export const kizunasync = createSupabaseKizunaSync({
supabase,
driver: createWebWorkerDriver('todos.db'),
config,
})defineConfig declares the tables with the options kizunasync status reports for them in step 4. createSupabaseKizunaSync composes the client over createWebWorkerDriver, and creating it opens nothing: the first query or write opens the browser database and runs the first sync. The client follows the Supabase session by itself, so the app signs in with supabase-js as usual, or passes anonymousSignIn: true when its first run has no sign-in screen. The engine fills the byOwner bucket with the signed-in user's id. On Expo and React Native the module swaps the driver for openExpoDriver, as Expo / React Native shows.
A write then goes through the same client:
// src/add-todo.ts
import { kizunasync } from './kizunasync'
export async function addTodo(title: string): Promise<void> {
await kizunasync.from('todos').insert({ title, done: false })
}The insert mirrors Supabase's insert, except that it commits locally first, mints the id itself, and fills user_id with the signed-in user. The client pushes it on its own, because a local write, a poll tick, the network coming back, and a return to the tab each start a sync run, and the server's verdict arrives with that run. sync() is there for a Sync now button or a test. The framework guides in the table above add the rest: the Supabase client file, the sign-in, the root file that provides the client, and the screens that read and write.
Install the client library for your platform beside Supabase's own client: kizunasync and @supabase/supabase-js from npm (JavaScript: Installing), the KizunaSync and supabase-swift Swift packages (Swift: Installing), or com.kizunasync:kizunasync and supabase-kt from Maven (Kotlin: Installing).
See Kizuna move without provisioning#
- demo.kizunasync.com: the hosted two-pane browser demo, with a wire viewer for pull and push.
- Playground: the five in-repository reference apps against a local Supabase stack.
Next steps#
- Install: the scripted and hosted provisioning matrix.
- CLI: every subcommand, flag, and exit code.
- Configuration: the
kizunasync._configandkizunasync._settingscolumns the CLI writes. - How Kizuna works: outbox, pull, verdicts, and files in five steps.
- Project status: what is implemented and what is verified.
- FAQ: long offline periods, supabase-js, large files, conflicts, and cost.
- Contribute: the monorepo path for maintaining Kizuna Sync itself.
Found a problem? Edit this page on GitHub