Skip to main content
After defining your streams on the server-side, your client app subscribes to them to start syncing data (this is an explicit operation unless streams are configured to auto-subscribe). This page covers everything you need to use Sync Streams from your client code.

Quick Start

Streams that are configured to auto-subscribe will automatically start syncing as soon as you connect to your PowerSync instance in your client-side application. For any other streams, the basic pattern is: subscribe to a stream, wait for data to sync, then unsubscribe when done.
// Subscribe to a stream with parameters
const sub = await db.syncStream('list_todos', { list_id: 'abc123' }).subscribe();

// Wait for initial data to sync
await sub.waitForFirstSync();

// Your data is now available - query it normally
const todos = await db.getAll('SELECT * FROM todos WHERE list_id = ?', ['abc123']);

// When leaving the screen or component...
sub.unsubscribe();

Framework Integrations

Most developers use framework-specific hooks that handle subscription lifecycle automatically.
The useSyncStream hook automatically subscribes when the component mounts and unsubscribes when it unmounts:
function TodoList({ listId }) {
  // Automatically subscribes/unsubscribes based on component lifecycle
  const stream = useSyncStream({ name: 'list_todos', parameters: { list_id: listId } });
  
  // Check if data has synced
  if (!stream?.subscription.hasSynced) {
    return <LoadingSpinner />;
  }
  
  // Data is ready - query and render
  const { data: todos } = useQuery('SELECT * FROM todos WHERE list_id = ?', [listId]);
  return <TodoItems todos={todos} />;
}
You can also have useQuery wait for a stream before running:
// This query waits for the stream to sync before executing
const { data: todos } = useQuery(
  'SELECT * FROM todos WHERE list_id = ?',
  [listId],
  { streams: [{ name: 'list_todos', parameters: { list_id: listId }, waitForStream: true }] }
);

Checking Sync Status

You can check whether a subscription has synced and monitor download progress:
const sub = await db.syncStream('list_todos', { list_id: 'abc123' }).subscribe();

// Check if this subscription has completed initial sync
const status = db.currentStatus.forStream(sub);
console.log(status?.subscription.hasSynced);  // true/false
console.log(status?.progress);                // download progress

TTL (Time-To-Live)

TTL controls how long data remains cached after you unsubscribe. This enables “warm cache” behavior — when users navigate back to a screen, data may already be available without waiting for a sync. Default behavior: Data is cached for 24 hours after unsubscribing. For most apps, this default works well.

Setting a Custom TTL

// Cache for 1 hour after unsubscribe (TTL in seconds)
const sub = await db.syncStream('todos', { list_id: 'abc' })
  .subscribe({ ttl: 3600 });

// Cache indefinitely (data never expires)
const sub = await db.syncStream('todos', { list_id: 'abc' })
  .subscribe({ ttl: Infinity });

// No caching (remove data immediately on unsubscribe)
const sub = await db.syncStream('todos', { list_id: 'abc' })
  .subscribe({ ttl: 0 });

How TTL Works

  • Per-subscription: Each (stream name, parameters) pair has its own TTL.
  • First subscription wins: If you subscribe to the same stream with the same parameters multiple times, the TTL from the first subscription is used.
  • After unsubscribe: Data continues syncing for the TTL duration, then is removed from the client-side SQLite database.
// Example: User opens two lists with different TTLs
const subA = await db.syncStream('todos', { list_id: 'A' }).subscribe({ ttl: 43200 }); // 12h
const subB = await db.syncStream('todos', { list_id: 'B' }).subscribe({ ttl: 86400 }); // 24h

// Each subscription is independent
// List A data cached for 12h after unsubscribe
// List B data cached for 24h after unsubscribe

Priority Override

Streams can have a default priority set in the YAML sync configuration (see Prioritized Sync). When subscribing, you can override this priority for a specific subscription:
// Override the stream's default priority
const sub = await db.syncStream('todos', { list_id: 'abc' }).subscribe({ priority: 1 });
When different components subscribe to the same stream with the same parameters but different priorities, PowerSync uses the highest priority for syncing. That higher priority is kept until the subscription ends (or its TTL expires). Subscriptions with different parameters are independent and do not conflict.

Connection Parameters

Connection parameters are a more advanced feature for values that apply to all streams in a session. They’re the Sync Streams equivalent of Client Parameters in legacy Sync Rules.
For most use cases, subscription parameters (passed when subscribing) are more flexible and recommended. Use connection parameters only when you need a single global value across all streams, like an environment flag.
Define streams that use connection parameters:
streams:
  config:
    auto_subscribe: true
    query: SELECT * FROM config WHERE env = connection.parameter('environment')
Set connection parameters when connecting:
await db.connect(connector, {
  params: { environment: 'production' }
});

API Reference

For quick reference, here are the key methods available in each SDK:
MethodDescription
db.syncStream(name, params)Get a SyncStream instance for a stream with optional parameters
stream.subscribe(options)Subscribe to the stream. Returns a SyncStreamSubscription
subscription.waitForFirstSync()Wait until the subscription has completed its initial sync
subscription.unsubscribe()Unsubscribe from the stream (data remains cached for TTL duration)
db.currentStatus.forStream(sub)Get sync status and progress for a subscription