Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
7f40fdd
New Sync Streams nav
benitav Sep 10, 2026
8d35e0d
Merge branch 'main' into sync-streams-nav
benitav Sep 16, 2026
f36b027
Update image directory
benitav Sep 16, 2026
23be733
Improve migration guide - better flow, more practical examples
benitav Sep 16, 2026
77f5406
Add a usage sidebar section for Sync Streams
benitav Sep 16, 2026
66218b2
Update snippets/binary-type.mdx
benitav Sep 16, 2026
23a86a9
Merge branch 'sync-streams-nav' of github.com:powersync-ja/powersync-…
benitav Sep 16, 2026
b89a5cd
Fix broken anchor link
benitav Sep 16, 2026
b7429c1
Better link for input-file and output-file
benitav Sep 16, 2026
f1c8f37
Merge branch 'main' into sync-streams-nav
benitav Sep 18, 2026
2831a32
Reorganize the Prioritized Sync page
benitav Sep 18, 2026
e16fa89
Update refs and add redirect
benitav Sep 18, 2026
ae3dabd
Self-contained sync rules and sync streams sections
benitav Sep 18, 2026
7c88c48
Simplify the check:links architecture
benitav Sep 18, 2026
c65a7e9
Sync Rules docs aren't frozen per se
benitav Sep 18, 2026
ef173d5
Remove compability page from sync rules
benitav Sep 21, 2026
6bf68f5
More generalized wording for shared pages
benitav Sep 21, 2026
3caf10b
Minor fixed based on Claude's review
benitav Sep 21, 2026
8b63aaf
The Reducing buckets page needs no sync rules equivalent
benitav Sep 21, 2026
7315366
Missed part of last commit
benitav Sep 21, 2026
733103d
Much thinner page for Storage V4 under Sync Rules
benitav Sep 21, 2026
931cd60
Other polish according to Claude
benitav Sep 21, 2026
88b6acd
Merge branch 'main' into sync-streams-nav
benitav Sep 21, 2026
a047ceb
Merge branch 'sync-streams-nav' into sync-rules-deprecation
benitav Sep 21, 2026
263482b
Remove sync rules mentions from guides
benitav Sep 22, 2026
2ed1fe2
Merge remote-tracking branch 'origin/main' into sync-rules-deprecation
benitav Sep 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions .claude/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,18 +142,18 @@ Update `docs.json` when adding, moving, or removing pages. Add redirects for mov

## Sync Streams and Sync Rules

Sync Streams are the default for new documentation. Keep legacy Sync Rules documentation accurate, but do not add new Sync Rules teaching, examples, or proactive references.
Sync Rules are deprecated, but many customers still run them. New documentation covers Sync Streams. Keep the Sync Rules docs accurate, and add to them when that makes them more accurate or helpful, without prioritizing that work. The goal is to avoid noisy Sync Rules references outside their own section, not to stop maintaining it.

When existing content shows both in tabs, preserve equivalent results and filters. Do not add new parallel Sync Rules examples.

For existing prose that mentions both, use "[Sync Streams](/sync/streams/overview) (or legacy [Sync Rules](/sync/rules/overview))" once per page or major section. Later mentions should omit Sync Rules.
- **Sync Rules section.** `sync/rules/` and `sync/grammar/sync-rules/` hold all Sync Rules content. Each page opens with an `<Info>` callout that starts "Sync Rules are deprecated." A split twin or concept page then links to its Sync Streams equivalent. A shared-snippet wrapper instead states that the page applies to both Sync Streams and Sync Rules unless a section says otherwise. The overview carries the full deprecation notice in a `<Warning>`. The sidebar group stays "Sync Rules (Legacy)".
- **Keep the engines apart.** Never place Sync Streams and Sync Rules content side by side: no engine tabs, no "(or legacy Sync Rules)" asides, no pointers to a Sync Rules equivalent. Outside `sync/rules/`, mention Sync Rules only to state a behavior difference that affects Sync Rules users, and remove other mentions when you edit a page.
- **Shared pages.** A page that applies to both engines keeps one body in `snippets/sync-shared/<slug>.mdx`, imported by a Sync Streams wrapper at the original path and a Sync Rules wrapper at `sync/rules/<slug>.mdx` that adds the callout and `noindex: true`. Edit the snippet, not the wrappers, and keep it valid for both engines.
- **Counterpart comments.** Every split twin, wrapper, and shared snippet starts with an MDX comment naming its counterpart. Read it before editing, apply a fix to both sides where content is shared, and keep the comment when restructuring.

## Verification

- Verify technical claims and run code examples before publication. Select other checks appropriate to the change.
- Run `vale <file>` for changed MDX pages. Add new technical terms to `.github/vale/config/vocabularies/PowerSync/accept.txt`; do not add ordinary misspellings.
- After link or navigation changes, run `npx mintlify broken-links`. Mintlify requires Node 20.17–24; if needed, use `PATH="/opt/homebrew/opt/node@24/bin:$PATH" npx mintlify broken-links`.
- For anchor and snippet checks, use `pnpm check:links`. Validate repository instruction links as file paths, since the site checker does not cover all of them.
- After link or navigation changes, run `pnpm check:links`. It runs the Mintlify path, anchor, and snippet checks through `scripts/check-links.mjs`, resolves anchors defined in imported snippets, and is the same check CI runs. It tells you if your Node version is unsupported. Validate repository instruction links as file paths, since the site checker does not cover them.
- Use [the lint command](commands/lint-docs.md) for the check workflow and [the reviewer](agents/document-reviewer.md) for editorial review. Passing linters does not establish technical accuracy or style compliance.

## Git Workflow
Expand Down
1 change: 1 addition & 0 deletions .claude/agents/document-reviewer.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ Apply the canonical standards in three passes and report findings from each:
1. **Accuracy:** claims, platform scope, versions, and consistency with the surrounding page. Before reporting a claim as unverified, check the sources the PR or the user cites, such as the source PR, divergence issue, release notes, or code at the merged commit, and follow the links inside them. If nothing is cited, look up the release the text names. Report a claim as unverified only when no source covers it or a source contradicts it. Give evidence, do not invent problems, and do not approve unverified claims as correct.
2. **Necessity:** list every sentence that describes what the product prints, displays, logs, or says in an error. Treat each one as a finding to remove unless it passes the restating rule in [Content Strategy](../CLAUDE.md#content-strategy), and report it even when the sentence is accurate. Describing visible output is not a mechanism, consequence, or signal. Also flag internal mechanics, rare exceptions, and repetition. Flag missing context only when readers need it, and do not require every entry to explain a mechanism, consequence, signal, action, and trade-off.
3. **Clarity and format:** plain technical English, clear actors and actions, and suitable examples and components.
4. **Sync Rules containment:** flag any Sync Rules mention, example, or tab outside `sync/rules/` and `snippets/sync-shared/` that does not state a behavior difference. Check that split twins, wrappers, and shared snippets keep their counterpart comment.

## Default Output

Expand Down
8 changes: 4 additions & 4 deletions .claude/commands/lint-docs.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
---
allowed-tools: Read, Bash(npx mintlify *), Bash(PATH=* npx mintlify *), Bash(vale *), Bash(git diff *), Bash(git status *)
description: Run Vale and Mintlify broken-link checks for documentation changes and report failures.
allowed-tools: Read, Bash(pnpm check:links), Bash(PATH=* pnpm check:links), Bash(vale *), Bash(git diff *), Bash(git status *)
description: Run Vale and the link check for documentation changes and report failures.
---

1. Read the canonical [Verification](../CLAUDE.md#verification) section for commands, supported Node versions, and vocabulary rules.
1. Read the canonical [Verification](../CLAUDE.md#verification) section for commands and vocabulary rules.
2. Use the requested file scope. Otherwise, identify changed MDX pages with `git diff main --name-only --diff-filter=ACMR -- '*.mdx'` and `git status --short`, including untracked pages.
3. Run `vale <file>` for each page and `npx mintlify broken-links` for the site. Use the canonical Node fallback if needed.
3. Run `vale <file>` for each page and `pnpm check:links` once for the site.
4. Report findings by file, suggested fixes, and totals for errors, warnings, and suggestions. Report failed or unavailable checks separately from content findings.
2 changes: 1 addition & 1 deletion .claude/skills/doc-author/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,6 @@ Use the canonical Working Process for scope changes and unresolved decisions.
1. Identify the reader, desired outcome, and affected feature or concept.
2. Research the implementation and existing coverage. Read the most relevant related pages and `docs.json`; avoid unnecessary duplication.
3. If a plan is needed, state the proposed pages, structure, and unresolved questions before drafting.
4. Write the update under the canonical standards. Keep the existing structure unless the task requires a change.
4. Write the update under the canonical standards. Write new content for Sync Streams and keep Sync Rules content inside its own section. Keep the existing structure unless the task requires a change.
5. Self-review for accuracy, reader understanding, minimum useful detail, and navigation fit. Run the canonical verification checks relevant to the change.
6. Present the result and any unresolved draft TODOs, or complete the delivery workflow already authorized by the user.
2 changes: 1 addition & 1 deletion .claude/skills/pr-to-docs/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,4 @@ Ask before expanding scope, documenting a deprecation that needs migration decis

## 4. Draft and Verify

Apply the canonical writing standards, navigation requirements, and verification checks. Preserve existing structure unless restructuring is part of the approved plan. Flag unresolved facts using the canonical draft-TODO convention and report what must be resolved before publication.
Apply the canonical writing standards, navigation requirements, and verification checks. Document new behavior for Sync Streams. Update Sync Rules pages when the change affects them too, and do not add Sync Rules mentions elsewhere. Preserve existing structure unless restructuring is part of the approved plan. Flag unresolved facts using the canonical draft-TODO convention and report what must be resolved before publication.
114 changes: 33 additions & 81 deletions client-sdks/advanced/custom-types-arrays-and-json.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

## JSON and JSONB

The PowerSync Service treats JSON and JSONB columns as text and provides many helpers for working with JSON in [Sync Streams](/sync/streams/overview) (or legacy [Sync Rules](/sync/rules/overview)).
The PowerSync Service treats JSON and JSONB columns as text and provides many helpers for working with JSON in [Sync Streams](/sync/streams/overview).

**Note:** Native Postgres arrays, JSON arrays, and JSONB arrays are effectively all equivalent in PowerSync.

Expand All @@ -22,36 +22,20 @@

### Sync Streams

<Tabs>
<Tab title="Sync Streams">
PowerSync treats JSON columns as text. Use `json_extract()` and other JSON functions in stream queries. Subscribe per list to sync only that list's todos:

```yaml
config:
edition: 3
streams:
my_json_todos:
auto_subscribe: true
with:
owned_lists: SELECT id AS list_id FROM lists WHERE owner_id = auth.user_id()
query: SELECT * FROM todos WHERE json_extract(custom_payload, '$.json_list') IN owned_lists
```
PowerSync treats JSON columns as text. Use `json_extract()` and other JSON functions in stream queries. Subscribe per list to sync only that list's todos:

```yaml
config:
edition: 3
streams:
my_json_todos:
auto_subscribe: true
with:
owned_lists: SELECT id AS list_id FROM lists WHERE owner_id = auth.user_id()
query: SELECT * FROM todos WHERE json_extract(custom_payload, '$.json_list') IN owned_lists
```

The client subscribes once per list (e.g. `db.syncStream('my_json_todos', { list_id: listId }).subscribe()`).
</Tab>
<Tab title="Sync Rules (Legacy)">
PowerSync treats JSON columns as text and provides transformation functions in Sync Rules such as `json_extract()`.

```yaml
bucket_definitions:
my_json_todos:
# Separate bucket per To-Do list
parameters: SELECT id AS list_id FROM lists WHERE owner_id = request.user_id()
data:
- SELECT * FROM todos WHERE json_extract(custom_payload, '$.json_list') = bucket.list_id
```
</Tab>
</Tabs>
The client subscribes once per list (e.g. `db.syncStream('my_json_todos', { list_id: listId }).subscribe()`).

### Client SDK

Expand Down Expand Up @@ -198,7 +182,7 @@

PowerSync treats array columns as JSON text. This means that the SQLite JSON operators can be used on any array columns.

Additionally, array membership is supported in [Sync Streams](/sync/streams/overview) (or legacy [Sync Rules](/sync/rules/overview)) so you can sync rows based on whether a parameter value appears in an array column.
Additionally, array membership is supported in [Sync Streams](/sync/streams/overview) so you can sync rows based on whether a parameter value appears in an array column.

**Note:** Native Postgres arrays, JSON arrays, and JSONB arrays are effectively all equivalent in PowerSync.

Expand All @@ -219,33 +203,17 @@

**Array Membership**

<Tabs>
<Tab title="Sync Streams">
Sync rows where a subscription parameter value is in the row's array column using `IN`:

```yaml
config:
edition: 3
streams:
custom_todos:
query: SELECT * FROM todos WHERE subscription.parameter('list_id') IN unique_identifiers
```
Sync rows where a subscription parameter value is in the row's array column using `IN`:

The client subscribes per list (e.g. `db.syncStream('custom_todos', { list_id: listId }).subscribe()`).
</Tab>
<Tab title="Sync Rules (Legacy)">
It's possible to sync rows dynamically based on the contents of array columns using the `IN` operator:

```yaml
bucket_definitions:
custom_todos:
# Separate bucket per To-Do list
parameters: SELECT id AS list_id FROM lists WHERE owner_id = request.user_id()
data:
- SELECT * FROM todos WHERE bucket.list_id IN unique_identifiers
```
</Tab>
</Tabs>
```yaml
config:
edition: 3
streams:
custom_todos:
query: SELECT * FROM todos WHERE subscription.parameter('list_id') IN unique_identifiers
```

The client subscribes per list (e.g. `db.syncStream('custom_todos', { list_id: listId }).subscribe()`).

<Tip>
See these additional details when using the `IN` operator: [Operators](/sync/supported-sql#operators)
Expand Down Expand Up @@ -416,31 +384,15 @@

### Sync Streams

<Tabs>
<Tab title="Sync Streams">
The custom type column is serialized as JSON and you can use `json_extract()` and other JSON functions in stream queries:

```yaml
config:
edition: 3
streams:
todos_by_city:
query: SELECT * FROM todos WHERE json_extract(location, '$.city') = subscription.parameter('city')
```
</Tab>
<Tab title="Sync Rules (Legacy)">
Custom type columns are converted to text by the PowerSync Service.
Depending on whether the `custom_postgres_types` [compatibility option](/sync/advanced/compatibility) is enabled,
PowerSync would sync the row as:

- `{"street":"1000 S Colorado Blvd.","city":"Denver","state":"CO","zip":80211}` if the option is enabled.
- `("1000 S Colorado Blvd.",Denver,CO,80211)` if the option is disabled.
The custom type column is serialized as JSON and you can use `json_extract()` and other JSON functions in stream queries:

You can use regular string and JSON manipulation functions in Sync Rules. This means that individual values of the type
can be synced with `json_extract` if the `custom_postgres_types` compatibility option is enabled.
Without the option, the entire column must be synced as text.
</Tab>
</Tabs>
```yaml
config:
edition: 3
streams:
todos_by_city:
query: SELECT * FROM todos WHERE json_extract(location, '$.city') = subscription.parameter('city')
```

### Client SDK

Expand Down Expand Up @@ -595,7 +547,7 @@
With arrays and JSON fields, it's common for only part of the value to change during an update. To make handling these writes easier, you can enable advanced schema options that let you track exactly what changed in each row—not just the new state.

- `trackPreviousValues` (or `trackPrevious` in our JS SDKs): Access previous values for diffing JSON or array fields. Accessible later via `CrudEntry.previousValues`.
- `trackMetadata`: Adds a `_metadata` column for storing custom metadata. Value of the column is accessible later via `CrudEntry.metadata`.

Check warning on line 550 in client-sdks/advanced/custom-types-arrays-and-json.mdx

View check run for this annotation

Mintlify / Mintlify Validation (powersync) - vale-spellcheck

client-sdks/advanced/custom-types-arrays-and-json.mdx#L550

Did you really mean 'orin'?
- `ignoreEmptyUpdates`: Skips updates when no data has actually changed.

<Note>
Expand Down
33 changes: 10 additions & 23 deletions client-sdks/advanced/gis-data-postgis.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
--SQL command to update the todos table with 3 additional columns:

ALTER TABLE todos
ADD COLUMN address location_address null,

Check warning on line 28 in client-sdks/advanced/gis-data-postgis.mdx

View check run for this annotation

Mintlify / Mintlify Validation (powersync) - vale-spellcheck

client-sdks/advanced/gis-data-postgis.mdx#L28

Did you really mean 'location_address'?
ADD COLUMN contact_numbers text [] null,
ADD COLUMN location geography (point) null
```
Expand Down Expand Up @@ -115,7 +115,7 @@

Example use case: Extract x (long) and y (lat) values from a PostGIS type, to use these values independently in an application.

PowerSync supports the following PostGIS functions in Sync Streams (or legacy Sync Rules): [Operators and Functions](/sync/supported-sql#functions)
PowerSync supports the following PostGIS functions in Sync Streams: [Operators and Functions](/sync/supported-sql#functions)

1. `ST_AsGeoJSON`
2. `ST_AsText`
Expand All @@ -126,25 +126,12 @@
IMPORTANT NOTE: These functions will only work if your Postgres instance has the PostGIS extension installed and you’re storing values as type `geography` or `geometry`.
</Note>

<Tabs>
<Tab title="Sync Streams">
```yaml
config:
edition: 3
streams:
global:
queries:
- SELECT * FROM lists
- SELECT *, st_x(location) as longitude, st_y(location) as latitude FROM todos
```
</Tab>
<Tab title="Sync Rules (Legacy)">
```yaml
bucket_definitions:
global:
data:
- SELECT * FROM lists
- SELECT *, st_x(location) as longitude, st_y(location) as latitude from todos
```
</Tab>
</Tabs>
```yaml
config:
edition: 3
streams:
global:
queries:
- SELECT * FROM lists
- SELECT *, st_x(location) as longitude, st_y(location) as latitude FROM todos
```
35 changes: 10 additions & 25 deletions client-sdks/advanced/pre-seeded-sqlite.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -20,34 +20,19 @@ If you're interested in seeing an end-to-end example, we've prepared a demo repo
## Main Concepts

### Generate a Scoped JWT Token
In most cases you'd want to pre-seed the SQLite database with user specific data and not all data from the source database, as you normally would when using PowerSync. For this you would need to generate JWT tokens that include the necessary properties to satisfy the conditions of the queries in your Sync Streams (or legacy Sync Rules).
In most cases you'd want to pre-seed the SQLite database with user specific data and not all data from the source database, as you normally would when using PowerSync. For this you would need to generate JWT tokens that include the necessary properties to satisfy the conditions of the queries in your Sync Streams.

Let's say we have the following Sync Config:

<Tabs>
<Tab title="Sync Streams">
```yaml
sync_config:
content: |
config:
edition: 3
streams:
store_products:
query: SELECT * FROM products WHERE store_id = auth.parameter('store_id')
```
</Tab>
<Tab title="Sync Rules (Legacy)">
```yaml
sync_config:
content: |
bucket_definitions:
store_products:
parameters: SELECT id as store_id FROM stores WHERE id = request.jwt() ->> 'store_id'
data:
- SELECT * FROM products WHERE store_id = bucket.store_id
```
</Tab>
</Tabs>
```yaml
sync_config:
content: |
config:
edition: 3
streams:
store_products:
query: SELECT * FROM products WHERE store_id = auth.parameter('store_id')
```

In the example above the `store_id` is part of the JWT payload and is used to filter products by store for a user. Given this we would want to do the following:
1. Query the source database, directly from the Node.js application, for all the store ids you'd want a pre-seeded SQLite database for.
Expand Down
2 changes: 1 addition & 1 deletion client-sdks/advanced/raw-tables.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -761,7 +761,7 @@ In PowerSync's [JSON-based view system](/architecture/client-architecture#client

### Adding Raw Tables as a New Table

When you're adding new tables to your Sync Streams (or legacy Sync Rules), clients will start to sync data on those tables - even if the tables aren't mentioned in the client's schema yet. So at the time you're introducing a new raw table to your app, it's possible that PowerSync has already synced some data for that table, which would be stored in `ps_untyped`. When adding regular tables, PowerSync will automatically extract rows from `ps_untyped`. With raw tables, that step is your responsibility. To copy data, run these statements in a transaction after creating the table:
When you're adding new tables to your Sync Streams, clients will start to sync data on those tables - even if the tables aren't mentioned in the client's schema yet. So at the time you're introducing a new raw table to your app, it's possible that PowerSync has already synced some data for that table, which would be stored in `ps_untyped`. When adding regular tables, PowerSync will automatically extract rows from `ps_untyped`. With raw tables, that step is your responsibility. To copy data, run these statements in a transaction after creating the table:

```
INSERT INTO my_table (id, my_column, ...)
Expand Down
Loading
Loading