> ## Documentation Index
> Fetch the complete documentation index at: https://docs.specific.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Real-time sync

> Stream Postgres changes to clients over HTTP, powered by Electric.

For Postgres databases, you can enable real-time data synchronization to build real-time and local-first applications.

Sync is powered by [Electric](https://electric-sql.com), a sync engine that streams changes from Postgres to clients using a plain HTTP API. It uses a proxy-based architecture: your backend proxies requests to the sync engine after handling authentication and authorization.

## Enabling sync

Reference `sync.url` and `sync.secret` on a Postgres database in your service env:

```hcl specific.hcl theme={null}
postgres "main" {}

service "api" {
  command = "node index.js"

  endpoint {
    public = true
  }

  env = {
    DATABASE_URL         = postgres.main.url
    DATABASE_SYNC_URL    = postgres.main.sync.url
    DATABASE_SYNC_SECRET = postgres.main.sync.secret
  }
}
```

When these references are present, Specific automatically runs a sync engine connected to your Postgres database, locally during `specific dev` and managed in production.

Sync attributes:

| Attribute     | Description                                                                                                                           |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| `sync.url`    | Sync engine HTTP endpoint URL (for example, `http://127.0.0.1:5133` in dev).                                                          |
| `sync.secret` | Secret for authenticating requests to the sync engine. Pass it as the `secret` query parameter from your backend to the Electric API. |

## How sync works

The sync engine streams "shapes" of data from Postgres to clients:

1. Your backend proxies shape requests to the sync engine (handling auth/authz).
2. The sync engine streams the initial data and subsequent changes.
3. Clients receive real-time updates over HTTP.

For implementation details, see the Electric documentation:

* [Authentication and proxying](https://electric-sql.com/docs/guides/auth)
* [Shapes and client usage](https://electric-sql.com/docs/guides/shapes)
* [Handling writes](https://electric-sql.com/docs/guides/writes)
* [Troubleshooting](https://electric-sql.com/docs/guides/troubleshooting)

## Proxy configuration

When proxying Electric requests from your backend, handle headers and query parameters carefully. Getting these wrong causes silent failures: browsers block responses or serve stale cached data without clear error messages.

### Expose Electric response headers

Electric returns custom `electric-*` headers that the client SDK needs to follow the shape log. Browsers block JavaScript from reading non-standard response headers unless they are listed in `Access-Control-Expose-Headers`. Don't strip any `electric-*` headers from the response, and expose them all:

```
Access-Control-Expose-Headers: electric-handle, electric-offset, electric-schema, electric-cursor, electric-up-to-date, electric-chunk-last-offset
```

### Strip upstream headers

Electric's response includes its own CORS and caching headers. If your proxy copies these through and your CORS middleware also adds its own, the browser receives duplicate `Access-Control-Allow-Origin` values and rejects the response. Before setting your own headers, strip these from Electric's response:

* `Access-Control-Allow-Origin` and other `Access-Control-*` headers
* `Content-Encoding` and `Content-Length` (fetch decompresses the body but doesn't update these headers, which breaks browser decoding)
* `Cache-Control` (see below)

### Override caching

Electric returns `Cache-Control` with `max-age` and `stale-age` for request collapsing at the CDN level. During development, this can cause browsers to serve stale cached responses. For example, responses cached before CORS headers were configured correctly keep failing even after the proxy is fixed. Override with `Cache-Control: no-store` on your proxy response.

### Control query parameters

Set these server-side and never pass them through from the client:

* `table` - letting clients specify the table allows access to any table
* `where` - this is your authorization filter
* `columns` - clients could request sensitive columns
* `secret` - never expose the API secret to clients

Only forward Electric protocol parameters from the client: `offset`, `handle`, `live`, `live_sse`, `cursor`, `expired_handle`, `replica`, `log`.

## Using the Electric TypeScript SDK

`ShapeStream` requires a full URL, not a relative one. In browsers, derive it from `window.location.origin`:

```typescript theme={null}
import { ShapeStream } from "@electric-sql/client";

const stream = new ShapeStream({
  url: `${window.location.origin}/api/sync/items`,
});
```

This works correctly regardless of the deployment environment.
