> ## Documentation Index
> Fetch the complete documentation index at: https://powersync-sync-streams-nav.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Migrate to Sync Streams

> Migrate an existing project from legacy Sync Rules to Sync Streams.

Sync Streams do everything Sync Rules do, and more. A stream with `auto_subscribe: true` syncs when the client connects, the same way a bucket definition does, so apps that sync all relevant data upfront for offline use keep working the same way after migrating. The [migration tool](#migration-tool) sets `auto_subscribe: true` on every generated stream, so no client-side changes are required when you first deploy.

## Why Migrate?

Beyond matching Sync Rules, Sync Streams add:

1. **More expressive queries**: Stream queries support JOINs, [CTEs](/sync/streams/ctes), subqueries, and [multiple queries per stream](/sync/streams/queries#multiple-queries-per-stream), with syntax closer to plain SQL. [Parameter queries become inline subqueries](#data-with-subqueries-replaces-parameter-queries), so you write one query instead of separate `parameters:` and `data:` blocks.

2. **On-demand syncing**: Define a stream once, then subscribe from your app one or more times with different parameters. Each subscription has its own lifecycle, so two screens or browser tabs can subscribe to the same stream independently. With Sync Rules, [Client Parameters](/sync/rules/client-parameters) approximate this, but you have to aggregate the parameter values yourself across screens and tabs, and remove them when they are no longer needed.

3. **Built-in caching**: Each subscription has a configurable `ttl` that keeps data on the device after unsubscribing. When users return to a screen, the data is often already available.

4. **Framework integration**: [React hooks, Vue composables, TanStack Query, and Kotlin Compose extensions](/sync/streams/client-usage#framework-integrations) let UI components manage subscriptions based on what is rendered.

5. **Access to new features**: Newer PowerSync Service features such as [wildcard schemas](/sync/advanced/schemas-and-connections) and [incremental reprocessing](/sync/advanced/storage-version-4) require Sync Streams.

You can migrate incrementally. Deploy the generated streams with `auto_subscribe: true` first, then convert individual streams to on-demand subscriptions where that benefits your app.

## Requirements

* PowerSync Service v1.20.0+ (Cloud instances already meet this)
* Latest SDK versions with [Rust-based sync client](https://releases.powersync.com/announcements/improved-sync-performance-in-our-client-sdks) (enabled by default on latest SDKs)
* `config: edition: 3` in your Sync Config

<Tabs>
  <Tab title="Minimum SDK Versions">
    | SDK          | Minimum Version | Rust Client Default       |
    | ------------ | --------------- | ------------------------- |
    | JS Web       | v1.27.0         | v1.32.0                   |
    | React Native | v1.25.0         | v1.29.0                   |
    | React hooks  | v1.8.0          | —                         |
    | Node.js      | v0.11.0         | v0.16.0                   |
    | Capacitor    | v0.0.1          | v0.3.0                    |
    | Tauri        | v0.0.1          | Always (Rust client only) |
    | Dart/Flutter | v1.16.0         | v1.17.0                   |
    | Kotlin       | v1.7.0          | v1.9.0                    |
    | Swift        | v1.11.0         | v1.8.0                    |
    | .NET         | v0.0.8-alpha.1  | v0.0.5-alpha.1            |
  </Tab>

  <Tab title="Enable Rust Client (older SDKs)">
    If you're on an SDK version below the "Rust Client Default" version, enable the Rust client manually:

    **JavaScript:**

    ```js theme={null}
    await db.connect(new MyConnector(), {
      clientImplementation: SyncClientImplementation.RUST
    });
    ```

    **Dart:**

    ```dart theme={null}
    database.connect(
      connector: YourConnector(),
      options: const SyncOptions(
        syncImplementation: SyncClientImplementation.rust,
      ),
    );
    ```

    **Kotlin:**

    ```kotlin theme={null}
    database.connect(MyConnector(), options = SyncOptions(
      newClientImplementation = true,
    ))
    ```

    **Swift:**

    ```swift theme={null}
    import PowerSync

    try await db.connect(connector: connector, options: ConnectOptions(
      newClientImplementation: true,
    ))
    ```
  </Tab>
</Tabs>

## Migration Tool

You can generate a Sync Streams draft from your existing Sync Rules in two ways:

1. **Dashboard:** In the [PowerSync Dashboard](https://dashboard.powersync.com/), use the **Migrate to Sync Streams** button. It converts your Sync Rules into a Sync Streams draft that you can review before deploying.

2. **CLI:** Run `powersync migrate sync-rules` to produce a Sync Streams draft from your current Sync Config.

The output uses `auto_subscribe: true` by default, preserving your existing sync-everything-upfront behavior so no client-side changes are required when you first deploy.

**Next steps:** Review the draft, then deploy it (via the Dashboard or `powersync deploy sync-config`). After that, you can optionally migrate individual streams to on-demand subscriptions over time — remove `auto_subscribe: true` from specific streams and update client code to use the `syncStream()` API where it makes sense for your app.

## Stream Definition Reference

```yaml theme={null}
config:
  edition: 3

streams:
  <stream_name>:
    # CTEs (optional) - define with block inside each stream
    with:
      <cte_name>: SELECT ... FROM ...

    # Behavior options (place above query/queries)
    auto_subscribe: true    # Auto-subscribe clients on connect (default: false)
    priority: 1             # Sync priority (optional). Lower number -> higher priority
    accept_potentially_dangerous_queries: true  # Silence security warnings (default: false)

    # Query options (use one)
    query: SELECT * FROM <table> WHERE ...         # Single query
    queries:                                       # Multiple queries
      - SELECT * FROM <table_a> WHERE ...
      - SELECT * FROM <table_b> WHERE ...

    
```

| Option                                 | Default | Description                                                                                                                                                                                                                                                                                                                     |
| -------------------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `query`                                | —       | SQL-like query defining which data to sync. Use either `query` or `queries`, not both. See [Writing Queries](/sync/streams/queries).                                                                                                                                                                                            |
| `queries`                              | —       | Array of queries defining which data to sync. More efficient than defining separate streams: the client manages one subscription and PowerSync merges the data from all queries (see [Multiple Queries per Stream](/sync/streams/queries#multiple-queries-per-stream)).                                                         |
| `with`                                 | —       | [CTEs](/sync/streams/ctes) available to this stream's queries. Define the `with` block inside each stream.                                                                                                                                                                                                                      |
| `auto_subscribe`                       | `false` | When `true`, clients automatically subscribe on connect.                                                                                                                                                                                                                                                                        |
| `priority`                             | —       | Sync priority (lower value = higher priority). See [Prioritized Sync](/sync/advanced/prioritized-sync).                                                                                                                                                                                                                         |
| `accept_potentially_dangerous_queries` | `false` | Silences security warnings when queries use client-controlled parameters (i.e. *connection parameters* and *subscription parameters*), as opposed to *authentication parameters* that are signed as part of the JWT. Set to `true` only if you've verified the query is safe. See [Using Parameters](/sync/streams/parameters). |

## Migration Examples

### Global Data (No Parameters)

In Sync Rules, a ["global" bucket](/sync/rules/global-buckets) syncs the same data to all users. In Sync Streams, you achieve this with queries that have no parameters. Add [`auto_subscribe: true`](/sync/streams/quickstart#using-auto-subscribe) to maintain the Sync Rules behavior where data syncs automatically on connect.

**Sync Rules:**

```yaml theme={null}
bucket_definitions:
  global:
    data:
      - SELECT * FROM todos
      - SELECT * FROM lists WHERE archived = false
```

**Sync Streams:**

```yaml theme={null}
config:
  edition: 3

streams:
  shared_data:
    auto_subscribe: true  # Sync automatically like Sync Rules
    queries:
      - SELECT * FROM todos
      - SELECT * FROM lists WHERE archived = false
```

<Note>
  Without `auto_subscribe: true`, clients would need to explicitly subscribe to these streams. This gives you flexibility to migrate incrementally or switch to on-demand syncing later.
</Note>

### User-Scoped Data

**Sync Rules:**

```yaml theme={null}
bucket_definitions:
  user_lists:
    priority: 1
    parameters: SELECT request.user_id() as user_id
    data:
      - SELECT * FROM lists WHERE owner_id = bucket.user_id
```

**Sync Streams:**

```yaml theme={null}
config:
  edition: 3

streams:
  user_lists:
    auto_subscribe: true
    priority: 1
    query: SELECT * FROM lists WHERE owner_id = auth.user_id()
```

### Data with Subqueries (Replaces Parameter Queries)

**Sync Rules:**

```yaml theme={null}
bucket_definitions:
  owned_lists:
    parameters: |
      SELECT id as list_id FROM lists WHERE owner_id = request.user_id()
    data:
      - SELECT * FROM lists WHERE lists.id = bucket.list_id
      - SELECT * FROM todos WHERE todos.list_id = bucket.list_id
```

**Sync Streams:**

```yaml theme={null}
config:
  edition: 3

streams:
  owned_lists:
    auto_subscribe: true
    query: SELECT * FROM lists WHERE owner_id = auth.user_id()
  list_todos:
    query: |
      SELECT * FROM todos 
      WHERE list_id = subscription.parameter('list_id') 
        AND list_id IN (SELECT id FROM lists WHERE owner_id = auth.user_id())
```

### Client Parameters → Subscription Parameters

**Sync Rules** used global [Client Parameters](/sync/rules/client-parameters):

```yaml theme={null}
bucket_definitions:
  posts:
    parameters: SELECT (request.parameters() ->> 'current_page') as page_number
    data:
      - SELECT * FROM posts WHERE page_number = bucket.page_number
```

**Sync Streams** use Subscription Parameters, which are more flexible — you can subscribe multiple times with different values:

```yaml theme={null}
config:
  edition: 3

streams:
  posts:
    query: SELECT * FROM posts WHERE page_number = subscription.parameter('page_number')
```

```js theme={null}
// Subscribe to multiple pages simultaneously
const page1 = await db.syncStream('posts', { page_number: 1 }).subscribe();
const page2 = await db.syncStream('posts', { page_number: 2 }).subscribe();
```

## Parameter Syntax Changes

| Sync Rules                       | Sync Streams                                                                                                                                                                                                           |
| -------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `request.user_id()`              | `auth.user_id()`                                                                                                                                                                                                       |
| `request.jwt() ->> 'claim'`      | `auth.parameter('claim')`                                                                                                                                                                                              |
| `request.parameters() ->> 'key'` | `subscription.parameter('key')` ([subscription parameter](/sync/streams/parameters#subscription-parameters)) or `connection.parameter('key')` ([connection parameter](/sync/streams/parameters#connection-parameters)) |
| `bucket.param_name`              | Use the parameter directly in the query e.g. `subscription.parameter('key')`                                                                                                                                           |

## Client-Side Changes

Streams generated by the migration tool with `auto_subscribe: true` need no client changes. When you convert a stream to on-demand syncing, replace connect-time parameters with a subscription:

```js theme={null}
// Before (Sync Rules with Client Parameters)
await db.connect(connector, {
  params: { current_project: projectId }
});

// After (Sync Streams with Subscriptions)
await db.connect(connector);
const sub = await db.syncStream('project_data', { project_id: projectId }).subscribe();
```

If you want to keep passing values at connect time instead, use [connection parameters](/sync/streams/parameters#connection-parameters).

See [Client-Side Usage](/sync/streams/client-usage) for detailed examples.
