Query Syntax
The supported SQL is based on a subset of the standard SQL syntax. Sync Streams support more SQL features than the legacy Sync Rules.- Sync Streams
- Sync Rules (Legacy)
SELECTwith column selection andWHEREfiltering- Subqueries with
IN (SELECT ...)and nested subqueries INNER JOIN(selected columns must come from a single table)- Common Table Expressions (CTEs) via the
with:block - Multiple queries per stream via
queries: - Table-valued functions such as
json_each()for expanding arrays BETWEENandCASEexpressions- A limited set of operators and functions
GROUP BY, ORDER BY, LIMIT, UNION, etc.). See Writing Queries for details.Filtering: WHERE Clause
Sync queries support a subset of SQLWHERE syntax. Allowed operators and combinations differ between Sync Streams and Sync Rules, and are more restrictive than standard SQL.
- Sync Streams
- Sync Rules (Legacy)
= and IS NULL — Compare a row column to a static value, a parameter, or another column:AND — Fully supported. You can mix parameter comparisons, subqueries, and row-value conditions in the same clause.OR — Supported, including OR nested inside AND. PowerSync rewrites combinations like A AND (B OR C) into separate branches before evaluating. Each OR branch must be a valid filter on its own; you cannot have a branch that only makes sense when combined with the other.NOT — Supported for simple conditions on row values. NOT IN with a literal set of values is supported: use a JSON array string (e.g. '["draft", "hidden"]'), or the ARRAY['draft', 'hidden'] and ROW('draft', 'hidden') forms. You cannot negate a subquery or a parameter array expansion.Operators
Operators can be used inWHERE clauses and in SELECT expressions. When filtering on parameters (e.g. auth.user_id(), subscription.parameter('id')), some combinations are restricted — see Filtering: WHERE Clause.
Comparison and null
Comparison and null
- Comparison:
=,!=,<,>,<=,>=— If either side isnull, the result isnull. - Null:
IS NULL,IS NOT NULL
Logical and mathematical
Logical and mathematical
- Logical:
AND,OR,NOT— See Filtering: WHERE Clause for restrictions when filtering on parameters. - Mathematical:
+,-,*,/
Text concatenation
Text concatenation
||— Joins two text values together.
JSON
JSON
json -> 'path'- Returns the value as a JSON string.json ->> 'path'— Returns the extracted value.
IN (Arrays)
IN (Arrays)
- Sync Streams:
left IN right—leftcan be a row column andrighta parameter array (e.g.id IN subscription.parameter('ids')), orlefta parameter andrighta row JSON array column. Also supports subqueries:id IN (SELECT ...). - Sync Rules: Returns true if
leftis in therightJSON array. In Data Queries,leftmust be a row column andrightcannot be a bucket parameter. In Parameter Queries, either side may be a parameter.
BETWEEN
BETWEEN
x BETWEEN a AND b,x NOT BETWEEN a AND b— True ifxis in the inclusive range[a, b]. Usable inWHEREor as aSELECTexpression. If any operand isnull, the result isnull.
WHERE price BETWEEN 10 AND 100Supported in Sync Streams only. Not available in Sync Rules.
&& (Array overlap)
&& (Array overlap)
<left> && <right>— True if the JSON array inleftand the setrightshare at least one value. Use when the row stores an array (e.g. atagged_userscolumn).leftmust be a row column (JSON array);rightmust be a subquery or parameter array.
WHERE tagged_users && (SELECT id FROM org_members WHERE org_id = auth.parameter('org_id'))Use IN when the row has a single value to check against a set; use && when the row has an array and you want to match any element.Supported in Sync Streams only. Not available in Sync Rules.
Functions
Functions can be used to transform columns/fields before being synced to a client. They operate on row data or parameters. Type names below (text, integer, real, blob, null) refer to SQLite storage classes.
Most functions are from SQLite built-in functions and SQLite JSON functions.
String and binary
String and binary
- upper(text) — Convert text to upper case.
- lower(text) — Convert text to lower case.
- substring(text, start, length) — Extracts a portion of a string based on specified start index and length. Start index is 1-based. Example:
substring(created_at, 1, 10)returns the date portion of the timestamp. - hex(data) — Convert blob or text data to hexadecimal text.
- base64(data) — Convert blob or text data to base64 text.
- length(data) — For text, return the number of characters. For blob, return the number of bytes. For null, return null. For integer and real, convert to text and return the number of characters.
Cast and types
Cast and types
CAST(x AS type)orx :: type— Cast totext,numeric,integer,real, orblob. See Type mapping and SQLite types.- typeof(data) — Returns
text,integer,real,blob, ornull.
JSON
JSON
- json_each(data) — Expands a JSON array into rows.
- Sync Streams: Works with auth and connection parameters (e.g.
JOIN json_each(auth.parameter('ids')) AS torWHERE id IN (SELECT value FROM json_each(auth.parameter('ids')))). Can also be used with columns from joined tables in some cases (e.g.SELECT * FROM lists WHERE id IN (SELECT lists.value FROM access_control a, json_each(a.allowed_lists) as lists WHERE a.user = auth.user_id())). See Expanding JSON arrays. - Sync Rules: Expands a JSON array or object from a request or token parameter into a set of parameter rows. Example:
SELECT value AS project_id FROM json_each(request.jwt() -> 'project_ids').
- Sync Streams: Works with auth and connection parameters (e.g.
- json_extract(data, path) — Same as
->>operator, but the path must start with$. - json_array_length(data) — Given a JSON array (as text), returns the length of the array. If data is null, returns null. If the value is not a JSON array, returns 0.
- json_valid(data) — Returns 1 if the data can be parsed as JSON, 0 otherwise.
- json_keys(data) — Returns the set of keys of a JSON object as a JSON array. Example:
SELECT * FROM items WHERE bucket.user_id IN json_keys(permissions_json).
Null handling
Null handling
- ifnull(x, y) — Returns x if non-null, otherwise returns y.
Conditional
Conditional
- iif(x, y, z) — Returns y if x is true, otherwise returns z.
Date/time and UUID
Date/time and UUID
- unixepoch(time-value, [modifier]) — Returns a time-value as Unix timestamp. If modifier is “subsec”, the result is a floating point number, with milliseconds included in the fraction. The time-value argument is required — this function cannot be used to get the current time.
- datetime(time-value, [modifier]) — Returns a time-value as a date and time string, in the format YYYY-MM-DD HH:MM:SS. If the specifier is “subsec”, milliseconds are also included. If the modifier is “unixepoch”, the argument is interpreted as a Unix timestamp. Both modifiers can be included:
datetime(timestamp, 'unixepoch', 'subsec'). The time-value argument is required — this function cannot be used to get the current time. - uuid_blob(id) — Convert a UUID string to bytes.
GIS (PostGIS)
GIS (PostGIS)
- ST_AsGeoJSON(geometry) — Convert PostGIS (in Postgres) geometry from WKB to GeoJSON. Combine with JSON operators to extract specific fields.
- ST_AsText(geometry) — Convert PostGIS (in Postgres) geometry from WKB to Well-Known Text (WKT).
- ST_X(point) — Get the X coordinate of a PostGIS point (in Postgres).
- ST_Y(point) — Get the Y coordinate of a PostGIS point (in Postgres).
JOIN Syntax
Supported in Sync Streams only. Not available in Sync Rules.
- Only inner joins: Use
JOINorINNER JOIN.LEFT,RIGHT, andOUTERjoins are not supported. - Single output table: All selected columns must come from one table. Use
table.*or list columns from that table (e.g.comments.*,comments.id). Selecting columns from multiple tables is invalid. - Simple join conditions: Join conditions must be equality comparisons of the form
table1.column = table2.column. Other comparisons (e.g.a.x > b.y) are not supported. - Table-valued functions in JOINs:
json_each()and similar functions work with auth or connection parameters (e.g.json_each(auth.parameter('ids'))). They can also be used with columns from joined tables in some cases.
CTE and WITH Syntax
Supported in Sync Streams only. Not available in Sync Rules.
with: block inside a stream. Each CTE is a name and a single SELECT query. The following rules apply:
- CTEs cannot reference other CTEs. Each CTE must be self-contained. To chain logic (e.g. orgs → projects), use nested subqueries in your stream query and reference only the CTE at the leaf level.
- CTE names take precedence over table names. If a CTE has the same name as a database table, the CTE is used. Use distinct names to avoid confusion.
- Short-hand
IN cte_nameworks only when the CTE has exactly one column.
CASE Expressions
Supported in Sync Streams only. Not available in Sync Rules.
CASE is allowed anywhere an expression is allowed — in SELECT columns or WHERE clauses.
Searched CASE — Each WHEN is an independent boolean condition:
ELSE is optional. If omitted and no WHEN matches, the result is null.