Electric SQL
Real-time sync for Postgres
Sync subsets of your Postgres data to local apps, services, and environments. Electric handles the hard problems: partial replication, fan-out, and data delivery.
Architecture
Postgres
Source of Truth
Your database with logical replication enabled
Electric Sync Service
Elixir Web Service
Consumes WAL, maintains Shape Logs, serves HTTP API
Web Apps
Mobile
AI/Agents
PGlite
Shapes
The fundamental sync primitive
What is a Shape?
A Shape defines a subset of your Postgres data to sync. It's the unit of sync in Electric.
tabletodosRoot table to sync (required)
whereuser_id = $1SQL filter for rows
columnsid,title,statusColumns to include
Shape Examples
// All todos
GET /v1/shape?table=todos
// User's todos only
GET /v1/shape?table=todos&where=user_id=$1¶ms[1]=123
// Selected columns
GET /v1/shape?table=todos&columns=id,title,statusLimitations
- • Single-table only (no JOINs) - subscribe to multiple shapes and join in client
- • Shape definitions are immutable once subscribed
Shape Log
The sync mechanism
A Shape Log is a sequence of database operations filtered for a specific shape. Clients consume the log to build and maintain their local replica.
Operations
insertNew row added to shapeupdateRow modifieddeleteRow removedControl Messages
up-to-dateCaught up with Postgresmust-refetchNeed to resync shapeExample Log Entry
{
"headers": { "operation": "insert" },
"key": "todo-1",
"value": {
"id": "todo-1",
"title": "Learn Electric",
"completed": false
}
}Sync Protocol
How data flows
1Initial Sync
Request
Client requests shape with offset=-1
GET /v1/shape?table=todos&offset=-1Response
Electric returns paginated Shape Log entries
[{insert...}, {insert...}, ...]Complete
Client receives up-to-date message
{"headers":{"control":"up-to-date"}}2Live Mode (Real-time Updates)
Subscribe
Client enables live mode with handle
GET ...&live=true&handle=abc123Wait
Server holds connection (long-poll)
Connection open...Push
Changes pushed when available
[{update...}, {delete...}]SSE Alternative: Use live_sse=true for Server-Sent Events instead of long-polling. More efficient for persistent connections.
Replication Pipeline
From Postgres to clients
Postgres WAL
Logical replication stream
Electric Consumes
Replication slot
Filter & Match
Where clause eval
Shape Cache
Disk-based storage
HTTP Response
To clients
Postgres Requirements
- • Postgres 14+ with logical replication
- • User with REPLICATION attribute
- • Direct connection (no pgBouncer <1.23)
Electric Creates
- • Publication:
electric_publication_default - • Slot:
electric_slot_default
Performance
Where Clause Optimization
where=user_id=$1
where=status='active'
where=id=$1 AND visible=true~5,000 changes/sec regardless of shape count
where=title LIKE '%search%'
where=created_at > now()~140 changes/sec for 100 shapes
Client Usage
React@electric-sql/react
import { useShape } from '@electric-sql/react'
function Todos() {
const { data, isLoading } = useShape({
url: 'http://localhost:3000/v1/shape',
params: { table: 'todos' }
})
if (isLoading) return <div>Loading...</div>
return (
<ul>
{data?.map(todo => (
<li key={todo.id}>{todo.title}</li>
))}
</ul>
)
}TypeScript@electric-sql/client
import { ShapeStream, Shape } from '@electric-sql/client'
const stream = new ShapeStream({
url: 'http://localhost:3000/v1/shape',
params: { table: 'todos' }
})
const shape = new Shape(stream)
// Wait for initial sync
await shape.rows
// Subscribe to changes
shape.subscribe(({ rows }) => {
console.log('Todos:', rows)
})Write Patterns
Electric is read-path only
Electric syncs data from Postgres to clients. For writes, you compose your own pattern:
Online Writes
Direct API calls to server
Optimistic State
Local optimistic updates + server sync
Persistent Optimistic
Persistent local store for optimistic writes
Through-the-DB
PGlite embedded + sync in/out
Deployment
Electric Cloud
Managed hosting
- • Zero config
- • Auto-scaling
- • Monitoring
Docker
Self-hosted container
- • Full control
- • Any cloud
- • Persistent volume needed
Platform Integrations
One-click deploys
- • Supabase
- • Neon
- • Fly.io
- • Render
Key Environment Variables
DATABASE_URLPostgres connection string
ELECTRIC_PORTHTTP port (default: 3000)
ELECTRIC_STORAGE_DIRPersistent storage path
ELECTRIC_POOLED_DATABASE_URLOptional pooled connection
Key Takeaways
Shapes are the primitive
Define what data to sync with table + where + columns. Single-table only, but you can join multiple shapes client-side.
HTTP-first design
Works with any language, behind CDNs, through firewalls. Long-polling or SSE for live updates.
Read-path only
Electric doesn't handle writes. Compose your own write pattern (API calls, optimistic state, PGlite).
CDN-native scaling
Request collapsing at the CDN layer means millions of concurrent users without linear cost.