Skip to main content
When streams need reusable filtering logic, you can define it once in a and reference it in queries. CTEs can be defined at the stream level (scoped to one stream) or at the top level of your sync config (shared across all streams). This keeps stream definitions DRY and makes it easier to maintain. For the supported syntax of the with block and CTE rules, see Supported SQL — CTE and WITH syntax.

Why Use CTEs

Consider an app where users belong to organizations. Several tables need to filter by the user’s organizations:
The same subquery appears three times. You can merge these into one stream and define the logic once as a stream-level CTE:
If the membership logic changes, you update it in one place. When the streams cannot be merged (for example, because they have different auto_subscribe settings or separate subscription parameters), use a global CTE to share the logic across streams instead.

Stream-Level CTEs

Define CTEs in a with block inside a stream. Each CTE has a name and a SELECT query, and is available only within that stream:
The CTE query can include any filtering logic, including parameters:

Global CTEs

When the same filtering logic is needed across streams that cannot be merged — for example because they have different auto_subscribe settings or different subscription parameters — define the CTE once in a top-level with block. All streams can then reference it without repeating the definition:
If the membership logic changes, you update it in one place and all streams pick up the change. A stream can still define its own local with block. Local CTEs take precedence over global CTEs when names conflict, so you can override a global CTE for a specific stream:

Using CTEs in Queries

Once defined — either in a stream’s local with block or in the top-level global with block — use the CTE name in the stream’s query or queries. You can use it like a subquery or join it as if it were a table. Short-hand syntax (when the CTE has exactly one column):
The short-hand IN cte_name is equivalent to IN (SELECT * FROM cte_name). If the CTE has more than one column, this form is an error; use explicit subquery or join syntax instead. Explicit subquery syntax (when you need to select specific columns):
Join syntax (you can join a CTE as if it were a table). Only INNER JOIN is supported:

Combining with Multiple Queries

CTEs work well with the queries feature (multiple queries per stream). This lets you share the CTE and keep all query results in one stream: the client only needs to manage one subscription instead of multiple.

Complete Example

A full configuration using both global and stream-level CTEs. The accessible_projects logic is shared across streams via a global CTE; user_orgs is only needed by org_and_projects, so it stays stream-level:

Limitations

The following rules apply to CTEs. For the full syntax reference, see Supported SQL — CTE and WITH syntax. CTE names must not shadow source table names. This prevents hard-to-debug situations where a global CTE silently changes the behavior of a stream defined elsewhere in the config. Local CTEs take precedence over global CTEs. If a stream defines a CTE with the same name as a global CTE, the stream-level definition is used within that stream. CTEs cannot reference other CTEs. Each CTE must be self-contained:
If you need to chain filters, use nested subqueries in your stream query instead:
The short-hand IN cte_name works only when the CTE has exactly one column. If the CTE has multiple columns, use explicit subquery syntax or join the CTE as a table. Stream-level CTE names take precedence over table/collection names. If you define a stream-level CTE with the same name as a database table/collection, the CTE is used within that stream. Choose distinct names to avoid confusion.