- Every generated stream has
auto_subscribe: true, so clients keep syncing all their data when they connect, exactly as they do with Sync Rules. - Client Parameters become connection parameters. Your app passes them the same way when it connects.
- Once your SDKs meet the minimum versions, no client-side code changes are needed.
bucket_definitions: section, you use Sync Rules and this guide applies to you. If it only has streams:, you already use Sync Streams and no action is needed.
Why Migrate?
Sync Rules are deprecated, and PowerSync is phasing them out in favor of Sync Streams. See our plan for phasing out Sync Rules for the full timeline. Beyond matching Sync Rules, Sync Streams add:- More expressive queries: Stream queries support JOINs, CTEs, subqueries, and multiple queries per stream, with syntax closer to plain SQL. You write one query instead of separate
parameters:anddata:blocks. - 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 approximate this. You have to aggregate the parameter values yourself across screens and tabs, and remove them when they are no longer needed.
- Built-in caching: Each subscription has a configurable
ttlthat keeps data on the device after unsubscribing. When users return to a screen, the data is often already available. - Framework integration: React hooks, Vue composables, TanStack Query, and Kotlin Compose extensions let UI components manage subscriptions based on what is rendered.
- Access to new features: Newer PowerSync Service features such as incremental reprocessing require Sync Streams.
Requirements
- PowerSync Service v1.20.0+ (Cloud instances already meet this)
- An SDK version that supports Sync Streams (see table). Streams run on the Rust-based sync client, which is the default in current SDKs. If your version is between the two columns, enable it manually.
config: edition: 3in your Sync Config (the migration tool sets this)
- Minimum SDK Versions
- Enable Rust Client (older SDKs)
Migrate With the Migration Tool
1
Generate the Sync Streams draft
Use one of the following:
- PowerSync Dashboard: Click Migrate to Sync Streams. The Dashboard converts the instance’s deployed Sync Rules and opens the result as a draft for you to review.
- CLI: Run
powersync migrate sync-rules. By default the command readssync-config.yamlin yourpowersyncconfig directory and overwrites it with the result. Use--input-fileand--output-fileto read from and write to other paths. See the command reference for all flags.
2
Review the draft
Compare the draft with your Sync Rules. See What the Tool Generates for how the output maps to your bucket definitions, and What to Check Before You Deploy for the items that need your attention.
3
Deploy
Deploy the draft from the Dashboard or with
powersync deploy sync-config. This works like any other Sync Config deploy: the Service reprocesses your data in the background while the current version keeps serving clients, then switches over without downtime. After the switch, each client does a one-time full re-sync.What the Tool Generates
The following Sync Rules define global data, user-scoped data, a parameter query that reads from a table, and a Client Parameter:- Compatibility edition: It sets
config: edition: 3, which Sync Streams require, and keeps any other options in yourconfigblock. - One stream per priority: Bucket definitions with the same priority are merged into one stream named
migrated_to_streams. Comments mark which bucket definition each group of queries came from. If your bucket definitions use different priorities, the tool creates one stream per priority, namedmigrated_to_streams_prio_<priority>. - Same sync behavior: Every stream has
auto_subscribe: true. Queries are always written as aqueries:list so that you can add more. - Parameters:
request.*functions becomeauth.*andconnection.*functions. See Parameter Syntax Changes for the full mapping. Parameter queries that only select request values, such asSELECT request.user_id() as user_id, are replaced by those values in the data queries:bucket.user_idbecomesauth.user_id(). Parameter queries that read from a table become CTEs in awith:block, named<bucket_definition>_param, and the data queries join them under the aliasbucket. - Cleanup: The
bucket_definitions:section is removed.
What to Check Before You Deploy
-
Compatibility edition: If your Sync Rules had no
editionset,edition: 3also turns on the edition 2 fixes, such as ISO 8601 timestamp formatting and custom Postgres type handling. These change how some values look in the client database. See Compatibility for the full list. To keep the old behavior for a fix, set its option tofalsenext to the edition: - Queries the tool cannot convert: This is rare. When it happens, the tool stops and reports the query it could not parse. The Dashboard shows the error and its line in the validation panel, and the CLI prints it. Convert that bucket definition by hand using Parameter Syntax Changes, or ask on Discord.
Adopt Sync Streams Features
After the deploy, the generated streams behave like your bucket definitions did. You can then make the following changes one stream at a time. Changes that keepauto_subscribe: true need no client changes. Changes that remove it or change the parameter type need an app update, because clients only receive that data once they subscribe.
When old and new app versions coexist, keep the old stream and add the changed one under a new name. Newer app versions opt out of auto-subscribed streams and subscribe explicitly. Remove the old stream when the older app versions are retired.
Split the Merged Stream
The tool merges your bucket definitions into one stream. Splitting them into named streams makes each stream’s purpose visible and lets you change each one independently later. Global data, which syncs the same rows to every user, and user-scoped data both keepauto_subscribe: true. Set priority per stream where needed:
Replace Parameter CTEs With Subqueries
A parameter query that read from a table becomes a CTE that the data query joins. A subquery expresses the same filter in one statement. The generatedlist_todos queries above become:
Sync Data On Demand
A stream withoutauto_subscribe: true syncs only while the app is subscribed to it. Use this for data that a user needs on one screen, such as the todos of the list they opened. Add a subscription parameter for the value the screen provides, and keep an auth.* filter so that clients can only subscribe to data they may access:
Convert Connection Parameters to Subscription Parameters
The tool converts Client Parameters to connection parameters because they behave the same way: the app passes them inconnect(), they apply to the whole connection, and the app has to reconnect to change them. This keeps your existing behavior, but it is not the best fit for on-demand syncing. Subscription parameters let the app subscribe to the same stream several times with different values, without reconnecting, and each subscription has its own lifecycle. If you prefer to keep passing values at connect time, keep the connection parameters. They need no client changes.
Before, the migrated page_posts query syncs one page per connection: