Early in 2025, I wrote about the 2025 roadmap for PowerSync. The main theme was improving PowerSync for the web:
PowerSync began as a sync engine for apps that need to work offline. But more and more developers are using sync engines for always-online web apps. Why? Because they make web apps super-fast, and significantly simplify state management.
PowerSync has some strong qualities, like how it exposes standard SQLite, and its robust offline capabilities. Yet it has areas where it needs to evolve to work better for online web apps.
Today we are announcing our roadmap for 2025 that will level-up PowerSync for the new era of web apps powered by sync engines, while preserving our offline & mobile DNA.
I shared some of the major projects on the roadmap in pursuit of that objective, including better allowing for on-demand syncing of data in addition to “pre-syncing” data, and some web-focused performance optimizations. I also mentioned supplementary goals in the roadmap: improved querying flexibility by allowing syncing from incrementally materialized views, a simplified Sync Rules mental model, and an improved developer onboarding experience — including specifically an improved PowerSync Dashboard.
In this post, I want to provide an update on how 2025 turned out. We refined our plans during the course of 2025 and ended up in what I think is a pretty good place, albeit slightly different than originally envisioned.
Exploring the problem more deeply
During this past year, we thought more deeply about the specifics of how to improve the design of the Sync Rules system at the heart of PowerSync. Back when we started PowerSync, Sync Rules were designed for syncing all data upfront when the user connects, which is what you typically want for offline-first architecture.
For web use cases where users are mostly online however, it makes more sense to sync only the relevant contextual data “just-in-time” based on the specific pages or UI sections that the user is browsing, and to keep that data around for a while after they close the tab or UI component.
A sync engine like PowerSync is still useful for these kinds of online apps for various reasons: resiliency against losing the network connection, instantly loading data when refreshing or navigating to pages (when the data is already synced), optimistic local state when making updates, real-time updates when changes are made remotely, and ease of working with local state — avoiding complexities of caching and cache invalidation.
It is technically possible to accomplish “sync only the relevant data on-demand” with client parameters in our existing Sync Rules system, but it gets ugly quickly. The user may have multiple tabs open, each needing to sync a different set of data. Implementing the logic to combine client parameters across different tabs and/or UI components, and deciding when to remove parameters quickly becomes complex and painful.
Enter Sync Streams
Our goals were clear: We wanted to allow clients to sync relevant data on-demand in an elegant way that works well across multiple UI components and multiple browser tabs, while not compromising on how well PowerSync works for offline-first “sync data upfront” use cases. And while making these improvements, we wanted to simplify the mental model and developer experience.
After iterating through a few ideas, we came up with a design we are calling Sync Streams. Here’s a quick rundown of the core ideas.
Sync Streams are defined upfront:
streams:
issue: # Define a stream to a specific issue
query: select * from issues where id = subscription.parameters() ->> 'id'
issue_comments: # Define a stream to a specific issue's comments
query: select * from comments where issue_id = subscription.parameters() ->> 'id'Clients can then subscribe to the Sync Streams one or more times with different parameters — each time creating a subscription. If a client unsubscribes a certain subscription, the associated data continues to sync within a TTL window, which means that if the client resubscribes a Sync Stream with the same parameters as before, there will be a warm client-side cache of the relevant data. This solves the problem of manual wrangling of client parameters:
const sub = await powerSync.syncStream('issues', {id: issue_id}).subscribe(ttl: 3600);One way to use Sync Streams is with React hooks, where a component can automatically subscribe to a Sync Stream with the relevant parameter(s) when it mounts, and then automatically unsubscribe when it unmounts. The TTL behavior means that when unmounting and remounting, the relevant data is already synced on the client:
const stream = useSyncStream({ name: 'todo_list', parameters: { list: 'foo' } });If you want “sync everything upfront” behavior (like the current Sync Rules system), that’s easy too: you can configure any of your Sync Streams to be auto-subscribed when the client connects:
streams:
issues_for_org:
query: select * from issues where org_id = auth.parameters() ->> 'org_id'
auto_subscribe: trueWe also simplified the mental model in Sync Streams: We eliminated the concepts of having separate Parameter Queries and Data Queries like we do in Sync Rules. Sync Streams just have queries with embedded subqueries. As you can see in the example above, you can use parameters directly in the query. And you can do things like this with subqueries:
# "in (subquery)" replaces parameter queries:
select * from comments where issue_id in (select id from issues where owner_id = auth.user_id())Sync Streams is currently in alpha and we want to get it to beta quickly in 2026. See all the details in the docs section.
How Sync Streams stack up
Our original 2025 roadmap ideas involved adding several new concepts to the Sync Rules system: Simple vs Advanced Sync Rules. Pre-Synced vs. On-Demand Buckets. Caching Helpers. In the end, we ended up throwing all that out the window, and rather designed a streamlined set of concepts and syntax from the ground up: Sync Streams. Sync Streams initially exists alongside Sync Rules, but is a long-term complete replacement for Sync Rules.
We feel that Sync Streams delivers on the vision in our 2025 roadmap by providing an elegant unified solution catering for both offline apps that sync data upfront, and online apps that sync data on-demand — while simplifying the mental model and developer experience.
Other major 2025 projects shipped
We also shipped a bunch of other things we said we would:
Web performance optimizations: We shipped all the optimizations we planned on: prioritized syncing to ensure critical data syncs first, allowing you to improve load times especially for web apps; compression of data in transit; an OPFS-based VFS for our web SDK, and incremental & differential watch queries to avoid re-running entire queries continuously whenever any row in an affected table is updated. We also went further in expanding the reactivity options available to developers, with experimental high-performance diffs in our JavaScript SDKs, and an initial TanStack DB integration, which allows leveraging the differential dataflow implementation in TanStack DB.
As we talked about, we are huge proponents of SQLite, and SQLite performance on the browser is a topic we care a lot about. If you are interested in learning more about what’s under the hood, you may enjoy my talk from Sync Conf 2025 about the state of the art of SQLite persistence in web browsers, which is based on Ralf’s blog post.
New PowerSync Dashboard
When it comes to developer onboarding experience, we said we would improve the PowerSync Cloud experience by improving the PowerSync Dashboard. We ended up going a step further than that: we completely rebuilt the PowerSync Dashboard from the ground up, and it uses the PowerSync web SDK under the hood! We will share more about the behind-the-scenes story in the near future. We think it’s a great use case of sync engines on the web and for PowerSync in particular.
Lots of other features shipped
We were quite busy in 2025. We shipped a bunch of other stuff, including:
- Moving various parts of the stack from alpha to beta and from beta to V1
- Sync progress tracking
- Postgres bucket storage, providing an alternative to MongoDB storage when self-hosting the PowerSync Service.
- SQL Server backend database support
- New client SDKs for Node.js, .NET and Capacitor
- A new Rust-based client architecture that improves performance
- Raw SQLite tables experimental support, as an alternative to the JSON-based SQLite views system
- Fine-grained update tracking in client SDKs with previous values & metadata
- Custom metadata in sync logs for improved observability
- OP-SQLite support for PowerSync on React Native
- Expo Go support for React Native
- Room and SQLDelight integrations for our Kotlin SDK
Still pending: Syncing from incrementally materialized views
One big thing that we didn’t manage to ship in 2025 is the ability to create materialized views that you can sync from, which are automatically incrementally updated using an Incremental View Maintenance (IVM) or a similar algorithm. We got started with this project in 2025, but the first release will only happen in 2026. Therefore, Sync Streams currently have similar limited querying flexibility to Sync Rules, e.g. when it comes to multi-level joins and many-to-many relationships.
We want your feedback
We feel that with Sync Streams and other web focused optimizations, PowerSync is now much more usable for a much larger percentage of web app use cases.
If you are using PowerSync on the web, we would love to chat to get your feedback on what you see as the remaining gaps.

