<!DOCTYPE html>
<html lang="en" dir="ltr">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <title>Auth - Guide | Electric</title>
    <meta name="description" content="How to do authentication and authorization with Electric.">
    <meta name="generator" content="VitePress v1.5.0">
    <link rel="preload stylesheet" href="/assets/style.Ct5iS31W.css" as="style">
    <link rel="preload stylesheet" href="/vp-icons.css" as="style">
    
    <script type="module" src="/assets/app.3skxP4-M.js"></script>
    <link rel="modulepreload" href="/assets/src_partials_home-features-after.md.BYSLlzCF.js">
    <link rel="modulepreload" href="/assets/chunks/framework.DjYoZdHD.js">
    <link rel="modulepreload" href="/assets/chunks/auth.C35HqSuI.js">
    <link rel="modulepreload" href="/assets/chunks/authorizing-proxy.D0nbgANd.js">
    <link rel="modulepreload" href="/assets/chunks/gatekeeper-flow.CQGbEvT1.js">
    <link rel="modulepreload" href="/assets/docs_guides_auth.md.79v9ZOqc.lean.js">
    <link rel="icon" type="image/png" href="/img/brand/favicon.png">
    <link rel="icon" type="image/svg+xml" href="/img/brand/favicon.svg">
    <link rel="prerender" href="https://airtable.com/embed/appDitPIpjlAxK7CL/pagrWjq3qw5Fp68Wa/form">
    <script defer data-domain="electric-sql.com" src="https://plausible.io/js/script.js"></script>
    <script id="check-dark-mode">document.documentElement.classList.add("dark");</script>
    <script id="check-mac-os">document.documentElement.classList.toggle("mac",/Mac|iPhone|iPod|iPad/i.test(navigator.platform));</script>
    <meta name="twitter:card" content="summary_large_image">
    <meta name="twitter:site" content="@ElectricSQL">
    <meta name="twitter:title" content="Auth - Guide | ElectricSQL">
    <meta name="twitter:description" content="How to do authentication and authorization with Electric.">
    <meta name="twitter:image" content="https://electric-sql.com/.netlify/images?url=https%3A%2F%2Felectric-sql.com%2Fimg%2Fmeta%2Felectric-sync-primitives.jpg&amp;w=1200&amp;h=630&amp;fit=cover&amp;fm=jpg&amp;q=80">
    <meta property="og:title" content="Auth - Guide | ElectricSQL">
    <meta property="og:description" content="How to do authentication and authorization with Electric.">
    <meta property="og:image" content="https://electric-sql.com/.netlify/images?url=https%3A%2F%2Felectric-sql.com%2Fimg%2Fmeta%2Felectric-sync-primitives.jpg&amp;w=1200&amp;h=630&amp;fit=cover&amp;fm=jpg&amp;q=80">
  </head>
  <body>
    <div id="app"><div class="Layout" data-v-a29e6ba2><!--[--><!--]--><!--[--><span tabindex="-1" data-v-c3906ed2></span><a href="#VPContent" class="VPSkipLink visually-hidden" data-v-c3906ed2> Skip to content </a><!--]--><!----><header class="VPNav" data-v-a29e6ba2 data-v-388babf5><div class="VPNavBar" data-v-388babf5 data-v-5d557444><div class="wrapper" data-v-5d557444><div class="container" data-v-5d557444><div class="title" data-v-5d557444><div class="VPNavBarTitle has-sidebar" data-v-5d557444 data-v-ac766fc0><a class="title" href="/" data-v-ac766fc0><!--[--><!--]--><!--[--><img class="VPImage logo" src="/img/brand/logo.svg" alt data-v-2d46c176><!--]--><!----><!--[--><!--]--></a></div></div><div class="content" data-v-5d557444><div class="content-body" data-v-5d557444><!--[--><!--]--><div class="VPNavBarSearch search" data-v-5d557444><!--[--><!----><div id="local-search"><button type="button" class="DocSearch DocSearch-Button" aria-label="Search"><span class="DocSearch-Button-Container"><span class="vp-icon DocSearch-Search-Icon"></span><span class="DocSearch-Button-Placeholder">Search</span></span><span class="DocSearch-Button-Keys"><kbd class="DocSearch-Button-Key"></kbd><kbd class="DocSearch-Button-Key">K</kbd></span></button></div><!--]--></div><nav aria-labelledby="main-nav-aria-label" class="VPNavBarMenu menu" data-v-5d557444 data-v-e138a194><span id="main-nav-aria-label" class="visually-hidden" data-v-e138a194> Main Navigation </span><!--[--><!--[--><a class="VPLink link VPNavBarMenuLink" href="/sync" tabindex="0" data-v-e138a194 data-v-4ebd574a><!--[--><span data-v-4ebd574a>Sync</span><!--]--></a><!--]--><!--[--><a class="VPLink link VPNavBarMenuLink" href="/primitives" tabindex="0" data-v-e138a194 data-v-4ebd574a><!--[--><span data-v-4ebd574a>Primitives</span><!--]--></a><!--]--><!--[--><a class="VPLink link VPNavBarMenuLink" href="/cloud" tabindex="0" data-v-e138a194 data-v-4ebd574a><!--[--><span data-v-4ebd574a>Cloud</span><!--]--></a><!--]--><!--[--><a class="VPLink link VPNavBarMenuLink" href="/pricing" tabindex="0" data-v-e138a194 data-v-4ebd574a><!--[--><span data-v-4ebd574a>Pricing</span><!--]--></a><!--]--><!--[--><a class="VPLink link VPNavBarMenuLink active" href="/docs/intro" tabindex="0" data-v-e138a194 data-v-4ebd574a><!--[--><span data-v-4ebd574a>Docs</span><!--]--></a><!--]--><!--[--><a class="VPLink link VPNavBarMenuLink" href="/demos" tabindex="0" data-v-e138a194 data-v-4ebd574a><!--[--><span data-v-4ebd574a>Demos</span><!--]--></a><!--]--><!--[--><a class="VPLink link VPNavBarMenuLink" href="/blog" tabindex="0" data-v-e138a194 data-v-4ebd574a><!--[--><span data-v-4ebd574a>Blog</span><!--]--></a><!--]--><!--[--><a class="VPLink link VPNavBarMenuLink" href="/about/community" tabindex="0" data-v-e138a194 data-v-4ebd574a><!--[--><span data-v-4ebd574a>About</span><!--]--></a><!--]--><!--[--><div class="nav-item VPNavBarMenuLink VPNavBarCTA" data-v-e138a194 data-v-f78c127b><div class="cta-lg" data-v-f78c127b><a class="VPButton medium brand" href="https://dashboard.electric-sql.cloud/" target="_blank" rel="noreferrer" data-v-f78c127b data-v-c160c05e>Go to Cloud</a></div><div class="cta-md" data-v-f78c127b><a class="VPButton medium brand" href="https://dashboard.electric-sql.cloud/" target="_blank" rel="noreferrer" data-v-f78c127b data-v-c160c05e>Cloud</a></div><div class="cta-sm" data-v-f78c127b><a class="VPButton medium brand" href="https://dashboard.electric-sql.cloud/" target="_blank" rel="noreferrer" data-v-f78c127b data-v-c160c05e>Go to Cloud</a></div></div><!--]--><!--]--></nav><!----><!----><div class="VPSocialLinks VPNavBarSocialLinks social-links" data-v-5d557444 data-v-c62242fd data-v-e5810ca3><!--[--><a class="VPSocialLink no-icon" href="https://durablestreams.com" aria-label="durable-streams" target="_blank" rel="noopener" data-v-e5810ca3 data-v-f0af069a><span class="vpi-social-durable-streams"></span></a><a class="VPSocialLink no-icon" href="https://tanstack.com/db" aria-label="tanstack" target="_blank" rel="noopener" data-v-e5810ca3 data-v-f0af069a><span class="vpi-social-tanstack"></span></a><a class="VPSocialLink no-icon" href="https://pglite.dev" aria-label="pglite" target="_blank" rel="noopener" data-v-e5810ca3 data-v-f0af069a><span class="vpi-social-pglite"></span></a><a class="VPSocialLink no-icon" href="https://x.com/ElectricSQL" aria-label="x" target="_blank" rel="noopener" data-v-e5810ca3 data-v-f0af069a><span class="vpi-social-x"></span></a><a class="VPSocialLink no-icon" href="https://discord.electric-sql.com" aria-label="discord" target="_blank" rel="noopener" data-v-e5810ca3 data-v-f0af069a><span class="vpi-social-discord"></span></a><a class="VPSocialLink no-icon" href="https://github.com/electric-sql/electric" aria-label="github" target="_blank" rel="noopener" data-v-e5810ca3 data-v-f0af069a><span class="vpi-social-github"></span></a><!--]--></div><div class="VPFlyout VPNavBarExtra extra" data-v-5d557444 data-v-73029a02 data-v-8015fcbb><button type="button" class="button" aria-haspopup="true" aria-expanded="false" aria-label="extra navigation" data-v-8015fcbb><span class="vpi-more-horizontal icon" data-v-8015fcbb></span></button><div class="menu" data-v-8015fcbb><div class="VPMenu" data-v-8015fcbb data-v-cbddf0cc><!----><!--[--><!--[--><!----><!----><div class="group" data-v-73029a02><div class="item social-links" data-v-73029a02><div class="VPSocialLinks social-links-list" data-v-73029a02 data-v-e5810ca3><!--[--><a class="VPSocialLink no-icon" href="https://durablestreams.com" aria-label="durable-streams" target="_blank" rel="noopener" data-v-e5810ca3 data-v-f0af069a><span class="vpi-social-durable-streams"></span></a><a class="VPSocialLink no-icon" href="https://tanstack.com/db" aria-label="tanstack" target="_blank" rel="noopener" data-v-e5810ca3 data-v-f0af069a><span class="vpi-social-tanstack"></span></a><a class="VPSocialLink no-icon" href="https://pglite.dev" aria-label="pglite" target="_blank" rel="noopener" data-v-e5810ca3 data-v-f0af069a><span class="vpi-social-pglite"></span></a><a class="VPSocialLink no-icon" href="https://x.com/ElectricSQL" aria-label="x" target="_blank" rel="noopener" data-v-e5810ca3 data-v-f0af069a><span class="vpi-social-x"></span></a><a class="VPSocialLink no-icon" href="https://discord.electric-sql.com" aria-label="discord" target="_blank" rel="noopener" data-v-e5810ca3 data-v-f0af069a><span class="vpi-social-discord"></span></a><a class="VPSocialLink no-icon" href="https://github.com/electric-sql/electric" aria-label="github" target="_blank" rel="noopener" data-v-e5810ca3 data-v-f0af069a><span class="vpi-social-github"></span></a><!--]--></div></div></div><!--]--><!--]--></div></div></div><!--[--><!--[--><!--[--><div class="nav-item VPNavBarMenuLink VPNavBarCTA" data-v-f78c127b><div class="cta-lg" data-v-f78c127b><a class="VPButton medium brand" href="https://dashboard.electric-sql.cloud/" target="_blank" rel="noreferrer" data-v-f78c127b data-v-c160c05e>Go to Cloud</a></div><div class="cta-md" data-v-f78c127b><a class="VPButton medium brand" href="https://dashboard.electric-sql.cloud/" target="_blank" rel="noreferrer" data-v-f78c127b data-v-c160c05e>Cloud</a></div><div class="cta-sm" data-v-f78c127b><a class="VPButton medium brand" href="https://dashboard.electric-sql.cloud/" target="_blank" rel="noreferrer" data-v-f78c127b data-v-c160c05e>Go to Cloud</a></div></div><!--]--><!--]--><!--]--><button type="button" class="VPNavBarHamburger hamburger" aria-label="mobile navigation" aria-expanded="false" aria-controls="VPNavScreen" data-v-5d557444 data-v-998521f3><span class="container" data-v-998521f3><span class="top" data-v-998521f3></span><span class="middle" data-v-998521f3></span><span class="bottom" data-v-998521f3></span></span></button></div></div></div></div><div class="divider" data-v-5d557444><div class="divider-line" data-v-5d557444></div></div></div><!----></header><div class="VPLocalNav has-sidebar empty" data-v-a29e6ba2 data-v-c84339ae><div class="container" data-v-c84339ae><button class="menu" aria-expanded="false" aria-controls="VPSidebarNav" data-v-c84339ae><span class="vpi-align-left menu-icon" data-v-c84339ae></span><span class="menu-text" data-v-c84339ae>Menu</span></button><div class="VPLocalNavOutlineDropdown" style="--vp-vh:0px;" data-v-c84339ae data-v-91cce16e><button data-v-91cce16e>Return to top</button><!----></div></div></div><aside class="VPSidebar" data-v-a29e6ba2 data-v-0a014151><div class="curtain" data-v-0a014151></div><nav class="nav" id="VPSidebarNav" aria-labelledby="sidebar-aria-label" tabindex="-1" data-v-0a014151><span class="visually-hidden" id="sidebar-aria-label" data-v-0a014151> Sidebar Navigation </span><!--[--><!--]--><!--[--><div class="no-transition group" data-v-665c6ffe><section class="VPSidebarItem level-0 collapsible" data-v-665c6ffe data-v-f65b8172><div class="item" role="button" tabindex="0" data-v-f65b8172><div class="indicator" data-v-f65b8172></div><h2 class="text" data-v-f65b8172>Docs</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-f65b8172><span class="vpi-chevron-right caret-icon" data-v-f65b8172></span></div></div><div class="items" data-v-f65b8172><!--[--><div class="VPSidebarItem level-1 is-link" data-v-f65b8172 data-v-f65b8172><div class="item" data-v-f65b8172><div class="indicator" data-v-f65b8172></div><a class="VPLink link link" href="/docs/intro" data-v-f65b8172><!--[--><p class="text" data-v-f65b8172>Intro</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-f65b8172 data-v-f65b8172><div class="item" data-v-f65b8172><div class="indicator" data-v-f65b8172></div><a class="VPLink link link" href="/docs/quickstart" data-v-f65b8172><!--[--><p class="text" data-v-f65b8172>Quickstart</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-f65b8172 data-v-f65b8172><div class="item" data-v-f65b8172><div class="indicator" data-v-f65b8172></div><a class="VPLink link link" href="/docs/stacks" data-v-f65b8172><!--[--><p class="text" data-v-f65b8172>Stacks</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-f65b8172 data-v-f65b8172><div class="item" data-v-f65b8172><div class="indicator" data-v-f65b8172></div><a class="VPLink link link" href="/docs/agents" data-v-f65b8172><!--[--><p class="text" data-v-f65b8172>AGENTS.md</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="no-transition group" data-v-665c6ffe><section class="VPSidebarItem level-0 collapsible has-active" data-v-665c6ffe data-v-f65b8172><div class="item" role="button" tabindex="0" data-v-f65b8172><div class="indicator" data-v-f65b8172></div><h2 class="text" data-v-f65b8172>Guides</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-f65b8172><span class="vpi-chevron-right caret-icon" data-v-f65b8172></span></div></div><div class="items" data-v-f65b8172><!--[--><div class="VPSidebarItem level-1 is-link" data-v-f65b8172 data-v-f65b8172><div class="item" data-v-f65b8172><div class="indicator" data-v-f65b8172></div><a class="VPLink link link" href="/docs/guides/auth" data-v-f65b8172><!--[--><p class="text" data-v-f65b8172>Auth</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-f65b8172 data-v-f65b8172><div class="item" data-v-f65b8172><div class="indicator" data-v-f65b8172></div><a class="VPLink link link" href="/docs/guides/shapes" data-v-f65b8172><!--[--><p class="text" data-v-f65b8172>Shapes</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-f65b8172 data-v-f65b8172><div class="item" data-v-f65b8172><div class="indicator" data-v-f65b8172></div><a class="VPLink link link" href="/docs/guides/writes" data-v-f65b8172><!--[--><p class="text" data-v-f65b8172>Writes</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-f65b8172 data-v-f65b8172><div class="item" data-v-f65b8172><div class="indicator" data-v-f65b8172></div><a class="VPLink link link" href="/docs/guides/installation" data-v-f65b8172><!--[--><p class="text" data-v-f65b8172>Installation</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-f65b8172 data-v-f65b8172><div class="item" data-v-f65b8172><div class="indicator" data-v-f65b8172></div><a class="VPLink link link" href="/docs/guides/postgres-permissions" data-v-f65b8172><!--[--><p class="text" data-v-f65b8172>PostgreSQL Permissions</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-f65b8172 data-v-f65b8172><div class="item" data-v-f65b8172><div class="indicator" data-v-f65b8172></div><a class="VPLink link link" href="/docs/guides/deployment" data-v-f65b8172><!--[--><p class="text" data-v-f65b8172>Deployment</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-f65b8172 data-v-f65b8172><div class="item" data-v-f65b8172><div class="indicator" data-v-f65b8172></div><a class="VPLink link link" href="/docs/guides/sharding" data-v-f65b8172><!--[--><p class="text" data-v-f65b8172>Sharding</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-f65b8172 data-v-f65b8172><div class="item" data-v-f65b8172><div class="indicator" data-v-f65b8172></div><a class="VPLink link link" href="/docs/guides/security" data-v-f65b8172><!--[--><p class="text" data-v-f65b8172>Security</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-f65b8172 data-v-f65b8172><div class="item" data-v-f65b8172><div class="indicator" data-v-f65b8172></div><a class="VPLink link link" href="/docs/guides/troubleshooting" data-v-f65b8172><!--[--><p class="text" data-v-f65b8172>Troubleshooting</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-f65b8172 data-v-f65b8172><div class="item" data-v-f65b8172><div class="indicator" data-v-f65b8172></div><a class="VPLink link link" href="/docs/guides/client-development" data-v-f65b8172><!--[--><p class="text" data-v-f65b8172>Client development</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="no-transition group" data-v-665c6ffe><section class="VPSidebarItem level-0 collapsible" data-v-665c6ffe data-v-f65b8172><div class="item" role="button" tabindex="0" data-v-f65b8172><div class="indicator" data-v-f65b8172></div><h2 class="text" data-v-f65b8172>API</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-f65b8172><span class="vpi-chevron-right caret-icon" data-v-f65b8172></span></div></div><div class="items" data-v-f65b8172><!--[--><div class="VPSidebarItem level-1 is-link" data-v-f65b8172 data-v-f65b8172><div class="item" data-v-f65b8172><div class="indicator" data-v-f65b8172></div><a class="VPLink link link" href="/docs/api/http" data-v-f65b8172><!--[--><p class="text" data-v-f65b8172>HTTP</p><!--]--></a><!----></div><!----></div><section class="VPSidebarItem level-1 collapsible" data-v-f65b8172 data-v-f65b8172><div class="item" role="button" tabindex="0" data-v-f65b8172><div class="indicator" data-v-f65b8172></div><h3 class="text" data-v-f65b8172>Clients</h3><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-f65b8172><span class="vpi-chevron-right caret-icon" data-v-f65b8172></span></div></div><div class="items" data-v-f65b8172><!--[--><div class="VPSidebarItem level-2 is-link" data-v-f65b8172 data-v-f65b8172><div class="item" data-v-f65b8172><div class="indicator" data-v-f65b8172></div><a class="VPLink link link" href="/docs/api/clients/typescript" data-v-f65b8172><!--[--><p class="text" data-v-f65b8172>TypeScript</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-f65b8172 data-v-f65b8172><div class="item" data-v-f65b8172><div class="indicator" data-v-f65b8172></div><a class="VPLink link link" href="/docs/api/clients/elixir" data-v-f65b8172><!--[--><p class="text" data-v-f65b8172>Elixir</p><!--]--></a><!----></div><!----></div><!--]--></div></section><div class="VPSidebarItem level-1 is-link" data-v-f65b8172 data-v-f65b8172><div class="item" data-v-f65b8172><div class="indicator" data-v-f65b8172></div><a class="VPLink link link" href="/docs/api/config" data-v-f65b8172><!--[--><p class="text" data-v-f65b8172>Config</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><div class="no-transition group" data-v-665c6ffe><section class="VPSidebarItem level-0 collapsible" data-v-665c6ffe data-v-f65b8172><div class="item" role="button" tabindex="0" data-v-f65b8172><div class="indicator" data-v-f65b8172></div><h2 class="text" data-v-f65b8172>Integrations</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-f65b8172><span class="vpi-chevron-right caret-icon" data-v-f65b8172></span></div></div><div class="items" data-v-f65b8172><!--[--><section class="VPSidebarItem level-1" data-v-f65b8172 data-v-f65b8172><div class="item" role="button" tabindex="0" data-v-f65b8172><div class="indicator" data-v-f65b8172></div><h3 class="text" data-v-f65b8172>Frameworks</h3><!----></div><div class="items" data-v-f65b8172><!--[--><div class="VPSidebarItem level-2 is-link" data-v-f65b8172 data-v-f65b8172><div class="item" data-v-f65b8172><div class="indicator" data-v-f65b8172></div><a class="VPLink link link" href="/docs/integrations/livestore" data-v-f65b8172><!--[--><p class="text" data-v-f65b8172>LiveStore</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-f65b8172 data-v-f65b8172><div class="item" data-v-f65b8172><div class="indicator" data-v-f65b8172></div><a class="VPLink link link" href="/docs/integrations/mobx" data-v-f65b8172><!--[--><p class="text" data-v-f65b8172>MobX</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-f65b8172 data-v-f65b8172><div class="item" data-v-f65b8172><div class="indicator" data-v-f65b8172></div><a class="VPLink link link" href="/docs/integrations/next" data-v-f65b8172><!--[--><p class="text" data-v-f65b8172>Next.js</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-f65b8172 data-v-f65b8172><div class="item" data-v-f65b8172><div class="indicator" data-v-f65b8172></div><a class="VPLink link link" href="/docs/integrations/phoenix" data-v-f65b8172><!--[--><p class="text" data-v-f65b8172>Phoenix</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-f65b8172 data-v-f65b8172><div class="item" data-v-f65b8172><div class="indicator" data-v-f65b8172></div><a class="VPLink link link" href="/docs/integrations/react" data-v-f65b8172><!--[--><p class="text" data-v-f65b8172>React</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-f65b8172 data-v-f65b8172><div class="item" data-v-f65b8172><div class="indicator" data-v-f65b8172></div><a class="VPLink link link" href="/docs/integrations/redis" data-v-f65b8172><!--[--><p class="text" data-v-f65b8172>Redis</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-f65b8172 data-v-f65b8172><div class="item" data-v-f65b8172><div class="indicator" data-v-f65b8172></div><a class="VPLink link link" href="/docs/integrations/tanstack" data-v-f65b8172><!--[--><p class="text" data-v-f65b8172>TanStack</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-f65b8172 data-v-f65b8172><div class="item" data-v-f65b8172><div class="indicator" data-v-f65b8172></div><a class="VPLink link link" href="/docs/integrations/yjs" data-v-f65b8172><!--[--><p class="text" data-v-f65b8172>Yjs</p><!--]--></a><!----></div><!----></div><!--]--></div></section><section class="VPSidebarItem level-1" data-v-f65b8172 data-v-f65b8172><div class="item" role="button" tabindex="0" data-v-f65b8172><div class="indicator" data-v-f65b8172></div><h3 class="text" data-v-f65b8172>Platforms</h3><!----></div><div class="items" data-v-f65b8172><!--[--><div class="VPSidebarItem level-2 is-link" data-v-f65b8172 data-v-f65b8172><div class="item" data-v-f65b8172><div class="indicator" data-v-f65b8172></div><a class="VPLink link link" href="/docs/integrations/aws" data-v-f65b8172><!--[--><p class="text" data-v-f65b8172>AWS</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-f65b8172 data-v-f65b8172><div class="item" data-v-f65b8172><div class="indicator" data-v-f65b8172></div><a class="VPLink link link" href="/docs/integrations/cloudflare" data-v-f65b8172><!--[--><p class="text" data-v-f65b8172>Cloudflare</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-f65b8172 data-v-f65b8172><div class="item" data-v-f65b8172><div class="indicator" data-v-f65b8172></div><a class="VPLink link link" href="/docs/integrations/crunchy" data-v-f65b8172><!--[--><p class="text" data-v-f65b8172>Crunchy</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-f65b8172 data-v-f65b8172><div class="item" data-v-f65b8172><div class="indicator" data-v-f65b8172></div><a class="VPLink link link" href="/docs/integrations/digital-ocean" data-v-f65b8172><!--[--><p class="text" data-v-f65b8172>Digital Ocean</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-f65b8172 data-v-f65b8172><div class="item" data-v-f65b8172><div class="indicator" data-v-f65b8172></div><a class="VPLink link link" href="/docs/integrations/expo" data-v-f65b8172><!--[--><p class="text" data-v-f65b8172>Expo</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-f65b8172 data-v-f65b8172><div class="item" data-v-f65b8172><div class="indicator" data-v-f65b8172></div><a class="VPLink link link" href="/docs/integrations/fly" data-v-f65b8172><!--[--><p class="text" data-v-f65b8172>Fly.io</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-f65b8172 data-v-f65b8172><div class="item" data-v-f65b8172><div class="indicator" data-v-f65b8172></div><a class="VPLink link link" href="/docs/integrations/gcp" data-v-f65b8172><!--[--><p class="text" data-v-f65b8172>GCP</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-f65b8172 data-v-f65b8172><div class="item" data-v-f65b8172><div class="indicator" data-v-f65b8172></div><a class="VPLink link link" href="/docs/integrations/neon" data-v-f65b8172><!--[--><p class="text" data-v-f65b8172>Neon</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-f65b8172 data-v-f65b8172><div class="item" data-v-f65b8172><div class="indicator" data-v-f65b8172></div><a class="VPLink link link" href="/docs/integrations/netlify" data-v-f65b8172><!--[--><p class="text" data-v-f65b8172>Netlify</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-f65b8172 data-v-f65b8172><div class="item" data-v-f65b8172><div class="indicator" data-v-f65b8172></div><a class="VPLink link link" href="/docs/integrations/planetscale" data-v-f65b8172><!--[--><p class="text" data-v-f65b8172>PlanetScale</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-f65b8172 data-v-f65b8172><div class="item" data-v-f65b8172><div class="indicator" data-v-f65b8172></div><a class="VPLink link link" href="/docs/integrations/render" data-v-f65b8172><!--[--><p class="text" data-v-f65b8172>Render</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-2 is-link" data-v-f65b8172 data-v-f65b8172><div class="item" data-v-f65b8172><div class="indicator" data-v-f65b8172></div><a class="VPLink link link" href="/docs/integrations/supabase" data-v-f65b8172><!--[--><p class="text" data-v-f65b8172>Supabase</p><!--]--></a><!----></div><!----></div><!--]--></div></section><!--]--></div></section></div><div class="no-transition group" data-v-665c6ffe><section class="VPSidebarItem level-0 collapsible" data-v-665c6ffe data-v-f65b8172><div class="item" role="button" tabindex="0" data-v-f65b8172><div class="indicator" data-v-f65b8172></div><h2 class="text" data-v-f65b8172>Reference</h2><div class="caret" role="button" aria-label="toggle section" tabindex="0" data-v-f65b8172><span class="vpi-chevron-right caret-icon" data-v-f65b8172></span></div></div><div class="items" data-v-f65b8172><!--[--><div class="VPSidebarItem level-1 is-link" data-v-f65b8172 data-v-f65b8172><div class="item" data-v-f65b8172><div class="indicator" data-v-f65b8172></div><a class="VPLink link link" href="/docs/reference/alternatives" data-v-f65b8172><!--[--><p class="text" data-v-f65b8172>Alternatives</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-f65b8172 data-v-f65b8172><div class="item" data-v-f65b8172><div class="indicator" data-v-f65b8172></div><a class="VPLink link link" href="/docs/reference/benchmarks" data-v-f65b8172><!--[--><p class="text" data-v-f65b8172>Benchmarks</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-f65b8172 data-v-f65b8172><div class="item" data-v-f65b8172><div class="indicator" data-v-f65b8172></div><a class="VPLink link link" href="/docs/reference/literature" data-v-f65b8172><!--[--><p class="text" data-v-f65b8172>Literature</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-f65b8172 data-v-f65b8172><div class="item" data-v-f65b8172><div class="indicator" data-v-f65b8172></div><a class="VPLink link link" href="/docs/reference/telemetry" data-v-f65b8172><!--[--><p class="text" data-v-f65b8172>Telemetry</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><!--]--><!--[--><!--]--></nav></aside><div class="VPContent has-sidebar" id="VPContent" data-v-a29e6ba2 data-v-93396c71><div class="VPDoc has-sidebar has-aside" data-v-93396c71 data-v-e5fa90be><!--[--><!--[--><!--[--><div class="markdown-link-local-nav-container"><a class="markdown-link-local-nav" href="/docs/guides/auth.md" target="_blank" rel="noopener" data-v-c9b8e4a6><span class="title" data-v-c9b8e4a6>✨ Markdown</span></a></div><div class="custom-local-nav-dropdown"><div class="VPLocalNavOutlineDropdown" style="--vp-vh:0px;" data-v-bf359f67><button data-v-bf359f67>Return to top</button><!----></div></div><!--]--><!--]--><!--]--><div class="container" data-v-e5fa90be><div class="aside" data-v-e5fa90be><div class="aside-curtain" data-v-e5fa90be></div><div class="aside-container" data-v-e5fa90be><div class="aside-content" data-v-e5fa90be><div class="VPDocAside" data-v-e5fa90be data-v-3e59a195><!--[--><!--]--><!--[--><!--[--><!--[--><!--[--><a class="markdown-link-aside pager-link" href="/docs/guides/auth.md" target="_blank" rel="noopener" data-v-c9b8e4a6><span class="title" data-v-c9b8e4a6>✨ Markdown</span></a><!--]--><!--]--><!--]--><!--]--><nav aria-labelledby="doc-outline-aria-label" class="VPDocAsideOutline" data-v-3e59a195 data-v-2fe00466><div class="content" data-v-2fe00466><div class="outline-marker" data-v-2fe00466></div><div aria-level="2" class="outline-title" id="doc-outline-aria-label" role="heading" data-v-2fe00466>On this page</div><ul class="VPDocOutlineItem root" data-v-2fe00466 data-v-1c0671af><!--[--><!--]--></ul></div></nav><!--[--><!--]--><div class="spacer" data-v-3e59a195></div><!--[--><!--]--><!----><!--[--><!--]--><!--[--><!--]--></div></div></div></div><div class="content" data-v-e5fa90be><div class="content-container" data-v-e5fa90be><!--[--><!--]--><main class="main" data-v-e5fa90be><div style="position:relative;" class="vp-doc _docs_guides_auth" data-v-e5fa90be><div><div style="display:none;" hidden="true" aria-hidden="true">Are you an LLM? You can read better optimized documentation at /docs/guides/auth.md for this page in Markdown format</div><p><img src="/img/icons/auth.svg" class="product-icon" style="width:72px;"></p><h1 id="auth" tabindex="-1">Auth <a class="header-anchor" href="#auth" aria-label="Permalink to &quot;Auth&quot;">​</a></h1><div class="hidden-xs"><p>How to do auth<span class="hidden-sm inline-md">entication and authorization</span> with Electric. Including examples for <span class="no-wrap-md"><a href="#proxy-auth">proxy</a> and</span> <a href="#gatekeeper-auth">gatekeeper</a> auth.</p></div><div class="block-xs"><p>How to do auth with Electric.</p><p>Including examples for <span class="no-wrap-md"><a href="#proxy-auth">proxy</a> and</span> <a href="#gatekeeper-auth">gatekeeper</a> auth.</p></div><h2 id="it-s-all-http" tabindex="-1">It&#39;s all HTTP <a class="header-anchor" href="#it-s-all-http" aria-label="Permalink to &quot;It&#39;s all HTTP&quot;">​</a></h2><p>The golden rule with Electric is that it&#39;s <a href="/docs/api/http">all just HTTP</a>.</p><p>So when it comes to auth, you can use existing primitives, such as your API, middleware and external authorization services.</p><h3 id="shapes-are-resources" tabindex="-1">Shapes are resources <a class="header-anchor" href="#shapes-are-resources" aria-label="Permalink to &quot;Shapes are resources&quot;">​</a></h3><p>With Electric, you sync data using <a href="/docs/guides/shapes">Shapes</a> and shapes are just resources.</p><p>You access them by making a request to <code>GET /v1/shape</code>, with the <a href="/docs/guides/shapes#defining-shapes">shape definition</a> in the query string (<code>?table=items</code>, etc.). You can authorise access to them exactly the same way you would any other web resource.</p><h3 id="requests-can-be-proxied" tabindex="-1">Requests can be proxied <a class="header-anchor" href="#requests-can-be-proxied" aria-label="Permalink to &quot;Requests can be proxied&quot;">​</a></h3><p>When you make a request to Electric, you can route it through an HTTP proxy or middleware stack. This allows you to authorise the request before it reaches Electric.</p><a href="/assets/authorizing-proxy.Ko3brxY6.jpg"><img src="/assets/authorizing-proxy.-X7O9Iw2.png" class="hidden-sm" alt="Illustration of an authorzing proxy"><img src="/assets/authorizing-proxy.sm.BmP4MML0.png" class="block-sm" alt="Illustration of an authorzing proxy"></a><p>You can proxy the request in your cloud, or at the edge, <a href="#cdn-proxy">in-front of a CDN</a>. Your auth logic can query your database, or call an external service. It&#39;s all completely up-to-you.</p><h3 id="rules-are-optional" tabindex="-1">Rules are optional <a class="header-anchor" href="#rules-are-optional" aria-label="Permalink to &quot;Rules are optional&quot;">​</a></h3><p>You <em>don&#39;t</em> have to codify your auth logic into a database rule system.</p><p>There&#39;s no need to use database rules to <a href="/docs/guides/security">secure data access</a> when your sync engine runs over standard HTTP.</p><h2 id="patterns" tabindex="-1">Patterns <a class="header-anchor" href="#patterns" aria-label="Permalink to &quot;Patterns&quot;">​</a></h2><p>The two patterns we recommend and describe below, with code and examples, are:</p><ul><li><a href="#proxy-auth">proxy auth</a> — authorising Shape requests using a proxy</li><li><a href="#gatekeeper-auth">gatekeeper auth</a> — using your API to generate shape-scoped access tokens</li></ul><h3 id="proxy-auth" tabindex="-1">Proxy auth <a class="header-anchor" href="#proxy-auth" aria-label="Permalink to &quot;Proxy auth&quot;">​</a></h3><div class="warning custom-block github-alert"><p class="custom-block-title">GitHub example</p><p>See the <a href="https://github.com/electric-sql/electric/tree/main/examples/proxy-auth" target="_blank" rel="noreferrer">proxy-auth example</a> on GitHub for an example that implements this pattern.</p></div><p>The simplest pattern is to authorise Shape requests using a reverse-proxy.</p><p>The proxy can be your API, or a separate proxy service or edge-function. When you make a request to sync a shape, route it via your API/proxy, validate the user credentials and set the shape parameters server-side, and then only proxy the data through if authorized.</p><p>For example:</p><ol><li>add an <code>Authorization</code> header to your <a href="/docs/api/http#syncing-shapes"><code>GET /v1/shape</code></a> request</li><li>use the header to check that the client exists and has access to the shape</li><li>if not, return a <code>401</code> or <code>403</code> status to tell the client it doesn&#39;t have access</li><li>if the client does have access, proxy the request to Electric and stream the response back to the client</li></ol><h4 id="example" tabindex="-1">Example <a class="header-anchor" href="#example" aria-label="Permalink to &quot;Example&quot;">​</a></h4><p>When using the <a href="/docs/api/clients/typescript">Typescript client</a>, you can pass in a <a href="/docs/api/clients/typescript#options"><code>headers</code> option</a> to add an <code>Authorization</code> header.</p><div class="language-tsx"><button title="Copy Code" class="copy"></button><span class="lang">tsx</span><pre class="shiki github-dark vp-code" tabindex="0"><code><span class="line"><span style="color:#F97583;">const</span><span style="color:#B392F0;"> usersShape</span><span style="color:#F97583;"> =</span><span style="color:#E1E4E8;"> ()</span><span style="color:#F97583;">:</span><span style="color:#B392F0;"> ShapeStreamOptions</span><span style="color:#F97583;"> =&gt;</span><span style="color:#E1E4E8;"> {</span></span>
<span class="line"><span style="color:#F97583;">  const</span><span style="color:#79B8FF;"> user</span><span style="color:#F97583;"> =</span><span style="color:#B392F0;"> loadCurrentUser</span><span style="color:#E1E4E8;">()</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583;">  return</span><span style="color:#E1E4E8;"> {</span></span>
<span class="line"><span style="color:#E1E4E8;">    url: </span><span style="color:#F97583;">new</span><span style="color:#B392F0;"> URL</span><span style="color:#E1E4E8;">(</span><span style="color:#9ECBFF;">`/api/shapes/users`</span><span style="color:#E1E4E8;">, window.location.origin).href,</span></span>
<span class="line"><span style="color:#E1E4E8;">    headers: {</span></span>
<span class="line"><span style="color:#E1E4E8;">      authorization: </span><span style="color:#9ECBFF;">`Bearer ${</span><span style="color:#E1E4E8;">user</span><span style="color:#9ECBFF;">.</span><span style="color:#E1E4E8;">token</span><span style="color:#9ECBFF;">}`</span><span style="color:#E1E4E8;">,</span></span>
<span class="line"><span style="color:#E1E4E8;">    },</span></span>
<span class="line"><span style="color:#E1E4E8;">  }</span></span>
<span class="line"><span style="color:#E1E4E8;">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583;">export</span><span style="color:#F97583;"> default</span><span style="color:#F97583;"> function</span><span style="color:#B392F0;"> ExampleComponent</span><span style="color:#E1E4E8;">() {</span></span>
<span class="line"><span style="color:#F97583;">  const</span><span style="color:#E1E4E8;"> { </span><span style="color:#FFAB70;">data</span><span style="color:#E1E4E8;">: </span><span style="color:#79B8FF;">users</span><span style="color:#E1E4E8;"> } </span><span style="color:#F97583;">=</span><span style="color:#B392F0;"> useShape</span><span style="color:#E1E4E8;">(</span><span style="color:#B392F0;">usersShape</span><span style="color:#E1E4E8;">())</span></span>
<span class="line"><span style="color:#E1E4E8;">}</span></span></code></pre></div><p>Then for the <code>/api/shapes/users</code> route:</p><div class="language-tsx"><button title="Copy Code" class="copy"></button><span class="lang">tsx</span><pre class="shiki github-dark vp-code" tabindex="0"><code><span class="line"><span style="color:#F97583;">import</span><span style="color:#E1E4E8;"> { ELECTRIC_PROTOCOL_QUERY_PARAMS } </span><span style="color:#F97583;">from</span><span style="color:#9ECBFF;"> &#39;@electric-sql/client&#39;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583;">export</span><span style="color:#F97583;"> async</span><span style="color:#F97583;"> function</span><span style="color:#B392F0;"> GET</span><span style="color:#E1E4E8;">(</span><span style="color:#FFAB70;">request</span><span style="color:#F97583;">:</span><span style="color:#B392F0;"> Request</span><span style="color:#E1E4E8;">) {</span></span>
<span class="line"><span style="color:#F97583;">  const</span><span style="color:#79B8FF;"> url</span><span style="color:#F97583;"> =</span><span style="color:#F97583;"> new</span><span style="color:#B392F0;"> URL</span><span style="color:#E1E4E8;">(request.url)</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;">  // Construct the upstream URL</span></span>
<span class="line"><span style="color:#F97583;">  const</span><span style="color:#79B8FF;"> originUrl</span><span style="color:#F97583;"> =</span><span style="color:#F97583;"> new</span><span style="color:#B392F0;"> URL</span><span style="color:#E1E4E8;">(</span><span style="color:#9ECBFF;">`http://localhost:3000/v1/shape`</span><span style="color:#E1E4E8;">)</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;">  // Only pass through Electric protocol parameters</span></span>
<span class="line"><span style="color:#E1E4E8;">  url.searchParams.</span><span style="color:#B392F0;">forEach</span><span style="color:#E1E4E8;">((</span><span style="color:#FFAB70;">value</span><span style="color:#E1E4E8;">, </span><span style="color:#FFAB70;">key</span><span style="color:#E1E4E8;">) </span><span style="color:#F97583;">=&gt;</span><span style="color:#E1E4E8;"> {</span></span>
<span class="line"><span style="color:#F97583;">    if</span><span style="color:#E1E4E8;"> (</span><span style="color:#79B8FF;">ELECTRIC_PROTOCOL_QUERY_PARAMS</span><span style="color:#E1E4E8;">.</span><span style="color:#B392F0;">includes</span><span style="color:#E1E4E8;">(key)) {</span></span>
<span class="line"><span style="color:#E1E4E8;">      originUrl.searchParams.</span><span style="color:#B392F0;">set</span><span style="color:#E1E4E8;">(key, value)</span></span>
<span class="line"><span style="color:#E1E4E8;">    }</span></span>
<span class="line"><span style="color:#E1E4E8;">  })</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;">  // Set the table server-side - not from client params</span></span>
<span class="line"><span style="color:#E1E4E8;">  originUrl.searchParams.</span><span style="color:#B392F0;">set</span><span style="color:#E1E4E8;">(</span><span style="color:#9ECBFF;">`table`</span><span style="color:#E1E4E8;">, </span><span style="color:#9ECBFF;">`users`</span><span style="color:#E1E4E8;">)</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;">  //</span></span>
<span class="line"><span style="color:#6A737D;">  // Authentication and authorization</span></span>
<span class="line"><span style="color:#6A737D;">  //</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583;">  const</span><span style="color:#79B8FF;"> user</span><span style="color:#F97583;"> =</span><span style="color:#F97583;"> await</span><span style="color:#B392F0;"> loadUser</span><span style="color:#E1E4E8;">(request.headers.</span><span style="color:#B392F0;">get</span><span style="color:#E1E4E8;">(</span><span style="color:#9ECBFF;">`authorization`</span><span style="color:#E1E4E8;">))</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;">  // If the user isn&#39;t set, return 401</span></span>
<span class="line"><span style="color:#F97583;">  if</span><span style="color:#E1E4E8;"> (</span><span style="color:#F97583;">!</span><span style="color:#E1E4E8;">user) {</span></span>
<span class="line"><span style="color:#F97583;">    return</span><span style="color:#F97583;"> new</span><span style="color:#B392F0;"> Response</span><span style="color:#E1E4E8;">(</span><span style="color:#9ECBFF;">`user not found`</span><span style="color:#E1E4E8;">, { status: </span><span style="color:#79B8FF;">401</span><span style="color:#E1E4E8;"> })</span></span>
<span class="line"><span style="color:#E1E4E8;">  }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;">  // Only query data the user has access to unless they&#39;re an admin.</span></span>
<span class="line"><span style="color:#F97583;">  if</span><span style="color:#E1E4E8;"> (</span><span style="color:#F97583;">!</span><span style="color:#E1E4E8;">user.roles.</span><span style="color:#B392F0;">includes</span><span style="color:#E1E4E8;">(</span><span style="color:#9ECBFF;">`admin`</span><span style="color:#E1E4E8;">)) {</span></span>
<span class="line"><span style="color:#6A737D;">    // For type-safe WHERE clause generation, see the section below</span></span>
<span class="line"><span style="color:#E1E4E8;">    originUrl.searchParams.</span><span style="color:#B392F0;">set</span><span style="color:#E1E4E8;">(</span><span style="color:#9ECBFF;">`where`</span><span style="color:#E1E4E8;">, </span><span style="color:#9ECBFF;">`org_id = &#39;${</span><span style="color:#E1E4E8;">user</span><span style="color:#9ECBFF;">.</span><span style="color:#E1E4E8;">org_id</span><span style="color:#9ECBFF;">}&#39;`</span><span style="color:#E1E4E8;">)</span></span>
<span class="line"><span style="color:#E1E4E8;">  }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583;">  const</span><span style="color:#79B8FF;"> response</span><span style="color:#F97583;"> =</span><span style="color:#F97583;"> await</span><span style="color:#B392F0;"> fetch</span><span style="color:#E1E4E8;">(originUrl)</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;">  // Fetch decompresses the body but doesn&#39;t remove the</span></span>
<span class="line"><span style="color:#6A737D;">  // content-encoding &amp; content-length headers which would</span></span>
<span class="line"><span style="color:#6A737D;">  // break decoding in the browser.</span></span>
<span class="line"><span style="color:#6A737D;">  //</span></span>
<span class="line"><span style="color:#6A737D;">  // See https://github.com/whatwg/fetch/issues/1729</span></span>
<span class="line"><span style="color:#F97583;">  const</span><span style="color:#79B8FF;"> headers</span><span style="color:#F97583;"> =</span><span style="color:#F97583;"> new</span><span style="color:#B392F0;"> Headers</span><span style="color:#E1E4E8;">(response.headers)</span></span>
<span class="line"><span style="color:#E1E4E8;">  headers.</span><span style="color:#B392F0;">delete</span><span style="color:#E1E4E8;">(</span><span style="color:#9ECBFF;">`content-encoding`</span><span style="color:#E1E4E8;">)</span></span>
<span class="line"><span style="color:#E1E4E8;">  headers.</span><span style="color:#B392F0;">delete</span><span style="color:#E1E4E8;">(</span><span style="color:#9ECBFF;">`content-length`</span><span style="color:#E1E4E8;">)</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583;">  return</span><span style="color:#F97583;"> new</span><span style="color:#B392F0;"> Response</span><span style="color:#E1E4E8;">(response.body, {</span></span>
<span class="line"><span style="color:#E1E4E8;">    status: response.status,</span></span>
<span class="line"><span style="color:#E1E4E8;">    statusText: response.statusText,</span></span>
<span class="line"><span style="color:#E1E4E8;">    headers,</span></span>
<span class="line"><span style="color:#E1E4E8;">  })</span></span>
<span class="line"><span style="color:#E1E4E8;">}</span></span></code></pre></div><h4 id="type-safe-where-clause-generation" tabindex="-1">Type-safe where clause generation <a class="header-anchor" href="#type-safe-where-clause-generation" aria-label="Permalink to &quot;Type-safe where clause generation&quot;">​</a></h4><p>The example above uses simple string-based WHERE clauses, which works well for straightforward cases. If you&#39;d like type-safe WHERE clause generation with compile-time validation, you can use query builder libraries like Drizzle or Kysely. This is particularly useful for complex queries or when you want to catch column reference errors at compile-time rather than runtime.</p><div class="tip custom-block github-alert"><p class="custom-block-title">General pattern</p><p>These examples show JavaScript/TypeScript APIs, but you can use this same pattern of type-safe where clause generation in any language with similar query builder libraries for your backend API.</p></div><p><strong>Drizzle</strong> — fully type-safe operators with schema inference:</p><div class="language-tsx"><button title="Copy Code" class="copy"></button><span class="lang">tsx</span><pre class="shiki github-dark vp-code" tabindex="0"><code><span class="line"><span style="color:#F97583;">import</span><span style="color:#E1E4E8;"> { QueryBuilder } </span><span style="color:#F97583;">from</span><span style="color:#9ECBFF;"> &#39;drizzle-orm/pg-core&#39;</span></span>
<span class="line"><span style="color:#F97583;">import</span><span style="color:#E1E4E8;"> { sql } </span><span style="color:#F97583;">from</span><span style="color:#9ECBFF;"> &#39;drizzle-orm&#39;</span></span>
<span class="line"><span style="color:#F97583;">import</span><span style="color:#E1E4E8;"> { users } </span><span style="color:#F97583;">from</span><span style="color:#9ECBFF;"> &#39;./schema&#39;</span><span style="color:#6A737D;"> // Your Drizzle schema definition</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583;">export</span><span style="color:#F97583;"> async</span><span style="color:#F97583;"> function</span><span style="color:#B392F0;"> GET</span><span style="color:#E1E4E8;">(</span><span style="color:#FFAB70;">request</span><span style="color:#F97583;">:</span><span style="color:#B392F0;"> Request</span><span style="color:#E1E4E8;">) {</span></span>
<span class="line"><span style="color:#6A737D;">  // ... setup code ...</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583;">  const</span><span style="color:#79B8FF;"> user</span><span style="color:#F97583;"> =</span><span style="color:#F97583;"> await</span><span style="color:#B392F0;"> loadUser</span><span style="color:#E1E4E8;">(request.headers.</span><span style="color:#B392F0;">get</span><span style="color:#E1E4E8;">(</span><span style="color:#9ECBFF;">&#39;authorization&#39;</span><span style="color:#E1E4E8;">))</span></span>
<span class="line"><span style="color:#F97583;">  if</span><span style="color:#E1E4E8;"> (</span><span style="color:#F97583;">!</span><span style="color:#E1E4E8;">user </span><span style="color:#F97583;">||</span><span style="color:#E1E4E8;"> user.roles.</span><span style="color:#B392F0;">includes</span><span style="color:#E1E4E8;">(</span><span style="color:#9ECBFF;">&#39;admin&#39;</span><span style="color:#E1E4E8;">)) {</span></span>
<span class="line"><span style="color:#6A737D;">    // admins see everything</span></span>
<span class="line"><span style="color:#E1E4E8;">  } </span><span style="color:#F97583;">else</span><span style="color:#E1E4E8;"> {</span></span>
<span class="line"><span style="color:#6A737D;">    // Build type-safe WHERE expression using column.name for unqualified column names</span></span>
<span class="line"><span style="color:#6A737D;">    // TypeScript will error if you reference users.nonexistentColumn</span></span>
<span class="line"><span style="color:#F97583;">    const</span><span style="color:#79B8FF;"> whereExpr</span><span style="color:#F97583;"> =</span><span style="color:#B392F0;"> sql</span><span style="color:#9ECBFF;">`${</span><span style="color:#E1E4E8;">sql</span><span style="color:#9ECBFF;">.</span><span style="color:#B392F0;">identifier</span><span style="color:#9ECBFF;">(</span><span style="color:#E1E4E8;">users</span><span style="color:#9ECBFF;">.</span><span style="color:#E1E4E8;">org_id</span><span style="color:#9ECBFF;">.</span><span style="color:#E1E4E8;">name</span><span style="color:#9ECBFF;">)</span><span style="color:#9ECBFF;">} = ${</span><span style="color:#E1E4E8;">user</span><span style="color:#9ECBFF;">.</span><span style="color:#E1E4E8;">org_id</span><span style="color:#9ECBFF;">}`</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;">    // Compile to SQL fragment without DB connection</span></span>
<span class="line"><span style="color:#F97583;">    const</span><span style="color:#79B8FF;"> qb</span><span style="color:#F97583;"> =</span><span style="color:#F97583;"> new</span><span style="color:#B392F0;"> QueryBuilder</span><span style="color:#E1E4E8;">()</span></span>
<span class="line"><span style="color:#F97583;">    const</span><span style="color:#E1E4E8;"> { </span><span style="color:#FFAB70;">sql</span><span style="color:#E1E4E8;">: </span><span style="color:#79B8FF;">query</span><span style="color:#E1E4E8;">, </span><span style="color:#79B8FF;">params</span><span style="color:#E1E4E8;"> } </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> qb</span></span>
<span class="line"><span style="color:#E1E4E8;">      .</span><span style="color:#B392F0;">select</span><span style="color:#E1E4E8;">()</span></span>
<span class="line"><span style="color:#E1E4E8;">      .</span><span style="color:#B392F0;">from</span><span style="color:#E1E4E8;">(users)</span></span>
<span class="line"><span style="color:#E1E4E8;">      .</span><span style="color:#B392F0;">where</span><span style="color:#E1E4E8;">(whereExpr)</span></span>
<span class="line"><span style="color:#E1E4E8;">      .</span><span style="color:#B392F0;">toSQL</span><span style="color:#E1E4E8;">()</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;">    // Extract just the WHERE clause fragment</span></span>
<span class="line"><span style="color:#F97583;">    const</span><span style="color:#79B8FF;"> fragment</span><span style="color:#F97583;"> =</span><span style="color:#E1E4E8;"> query.</span><span style="color:#B392F0;">replace</span><span style="color:#E1E4E8;">(</span><span style="color:#9ECBFF;">/</span><span style="color:#F97583;">^</span><span style="color:#DBEDFF;">SELECT </span><span style="color:#79B8FF;">.</span><span style="color:#F97583;">*</span><span style="color:#DBEDFF;"> FROM </span><span style="color:#79B8FF;">.</span><span style="color:#F97583;">*</span><span style="color:#DBEDFF;"> WHERE</span><span style="color:#79B8FF;">\s</span><span style="color:#F97583;">+</span><span style="color:#9ECBFF;">/</span><span style="color:#F97583;">i</span><span style="color:#E1E4E8;">, </span><span style="color:#9ECBFF;">&#39;&#39;</span><span style="color:#E1E4E8;">)</span></span>
<span class="line"><span style="color:#E1E4E8;">    originUrl.searchParams.</span><span style="color:#B392F0;">set</span><span style="color:#E1E4E8;">(</span><span style="color:#9ECBFF;">&#39;where&#39;</span><span style="color:#E1E4E8;">, fragment)</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;">    // Add params as individual query parameters: params[1]=value, params[2]=value, etc.</span></span>
<span class="line"><span style="color:#E1E4E8;">    params.</span><span style="color:#B392F0;">forEach</span><span style="color:#E1E4E8;">((</span><span style="color:#FFAB70;">value</span><span style="color:#E1E4E8;">, </span><span style="color:#FFAB70;">index</span><span style="color:#E1E4E8;">) </span><span style="color:#F97583;">=&gt;</span><span style="color:#E1E4E8;"> {</span></span>
<span class="line"><span style="color:#E1E4E8;">      originUrl.searchParams.</span><span style="color:#B392F0;">set</span><span style="color:#E1E4E8;">(</span><span style="color:#9ECBFF;">`params[${</span><span style="color:#E1E4E8;">index</span><span style="color:#F97583;"> +</span><span style="color:#79B8FF;"> 1</span><span style="color:#9ECBFF;">}]`</span><span style="color:#E1E4E8;">, </span><span style="color:#B392F0;">String</span><span style="color:#E1E4E8;">(value))</span></span>
<span class="line"><span style="color:#E1E4E8;">    })</span></span>
<span class="line"><span style="color:#E1E4E8;">  }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;">  // ... fetch and return response ...</span></span>
<span class="line"><span style="color:#E1E4E8;">}</span></span></code></pre></div><p><strong>Kysely</strong> — type-safe expression builder with generated schema:</p><div class="language-tsx"><button title="Copy Code" class="copy"></button><span class="lang">tsx</span><pre class="shiki github-dark vp-code" tabindex="0"><code><span class="line"><span style="color:#F97583;">import</span><span style="color:#E1E4E8;"> { db } </span><span style="color:#F97583;">from</span><span style="color:#9ECBFF;"> &#39;./db&#39;</span><span style="color:#6A737D;"> // Your Kysely instance with generated types</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583;">export</span><span style="color:#F97583;"> async</span><span style="color:#F97583;"> function</span><span style="color:#B392F0;"> GET</span><span style="color:#E1E4E8;">(</span><span style="color:#FFAB70;">request</span><span style="color:#F97583;">:</span><span style="color:#B392F0;"> Request</span><span style="color:#E1E4E8;">) {</span></span>
<span class="line"><span style="color:#6A737D;">  // ... setup code ...</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583;">  if</span><span style="color:#E1E4E8;"> (</span><span style="color:#F97583;">!</span><span style="color:#E1E4E8;">user.roles.</span><span style="color:#B392F0;">includes</span><span style="color:#E1E4E8;">(</span><span style="color:#9ECBFF;">&#39;admin&#39;</span><span style="color:#E1E4E8;">)) {</span></span>
<span class="line"><span style="color:#6A737D;">    // TypeScript will error if you reference invalid columns</span></span>
<span class="line"><span style="color:#F97583;">    const</span><span style="color:#79B8FF;"> query</span><span style="color:#F97583;"> =</span><span style="color:#E1E4E8;"> db</span></span>
<span class="line"><span style="color:#E1E4E8;">      .</span><span style="color:#B392F0;">selectFrom</span><span style="color:#E1E4E8;">(</span><span style="color:#9ECBFF;">&#39;users&#39;</span><span style="color:#E1E4E8;">)</span></span>
<span class="line"><span style="color:#E1E4E8;">      .</span><span style="color:#B392F0;">selectAll</span><span style="color:#E1E4E8;">()</span></span>
<span class="line"><span style="color:#E1E4E8;">      .</span><span style="color:#B392F0;">where</span><span style="color:#E1E4E8;">(</span><span style="color:#9ECBFF;">&#39;org_id&#39;</span><span style="color:#E1E4E8;">, </span><span style="color:#9ECBFF;">&#39;=&#39;</span><span style="color:#E1E4E8;">, user.org_id)</span></span>
<span class="line"><span style="color:#E1E4E8;">      .</span><span style="color:#B392F0;">where</span><span style="color:#E1E4E8;">(</span><span style="color:#9ECBFF;">&#39;status&#39;</span><span style="color:#E1E4E8;">, </span><span style="color:#9ECBFF;">&#39;=&#39;</span><span style="color:#E1E4E8;">, </span><span style="color:#9ECBFF;">&#39;active&#39;</span><span style="color:#E1E4E8;">)</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583;">    const</span><span style="color:#E1E4E8;"> { </span><span style="color:#FFAB70;">sql</span><span style="color:#E1E4E8;">: </span><span style="color:#79B8FF;">query</span><span style="color:#E1E4E8;">, </span><span style="color:#79B8FF;">parameters</span><span style="color:#E1E4E8;"> } </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> query.</span><span style="color:#B392F0;">compile</span><span style="color:#E1E4E8;">()</span></span>
<span class="line"><span style="color:#F97583;">    const</span><span style="color:#79B8FF;"> fragment</span><span style="color:#F97583;"> =</span><span style="color:#E1E4E8;"> query.</span><span style="color:#B392F0;">replace</span><span style="color:#E1E4E8;">(</span><span style="color:#9ECBFF;">/</span><span style="color:#F97583;">^</span><span style="color:#DBEDFF;">SELECT </span><span style="color:#79B8FF;">.</span><span style="color:#F97583;">*</span><span style="color:#DBEDFF;"> FROM </span><span style="color:#79B8FF;">.</span><span style="color:#F97583;">*</span><span style="color:#DBEDFF;"> WHERE</span><span style="color:#79B8FF;">\s</span><span style="color:#F97583;">+</span><span style="color:#9ECBFF;">/</span><span style="color:#F97583;">i</span><span style="color:#E1E4E8;">, </span><span style="color:#9ECBFF;">&#39;&#39;</span><span style="color:#E1E4E8;">)</span></span>
<span class="line"><span style="color:#E1E4E8;">    fragment </span><span style="color:#F97583;">=</span><span style="color:#E1E4E8;"> fragment.</span><span style="color:#B392F0;">replace</span><span style="color:#E1E4E8;">(</span><span style="color:#9ECBFF;">/</span><span style="color:#F97583;">\b</span><span style="color:#79B8FF;">\w</span><span style="color:#F97583;">+</span><span style="color:#85E89D;font-weight:bold;">\.</span><span style="color:#9ECBFF;">/</span><span style="color:#F97583;">g</span><span style="color:#E1E4E8;">, </span><span style="color:#9ECBFF;">&#39;&#39;</span><span style="color:#E1E4E8;">) </span><span style="color:#6A737D;">// Remove table prefixes</span></span>
<span class="line"><span style="color:#E1E4E8;">    originUrl.searchParams.</span><span style="color:#B392F0;">set</span><span style="color:#E1E4E8;">(</span><span style="color:#9ECBFF;">&#39;where&#39;</span><span style="color:#E1E4E8;">, fragment)</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;">    // Add params as individual query parameters: params[1]=value, params[2]=value, etc.</span></span>
<span class="line"><span style="color:#E1E4E8;">    parameters.</span><span style="color:#B392F0;">forEach</span><span style="color:#E1E4E8;">((</span><span style="color:#FFAB70;">value</span><span style="color:#E1E4E8;">, </span><span style="color:#FFAB70;">index</span><span style="color:#E1E4E8;">) </span><span style="color:#F97583;">=&gt;</span><span style="color:#E1E4E8;"> {</span></span>
<span class="line"><span style="color:#E1E4E8;">      originUrl.searchParams.</span><span style="color:#B392F0;">set</span><span style="color:#E1E4E8;">(</span><span style="color:#9ECBFF;">`params[${</span><span style="color:#E1E4E8;">index</span><span style="color:#F97583;"> +</span><span style="color:#79B8FF;"> 1</span><span style="color:#9ECBFF;">}]`</span><span style="color:#E1E4E8;">, </span><span style="color:#B392F0;">String</span><span style="color:#E1E4E8;">(value))</span></span>
<span class="line"><span style="color:#E1E4E8;">    })</span></span>
<span class="line"><span style="color:#E1E4E8;">  }</span></span>
<span class="line"><span style="color:#E1E4E8;">}</span></span></code></pre></div><div class="note custom-block github-alert"><p class="custom-block-title">Handling parameterized queries</p><p>Electric&#39;s HTTP API accepts parameters via individual query params (<code>params[1]=value</code>, <code>params[2]=value</code>) which are used to safely substitute <code>$1</code>, <code>$2</code> placeholders in WHERE clauses. This prevents SQL injection while maintaining type safety.</p><p><strong>Drizzle</strong>: Use <code>column.name</code> with <code>sql.identifier()</code> to generate unqualified column names (e.g., <code>&quot;org_id&quot;</code> instead of <code>&quot;users&quot;.&quot;org_id&quot;</code>), since Electric expects column names without table prefixes.</p><p><strong>Kysely</strong>: Table prefixes are included by default, so strip them with <code>.replace(/\b\w+\./g, &#39;&#39;)</code> after extracting the WHERE fragment.</p></div><p>Both <strong>Drizzle</strong> and <strong>Kysely</strong> provide full compile-time type safety based on your schema definitions. TypeScript will error if you reference invalid columns, use incorrect types, or apply incompatible operators.</p><p>Benefits:</p><ul><li><strong>Compile-time validation</strong>: Catch errors before runtime</li><li><strong>SQL injection protection</strong>: Values are properly escaped and parameterized</li><li><strong>Refactoring safety</strong>: Renaming columns updates all references automatically</li><li><strong>IDE support</strong>: Auto-completion for column names and types</li></ul><h4 id="using-post-for-subset-queries" tabindex="-1">Using POST for subset queries <a class="header-anchor" href="#using-post-for-subset-queries" aria-label="Permalink to &quot;Using POST for subset queries&quot;">​</a></h4><p>When WHERE clauses become large (complex ACL subqueries, many parameters, or <code>WHERE id = ANY($1)</code> with hundreds of IDs), GET requests can fail with <code>HTTP 414 Request-URI Too Long</code> errors. Electric supports POST requests with subset parameters in the JSON body to avoid URL length limits.</p><div class="warning custom-block"><p class="custom-block-title">URL Length Limits and GET Deprecation</p><p>GET requests with subset parameters in the URL can fail with <code>414 Request-URI Too Long</code> errors. This is common when:</p><ul><li>ACL subqueries generate long WHERE clauses</li><li>Join queries produce large filter lists</li><li>Parameter arrays contain many values</li></ul><p><strong>Use POST to avoid this limitation.</strong></p><blockquote><p><strong>Deprecation Notice:</strong> In Electric 2.0, GET requests for subset snapshots will be deprecated and only POST will be supported. Implement POST support now to ensure forward compatibility.</p></blockquote></div><h5 id="post-body-format" tabindex="-1">POST body format <a class="header-anchor" href="#post-body-format" aria-label="Permalink to &quot;POST body format&quot;">​</a></h5><p>The POST body accepts these subset parameters as JSON:</p><table tabindex="0"><thead><tr><th>Parameter</th><th>Type</th><th>Description</th></tr></thead><tbody><tr><td><code>where</code></td><td>string</td><td>WHERE clause to filter the subset</td></tr><tr><td><code>params</code></td><td>object</td><td>Parameters as <code>{&quot;1&quot;: &quot;value1&quot;, &quot;2&quot;: &quot;value2&quot;}</code> for <code>$1</code>, <code>$2</code> placeholders</td></tr><tr><td><code>limit</code></td><td>integer</td><td>Maximum rows to return (requires <code>order_by</code>)</td></tr><tr><td><code>offset</code></td><td>integer</td><td>Rows to skip for pagination (requires <code>order_by</code>)</td></tr><tr><td><code>order_by</code></td><td>string</td><td>ORDER BY clause (required when using limit/offset)</td></tr></tbody></table><p>Example POST body:</p><div class="language-json"><button title="Copy Code" class="copy"></button><span class="lang">json</span><pre class="shiki github-dark vp-code" tabindex="0"><code><span class="line"><span style="color:#E1E4E8;">{</span></span>
<span class="line"><span style="color:#79B8FF;">  &quot;where&quot;</span><span style="color:#E1E4E8;">: </span><span style="color:#9ECBFF;">&quot;</span><span style="color:#79B8FF;">\&quot;</span><span style="color:#9ECBFF;">organization_id</span><span style="color:#79B8FF;">\&quot;</span><span style="color:#9ECBFF;"> = $1 AND (</span><span style="color:#79B8FF;">\&quot;</span><span style="color:#9ECBFF;">owner_user_id</span><span style="color:#79B8FF;">\&quot;</span><span style="color:#9ECBFF;"> = $2 OR ...)&quot;</span><span style="color:#E1E4E8;">,</span></span>
<span class="line"><span style="color:#79B8FF;">  &quot;params&quot;</span><span style="color:#E1E4E8;">: {</span><span style="color:#79B8FF;">&quot;1&quot;</span><span style="color:#E1E4E8;">: </span><span style="color:#9ECBFF;">&quot;org_123&quot;</span><span style="color:#E1E4E8;">, </span><span style="color:#79B8FF;">&quot;2&quot;</span><span style="color:#E1E4E8;">: </span><span style="color:#9ECBFF;">&quot;user_456&quot;</span><span style="color:#E1E4E8;">},</span></span>
<span class="line"><span style="color:#79B8FF;">  &quot;order_by&quot;</span><span style="color:#E1E4E8;">: </span><span style="color:#9ECBFF;">&quot;created_at DESC&quot;</span><span style="color:#E1E4E8;">,</span></span>
<span class="line"><span style="color:#79B8FF;">  &quot;limit&quot;</span><span style="color:#E1E4E8;">: </span><span style="color:#79B8FF;">100</span></span>
<span class="line"><span style="color:#E1E4E8;">}</span></span></code></pre></div><h5 id="url-vs-post-body-parameters" tabindex="-1">URL vs POST body parameters <a class="header-anchor" href="#url-vs-post-body-parameters" aria-label="Permalink to &quot;URL vs POST body parameters&quot;">​</a></h5><p>Electric separates parameters by purpose:</p><p><strong>URL query parameters</strong> (shape definition — always in URL):</p><ul><li><code>table</code> — Root table name (required)</li><li><code>offset</code> — Shape log position (required, e.g., <code>-1</code> for initial sync)</li><li><code>handle</code> — Shape handle for continuation requests</li><li><code>columns</code> — Column selection</li><li><code>where</code> — Main shape WHERE clause (for non-subset queries)</li><li><code>replica</code>, <code>log</code>, <code>live</code>, <code>live_sse</code> — Protocol options</li><li><code>secret</code> / <code>api_secret</code> — API authentication</li></ul><p><strong>POST body parameters</strong> (subset snapshot parameters):</p><ul><li><code>where</code> — Subset WHERE clause (applied <em>in addition to</em> main shape WHERE)</li><li><code>params</code> — Parameters for the subset WHERE clause</li><li><code>limit</code>, <code>offset</code>, <code>order_by</code> — Pagination controls</li></ul><h5 id="security-how-electric-combines-where-clauses" tabindex="-1">Security: how Electric combines WHERE clauses <a class="header-anchor" href="#security-how-electric-combines-where-clauses" aria-label="Permalink to &quot;Security: how Electric combines WHERE clauses&quot;">​</a></h5><p>Electric always combines the main shape WHERE (URL) with the subset WHERE (POST body) using <code>AND</code>:</p><div class="language-sql"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki github-dark vp-code" tabindex="0"><code><span class="line"><span style="color:#F97583;">WHERE</span><span style="color:#E1E4E8;"> {main_shape_where} </span><span style="color:#F97583;">AND</span><span style="color:#E1E4E8;"> ({subset_where})</span></span></code></pre></div><p>This means <strong>subset queries can only narrow results, never widen them</strong>. Even if a client sends <code>where: &quot;1=1&quot;</code> in the POST body, the main shape WHERE still applies. The subset WHERE is validated for syntax and prohibited from containing subqueries.</p><h5 id="parameters-your-proxy-must-control" tabindex="-1">Parameters your proxy must control <a class="header-anchor" href="#parameters-your-proxy-must-control" aria-label="Permalink to &quot;Parameters your proxy must control&quot;">​</a></h5><p>The proxy must set these <strong>shape definition parameters</strong> server-side — they define what data the client can access:</p><table tabindex="0"><thead><tr><th>Parameter</th><th>Where</th><th>Security Consideration</th></tr></thead><tbody><tr><td><code>table</code></td><td>URL</td><td><strong>Must be set server-side.</strong> Letting clients specify the table allows access to any table.</td></tr><tr><td><code>columns</code></td><td>URL</td><td><strong>Should be set server-side.</strong> Clients could request sensitive columns.</td></tr><tr><td><code>where</code></td><td>URL</td><td><strong>Must be set server-side.</strong> This is your authorization filter — the main shape WHERE that restricts all queries.</td></tr><tr><td><code>secret</code></td><td>URL</td><td><strong>Must be set server-side.</strong> Never expose the API secret to clients.</td></tr></tbody></table><h5 id="parameters-safe-to-pass-through-from-clients" tabindex="-1">Parameters safe to pass through from clients <a class="header-anchor" href="#parameters-safe-to-pass-through-from-clients" aria-label="Permalink to &quot;Parameters safe to pass through from clients&quot;">​</a></h5><p>These parameters are safe because they either can&#39;t widen data access or are needed for client sync state:</p><table tabindex="0"><thead><tr><th>Parameter</th><th>Where</th><th>Notes</th></tr></thead><tbody><tr><td><code>offset</code></td><td>URL</td><td>Shape log position — clients need to track their sync position</td></tr><tr><td><code>handle</code></td><td>URL</td><td>Shape handle — clients need this to continue syncing</td></tr><tr><td><code>live</code></td><td>URL</td><td>Live mode flag — controls long-polling behavior</td></tr><tr><td><code>live_sse</code></td><td>URL</td><td>SSE mode flag — controls streaming behavior</td></tr><tr><td><code>replica</code></td><td>URL</td><td>Replica mode — controls update message format</td></tr><tr><td><code>log</code></td><td>URL</td><td>Log mode — <code>full</code> or <code>changes_only</code></td></tr><tr><td><code>where</code></td><td>POST body</td><td>Subset WHERE — combined with AND, can only narrow results</td></tr><tr><td><code>params</code></td><td>POST body</td><td>Parameters for subset WHERE</td></tr><tr><td><code>limit</code></td><td>POST body</td><td>Pagination limit</td></tr><tr><td><code>offset</code></td><td>POST body</td><td>Pagination offset (different from shape log offset in URL)</td></tr><tr><td><code>order_by</code></td><td>POST body</td><td>Sorting for pagination</td></tr></tbody></table><div class="tip custom-block"><p class="custom-block-title">Key Principle</p><p>Your proxy is an <strong>authorization layer</strong> that controls the <strong>shape definition</strong> (table, columns, main WHERE). Clients can freely use subset parameters to filter and paginate within that shape — Electric ensures they can only narrow results, never escape the main WHERE clause.</p></div><h5 id="implementing-post-support-in-your-proxy" tabindex="-1">Implementing POST support in your proxy <a class="header-anchor" href="#implementing-post-support-in-your-proxy" aria-label="Permalink to &quot;Implementing POST support in your proxy&quot;">​</a></h5><p>To support both GET and POST requests:</p><ol><li><strong>Accept both methods</strong> on your proxy endpoints</li><li><strong>Set shape definition server-side</strong> — table, columns, and main WHERE clause</li><li><strong>For POST</strong>: Forward client subset params (they can only narrow results)</li><li><strong>For GET</strong>: Send WHERE as URL query parameters (existing behavior)</li></ol><div class="language-tsx"><button title="Copy Code" class="copy"></button><span class="lang">tsx</span><pre class="shiki github-dark vp-code" tabindex="0"><code><span class="line"><span style="color:#F97583;">import</span><span style="color:#E1E4E8;"> { ELECTRIC_PROTOCOL_QUERY_PARAMS } </span><span style="color:#F97583;">from</span><span style="color:#9ECBFF;"> &#39;@electric-sql/client&#39;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583;">export</span><span style="color:#F97583;"> async</span><span style="color:#F97583;"> function</span><span style="color:#B392F0;"> handler</span><span style="color:#E1E4E8;">(</span><span style="color:#FFAB70;">request</span><span style="color:#F97583;">:</span><span style="color:#B392F0;"> Request</span><span style="color:#E1E4E8;">) {</span></span>
<span class="line"><span style="color:#F97583;">  const</span><span style="color:#79B8FF;"> url</span><span style="color:#F97583;"> =</span><span style="color:#F97583;"> new</span><span style="color:#B392F0;"> URL</span><span style="color:#E1E4E8;">(request.url)</span></span>
<span class="line"><span style="color:#F97583;">  const</span><span style="color:#79B8FF;"> method</span><span style="color:#F97583;"> =</span><span style="color:#E1E4E8;"> request.method</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;">  // Construct the upstream Electric URL</span></span>
<span class="line"><span style="color:#F97583;">  const</span><span style="color:#79B8FF;"> originUrl</span><span style="color:#F97583;"> =</span><span style="color:#F97583;"> new</span><span style="color:#B392F0;"> URL</span><span style="color:#E1E4E8;">(</span><span style="color:#9ECBFF;">`http://localhost:3000/v1/shape`</span><span style="color:#E1E4E8;">)</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;">  // Pass through Electric protocol parameters (offset, handle, live, etc.)</span></span>
<span class="line"><span style="color:#E1E4E8;">  url.searchParams.</span><span style="color:#B392F0;">forEach</span><span style="color:#E1E4E8;">((</span><span style="color:#FFAB70;">value</span><span style="color:#E1E4E8;">, </span><span style="color:#FFAB70;">key</span><span style="color:#E1E4E8;">) </span><span style="color:#F97583;">=&gt;</span><span style="color:#E1E4E8;"> {</span></span>
<span class="line"><span style="color:#F97583;">    if</span><span style="color:#E1E4E8;"> (</span><span style="color:#79B8FF;">ELECTRIC_PROTOCOL_QUERY_PARAMS</span><span style="color:#E1E4E8;">.</span><span style="color:#B392F0;">includes</span><span style="color:#E1E4E8;">(key)) {</span></span>
<span class="line"><span style="color:#E1E4E8;">      originUrl.searchParams.</span><span style="color:#B392F0;">set</span><span style="color:#E1E4E8;">(key, value)</span></span>
<span class="line"><span style="color:#E1E4E8;">    }</span></span>
<span class="line"><span style="color:#E1E4E8;">  })</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;">  // Authentication</span></span>
<span class="line"><span style="color:#F97583;">  const</span><span style="color:#79B8FF;"> user</span><span style="color:#F97583;"> =</span><span style="color:#F97583;"> await</span><span style="color:#B392F0;"> loadUser</span><span style="color:#E1E4E8;">(request.headers.</span><span style="color:#B392F0;">get</span><span style="color:#E1E4E8;">(</span><span style="color:#9ECBFF;">`authorization`</span><span style="color:#E1E4E8;">))</span></span>
<span class="line"><span style="color:#F97583;">  if</span><span style="color:#E1E4E8;"> (</span><span style="color:#F97583;">!</span><span style="color:#E1E4E8;">user) {</span></span>
<span class="line"><span style="color:#F97583;">    return</span><span style="color:#F97583;"> new</span><span style="color:#B392F0;"> Response</span><span style="color:#E1E4E8;">(</span><span style="color:#9ECBFF;">`unauthorized`</span><span style="color:#E1E4E8;">, { status: </span><span style="color:#79B8FF;">401</span><span style="color:#E1E4E8;"> })</span></span>
<span class="line"><span style="color:#E1E4E8;">  }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;">  // Set shape definition server-side (this is your authorization layer)</span></span>
<span class="line"><span style="color:#E1E4E8;">  originUrl.searchParams.</span><span style="color:#B392F0;">set</span><span style="color:#E1E4E8;">(</span><span style="color:#9ECBFF;">`table`</span><span style="color:#E1E4E8;">, </span><span style="color:#9ECBFF;">`items`</span><span style="color:#E1E4E8;">)</span></span>
<span class="line"><span style="color:#E1E4E8;">  originUrl.searchParams.</span><span style="color:#B392F0;">set</span><span style="color:#E1E4E8;">(</span><span style="color:#9ECBFF;">`where`</span><span style="color:#E1E4E8;">, </span><span style="color:#9ECBFF;">`&quot;organization_id&quot; = &#39;${</span><span style="color:#E1E4E8;">user</span><span style="color:#9ECBFF;">.</span><span style="color:#E1E4E8;">org_id</span><span style="color:#9ECBFF;">}&#39;`</span><span style="color:#E1E4E8;">)</span></span>
<span class="line"><span style="color:#E1E4E8;">  originUrl.searchParams.</span><span style="color:#B392F0;">set</span><span style="color:#E1E4E8;">(</span><span style="color:#9ECBFF;">`secret`</span><span style="color:#E1E4E8;">, process.env.</span><span style="color:#79B8FF;">ELECTRIC_SECRET</span><span style="color:#E1E4E8;">)</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;">  // Forward request to Electric</span></span>
<span class="line"><span style="color:#F97583;">  let</span><span style="color:#E1E4E8;"> response</span><span style="color:#F97583;">:</span><span style="color:#B392F0;"> Response</span></span>
<span class="line"><span style="color:#F97583;">  if</span><span style="color:#E1E4E8;"> (method </span><span style="color:#F97583;">===</span><span style="color:#9ECBFF;"> &#39;POST&#39;</span><span style="color:#E1E4E8;">) {</span></span>
<span class="line"><span style="color:#6A737D;">    // POST: Forward client body (subset params can only narrow results)</span></span>
<span class="line"><span style="color:#F97583;">    const</span><span style="color:#79B8FF;"> clientBody</span><span style="color:#F97583;"> =</span><span style="color:#F97583;"> await</span><span style="color:#E1E4E8;"> request.</span><span style="color:#B392F0;">text</span><span style="color:#E1E4E8;">()</span></span>
<span class="line"><span style="color:#E1E4E8;">    response </span><span style="color:#F97583;">=</span><span style="color:#F97583;"> await</span><span style="color:#B392F0;"> fetch</span><span style="color:#E1E4E8;">(originUrl, {</span></span>
<span class="line"><span style="color:#E1E4E8;">      method: </span><span style="color:#9ECBFF;">&#39;POST&#39;</span><span style="color:#E1E4E8;">,</span></span>
<span class="line"><span style="color:#E1E4E8;">      headers: { </span><span style="color:#9ECBFF;">&#39;Content-Type&#39;</span><span style="color:#E1E4E8;">: </span><span style="color:#9ECBFF;">&#39;application/json&#39;</span><span style="color:#E1E4E8;"> },</span></span>
<span class="line"><span style="color:#E1E4E8;">      body: clientBody, </span><span style="color:#6A737D;">// Client subset params (where, limit, order_by, etc.)</span></span>
<span class="line"><span style="color:#E1E4E8;">    })</span></span>
<span class="line"><span style="color:#E1E4E8;">  } </span><span style="color:#F97583;">else</span><span style="color:#E1E4E8;"> {</span></span>
<span class="line"><span style="color:#6A737D;">    // GET: Simple proxy</span></span>
<span class="line"><span style="color:#E1E4E8;">    response </span><span style="color:#F97583;">=</span><span style="color:#F97583;"> await</span><span style="color:#B392F0;"> fetch</span><span style="color:#E1E4E8;">(originUrl)</span></span>
<span class="line"><span style="color:#E1E4E8;">  }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;">  // Forward response to client (remove problematic headers)</span></span>
<span class="line"><span style="color:#F97583;">  const</span><span style="color:#79B8FF;"> headers</span><span style="color:#F97583;"> =</span><span style="color:#F97583;"> new</span><span style="color:#B392F0;"> Headers</span><span style="color:#E1E4E8;">(response.headers)</span></span>
<span class="line"><span style="color:#E1E4E8;">  headers.</span><span style="color:#B392F0;">delete</span><span style="color:#E1E4E8;">(</span><span style="color:#9ECBFF;">`content-encoding`</span><span style="color:#E1E4E8;">)</span></span>
<span class="line"><span style="color:#E1E4E8;">  headers.</span><span style="color:#B392F0;">delete</span><span style="color:#E1E4E8;">(</span><span style="color:#9ECBFF;">`content-length`</span><span style="color:#E1E4E8;">)</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583;">  return</span><span style="color:#F97583;"> new</span><span style="color:#B392F0;"> Response</span><span style="color:#E1E4E8;">(response.body, {</span></span>
<span class="line"><span style="color:#E1E4E8;">    status: response.status,</span></span>
<span class="line"><span style="color:#E1E4E8;">    statusText: response.statusText,</span></span>
<span class="line"><span style="color:#E1E4E8;">    headers,</span></span>
<span class="line"><span style="color:#E1E4E8;">  })</span></span>
<span class="line"><span style="color:#E1E4E8;">}</span></span></code></pre></div><h5 id="client-configuration-for-post" tabindex="-1">Client configuration for POST <a class="header-anchor" href="#client-configuration-for-post" aria-label="Permalink to &quot;Client configuration for POST&quot;">​</a></h5><p>When using the TypeScript client with a proxy, configure <code>subsetMethod: &#39;POST&#39;</code> in your shape options:</p><div class="language-tsx"><button title="Copy Code" class="copy"></button><span class="lang">tsx</span><pre class="shiki github-dark vp-code" tabindex="0"><code><span class="line"><span style="color:#F97583;">import</span><span style="color:#E1E4E8;"> { ShapeStream } </span><span style="color:#F97583;">from</span><span style="color:#9ECBFF;"> &#39;@electric-sql/client&#39;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583;">const</span><span style="color:#79B8FF;"> stream</span><span style="color:#F97583;"> =</span><span style="color:#F97583;"> new</span><span style="color:#B392F0;"> ShapeStream</span><span style="color:#E1E4E8;">({</span></span>
<span class="line"><span style="color:#E1E4E8;">  url: </span><span style="color:#9ECBFF;">&#39;/api/shapes/items&#39;</span><span style="color:#E1E4E8;">,  </span><span style="color:#6A737D;">// Your proxy endpoint</span></span>
<span class="line"><span style="color:#E1E4E8;">  headers: {</span></span>
<span class="line"><span style="color:#E1E4E8;">    Authorization: </span><span style="color:#9ECBFF;">`Bearer ${</span><span style="color:#E1E4E8;">token</span><span style="color:#9ECBFF;">}`</span><span style="color:#E1E4E8;">,</span></span>
<span class="line"><span style="color:#E1E4E8;">  },</span></span>
<span class="line"><span style="color:#E1E4E8;">  subsetMethod: </span><span style="color:#9ECBFF;">&#39;POST&#39;</span><span style="color:#E1E4E8;">,  </span><span style="color:#6A737D;">// Send subset requests as POST</span></span>
<span class="line"><span style="color:#E1E4E8;">})</span></span></code></pre></div><p>Or with TanStack DB collections:</p><div class="language-tsx"><button title="Copy Code" class="copy"></button><span class="lang">tsx</span><pre class="shiki github-dark vp-code" tabindex="0"><code><span class="line"><span style="color:#F97583;">const</span><span style="color:#79B8FF;"> collection</span><span style="color:#F97583;"> =</span><span style="color:#B392F0;"> createCollection</span><span style="color:#E1E4E8;">(</span></span>
<span class="line"><span style="color:#B392F0;">  electricCollectionOptions</span><span style="color:#E1E4E8;">({</span></span>
<span class="line"><span style="color:#E1E4E8;">    schema: itemsSchema,</span></span>
<span class="line"><span style="color:#E1E4E8;">    shapeOptions: {</span></span>
<span class="line"><span style="color:#E1E4E8;">      url: </span><span style="color:#9ECBFF;">&#39;/api/shapes/items&#39;</span><span style="color:#E1E4E8;">,</span></span>
<span class="line"><span style="color:#E1E4E8;">      headers: {</span></span>
<span class="line"><span style="color:#B392F0;">        Authorization</span><span style="color:#E1E4E8;">: </span><span style="color:#F97583;">async</span><span style="color:#E1E4E8;"> () </span><span style="color:#F97583;">=&gt;</span><span style="color:#9ECBFF;"> `Bearer ${</span><span style="color:#F97583;">await</span><span style="color:#B392F0;"> getToken</span><span style="color:#9ECBFF;">()</span><span style="color:#9ECBFF;">}`</span><span style="color:#E1E4E8;">,</span></span>
<span class="line"><span style="color:#E1E4E8;">      },</span></span>
<span class="line"><span style="color:#E1E4E8;">      subsetMethod: </span><span style="color:#9ECBFF;">&#39;POST&#39;</span><span style="color:#E1E4E8;">,  </span><span style="color:#6A737D;">// Use POST for subset queries</span></span>
<span class="line"><span style="color:#E1E4E8;">    },</span></span>
<span class="line"><span style="color:#B392F0;">    getKey</span><span style="color:#E1E4E8;">: (</span><span style="color:#FFAB70;">item</span><span style="color:#E1E4E8;">) </span><span style="color:#F97583;">=&gt;</span><span style="color:#E1E4E8;"> item.id,</span></span>
<span class="line"><span style="color:#E1E4E8;">  }),</span></span>
<span class="line"><span style="color:#E1E4E8;">)</span></span></code></pre></div><div class="warning custom-block"><p class="custom-block-title">GET Deprecation in Electric 2.0</p><p>GET requests for subset snapshots will be <strong>deprecated in Electric 2.0</strong> — only POST will be supported. Plan your migration now:</p><ol><li>Deploy proxy with dual GET/POST support (backwards compatible)</li><li>Update clients to use <code>subsetMethod: &#39;POST&#39;</code></li><li>Monitor for 414 errors — they should disappear</li><li>Before upgrading to Electric 2.0, ensure all clients use POST</li></ol></div><h3 id="gatekeeper-auth" tabindex="-1">Gatekeeper auth <a class="header-anchor" href="#gatekeeper-auth" aria-label="Permalink to &quot;Gatekeeper auth&quot;">​</a></h3><div class="warning custom-block github-alert"><p class="custom-block-title">GitHub example</p><p>See the <a href="https://github.com/electric-sql/electric/tree/main/examples/gatekeeper-auth" target="_blank" rel="noreferrer">gatekeeper-auth example</a> on GitHub for an example that implements this pattern.</p></div><div class="note custom-block github-alert"><p class="custom-block-title">Exception to the proxy pattern</p><p>Unlike the proxy pattern above where shape parameters are set server-side, the gatekeeper pattern is designed to authorize specific shape configurations requested by the client. The client provides the full shape definition, and the gatekeeper explicitly authorizes that exact shape configuration.</p></div><p>The Gatekeeper pattern works as follows:</p><ol><li>post to a gatekeeper endpoint in your API to generate a shape-scoped auth token</li><li>make shape requests to Electric via an authorising proxy that validates the auth token against the request parameters</li></ol><p>The auth token should include a claim containing the shape definition. This allows the proxy to authorize the shape request by comparing the shape claim signed into the token with the <a href="/docs/quickstart#http-api">shape defined in the request parameters</a>. The proxy validates that the client is requesting exactly the same shape that was authorized by the gatekeeper.</p><p>This keeps your main auth logic:</p><ul><li>in your API (in the gatekeeper endpoint) where it&#39;s natural to do things like query the database and call external services</li><li>running <em>once</em> when generating a token, rather than on the &quot;hot path&quot; of every shape request in your authorising proxy</li></ul><h4 id="implementation" tabindex="-1">Implementation <a class="header-anchor" href="#implementation" aria-label="Permalink to &quot;Implementation&quot;">​</a></h4><p>The <a href="https://github.com/electric-sql/electric/tree/main/examples/gatekeeper-auth" target="_blank" rel="noreferrer">GitHub example</a> provides an <a href="https://github.com/electric-sql/electric/tree/main/examples/gatekeeper-auth/api" target="_blank" rel="noreferrer"><code>./api</code></a> service for generating auth tokens and three options for validating those auth tokens when proxying requests to Electric:</p><ol><li><a href="https://github.com/electric-sql/electric/tree/main/examples/gatekeeper-auth/api" target="_blank" rel="noreferrer"><code>./api</code></a> the API itself</li><li><a href="https://github.com/electric-sql/electric/tree/main/examples/gatekeeper-auth/caddy" target="_blank" rel="noreferrer"><code>./caddy</code></a> a Caddy web server as a reverse proxy</li><li><a href="https://github.com/electric-sql/electric/tree/main/examples/gatekeeper-auth/edge" target="_blank" rel="noreferrer"><code>./edge</code></a> an edge function that you can run in front of a CDN</li></ol><p>The API is an <a href="/docs/integrations/phoenix">Elixir/Phoenix</a> web application that <a href="https://github.com/electric-sql/electric/blob/main/examples/gatekeeper-auth/api/lib/api_web/router.ex" target="_blank" rel="noreferrer">exposes</a> two endpoints:</p><ol><li>a gatekeeper endpoint at <code>POST /gatekeeper/:table</code></li><li>a proxy endpoint at <code>GET /proxy/v1/shape</code></li></ol><figure><a href="/assets/gatekeeper-flow.CsUNNIXv.jpg" target="_blank"><img src="/assets/gatekeeper-flow.dark.BpHfoAxB.png" alt="Illustration of the gatekeeper request flow"></a></figure><h5 id="gatekeeper-endpoint" tabindex="-1">Gatekeeper endpoint <a class="header-anchor" href="#gatekeeper-endpoint" aria-label="Permalink to &quot;Gatekeeper endpoint&quot;">​</a></h5><ol><li>the user makes a <code>POST</code> request to <code>POST /gatekeeper/:table</code> with some authentication credentials and a shape definition in the request parameters; the gatekeeper is then responsible for authorising the user&#39;s access to the shape</li><li>if access is granted, the gatekeeper generates a shape-scoped auth token and returns it to the client</li><li>the client can then use the auth token when connecting to the Electric HTTP API, via the proxy endpoint</li></ol><h5 id="proxy-endpoint" tabindex="-1">Proxy endpoint <a class="header-anchor" href="#proxy-endpoint" aria-label="Permalink to &quot;Proxy endpoint&quot;">​</a></h5><ol start="4"><li>the proxy validates the JWT and verifies that the shape claim in the token matches the shape being requested; if so it sends the request on to Electric</li><li>Electric then handles the request as normal</li><li>sending a response back <em>through the proxy</em> to the client</li></ol><p>The client can then process the data and make additional requests using the same token (step 3). If the token expires or is rejected, the client starts again (step 1).</p><div class="tip custom-block github-alert"><p class="custom-block-title">Interactive walkthrough</p><p>See <a href="https://github.com/electric-sql/electric/blob/main/examples/gatekeeper-auth/README.md#how-to-run" target="_blank" rel="noreferrer">How to run</a> on GitHub for an interactive walkthrough of the three different gatekeeper-auth example proxy options.</p></div><h4 id="example-1" tabindex="-1">Example <a class="header-anchor" href="#example-1" aria-label="Permalink to &quot;Example&quot;">​</a></h4><p>See the <a href="https://github.com/electric-sql/electric/tree/main/examples/gatekeeper-auth/client" target="_blank" rel="noreferrer">./client</a> for an example using the <a href="/docs/api/clients/typescript">Typescript client</a> with gatekeeper and proxy endpoints:</p><div class="language-typescript"><button title="Copy Code" class="copy"></button><span class="lang">typescript</span><pre class="shiki github-dark vp-code" tabindex="0"><code><span class="line"><span style="color:#F97583;">import</span><span style="color:#E1E4E8;"> { FetchError, Shape, ShapeStream } </span><span style="color:#F97583;">from</span><span style="color:#9ECBFF;"> &#39;@electric-sql/client&#39;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583;">const</span><span style="color:#79B8FF;"> API_URL</span><span style="color:#F97583;"> =</span><span style="color:#E1E4E8;"> process.env.</span><span style="color:#79B8FF;">API_URL</span><span style="color:#F97583;"> ||</span><span style="color:#9ECBFF;"> `http://localhost:4000`</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;">/*</span></span>
<span class="line"><span style="color:#6A737D;"> * Makes a request to the gatekeeper endpoint to fetch a config object</span></span>
<span class="line"><span style="color:#6A737D;"> * in the format expected by the ShapeStreamOptions including the</span></span>
<span class="line"><span style="color:#6A737D;"> * proxy `url` to connect to and auth `headers`.</span></span>
<span class="line"><span style="color:#6A737D;"> */</span></span>
<span class="line"><span style="color:#F97583;">async</span><span style="color:#F97583;"> function</span><span style="color:#B392F0;"> fetchConfig</span><span style="color:#E1E4E8;">() {</span></span>
<span class="line"><span style="color:#F97583;">  const</span><span style="color:#79B8FF;"> url</span><span style="color:#F97583;"> =</span><span style="color:#9ECBFF;"> `${</span><span style="color:#79B8FF;">API_URL</span><span style="color:#9ECBFF;">}/gatekeeper/items`</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583;">  const</span><span style="color:#79B8FF;"> resp</span><span style="color:#F97583;"> =</span><span style="color:#F97583;"> await</span><span style="color:#B392F0;"> fetch</span><span style="color:#E1E4E8;">(url, { method: </span><span style="color:#9ECBFF;">`POST`</span><span style="color:#E1E4E8;"> })</span></span>
<span class="line"><span style="color:#F97583;">  return</span><span style="color:#F97583;"> await</span><span style="color:#E1E4E8;"> resp.</span><span style="color:#B392F0;">json</span><span style="color:#E1E4E8;">()</span></span>
<span class="line"><span style="color:#E1E4E8;">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;">// Stream the shape through the proxy, using the url and auth headers</span></span>
<span class="line"><span style="color:#6A737D;">// provided by the gatekeeper.</span></span>
<span class="line"><span style="color:#F97583;">const</span><span style="color:#79B8FF;"> config</span><span style="color:#F97583;"> =</span><span style="color:#F97583;"> await</span><span style="color:#B392F0;"> fetchConfig</span><span style="color:#E1E4E8;">()</span></span>
<span class="line"><span style="color:#F97583;">const</span><span style="color:#79B8FF;"> stream</span><span style="color:#F97583;"> =</span><span style="color:#F97583;"> new</span><span style="color:#B392F0;"> ShapeStream</span><span style="color:#E1E4E8;">({</span></span>
<span class="line"><span style="color:#F97583;">  ...</span><span style="color:#E1E4E8;">config,</span></span>
<span class="line"><span style="color:#B392F0;">  onError</span><span style="color:#E1E4E8;">: </span><span style="color:#F97583;">async</span><span style="color:#E1E4E8;"> (</span><span style="color:#FFAB70;">error</span><span style="color:#E1E4E8;">) </span><span style="color:#F97583;">=&gt;</span><span style="color:#E1E4E8;"> {</span></span>
<span class="line"><span style="color:#F97583;">    if</span><span style="color:#E1E4E8;"> (error </span><span style="color:#F97583;">instanceof</span><span style="color:#B392F0;"> FetchError</span><span style="color:#E1E4E8;">) {</span></span>
<span class="line"><span style="color:#F97583;">      const</span><span style="color:#79B8FF;"> status</span><span style="color:#F97583;"> =</span><span style="color:#E1E4E8;"> error.status</span></span>
<span class="line"><span style="color:#E1E4E8;">      console.</span><span style="color:#B392F0;">log</span><span style="color:#E1E4E8;">(</span><span style="color:#9ECBFF;">`handling fetch error: `</span><span style="color:#E1E4E8;">, status)</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;">      // If the auth token is invalid or expires, hit the gatekeeper</span></span>
<span class="line"><span style="color:#6A737D;">      // again to update the auth headers and thus keep streaming</span></span>
<span class="line"><span style="color:#6A737D;">      // without interruption.</span></span>
<span class="line"><span style="color:#F97583;">      if</span><span style="color:#E1E4E8;"> (status </span><span style="color:#F97583;">===</span><span style="color:#79B8FF;"> 401</span><span style="color:#F97583;"> ||</span><span style="color:#E1E4E8;"> status </span><span style="color:#F97583;">===</span><span style="color:#79B8FF;"> 403</span><span style="color:#E1E4E8;">) {</span></span>
<span class="line"><span style="color:#F97583;">        return</span><span style="color:#F97583;"> await</span><span style="color:#B392F0;"> fetchConfig</span><span style="color:#E1E4E8;">()</span></span>
<span class="line"><span style="color:#E1E4E8;">      }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;">      // Note: 5xx errors are automatically retried by the client</span></span>
<span class="line"><span style="color:#6A737D;">      // For other 4xx errors, stop the stream</span></span>
<span class="line"><span style="color:#F97583;">      if</span><span style="color:#E1E4E8;"> (status </span><span style="color:#F97583;">&gt;=</span><span style="color:#79B8FF;"> 400</span><span style="color:#F97583;"> &amp;&amp;</span><span style="color:#E1E4E8;"> status </span><span style="color:#F97583;">&lt;</span><span style="color:#79B8FF;"> 500</span><span style="color:#E1E4E8;">) {</span></span>
<span class="line"><span style="color:#E1E4E8;">        console.</span><span style="color:#B392F0;">error</span><span style="color:#E1E4E8;">(</span><span style="color:#9ECBFF;">`Client error ${</span><span style="color:#E1E4E8;">status</span><span style="color:#9ECBFF;">}, stopping stream`</span><span style="color:#E1E4E8;">)</span></span>
<span class="line"><span style="color:#F97583;">        return</span><span style="color:#6A737D;"> // Return void to stop</span></span>
<span class="line"><span style="color:#E1E4E8;">      }</span></span>
<span class="line"><span style="color:#E1E4E8;">    }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;">    // For other errors (network errors, etc.), retry with same config</span></span>
<span class="line"><span style="color:#E1E4E8;">    console.</span><span style="color:#B392F0;">log</span><span style="color:#E1E4E8;">(</span><span style="color:#9ECBFF;">`Retrying after error:`</span><span style="color:#E1E4E8;">, error.message)</span></span>
<span class="line"><span style="color:#F97583;">    return</span><span style="color:#E1E4E8;"> {}</span></span>
<span class="line"><span style="color:#E1E4E8;">  },</span></span>
<span class="line"><span style="color:#E1E4E8;">})</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;">// Materialize the stream into a `Shape` and subscibe to data changes</span></span>
<span class="line"><span style="color:#6A737D;">// so we can see the client working.</span></span>
<span class="line"><span style="color:#F97583;">const</span><span style="color:#79B8FF;"> shape</span><span style="color:#F97583;"> =</span><span style="color:#F97583;"> new</span><span style="color:#B392F0;"> Shape</span><span style="color:#E1E4E8;">(stream)</span></span>
<span class="line"><span style="color:#E1E4E8;">shape.</span><span style="color:#B392F0;">subscribe</span><span style="color:#E1E4E8;">(({ </span><span style="color:#FFAB70;">rows</span><span style="color:#E1E4E8;"> }) </span><span style="color:#F97583;">=&gt;</span><span style="color:#E1E4E8;"> {</span></span>
<span class="line"><span style="color:#E1E4E8;">  console.</span><span style="color:#B392F0;">log</span><span style="color:#E1E4E8;">(</span><span style="color:#9ECBFF;">`num rows: `</span><span style="color:#E1E4E8;">, rows </span><span style="color:#F97583;">?</span><span style="color:#E1E4E8;"> rows.</span><span style="color:#79B8FF;">length</span><span style="color:#F97583;"> :</span><span style="color:#79B8FF;"> 0</span><span style="color:#E1E4E8;">)</span></span>
<span class="line"><span style="color:#E1E4E8;">})</span></span></code></pre></div><h3 id="dynamic-auth-options" tabindex="-1">Dynamic Auth Options <a class="header-anchor" href="#dynamic-auth-options" aria-label="Permalink to &quot;Dynamic Auth Options&quot;">​</a></h3><p>The TypeScript client supports function-based options for headers and params, making it easy to handle dynamic auth tokens:</p><div class="language-typescript"><button title="Copy Code" class="copy"></button><span class="lang">typescript</span><pre class="shiki github-dark vp-code" tabindex="0"><code><span class="line"><span style="color:#F97583;">const</span><span style="color:#79B8FF;"> stream</span><span style="color:#F97583;"> =</span><span style="color:#F97583;"> new</span><span style="color:#B392F0;"> ShapeStream</span><span style="color:#E1E4E8;">({</span></span>
<span class="line"><span style="color:#E1E4E8;">  url: </span><span style="color:#9ECBFF;">&#39;http://localhost:3000/v1/shape&#39;</span><span style="color:#E1E4E8;">,</span></span>
<span class="line"><span style="color:#E1E4E8;">  headers: {</span></span>
<span class="line"><span style="color:#6A737D;">    // Token will be refreshed on each request</span></span>
<span class="line"><span style="color:#B392F0;">    Authorization</span><span style="color:#E1E4E8;">: </span><span style="color:#F97583;">async</span><span style="color:#E1E4E8;"> () </span><span style="color:#F97583;">=&gt;</span><span style="color:#9ECBFF;"> `Bearer ${</span><span style="color:#F97583;">await</span><span style="color:#B392F0;"> getAccessToken</span><span style="color:#9ECBFF;">()</span><span style="color:#9ECBFF;">}`</span><span style="color:#E1E4E8;">,</span></span>
<span class="line"><span style="color:#E1E4E8;">  },</span></span>
<span class="line"><span style="color:#E1E4E8;">})</span></span></code></pre></div><p>This pattern is particularly useful when:</p><ul><li>Your auth tokens need periodic refreshing</li><li>You&#39;re using session-based authentication</li><li>You need to fetch tokens from a secure storage</li><li>You want to handle token rotation automatically</li></ul><p>The function is called when needed and its value is resolved in parallel with other dynamic options, making it efficient for real-world auth scenarios.</p><h3 id="handling-auth-errors" tabindex="-1">Handling Auth Errors <a class="header-anchor" href="#handling-auth-errors" aria-label="Permalink to &quot;Handling Auth Errors&quot;">​</a></h3><p>Both proxy and gatekeeper patterns can return 401 or 403 errors when authentication fails or tokens expire. Use the <code>onError</code> callback to handle these errors and retry with refreshed credentials:</p><div class="language-typescript"><button title="Copy Code" class="copy"></button><span class="lang">typescript</span><pre class="shiki github-dark vp-code" tabindex="0"><code><span class="line"><span style="color:#F97583;">const</span><span style="color:#79B8FF;"> stream</span><span style="color:#F97583;"> =</span><span style="color:#F97583;"> new</span><span style="color:#B392F0;"> ShapeStream</span><span style="color:#E1E4E8;">({</span></span>
<span class="line"><span style="color:#E1E4E8;">  url: </span><span style="color:#9ECBFF;">&#39;/api/shapes/items&#39;</span><span style="color:#E1E4E8;">,</span></span>
<span class="line"><span style="color:#E1E4E8;">  headers: {</span></span>
<span class="line"><span style="color:#E1E4E8;">    Authorization: </span><span style="color:#9ECBFF;">`Bearer ${</span><span style="color:#E1E4E8;">currentToken</span><span style="color:#9ECBFF;">}`</span><span style="color:#E1E4E8;">,</span></span>
<span class="line"><span style="color:#E1E4E8;">  },</span></span>
<span class="line"><span style="color:#B392F0;">  onError</span><span style="color:#E1E4E8;">: </span><span style="color:#F97583;">async</span><span style="color:#E1E4E8;"> (</span><span style="color:#FFAB70;">error</span><span style="color:#E1E4E8;">) </span><span style="color:#F97583;">=&gt;</span><span style="color:#E1E4E8;"> {</span></span>
<span class="line"><span style="color:#F97583;">    if</span><span style="color:#E1E4E8;"> (error </span><span style="color:#F97583;">instanceof</span><span style="color:#B392F0;"> FetchError</span><span style="color:#F97583;"> &amp;&amp;</span><span style="color:#E1E4E8;"> error.status </span><span style="color:#F97583;">===</span><span style="color:#79B8FF;"> 401</span><span style="color:#E1E4E8;">) {</span></span>
<span class="line"><span style="color:#6A737D;">      // Token expired - refresh and retry</span></span>
<span class="line"><span style="color:#F97583;">      const</span><span style="color:#79B8FF;"> newToken</span><span style="color:#F97583;"> =</span><span style="color:#F97583;"> await</span><span style="color:#B392F0;"> refreshAuthToken</span><span style="color:#E1E4E8;">()</span></span>
<span class="line"><span style="color:#F97583;">      return</span><span style="color:#E1E4E8;"> {</span></span>
<span class="line"><span style="color:#E1E4E8;">        headers: {</span></span>
<span class="line"><span style="color:#E1E4E8;">          Authorization: </span><span style="color:#9ECBFF;">`Bearer ${</span><span style="color:#E1E4E8;">newToken</span><span style="color:#9ECBFF;">}`</span><span style="color:#E1E4E8;">,</span></span>
<span class="line"><span style="color:#E1E4E8;">        },</span></span>
<span class="line"><span style="color:#E1E4E8;">      }</span></span>
<span class="line"><span style="color:#E1E4E8;">    }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;">    // For other errors, stop syncing</span></span>
<span class="line"><span style="color:#E1E4E8;">  },</span></span>
<span class="line"><span style="color:#E1E4E8;">})</span></span></code></pre></div><p><strong>Important:</strong> The return value controls stream behavior:</p><ul><li><strong>Return <code>{ headers }</code></strong> or <strong><code>{ params }</code></strong> - Retry with updated values</li><li><strong>Return <code>{}</code></strong> - Retry with same config (useful for transient errors)</li><li><strong>Return void</strong> - Stop the stream</li></ul><p>Note: 5xx server errors are automatically retried with exponential backoff. See the <a href="/docs/api/clients/typescript#error-handling">TypeScript client error handling docs</a> for complete details.</p><h2 id="session-invalidation-with-vary-headers" tabindex="-1">Session Invalidation with Vary Headers <a class="header-anchor" href="#session-invalidation-with-vary-headers" aria-label="Permalink to &quot;Session Invalidation with Vary Headers&quot;">​</a></h2><p>When users log out or their authentication status changes, it&#39;s important to ensure they can&#39;t access cached shapes that they should no longer have access to. The HTTP <code>Vary</code> header is crucial for this.</p><h3 id="the-problem" tabindex="-1">The Problem <a class="header-anchor" href="#the-problem" aria-label="Permalink to &quot;The Problem&quot;">​</a></h3><p>Without proper cache control, browsers and CDNs might serve cached shape responses even after a user logs out. This happens because the cache key typically only includes the URL, not the authentication context.</p><h3 id="the-solution-vary-header" tabindex="-1">The Solution: Vary Header <a class="header-anchor" href="#the-solution-vary-header" aria-label="Permalink to &quot;The Solution: Vary Header&quot;">​</a></h3><p>Add a <code>Vary</code> header to your shape responses to include authentication information in the cache key:</p><div class="language-http"><button title="Copy Code" class="copy"></button><span class="lang">http</span><pre class="shiki github-dark vp-code" tabindex="0"><code><span class="line"><span style="color:#85E89D;">Vary</span><span style="color:#F97583;">:</span><span style="color:#9ECBFF;"> Authorization</span></span></code></pre></div><p>or for cookie-based auth:</p><div class="language-http"><button title="Copy Code" class="copy"></button><span class="lang">http</span><pre class="shiki github-dark vp-code" tabindex="0"><code><span class="line"><span style="color:#85E89D;">Vary</span><span style="color:#F97583;">:</span><span style="color:#9ECBFF;"> Cookie</span></span></code></pre></div><h3 id="implementation-examples" tabindex="-1">Implementation Examples <a class="header-anchor" href="#implementation-examples" aria-label="Permalink to &quot;Implementation Examples&quot;">​</a></h3><h4 id="with-authorization-headers" tabindex="-1">With Authorization Headers <a class="header-anchor" href="#with-authorization-headers" aria-label="Permalink to &quot;With Authorization Headers&quot;">​</a></h4><div class="language-tsx"><button title="Copy Code" class="copy"></button><span class="lang">tsx</span><pre class="shiki github-dark vp-code" tabindex="0"><code><span class="line"><span style="color:#F97583;">export</span><span style="color:#F97583;"> async</span><span style="color:#F97583;"> function</span><span style="color:#B392F0;"> GET</span><span style="color:#E1E4E8;">(</span><span style="color:#FFAB70;">request</span><span style="color:#F97583;">:</span><span style="color:#B392F0;"> Request</span><span style="color:#E1E4E8;">) {</span></span>
<span class="line"><span style="color:#6A737D;">  // ... auth logic ...</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583;">  const</span><span style="color:#79B8FF;"> response</span><span style="color:#F97583;"> =</span><span style="color:#F97583;"> await</span><span style="color:#B392F0;"> fetch</span><span style="color:#E1E4E8;">(originUrl)</span></span>
<span class="line"><span style="color:#F97583;">  const</span><span style="color:#79B8FF;"> headers</span><span style="color:#F97583;"> =</span><span style="color:#F97583;"> new</span><span style="color:#B392F0;"> Headers</span><span style="color:#E1E4E8;">(response.headers)</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;">  // Add Vary header for Authorization-based auth</span></span>
<span class="line"><span style="color:#E1E4E8;">  headers.</span><span style="color:#B392F0;">set</span><span style="color:#E1E4E8;">(</span><span style="color:#9ECBFF;">&#39;Vary&#39;</span><span style="color:#E1E4E8;">, </span><span style="color:#9ECBFF;">&#39;Authorization&#39;</span><span style="color:#E1E4E8;">)</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583;">  return</span><span style="color:#F97583;"> new</span><span style="color:#B392F0;"> Response</span><span style="color:#E1E4E8;">(response.body, {</span></span>
<span class="line"><span style="color:#E1E4E8;">    status: response.status,</span></span>
<span class="line"><span style="color:#E1E4E8;">    statusText: response.statusText,</span></span>
<span class="line"><span style="color:#E1E4E8;">    headers,</span></span>
<span class="line"><span style="color:#E1E4E8;">  })</span></span>
<span class="line"><span style="color:#E1E4E8;">}</span></span></code></pre></div><h4 id="with-cookie-based-auth" tabindex="-1">With Cookie-based Auth <a class="header-anchor" href="#with-cookie-based-auth" aria-label="Permalink to &quot;With Cookie-based Auth&quot;">​</a></h4><div class="language-tsx"><button title="Copy Code" class="copy"></button><span class="lang">tsx</span><pre class="shiki github-dark vp-code" tabindex="0"><code><span class="line"><span style="color:#F97583;">export</span><span style="color:#F97583;"> async</span><span style="color:#F97583;"> function</span><span style="color:#B392F0;"> GET</span><span style="color:#E1E4E8;">(</span><span style="color:#FFAB70;">request</span><span style="color:#F97583;">:</span><span style="color:#B392F0;"> Request</span><span style="color:#E1E4E8;">) {</span></span>
<span class="line"><span style="color:#6A737D;">  // ... auth logic ...</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583;">  const</span><span style="color:#79B8FF;"> response</span><span style="color:#F97583;"> =</span><span style="color:#F97583;"> await</span><span style="color:#B392F0;"> fetch</span><span style="color:#E1E4E8;">(originUrl)</span></span>
<span class="line"><span style="color:#F97583;">  const</span><span style="color:#79B8FF;"> headers</span><span style="color:#F97583;"> =</span><span style="color:#F97583;"> new</span><span style="color:#B392F0;"> Headers</span><span style="color:#E1E4E8;">(response.headers)</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;">  // Add Vary header for cookie-based auth</span></span>
<span class="line"><span style="color:#E1E4E8;">  headers.</span><span style="color:#B392F0;">set</span><span style="color:#E1E4E8;">(</span><span style="color:#9ECBFF;">&#39;Vary&#39;</span><span style="color:#E1E4E8;">, </span><span style="color:#9ECBFF;">&#39;Cookie&#39;</span><span style="color:#E1E4E8;">)</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583;">  return</span><span style="color:#F97583;"> new</span><span style="color:#B392F0;"> Response</span><span style="color:#E1E4E8;">(response.body, {</span></span>
<span class="line"><span style="color:#E1E4E8;">    status: response.status,</span></span>
<span class="line"><span style="color:#E1E4E8;">    statusText: response.statusText,</span></span>
<span class="line"><span style="color:#E1E4E8;">    headers,</span></span>
<span class="line"><span style="color:#E1E4E8;">  })</span></span>
<span class="line"><span style="color:#E1E4E8;">}</span></span></code></pre></div><h4 id="multiple-auth-methods" tabindex="-1">Multiple Auth Methods <a class="header-anchor" href="#multiple-auth-methods" aria-label="Permalink to &quot;Multiple Auth Methods&quot;">​</a></h4><p>If you support multiple authentication methods:</p><div class="language-tsx"><button title="Copy Code" class="copy"></button><span class="lang">tsx</span><pre class="shiki github-dark vp-code" tabindex="0"><code><span class="line"><span style="color:#6A737D;">// For both Authorization header and Cookie support</span></span>
<span class="line"><span style="color:#E1E4E8;">headers.</span><span style="color:#B392F0;">set</span><span style="color:#E1E4E8;">(</span><span style="color:#9ECBFF;">&#39;Vary&#39;</span><span style="color:#E1E4E8;">, </span><span style="color:#9ECBFF;">&#39;Authorization, Cookie&#39;</span><span style="color:#E1E4E8;">)</span></span></code></pre></div><h3 id="how-it-works" tabindex="-1">How It Works <a class="header-anchor" href="#how-it-works" aria-label="Permalink to &quot;How It Works&quot;">​</a></h3><p>The <code>Vary</code> header tells browsers and CDNs to include the specified headers when creating cache keys. This means:</p><ul><li>Authenticated requests get cached separately from unauthenticated ones</li><li>Different users&#39; requests are cached separately</li><li>When a user logs out and loses their auth credentials, they can&#39;t access cached authenticated responses</li></ul><p>This ensures proper isolation of cached shape data based on authentication context.</p><h3 id="clearing-client-side-data-on-logout" tabindex="-1">Clearing Client-Side Data on Logout <a class="header-anchor" href="#clearing-client-side-data-on-logout" aria-label="Permalink to &quot;Clearing Client-Side Data on Logout&quot;">​</a></h3><p>The <code>Vary</code> header handles HTTP cache isolation, but your client application also holds synced shape data in memory. When a user logs out, do a full page refresh to clear the previous user&#39;s data from memory.</p><div class="language-typescript"><button title="Copy Code" class="copy"></button><span class="lang">typescript</span><pre class="shiki github-dark vp-code" tabindex="0"><code><span class="line"><span style="color:#F97583;">async</span><span style="color:#F97583;"> function</span><span style="color:#B392F0;"> handleLogout</span><span style="color:#E1E4E8;">() {</span></span>
<span class="line"><span style="color:#F97583;">  await</span><span style="color:#B392F0;"> clearAuthToken</span><span style="color:#E1E4E8;">()</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;">  // Full refresh clears all synced shape data from memory</span></span>
<span class="line"><span style="color:#E1E4E8;">  window.location.</span><span style="color:#B392F0;">reload</span><span style="color:#E1E4E8;">()</span></span>
<span class="line"><span style="color:#E1E4E8;">}</span></span></code></pre></div><h2 id="notes" tabindex="-1">Notes <a class="header-anchor" href="#notes" aria-label="Permalink to &quot;Notes&quot;">​</a></h2><h3 id="external-services" tabindex="-1">External services <a class="header-anchor" href="#external-services" aria-label="Permalink to &quot;External services&quot;">​</a></h3><p>Both proxy and gatekeeper patterns work well with external auth services.</p><p>If you&#39;re using an external authentication service, such as <a href="https://auth0.com" target="_blank" rel="noreferrer">Auth0</a>, to generate user credentials, for example, to generate a JWT, you just need to make sure that you can decode the JWT in your proxy or gatekeeper endpoint.</p><p>If you&#39;re using an external authorization service to authorize a user&#39;s access to a shape, then you can call this whereever you run your authorization logic. For proxy auth this is the proxy. For gatekeeper auth this is the gatekeeper endpoint.</p><p>Note that if you&#39;re using a distributed auth service to ensure consistent distributed auth, such as <a href="https://authzed.com/" target="_blank" rel="noreferrer">Authzed</a>, then this works best with the proxy auth pattern. This is because you explicitly <em>want</em> to authorize the user each shape request, as opposed to the gatekeeper generating a token that can potentially become stale.</p><h3 id="cdn-proxy" tabindex="-1">CDN &lt;-&gt; Proxy <a class="header-anchor" href="#cdn-proxy" aria-label="Permalink to &quot;CDN &lt;-&gt; Proxy&quot;">​</a></h3><p>If you&#39;re deploying Electric <a href="/docs/guides/deployment#caching-proxy">behind a CDN</a>, then it&#39;s best to run your authorising proxy at the edge, between your CDN and your user. Both proxy and gatekeeper patterns work well for this.</p><p>The gatekeeper pattern is ideal because it minimises the logic that your proxy needs to perform at the edge and minimises the network and database access that you need to provide to your edge worker. See the <a href="https://github.com/electric-sql/electric/tree/main/examples/gatekeeper-auth/edge" target="_blank" rel="noreferrer">edge function</a> proxy option in the gatekeeper example for an example designed to run at the edge on <a href="/docs/integrations/supabase">Supabase Edge Functions</a>.</p></div></div></main><footer class="VPDocFooter" data-v-e5fa90be data-v-429f87a2><!--[--><!--[--><!--[--><!--[--><div class="markdown-link-footer-container"><a class="markdown-link-footer" href="/docs/guides/auth.md" target="_blank" rel="noopener" data-v-c9b8e4a6><span class="title" data-v-c9b8e4a6>✨ Markdown</span></a></div><!--]--><!--]--><!--]--><!--]--><div class="edit-info" data-v-429f87a2><div class="edit-link" data-v-429f87a2><a class="VPLink link vp-external-link-icon no-icon edit-link-button" href="https://github.com/electric-sql/electric/edit/main/website/docs/guides/auth.md" target="_blank" rel="noreferrer" data-v-429f87a2><!--[--><span class="vpi-square-pen edit-link-icon" data-v-429f87a2></span> Edit this page<!--]--></a></div><!----></div><nav class="prev-next" aria-labelledby="doc-footer-aria-label" data-v-429f87a2><span class="visually-hidden" id="doc-footer-aria-label" data-v-429f87a2>Pager</span><div class="pager" data-v-429f87a2><a class="VPLink link pager-link prev" href="/docs/agents" data-v-429f87a2><!--[--><span class="desc" data-v-429f87a2>Previous page</span><span class="title" data-v-429f87a2>AGENTS.md</span><!--]--></a></div><div class="pager" data-v-429f87a2><a class="VPLink link pager-link next" href="/docs/guides/shapes" data-v-429f87a2><!--[--><span class="desc" data-v-429f87a2>Next page</span><span class="title" data-v-429f87a2>Shapes</span><!--]--></a></div></nav></footer><!--[--><!--]--></div></div></div><!--[--><!--]--></div></div><!----><!--[--><!--]--></div></div>
    <script>window.__VP_HASH_MAP__=JSON.parse("{\"about_community.md\":\"Dq5HA5GI\",\"about_contact.md\":\"hDEdQZrb\",\"about_jobs_founders-associate.md\":\"BOordfnN\",\"about_jobs_index.md\":\"BlHU3vhz\",\"about_jobs_pglite-engineer.md\":\"D00M88r8\",\"about_legal_dpa.md\":\"CXGxOQv7\",\"about_legal_privacy.md\":\"DJVsPHwD\",\"about_legal_subprocessors.md\":\"D-UoEvmh\",\"about_legal_terms.md\":\"WuawsyIG\",\"about_team.md\":\"CeaLPrBs\",\"blog.md\":\"5lt-vmbt\",\"blog_2022_05_03_introducing-rich-crdts.md\":\"CoXmRfX3\",\"blog_2022_05_20_relativity-causal-consistency.md\":\"BKtuc4Iv\",\"blog_2022_12_16_evolution-state-transfer.md\":\"B5_Xd_e_\",\"blog_2023_02_02_introducing-jose-kevin-garry.md\":\"DNEng3Dl\",\"blog_2023_02_09_developing-local-first-software.md\":\"ChhiNR07\",\"blog_2023_05_10_welcome-andrei-oleksii.md\":\"B7bbWDLs\",\"blog_2023_09_20_introducing-electricsql-v0.6.md\":\"DCG2XhdF\",\"blog_2023_10_10_welcome-sam.md\":\"DsBXGEsO\",\"blog_2023_10_12_linerlite-local-first-with-react.md\":\"cRX5YHYA\",\"blog_2023_10_26_local-first-software-london-meet-up.md\":\"DOR3LKwN\",\"blog_2023_11_02_electricsql-v0.7-released.md\":\"BjhpvO9D\",\"blog_2023_11_02_using-electricsql-with-the-ionic-framework-and-capacitor.md\":\"CIHVdwL4\",\"blog_2023_12_13_electricsql-v0.8-released.md\":\"BMGJB2M4\",\"blog_2023_12_15_secure-transactions-with-local-first.md\":\"DI7uGCuQ\",\"blog_2024_01_24_electricsql-v0.9-released.md\":\"B9ayNVE6\",\"blog_2024_02_05_local-first-ai-with-tauri-postgres-pgvector-llama.md\":\"Bj6oqeMh\",\"blog_2024_02_27_intel-ignite.md\":\"Be3eWNGk\",\"blog_2024_04_10_electricsql-v0.10-released.md\":\"CPU96tYW\",\"blog_2024_05_14_electricsql-postgres-client-support.md\":\"DZmgob4W\",\"blog_2024_07_17_electric-next.md\":\"7kMRzpmt\",\"blog_2024_11_21_local-first-with-your-existing-api.md\":\"DlQ_BplG\",\"blog_2024_12_10_electric-beta-release.md\":\"DZy7gebQ\",\"blog_2025_03_17_electricsql-1.0-released.md\":\"BWPZMT9q\",\"blog_2025_04_07_electric-cloud-public-beta-release.md\":\"TweaC6M0\",\"blog_2025_04_09_building-ai-apps-on-sync.md\":\"ClvuoD3E\",\"blog_2025_04_22_untangling-llm-spaghetti.md\":\"DuKRg7ns\",\"blog_2025_06_05_database-in-the-sandbox.md\":\"BAIDpgM7\",\"blog_2025_07_29_super-fast-apps-on-sync-with-tanstack-db.md\":\"JLJDc4ae\",\"blog_2025_08_04_reliability-sprint.md\":\"DA4eORXQ\",\"blog_2025_08_12_bringing-agents-back-down-to-earth.md\":\"nIxzz2OT\",\"blog_2025_08_13_electricsql-v1.1-released.md\":\"CFbAPl9T\",\"blog_2025_12_09_announcing-durable-streams.md\":\"jwhV2DkQ\",\"blog_2025_12_23_durable-streams-0.1.0.md\":\"C7aWEm88\",\"blog_2026_01_12_durable-sessions-for-collaborative-ai.md\":\"BO-AtQUu\",\"blog_2026_01_20_from-science-fiction-to-reality-you-can-build-difficult-things-now.md\":\"LnuVp2w9\",\"blog_2026_01_22_announcing-hosted-durable-streams.md\":\"B_4ogm_E\",\"blog_2026_02_02_configurancy.md\":\"BAXrJQIn\",\"blog_2026_02_19_amdahls-law-for-ai-agents.md\":\"BschsMiR\",\"blog_2026_03_06_agent-skills-now-shipping.md\":\"yrmK3E4s\",\"blog_2026_03_24_durable-transport-ai-sdks.md\":\"3FNkt0fT\",\"blog_2026_03_25_announcing-pglite-v04.md\":\"SZ_RDfwx\",\"blog_2026_03_25_tanstack-db-0.6-app-ready-with-persistence-and-includes.md\":\"IpVYCg7q\",\"blog_2026_03_26_stream-db.md\":\"BUDnZjRT\",\"blog_2026_04_02_electric-cloud-pricing.md\":\"iVpPD97q\",\"changelog.md\":\"8tBYg29L\",\"cloud_index.md\":\"DaUq66du\",\"cloud_protocols.md\":\"Dik0becM\",\"cloud_usage.md\":\"CCgUQodH\",\"demos.md\":\"D3FG7HeP\",\"demos_ai-chat.md\":\"BkpJV1kh\",\"demos_bash.md\":\"DZMtno9C\",\"demos_burn.md\":\"zv2atq2o\",\"demos_encryption.md\":\"D1jFbjqX\",\"demos_gatekeeper-auth.md\":\"CEkURS8m\",\"demos_linearlite.md\":\"B5QLpHe0\",\"demos_nextjs.md\":\"ron_Ukcq\",\"demos_notes.md\":\"D7d4Knmf\",\"demos_phoenix-liveview.md\":\"D163eCSW\",\"demos_pixel-art.md\":\"B7m38HvU\",\"demos_proxy-auth.md\":\"Dloqx_Ln\",\"demos_react.md\":\"CvnOUQQe\",\"demos_redis.md\":\"dPJq6Kp_\",\"demos_remix.md\":\"C1OhmwN6\",\"demos_tanstack.md\":\"Cj5wW-JE\",\"demos_todo-app.md\":\"Ds4reuAr\",\"demos_write-patterns.md\":\"DAa7F1TN\",\"demos_yjs.md\":\"VUJClXHz\",\"docs__tutorial.md\":\"DzTNiqZO\",\"docs_agents.md\":\"-hxM5KuS\",\"docs_api_clients_elixir.md\":\"jPymefUQ\",\"docs_api_clients_typescript.md\":\"DuIjdVyw\",\"docs_api_config.md\":\"C1Z4YeFh\",\"docs_api_http.md\":\"D1zE0Rmg\",\"docs_guides_auth.md\":\"79v9ZOqc\",\"docs_guides_client-development.md\":\"D8sD8skx\",\"docs_guides_deployment.md\":\"CFli_dKV\",\"docs_guides_installation.md\":\"DE1OkWLz\",\"docs_guides_postgres-permissions.md\":\"CdbDCMVx\",\"docs_guides_security.md\":\"D-l3zYXi\",\"docs_guides_shapes.md\":\"CPuokKrK\",\"docs_guides_sharding.md\":\"CquzB4ei\",\"docs_guides_troubleshooting.md\":\"CAekaf3H\",\"docs_guides_writes.md\":\"-QpfGb-B\",\"docs_integrations_aws.md\":\"CFjZQY_n\",\"docs_integrations_cloudflare.md\":\"MvZUcUoj\",\"docs_integrations_crunchy.md\":\"DnZ5DCzf\",\"docs_integrations_digital-ocean.md\":\"Bwe9GDS1\",\"docs_integrations_expo.md\":\"Cs6atpvP\",\"docs_integrations_fly.md\":\"BFRbm_lw\",\"docs_integrations_gcp.md\":\"DcL3_wDy\",\"docs_integrations_livestore.md\":\"BExYNslP\",\"docs_integrations_mobx.md\":\"BdsYB7WB\",\"docs_integrations_neon.md\":\"Dqb_q5_o\",\"docs_integrations_netlify.md\":\"DEOT7v0R\",\"docs_integrations_next.md\":\"BFAy43is\",\"docs_integrations_phoenix.md\":\"iwQ20sD-\",\"docs_integrations_planetscale.md\":\"DEVxApVu\",\"docs_integrations_react.md\":\"Cx2sxq67\",\"docs_integrations_redis.md\":\"DUEckOG7\",\"docs_integrations_render.md\":\"Dn_z6DJo\",\"docs_integrations_supabase.md\":\"MsiVS4fy\",\"docs_integrations_tanstack.md\":\"BfouXNnJ\",\"docs_integrations_vercel.md\":\"CCrepP5b\",\"docs_integrations_yjs.md\":\"CwoUxCrD\",\"docs_intro.md\":\"B51O8NVh\",\"docs_llms__intro_redux.md\":\"b0wf2SP7\",\"docs_llms__quickstart_redux.md\":\"B27brHB8\",\"docs_quickstart.md\":\"DhHw3UBM\",\"docs_reference_alternatives.md\":\"QfrFTLye\",\"docs_reference_benchmarks.md\":\"BRuD3kNN\",\"docs_reference_literature.md\":\"Bq8FNy3z\",\"docs_reference_telemetry.md\":\"BppQTfl8\",\"docs_stacks.md\":\"BOeSdfsC\",\"index.md\":\"CjlocA1i\",\"old-index.md\":\"CWTkyUj2\",\"pricing.md\":\"BOEps6hP\",\"primitives_durable-streams.md\":\"DhX4OzAu\",\"primitives_index.md\":\"CTzjhOaQ\",\"primitives_pglite.md\":\"DjdMO3kr\",\"primitives_postgres-sync.md\":\"Bdap5thf\",\"primitives_tanstack-db.md\":\"BLn4KX8q\",\"src_partials_home-cta.md\":\"CC1folU4\",\"src_partials_home-features-after.md\":\"BYSLlzCF\",\"src_partials_home-features-before.md\":\"pikadsOB\",\"src_partials_home-your-stack-simplified.md\":\"4e8Yiw8a\",\"sync.md\":\"DYxcTOZ7\"}");window.__VP_SITE_DATA__=JSON.parse("{\"lang\":\"en\",\"dir\":\"ltr\",\"title\":\"Electric\",\"description\":\"Electric provides the data primitives and infra to build collaborative, multi-agent systems. Including Postgres Sync, Durable Streams, TanStack DB and PGlite.\",\"base\":\"/\",\"head\":[],\"router\":{\"prefetchLinks\":true},\"appearance\":\"force-dark\",\"themeConfig\":{\"editLink\":{\"pattern\":\"https://github.com/electric-sql/electric/edit/main/website/:path\"},\"logo\":\"/img/brand/logo.svg\",\"nav\":[{\"text\":\"Sync\",\"link\":\"/sync\",\"activeMatch\":\"/sync\"},{\"text\":\"Primitives\",\"link\":\"/primitives\",\"activeMatch\":\"/primitives\"},{\"text\":\"Cloud\",\"link\":\"/cloud\",\"activeMatch\":\"/cloud\"},{\"text\":\"Pricing\",\"link\":\"/pricing\",\"activeMatch\":\"/pricing\"},{\"text\":\"Docs\",\"link\":\"/docs/intro\",\"activeMatch\":\"/docs/\"},{\"text\":\"Demos\",\"link\":\"/demos\",\"activeMatch\":\"/demos\"},{\"text\":\"Blog\",\"link\":\"/blog\",\"activeMatch\":\"/blog\"},{\"text\":\"About\",\"link\":\"/about/community\",\"activeMatch\":\"/about/\"},{\"component\":\"NavSignupButton\"}],\"search\":{\"provider\":\"local\"},\"sidebar\":{\"/primitives\":[{\"text\":\"Primitives\",\"items\":[{\"text\":\"Overview\",\"link\":\"/primitives/\"},{\"text\":\"Postgres Sync\",\"link\":\"/primitives/postgres-sync\"},{\"text\":\"Durable Streams\",\"link\":\"/primitives/durable-streams\"},{\"text\":\"TanStack DB\",\"link\":\"/primitives/tanstack-db\"},{\"text\":\"PGlite\",\"link\":\"/primitives/pglite\"}]}],\"/cloud\":[{\"text\":\"Electric Cloud\",\"items\":[{\"text\":\"Overview\",\"link\":\"/cloud/\"},{\"text\":\"Usage\",\"link\":\"/cloud/usage\"},{\"text\":\"Protocols\",\"link\":\"/cloud/protocols\"},{\"text\":\"Pricing\",\"link\":\"/pricing\"}]}],\"/docs\":[{\"text\":\"Docs\",\"collapsed\":false,\"items\":[{\"text\":\"Intro\",\"link\":\"/docs/intro\"},{\"text\":\"Quickstart\",\"link\":\"/docs/quickstart\"},{\"text\":\"Stacks\",\"link\":\"/docs/stacks\"},{\"text\":\"AGENTS.md\",\"link\":\"/docs/agents\"}]},{\"text\":\"Guides\",\"collapsed\":false,\"items\":[{\"text\":\"Auth\",\"link\":\"/docs/guides/auth\"},{\"text\":\"Shapes\",\"link\":\"/docs/guides/shapes\"},{\"text\":\"Writes\",\"link\":\"/docs/guides/writes\"},{\"text\":\"Installation\",\"link\":\"/docs/guides/installation\"},{\"text\":\"PostgreSQL Permissions\",\"link\":\"/docs/guides/postgres-permissions\"},{\"text\":\"Deployment\",\"link\":\"/docs/guides/deployment\"},{\"text\":\"Sharding\",\"link\":\"/docs/guides/sharding\"},{\"text\":\"Security\",\"link\":\"/docs/guides/security\"},{\"text\":\"Troubleshooting\",\"link\":\"/docs/guides/troubleshooting\"},{\"text\":\"Client development\",\"link\":\"/docs/guides/client-development\"}]},{\"text\":\"API\",\"collapsed\":false,\"items\":[{\"text\":\"HTTP\",\"link\":\"/docs/api/http\"},{\"text\":\"Clients\",\"items\":[{\"text\":\"TypeScript\",\"link\":\"/docs/api/clients/typescript\"},{\"text\":\"Elixir\",\"link\":\"/docs/api/clients/elixir\"}],\"collapsed\":false},{\"text\":\"Config\",\"link\":\"/docs/api/config\"}]},{\"text\":\"Integrations\",\"collapsed\":false,\"items\":[{\"text\":\"Frameworks\",\"items\":[{\"text\":\"LiveStore\",\"link\":\"/docs/integrations/livestore\"},{\"text\":\"MobX\",\"link\":\"/docs/integrations/mobx\"},{\"text\":\"Next.js\",\"link\":\"/docs/integrations/next\"},{\"text\":\"Phoenix\",\"link\":\"/docs/integrations/phoenix\"},{\"text\":\"React\",\"link\":\"/docs/integrations/react\"},{\"text\":\"Redis\",\"link\":\"/docs/integrations/redis\"},{\"text\":\"TanStack\",\"link\":\"/docs/integrations/tanstack\"},{\"text\":\"Yjs\",\"link\":\"/docs/integrations/yjs\"}]},{\"text\":\"Platforms\",\"items\":[{\"text\":\"AWS\",\"link\":\"/docs/integrations/aws\"},{\"text\":\"Cloudflare\",\"link\":\"/docs/integrations/cloudflare\"},{\"text\":\"Crunchy\",\"link\":\"/docs/integrations/crunchy\"},{\"text\":\"Digital Ocean\",\"link\":\"/docs/integrations/digital-ocean\"},{\"text\":\"Expo\",\"link\":\"/docs/integrations/expo\"},{\"text\":\"Fly.io\",\"link\":\"/docs/integrations/fly\"},{\"text\":\"GCP\",\"link\":\"/docs/integrations/gcp\"},{\"text\":\"Neon\",\"link\":\"/docs/integrations/neon\"},{\"text\":\"Netlify\",\"link\":\"/docs/integrations/netlify\"},{\"text\":\"PlanetScale\",\"link\":\"/docs/integrations/planetscale\"},{\"text\":\"Render\",\"link\":\"/docs/integrations/render\"},{\"text\":\"Supabase\",\"link\":\"/docs/integrations/supabase\"}]}]},{\"text\":\"Reference\",\"collapsed\":false,\"items\":[{\"text\":\"Alternatives\",\"link\":\"/docs/reference/alternatives\"},{\"text\":\"Benchmarks\",\"link\":\"/docs/reference/benchmarks\"},{\"text\":\"Literature\",\"link\":\"/docs/reference/literature\"},{\"text\":\"Telemetry\",\"link\":\"/docs/reference/telemetry\"}]}],\"/demos\":[{\"text\":\"Demos\",\"collapsed\":false,\"items\":[{\"text\":\"Burn\",\"link\":\"/demos/burn\"},{\"text\":\"AI Chat\",\"link\":\"/demos/ai-chat\"},{\"text\":\"Linearlite\",\"link\":\"/demos/linearlite\"},{\"text\":\"Notes\",\"link\":\"/demos/notes\"},{\"text\":\"Pixel art\",\"link\":\"/demos/pixel-art\"}]},{\"text\":\"Examples\",\"collapsed\":false,\"items\":[{\"text\":\"Bash\",\"link\":\"/demos/bash\"},{\"text\":\"Encryption\",\"link\":\"/demos/encryption\"},{\"text\":\"Gatekeeper auth\",\"link\":\"/demos/gatekeeper-auth\"},{\"text\":\"Next.js\",\"link\":\"/demos/nextjs\"},{\"text\":\"Phoenix LiveView\",\"link\":\"/demos/phoenix-liveview\"},{\"text\":\"Proxy auth\",\"link\":\"/demos/proxy-auth\"},{\"text\":\"React\",\"link\":\"/demos/react\"},{\"text\":\"Redis\",\"link\":\"/demos/redis\"},{\"text\":\"Remix\",\"link\":\"/demos/remix\"},{\"text\":\"Tanstack\",\"link\":\"/demos/tanstack\"},{\"text\":\"Todo app\",\"link\":\"/demos/todo-app\"},{\"text\":\"Write patterns\",\"link\":\"/demos/write-patterns\"},{\"text\":\"Yjs\",\"link\":\"/demos/yjs\"}]}],\"/blog\":[{\"text\":\"Blog\",\"items\":[{\"text\":\"Electric Cloud pricing is live\",\"link\":\"/blog/2026/04/02/electric-cloud-pricing\"},{\"text\":\"StreamDB — a reactive database in a Durable Stream\",\"link\":\"/blog/2026/03/26/stream-db\"},{\"text\":\"Electric apps get persistence and includes with TanStack DB 0.6\",\"link\":\"/blog/2026/03/25/tanstack-db-0.6-app-ready-with-persistence-and-includes\"},{\"text\":\"Announcing PGlite v0.4: PostGIS, connection multiplexing, and a new architecture\",\"link\":\"/blog/2026/03/25/announcing-pglite-v04\"},{\"text\":\"Durable Transports for your AI SDK\",\"link\":\"/blog/2026/03/24/durable-transport-ai-sdks\"},{\"text\":\"Agent skills now shipping in our npm packages\",\"link\":\"/blog/2026/03/06/agent-skills-now-shipping\"},{\"text\":\"Amdahl's law for AI agents\",\"link\":\"/blog/2026/02/19/amdahls-law-for-ai-agents\"},{\"text\":\"Configurancy: keeping systems intelligible when agents write all the code\",\"link\":\"/blog/2026/02/02/configurancy\"},{\"text\":\"Announcing Hosted Durable Streams\",\"link\":\"/blog/2026/01/22/announcing-hosted-durable-streams\"},{\"text\":\"From fiction to reality, you can now build difficult things\",\"link\":\"/blog/2026/01/20/from-science-fiction-to-reality-you-can-build-difficult-things-now\"},{\"text\":\"Durable Sessions — the key pattern for collaborative AI\",\"link\":\"/blog/2026/01/12/durable-sessions-for-collaborative-ai\"},{\"text\":\"Durable Streams 0.1.0 and State Protocol\",\"link\":\"/blog/2025/12/23/durable-streams-0.1.0\"},{\"text\":\"Announcing Durable Streams\",\"link\":\"/blog/2025/12/09/announcing-durable-streams\"},{\"text\":\"Electric 1.1: new storage engine with 100x faster writes\",\"link\":\"/blog/2025/08/13/electricsql-v1.1-released\"},{\"text\":\"Bringing agents back down to earth\",\"link\":\"/blog/2025/08/12/bringing-agents-back-down-to-earth\"},{\"text\":\"120 days of hardening – the post‑1.0 reliability sprint\",\"link\":\"/blog/2025/08/04/reliability-sprint\"},{\"text\":\"Super-fast apps on sync with Electric and TanStack DB\",\"link\":\"/blog/2025/07/29/super-fast-apps-on-sync-with-tanstack-db\"},{\"text\":\"Vibe coding with a database in the sandbox\",\"link\":\"/blog/2025/06/05/database-in-the-sandbox\"},{\"text\":\"Untangling the LLM spaghetti\",\"link\":\"/blog/2025/04/22/untangling-llm-spaghetti\"},{\"text\":\"Building AI apps? You need sync\",\"link\":\"/blog/2025/04/09/building-ai-apps-on-sync\"},{\"text\":\"Electric Cloud public BETA: Sync in 30 seconds\",\"link\":\"/blog/2025/04/07/electric-cloud-public-beta-release\"},{\"text\":\"Electric 1.0 released\",\"link\":\"/blog/2025/03/17/electricsql-1.0-released\"},{\"text\":\"Electric BETA release\",\"link\":\"/blog/2024/12/10/electric-beta-release\"},{\"text\":\"Local-first with your existing API\",\"link\":\"/blog/2024/11/21/local-first-with-your-existing-api\"},{\"text\":\"A new approach to building Electric\",\"link\":\"/blog/2024/07/17/electric-next\"},{\"text\":\"Electric v0.11 released with support for Postgres in the client\",\"link\":\"/blog/2024/05/14/electricsql-postgres-client-support\"},{\"text\":\"Electric v0.10 released with shape filtering\",\"link\":\"/blog/2024/04/10/electricsql-v0.10-released\"},{\"text\":\"Electrify, Ignition, Liftoff!\",\"link\":\"/blog/2024/02/27/intel-ignite\"},{\"text\":\"Local AI with Postgres, pgvector and llama2, inside a Tauri app\",\"link\":\"/blog/2024/02/05/local-first-ai-with-tauri-postgres-pgvector-llama\"},{\"text\":\"ElectricSQL v0.9 released\",\"link\":\"/blog/2024/01/24/electricsql-v0.9-released\"},{\"text\":\"Secure transactions with local-first\",\"link\":\"/blog/2023/12/15/secure-transactions-with-local-first\"},{\"text\":\"ElectricSQL v0.8 released with JSON and Supabase support\",\"link\":\"/blog/2023/12/13/electricsql-v0.8-released\"},{\"text\":\"Use ElectricSQL with the Ionic Framework and Capacitor\",\"link\":\"/blog/2023/11/02/using-electricsql-with-the-ionic-framework-and-capacitor\"},{\"text\":\"ElectricSQL v0.7 released\",\"link\":\"/blog/2023/11/02/electricsql-v0.7-released\"},{\"text\":\"ElectricSQL hosted the first \\\"Local-first Software London\\\" meet-up\",\"link\":\"/blog/2023/10/26/local-first-software-London-meet-up\"},{\"text\":\"Linearlite - A local-first app built with ElectricSQL and React\",\"link\":\"/blog/2023/10/12/linerlite-local-first-with-react\"},{\"text\":\"Welcome Sam Willis!\",\"link\":\"/blog/2023/10/10/welcome-sam\"},{\"text\":\"Local-first sync for Postgres from the inventors of CRDTs\",\"link\":\"/blog/2023/09/20/introducing-electricsql-v0.6\"},{\"text\":\"Welcome Andrei and Oleksii!\",\"link\":\"/blog/2023/05/10/welcome-andrei-oleksii\"},{\"text\":\"Developing local-first software\",\"link\":\"/blog/2023/02/09/developing-local-first-software\"},{\"text\":\"Welcome José, Kevin and Garry!\",\"link\":\"/blog/2023/02/02/introducing-jose-kevin-garry\"},{\"text\":\"The evolution of state transfer\",\"link\":\"/blog/2022/12/16/evolution-state-transfer\"},{\"text\":\"Relativity and causal consistency\",\"link\":\"/blog/2022/05/20/relativity-causal-consistency\"},{\"text\":\"Introducing Rich-CRDTs\",\"link\":\"/blog/2022/05/03/introducing-rich-crdts\"}]}],\"/about\":[{\"text\":\"About\",\"items\":[{\"text\":\"Community\",\"link\":\"/about/community\"},{\"text\":\"Team\",\"link\":\"/about/team\"},{\"text\":\"Jobs\",\"link\":\"/about/jobs\",\"items\":[{\"text\":\"Founders Associate\",\"link\":\"/about/jobs/founders-associate\"}],\"collapsed\":false},{\"text\":\"Legal\",\"items\":[{\"text\":\"Terms\",\"link\":\"/about/legal/terms\"},{\"text\":\"Privacy\",\"link\":\"/about/legal/privacy\"}],\"collapsed\":false},{\"text\":\"Contact\",\"link\":\"/about/contact\"}]}]},\"siteTitle\":false,\"socialLinks\":[{\"icon\":\"durable-streams\",\"link\":\"https://durablestreams.com\"},{\"icon\":\"tanstack\",\"link\":\"https://tanstack.com/db\"},{\"icon\":\"pglite\",\"link\":\"https://pglite.dev\"},{\"icon\":\"x\",\"link\":\"https://x.com/ElectricSQL\"},{\"icon\":\"discord\",\"link\":\"https://discord.electric-sql.com\"},{\"icon\":\"github\",\"link\":\"https://github.com/electric-sql/electric\"}]},\"locales\":{},\"scrollOffset\":134,\"cleanUrls\":true}");</script>
    
  </body>
</html>