@supabase-labs/tanstack-db
v0.0.1
Published
A library for creating a Supabase collection.
Downloads
150
Readme
@supabase-labs/tanstack-db
Experimental. The Realtime integration is still being stabilized and may consume more Realtime messages than expected.
Test in orgs on the free plan or with a spend cap. Set
realtime: falseon individual collections to opt out.
A TanStack DB collection adapter backed by Supabase. It connects queries, mutations, and Realtime subscriptions so your UI stays in sync with Postgres.
- Live queries - components re-render when data changes, no manual cache invalidation.
- Optimistic mutations - inserts, updates, and deletes apply instantly in the UI, then roll back automatically if the server rejects them.
- Automatic Realtime sync - when another user changes a row, every client with a live query on that collection sees the update without subscription code.
- Fully typed - collections derive their types from your schema, so queries and mutations are type-checked end to end.
Your Supabase database remains the source of truth. Postgres, RLS, Auth, and the rest of your stack stay unchanged. This is a frontend data layer that plugs into what you already have, with no migration required.
Prerequisites
You need an existing Supabase project with the client library and environment variables configured. If you have not set that up yet, follow the Supabase getting started guide.
TanStack DB has official libraries for the major frontend frameworks. It does not yet support server-side rendering, so collections fetch on the client side.
Installation
npm install @supabase-labs/tanstack-db @tanstack/react-db @supabase/supabase-jsQuick Start
1. Enable Realtime For Synced Tables
Realtime is optional, but required if you want changes from other clients to appear automatically.
Enable Realtime for your table in the Supabase Table Editor, or run this in the SQL Editor:
alter publication supabase_realtime add table "public"."todos";2. Define A Collection
In TanStack DB, a collection corresponds to a Postgres table. Create one collection for each table you want to access from your frontend:
import { createCollection } from "@tanstack/react-db";
import { supabaseCollectionOptions } from "@supabase-labs/tanstack-db";
import { createClient } from "@supabase/supabase-js";
import { z } from "zod";
const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY);
const todosSchema = z.object({
id: z.number(),
title: z.string(),
description: z.string(),
completed: z.boolean(),
});
const todos = createCollection(
supabaseCollectionOptions({
tableName: "todos",
schema: todosSchema,
keys: ["id"], // should match your primary key(s)
supabase,
realtime: true,
}),
);3. Use Collections In Components
Query collections with useLiveQuery. Mutations are methods on the collection, so call collection.update or collection.delete directly. No hooks or mutation objects are required.
import { useLiveQuery, eq } from "@tanstack/react-db";
import { todos } from "../db";
const ActiveTodoList = () => {
const { data: activeTodosWithAuthors, isLoading } = useLiveQuery(
(q) =>
q
.from({ todo: todos })
.join({ user: users }, ({ todo, user }) => eq(todo.user_id, user.id))
.where(({ todo }) => eq(todo.completed, false))
.orderBy(({ todo }) => todo.priority, "desc"),
[] // re-run the query when these dependencies change
);
// applies instantly in the UI; rolls back if the server rejects it
const updateTodo = (id: string, checked: boolean) => {
todos.update(id, (draft) => {
draft.checked = checked;
});
};
const deleteTodo = (id: string) => {
todos.delete(id);
};
// ...
};API Reference
This package exports supabaseCollectionOptions and queryOnce. Everything else in the examples, including createCollection, useLiveQuery, and eq, comes from TanStack DB.
supabaseCollectionOptions(options)
Builds the options object passed to TanStack DB's createCollection. It wires the collection's query, mutation, and optional Realtime sync behavior to a Supabase table.
const todos = createCollection(
supabaseCollectionOptions({
tableName: "todos",
schema: todoSchema,
keys: ["id"],
supabase,
realtime: true,
}),
);Options
| Option | Type | Required | Description |
| ------------- | ------------------ | -------- | ---------------------------------------------------------------------------------------------------------------------- |
| tableName | string | Yes | Name of the Postgres table. Maps to the PostgREST endpoint. |
| schema | StandardSchemaV1 | Yes | Schema for a single row. Supports any Standard Schema-compatible library, including Zod and Valibot. |
| keys | string[] | Yes | Column or columns that uniquely identify a row. Should match the primary key(s) on your table. |
| supabase | SupabaseClient | Yes | Supabase client instance used for queries, mutations, and the Realtime subscription. |
| realtime | boolean | No | When true, subscribes to Postgres changes and reconciles inserts, updates, and deletes into the collection. Defaults to false. |
| queryClient | QueryClient | No | TanStack Query client. If omitted, a shared global client is used. |
Returns a collection options object to pass to createCollection.
queryOnce(callback, supabase)
Runs a TanStack DB query once against Supabase and resolves with the result. It is non-reactive: it issues the needed requests and does not subscribe to changes.
import { eq } from "@tanstack/react-db";
import { queryOnce } from "@supabase-labs/tanstack-db";
const completedTodos = await queryOnce(
(q) => q.from({ todo: todos }).where(({ todo }) => eq(todo.completed, true)),
supabase
);Parameters
| Parameter | Type | Description |
| ---------- | --------------------- | ------------------------------------------------------ |
| callback | (q) => QueryBuilder | Builds the query using the same API as useLiveQuery. |
| supabase | SupabaseClient | Supabase client used to execute the request. |
Returns a Promise that resolves to the query result, typed from the query you built.
Use queryOnce when you need a one-shot fetch, such as in server components, API routes, or form submissions where live updates are not needed.
Filters, ordering, limit, offset, joins, and aggregate functions (count, sum, avg, min, max) are pushed to PostgREST. Operations that cannot be pushed fall back to fetching matching rows and processing them client-side.
Fallback operations include GROUP BY, HAVING, DISTINCT, and computed SELECT expressions.
How It Works
Most query operations are translated to PostgREST filters and run server-side. A handful of operations run client-side instead.
Pushed To PostgREST
| Operation | Notes |
| ---------------------------------------------------- | --------------------------------------------------------------------------------- |
| FROM | Maps to the PostgREST table endpoint. |
| WHERE (eq, gt, gte, lt, lte, inArray, not, isNull) | Translated to PostgREST filter syntax. |
| AND (multiple conditions or chained .where) | Translated to PostgREST filter syntax. |
| ORDER BY (on source columns) | Translated to PostgREST filter syntax. |
| LIMIT | Translated to PostgREST filter syntax. |
| JOIN | Each table is fetched separately. The join key is pushed as an in filter on the second query. |
Evaluated Client-Side
These operations fetch the required rows and process them in memory:
SELECTcolumn subsets, renaming, and computed fields (upper,lower,concat,length,add,coalesce)- Aggregate functions:
COUNT,SUM,AVG,MIN,MAX(except when usingqueryOnce) GROUP BYandHAVINGDISTINCTORDER BYon computed fields
FAQ
Will this work with my RLS policies?
Yes. tanstack-db goes through PostgREST and Realtime, so your existing RLS policies apply automatically. To enable Realtime sync for a table, run:
alter publication supabase_realtime add table "public"."todos";What if I do not use RLS?
Without RLS, all Realtime changes broadcast to every client. Depending on whether your app is public or private, that may not be what you want.
Set realtime: false on specific collections. You will still get optimistic mutations and automatic cache invalidation.
Can I use tanstack-db and supabase-js in parallel?
Yes. This library uses supabase-js under the hood, so they are compatible. Data fetched directly through supabase-js will not appear in the tanstack-db cache.
Use supabase-js as a fallback for features TanStack DB does not cover, such as database functions through .rpc or complex GROUP BY queries.
Will this work with a custom API server?
This library targets Supabase and PostgREST tables. For custom backends, write your own TanStack DB collection. The TanStack DB docs cover collection adapters.
Roadmap
- Generate collection definitions from your database schema via the Supabase CLI, keeping them in sync as your schema evolves.
- Add
ORconditions and nestedAND/ORsupport.
