🏡/tech/

Electric SQL

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

HTTP
Protocol
Long-Poll / SSE
Live Updates
CDN-Ready
Request Collapsing
🔷

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.

tabletodos

Root table to sync (required)

whereuser_id = $1

SQL filter for rows

columnsid,title,status

Columns to include

Shape Examples

// All todos
GET /v1/shape?table=todos

// User's todos only
GET /v1/shape?table=todos&where=user_id=$1&params[1]=123

// Selected columns
GET /v1/shape?table=todos&columns=id,title,status
⚠️

Limitations

  • • 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 shape
updateRow modified
deleteRow removed

Control Messages

up-to-dateCaught up with Postgres
must-refetchNeed to resync shape

Example 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=-1

Response

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=abc123

Wait

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

Millions
Concurrent Users
Via CDN request collapsing
5,000/s
Row Changes
With optimized where clauses
~6ms
Update Latency
Postgres → Client
Low
Memory Usage
Disk-based shape cache

Where Clause Optimization

✓ Optimized (Fast)
where=user_id=$1
where=status='active'
where=id=$1 AND visible=true

~5,000 changes/sec regardless of shape count

⚠ Non-optimized (Slower)
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

+SimpleConsistent
-Requires online

Optimistic State

Local optimistic updates + server sync

+Feels instantWorks offline briefly
-More complex

Persistent Optimistic

Persistent local store for optimistic writes

+True offlineMulti-device
-Conflict resolution needed

Through-the-DB

PGlite embedded + sync in/out

+Pure local-firstFull SQL locally
-Most complex
🚢

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_URL

Postgres connection string

ELECTRIC_PORT

HTTP port (default: 3000)

ELECTRIC_STORAGE_DIR

Persistent storage path

ELECTRIC_POOLED_DATABASE_URL

Optional 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.