AGENTS.md
Load our AGENTS.md
file to use Electric with AI coding agents.
Using Electric with coding agents
Electric provides an AGENTS.md at https://electric-sql.com/AGENTS.md.
AGENTS.md is a simple open format for guiding coding agents. It contains instructions for AI coding agents, formatted in a way they can easily digest.
Your coding agent or AI code editor may automatically read AGENTS.md. Or you can tell it to do so in your prompt, for example:
sh
claude "Read AGENTS.md. Build an app with Electric and TanStack DB."
File contents
Copy our AGENTS.md
file to the root of your repo or package. Edit it and / or combine with your other project-specific instructions, as you see fit.
md
# AGENTS.md – ElectricSQL + TanStack DB
> **Audience:** coding agents/codegen tools
> **Goal:** ship fast, reliable, local-first apps by pairing **Electric** (Postgres sync engine over HTTP) with **TanStack DB** (embedded client DB with live queries & optimistic mutations).
> **Status:** current as of **2025-09-18**.
## TL;DR
* **Electric:** read-path sync Postgres→clients via HTTP (shapes→changelog→client) ([Electric][1])
* **TanStack DB:** client collections+live queries+transactional optimistic mutations. Swap `queryCollectionOptions`→`electricCollectionOptions` without touching components ([TanStack][2])
* **Electric Collection:** subscribes to Electric Shapes (single-table, optional `where`/`columns`) ([TanStack][3])
* **Writes:** mutations→API→Postgres txid→await in Electric collection→drop optimistic state when change arrives ([TanStack][3])
* **Live queries:** differential dataflow→sub-ms updates+cross-collection joins ([TanStack][2])
* **Security/scale:** proxy auth, shape-scoped authorization, CDN caching. Use Electric Cloud to skip ops ([Electric][4])
## 🔒 Security Rules (ALWAYS)
1. **Never expose `SOURCE_SECRET` to browser** – inject server-side via proxy
2. **Electric HTTP API public by default** – enforce auth at proxy
3. **Put Electric behind server/proxy** – never call directly from production ([Electric][10])
5. **Define shapes in server/proxy** – no client-defined tables/WHERE clauses
## Golden Path
### 0) Create project
```sh
npx gitpick electric-sql/electric/tree/main/examples/tanstack-db-web-starter my-tanstack-db-project
cd my-tanstack-db-project
cp .env.example .env
pnpm install
pnpm dev
# in new terminal
pnpm migrate
```
### 1) Electric proxy (server)
```ts
// TanStack Start server function
import { createServerFileRoute } from '@tanstack/react-start/server'
import { ELECTRIC_PROTOCOL_QUERY_PARAMS } from '@electric-sql/client'
const ELECTRIC_URL = 'https://api.electric-sql.cloud/v1/shape'
const serve = async ({ request }: { request: Request }) => {
const url = new URL(request.url)
const origin = new URL(ELECTRIC_URL)
// Pass Electric protocol params
url.searchParams.forEach((v, k) => {
if (ELECTRIC_PROTOCOL_QUERY_PARAMS.includes(k)) origin.searchParams.set(k, v)
})
// Server decides shape
origin.searchParams.set('table', 'todos')
// Tenant isolation: origin.searchParams.set('where', `user_id=$1`)
// origin.searchParams.set('params', JSON.stringify([user.id]))
origin.searchParams.set('source_id', process.env.SOURCE_ID!)
origin.searchParams.set('secret', process.env.SOURCE_SECRET!)
const res = await fetch(origin)
const headers = new Headers(res.headers)
headers.delete('content-encoding')
headers.delete('content-length')
return new Response(res.body, { status: res.status, statusText: res.statusText, headers })
}
export const ServerRoute = createServerFileRoute('/api/todos').methods({ GET: serve })
```
### 2) Electric Collection (client)
```ts
import { createCollection } from '@tanstack/react-db'
import { electricCollectionOptions } from '@tanstack/electric-db-collection'
import { todoSchema } from './schema'
export const todoCollection = createCollection(
electricCollectionOptions({
id: 'todos',
schema: todoSchema,
getKey: (row) => row.id,
shapeOptions: { url: '/api/todos' },
onInsert: async ({ transaction }) => {
const newTodo = transaction.mutations[0].modified
const { txid } = await api.todos.create(newTodo)
return { txid }
},
// onUpdate/onDelete same pattern
})
)
```
**Shape config:**
- Single-table only + optional `where`/`columns`
- Include PK if using `columns`
- Shapes immutable per subscription ([Electric][6]) use collection factory function to make dynamic
### 3) Write-path contract
1. UI mutates collection (instant optimistic)
2. Collection calls API in `onInsert`/`onUpdate`/`onDelete`
3. API writes Postgres, returns txid
4. Client awaits tx on Electric stream→drops optimistic state
**Backend: get Postgres txid and return as an integer**
```sql
SELECT pg_current_xact_id()::xid::text as txid
```
### 4) Live queries
TanStack DB SQL-like queries **sub-ms performance** differential dataflow ([TanStack][7]):
```tsx
import { useLiveQuery, eq } from '@tanstack/react-db'
export function TodoList() {
const { data: todos } = useLiveQuery((q) =>
q.from({ todo: todoCollection })
.where(({ todo }) => eq(todo.completed, false))
.orderBy(({ todo }) => todo.created_at, 'desc')
.limit(50)
)
return <ul>{todos.map((todo) => <li key={todo.id}>{todo.text}</li>)}</ul>
}
```
Dependencies:
```tsx
const [direction, setDirection] = useState('desc')
const { data } = useLiveQuery((q) =>
q.from({ todo: todoCollection })
.orderBy(({ todo }) => todo.createdAt, direction)
.limit(50),
[direction]
)
```
Cross-collection joins:
```tsx
.join({ user: userCollection }, ({ todo, user }) => eq(todo.user_id, user.id))
.where(({ user }) => eq(u.active, true))
.select(({ todo, user }) => ({ id: todo.id, text: todo.text, userName: user.name }))
```
Aggregations:
```tsx
.groupBy(({ todo }) => todo.listId)
.select(({ todo }) => ({ listId: todo.listId, totalTodos: count(todo.id) }))
```
## Optimistic Mutations
### Direct mutations
```tsx
function TodoActions() {
const handleAdd = () => {
todoCollection.insert({
id: crypto.randomUUID(),
text: 'New todo',
completed: false,
createdAt: Date.now()
})
}
const handleToggle = (todo) => {
todoCollection.update(todo.id, (draft) => {
draft.completed = !draft.completed
})
}
const handleDelete = (todoId) => todoCollection.delete(todoId)
}
```
### Custom optimistic actions
```tsx
import { createOptimisticAction } from '@tanstack/react-db'
const bootstrapTodoListAction = createOptimisticAction<string>({
onMutate: (listId, itemText) => {
listCollection.insert({id: listId})
todoCollection.insert({id: crypto.randomUUID(), text: itemText, listId})
},
mutationFn: async (listId, itemText) => {
const { txid } = await api.todos.bootstrapTodoList({ listId, itemText })
await Promise.all([listCollection.utils.awaitTxId(txid), todoCollection.utils.awaitTxId(txid)])
}
})
```
## Testing
```ts
shapeOptions: {
url: '/api/todos',
fetchClient: vi.fn(), // mock fetch
onError: (error) => // ... handle fetch errors
}
```
## ⚠️ Critical Gotchas
1. **Use latest packages** - Check npm for `@electric-sql/*` & `@tanstack/*-db`
2. **txid handshake required** - Prevents UI flicker when optimistic→synced state
3. **Local dev slow shapes** - HTTP/1.1 6-connection limit. Fix: HTTP/2 proxy (Caddy/nginx) or Electric Cloud ([Electric][18])
4. **Proxy must forward headers/params** - Preserve Electric query params
5. **Parse custom types:**
```ts
shapeOptions: {
parser: { timestamptz: (date: string) => new Date(date) }
}
```
## Framework integrations
```sh
npm install @tanstack/{angular,react,solid,svelte,vue}-db
```
```ts
import { useLiveQuery } from '...'
const { data, isLoading } = useLiveQuery((q) => q.from({ todos: todosCollection }))
```
**React Native:** Requires `react-native-random-uuid` + import in entry point
## Migration from TanStack Query
1. Wrap `useQuery` in Query Collection (`queryCollectionOptions`)
2. Replace selectors with live queries
3. Port mutations to collection handlers
4. Switch to Electric Collection (no component changes)
## Deployment
### Electric Cloud
```sh
npx @electric-sql/start my-app
pnpm claim && pnpm deploy
```
### Self-hosted
```sh
docker run -e DATABASE_URL=postgres://... electricsql/electric
```
Docker compose:
```yaml
name: "electric-backend"
services:
postgres:
image: postgres:16-alpine
environment:
POSTGRES_DB: electric
POSTGRES_USER: postgres
POSTGRES_PASSWORD: password
ports: ["54321:5432"]
volumes: ["./postgres.conf:/etc/postgresql/postgresql.conf:ro"]
tmpfs: ["/var/lib/postgresql/data", "/tmp"]
command: ["postgres", "-c", "config_file=/etc/postgresql/postgresql.conf"]
backend:
image: electricsql/electric:canary
environment:
DATABASE_URL: postgresql://postgres:password@postgres:5432/electric?sslmode=disable
ELECTRIC_INSECURE: true
ports: ["3000:3000"]
depends_on: ["postgres"]
```
**Postgres requirements:** v14+, logical replication, user with REPLICATION role, `wal_level=logical`
## Stack (web/mobile)
* **DB:** Postgres (Neon/Supabase/Crunchy with logical replication)
* **Backend:** TanStack Start+Drizzle+tRPC/REST
* **Proxy:** Edge function/server route
* **Client:** TanStack DB (React/Expo)
## Evolution from Old Electric
**Old:** Bidirectional SQLite sync, handled reads+writes
**New:** Electric (Read-only HTTP streaming from Postgres) + TanStack DB (optimistic writes via API)
Avoid old patterns:
```ts
// ❌ OLD (doesn't exist)
const { db } = await electrify(conn, schema)
await db.todos.create({ text: 'New todo' })
```
Write path: `todos.insert()`→optimistic→`onInsert`→API→Postgres txid→Electric streams→reconcile→drop optimistic
Prefer TanStack DB collections over lower-level Shape/ShapeStream/useShape APIs.
## References
[1]: https://electric-sql.com/docs/api/http
[2]: https://tanstack.com/db/latest/docs/overview
[3]: https://tanstack.com/db/latest/docs/collections/electric-collection
[4]: https://electric-sql.com/docs/guides/auth
[5]: https://electric-sql.com/docs/quickstart
[6]: https://electric-sql.com/docs/guides/shapes
[7]: https://tanstack.com/db/latest/docs/guides/live-queries
[8]: https://electric-sql.com/blog/2024/11/21/local-first-with-your-existing-api
[9]: https://electric-sql.com/docs/api/clients/typescript
[10]: https://electric-sql.com/docs/guides/security
[11]: https://electric-sql.com/product/cloud
[12]: https://tanstack.com/db/latest/docs/collections/query-collection
[13]: https://tanstack.com/db/latest/docs/guides/error-handling
[14]: https://electric-sql.com/docs/stacks
[15]: https://electric-sql.com/blog/2025/07/29/local-first-sync-with-tanstack-db
[16]: https://tanstack.com/blog/tanstack-db-0.1-the-embedded-client-database-for-tanstack-query
[17]: https://frontendatscale.com/blog/tanstack-db/
[18]: https://electric-sql.com/docs/guides/troubleshooting#slow-shapes
More information
See the AGENTS.md website for more information. You may also be interested in our blog posts on: