No server of your own · Real per-user login · $0 when idle
Ship apps with no backend at all.
Not serverless functions. Not a backend-as-a-service SDK. No server of your own to run. Your app is a folder of static files. jaydb is the login and the database — your users sign in with their own identity and the browser reads and writes their data directly. Nothing sits in the middle.
Free forever: 1 GB, 100k reads and 10k writes every month. Hard stops on the free tier — never surprise bills.
- Your app Static files on a CDN HTML · JS · CSS — nothing to run
-
Sign in
Your user, their identity
Google · GitHub · Microsoft · OIDC — via
acme.jaydb.com - Token Browser gets its own bearer token PKCE — no client secret ever ships to the browser
-
Data
Browser talks straight to jaydb
GET/PUT acme.jaydb.com/v1/n/app/docs/…withBearer
No API server, no auth service, no session store — the browser reaches jaydb directly, and every user acts as themselves.
Why you can rely on it
0 servers
to run, patch, or scale
No API tier, no auth service, no session store. Your app is static files; jaydb is everything behind them.
Real login
Users sign in as themselves
A per-tenant OIDC issuer with PKCE. Bring Google, GitHub, Microsoft, or any OIDC provider — no secret in the browser.
$0
Cost while idle
No instance to keep warm. Spin up 50 prototypes or side projects, pay only for active traffic.
MIT license
100% Open source
Self-host the same Go engine anytime, export plain JSON. View on GitHub.
The shift
Delete the backend. Keep the app.
Every app used to need a server in the middle: something to check who you are, something to hold the session, something to talk to the database, and a pipeline to deploy and keep it all alive. jaydb takes over that whole layer — so the boxes below simply stop existing.
What you no longer run
Auth service→ per-tenant OIDC issuer, built inSession store→ each browser holds its own tokenAPI server & routes→ the browser calls the data plane directlyORM & migrations→ write-to-exist JSON documentsConnection pool→ stateless HTTP, nothing to exhaustServer deploy pipeline→ push static files to any CDNAlways-on instance bill→ $0 while idle
What's left to ship
A folder of static files and a jaydb tenant. Drop it on GitHub Pages, S3, Netlify, a Cloudflare bucket — anywhere that serves files. There is no server process to start, so there is nothing to keep running, nothing to autoscale, and nothing to page you at 3 a.m.
“Serverless” functions still boot a server for each request. This has no server at all — the only always-on thing is jaydb, and you don't run that either.
Identity, without an identity server
Your users sign in as themselves — and that's what makes it possible
A frontend-only app can't keep a secret. The reason it can still be safe is that
nobody ships one: every organization gets its own OIDC issuer at
<org>.jaydb.com, and each end-user signs in through it with
standard Authorization Code + PKCE. The browser walks away with a token scoped to
that one user. No shared API key in your JavaScript, ever.
A real OIDC issuer per tenant
Discovery metadata and the JWKS to verify tokens are published under
https://<org>.jaydb.com. It's an identity provider your app talks to
like any other — you just didn't have to build or operate it.
Bring your own identity providers
Connect Google, GitHub, Microsoft, any generic OIDC provider, or a custom OAuth2 one from the console. Your users sign in with the account they already have.
PKCE — no secret in the browser
Register your app as a public PKCE client with its redirect URIs and allowed origins. The proof key replaces the client secret, so nothing confidential ships to the front end.
The token is the whole backend call
The browser sends Authorization: Bearer <jwt> straight at the data
plane. Each user's reads and writes are their own — no server of yours brokers the
request.
// 1. Send the user to their org's issuer to sign in (Authorization Code + PKCE).
const verifier = randomVerifier();
sessionStorage.setItem('pkce', verifier);
location.href = 'https://acme.jaydb.com/oauth/v2/authorize?' + new URLSearchParams({
response_type: 'code',
client_id: 'app_kanban', // your public PKCE client
redirect_uri: 'https://kanban.example/callback',
scope: 'openid profile email',
code_challenge: await s256(verifier),
code_challenge_method: 'S256',
});
// 2. On your callback page, exchange the code for the user's own token.
const tok = await fetch('https://acme.jaydb.com/oauth/v2/token', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'authorization_code',
code, client_id: 'app_kanban',
redirect_uri: 'https://kanban.example/callback',
code_verifier: sessionStorage.getItem('pkce'),
}),
}).then((r) => r.json());
// 3. Talk to the database directly as that user. No server of yours in between.
const me = await fetch('https://acme.jaydb.com/v1/n/app/docs/users/me', {
headers: { Authorization: `Bearer ${tok.access_token}` },
}).then((r) => r.json());
Prefer keys on the server side? The data plane also accepts an
X-JayDB-API-Key for your own scripts, CI, and REST integrations — so a
backend is an option you can add, never a requirement you start with.
Building a frontend application? Use our official zero-dependency Frontend SDK (@jaydb/cloud) for turnkey PKCE authentication, silent token refresh, and typed CAS document operations.
A perfect fit: apps your AI builds
And when an AI writes the app, it writes it frontend-only too
A no-backend app is exactly what an AI assistant can ship end to end. No server to scaffold, no migrations to fail, no connection pool to configure — just static files plus jaydb for sign-in and storage. Give your assistant the storage layer and it builds the whole thing.
Write-to-exist
No database creation step. No SQL migrations to generate or fail. The first PUT to a key creates the namespace and collection automatically.
fetch is the entire SDK
Three standard HTTP verbs and one header. No package to install, no version conflicts, no client wrapper to keep in sync — nothing an AI can get wrong.
Safe for parallel writes
Every write carries an ETag version check. Multiple users or background tasks updating the same document won't silently clobber each other.
# Instruction for your AI to add persistent backend storage:
You have access to persistent key-value storage at:
BASE_URL: https://<tenant>.jaydb.com/v1/n/app/docs
HEADER: X-JayDB-API-Key: $JAYDB_KEY
- Read data: GET $BASE_URL/<key> (Keep the ETag response header)
- Save data: PUT $BASE_URL/<key> with JSON body
- Safe update: PUT $BASE_URL/<key> with Header "If-Match: <etag>"
- List keys: GET $BASE_URL?list&prefix=<prefix/>
Reach for it when
- An AI agent needs to add storage to an app it is building.
- Multiple users or workers share documents safely.
- State belongs to a user, a session, or a tenant.
- Idle projects should cost $0.
- You want to self-host the same engine later.
Use something else when
- You need joins or foreign-key constraints.
- Writes must be atomic across multiple documents.
- You query by field values — there are no secondary indexes.
- You need full-text search or aggregation.
- One key takes sustained concurrent writes from many writers.
Getting started
Three steps. Under a minute.
No schema, no migration, no connection pool, no instance to size.
-
Create a namespace
One click. You get a URL and an API key, and no card is asked for at any point.
-
Write a document
One
PUTto a key of your choosing. The namespace exists the moment you write to it. -
Read it from anywhere
By key or by prefix. Every response carries an
ETag, so two writers cannot silently overwrite each other.
The data model
A tree of documents for any application
Keys are slash-separated paths, every path holds a JSON document, and branches form collections. See how real-world workloads map cleanly without schemas, migrations, or ORMs.
Autonomous multi-agent state, persistent memory, and step execution traces.
Key is the REST URL
No tables or column schemas. A document key like tenants/acme/users/101 is identical to the HTTP endpoint you fetch or PUT to.
Scoped Prefix Queries
List any branch in one call with prefix=agents/run-91/steps/. Deeply nested collections without secondary indexing overhead.
Isolated Concurrency
Every document node has its own version tag (ETag). Updating tasks/101 never blocks or locks someone updating tasks/102.
The model
One API you already know how to use
A key holds a JSON document. Read it, write it, or swap it atomically. That's the whole surface — and the pricing is just as simple.
-
Pay only for what runs
No monthly minimum. Idle projects cost zero. A typical app with 5M reads and 1M writes runs about $2.43/mo — less than half what Firestore charges for the same workload.
-
Simple pricing units
One read is one read, regardless of document size. No 4 KB tranches, no per-second capacity, no surprise multipliers when your documents grow.
-
Built-in collision safety
Every response carries a version. Send it back on write and the server rejects the request if someone else wrote first — no lock servers, no transaction coordinators.
# create — fails with 412 if the key already exists
curl -X PUT https://acme.jaydb.com/v1/n/app/docs/users/101 \
-H "X-JayDB-API-Key: $JAYDB_KEY" \
-H "If-None-Match: *" \
-d '{"name":"Alice","plan":"free"}'
# read it back — the ETag comes with it
curl https://acme.jaydb.com/v1/n/app/docs/users/101 \
-H "X-JayDB-API-Key: $JAYDB_KEY"
# update only if nobody moved it since
curl -X PUT https://acme.jaydb.com/v1/n/app/docs/users/101 \
-H "X-JayDB-API-Key: $JAYDB_KEY" \
-H 'If-Match: "3f9a1c72"' \
-d '{"name":"Alice","plan":"pro"}'
const BASE = 'https://acme.jaydb.com/v1/n/app/docs';
const headers = { 'X-JayDB-API-Key': process.env.JAYDB_KEY };
// read, and keep the version it came with
const res = await fetch(`${BASE}/users/101`, { headers });
const user = await res.json();
const etag = res.headers.get('ETag');
// write only if nothing changed underneath
const put = await fetch(`${BASE}/users/101`, {
method: 'PUT',
headers: { ...headers, 'If-Match': etag },
body: JSON.stringify({ ...user, plan: 'pro' }),
});
if (put.status === 412) {
// another writer won — re-read and try again
}
import os, requests
BASE = "https://acme.jaydb.com/v1/n/app/docs"
H = {"X-JayDB-API-Key": os.environ["JAYDB_KEY"]}
# read, and keep the version it came with
r = requests.get(f"{BASE}/users/101", headers=H)
user, etag = r.json(), r.headers["ETag"]
# write only if nothing changed underneath
user["plan"] = "pro"
w = requests.put(
f"{BASE}/users/101",
headers={**H, "If-Match": etag},
json=user,
)
if w.status_code == 412:
# another writer won — re-read and try again
...
base := "https://acme.jaydb.com/v1/n/app/docs"
key := os.Getenv("JAYDB_KEY")
// read, and keep the version it came with
get, _ := http.NewRequest("GET", base+"/users/101", nil)
get.Header.Set("X-JayDB-API-Key", key)
res, err := http.DefaultClient.Do(get)
if err != nil {
return err
}
defer res.Body.Close()
etag := res.Header.Get("ETag")
// write only if nothing changed underneath
put, _ := http.NewRequest("PUT", base+"/users/101", body)
put.Header.Set("X-JayDB-API-Key", key)
put.Header.Set("If-Match", etag)
// 412 means another writer won — re-read and try again
Multi-user state without running a backend
Two people (or two agents) editing the same document can't silently overwrite each other. The version check handles it — no WebSocket server, no realtime subscription to manage.
// Apply `mutate` to a document, retrying when someone else wins the race.
async function update(key, mutate, attempts = 5) {
for (let i = 0; i < attempts; i++) {
const res = await fetch(`${BASE}/${key}`, { headers });
const doc = res.ok ? await res.json() : {};
// If-Match on an existing doc; create-only when there is none yet.
const guard = res.ok
? { 'If-Match': res.headers.get('ETag') }
: { 'If-None-Match': '*' };
const put = await fetch(`${BASE}/${key}`, {
method: 'PUT',
headers: { ...headers, ...guard },
body: JSON.stringify(mutate(doc)),
});
if (put.ok) return put.json();
if (put.status !== 412) throw new Error(await put.text());
}
throw new Error('too much contention on ' + key);
}
// Two people adding a card to the same board cannot clobber each other.
await update('boards/42', (b) => ({ ...b, cards: [...(b.cards ?? []), card] }));
Cost
One workload. Six platforms. Your math.
20 GB stored, 5M reads, 1M writes/month, 8 KB documents. Published rates, free tiers applied, arithmetic you can redo.
Assumptions, stated so you can check them: US East list prices and every platform's free tier applied, including the ones more generous than ours. DynamoDB reads are strongly consistent, so an 8 KB document is 2 read units and 8 write units. Convex meters function calls, database I/O and storage, and all three are counted here. Supabase Pro is a floor rather than a usage figure — this workload fits inside it, and a fourth developer would make it $100 on Convex Pro. Egress is excluded for every platform including ours: at 8 KB a read this workload moves roughly 40 GB, which would add about $3.60 on Firestore and nothing here, where egress is included.
| Platform | Monthly floor | Storage /GB-mo | Reads | Writes | Egress |
|---|---|---|---|---|---|
| jaydb | $0 | $0.05 | $0.20 /M | $0.50 /M | included |
| Firestore | $0 | $0.15 | $0.30 /M | $0.90 /M | $0.12/GiB over 10 GiB |
| DynamoDB on-demand | $0 | $0.25 | $0.125 /M RRU | $0.625 /M WRU | not published |
| Supabase | $25 | $0.125 over 8 GB | no per-read charge | no per-write charge | $0.09/GB over 250 GB |
| Convex | $0, Pro $25/developer | $0.20–0.22 | I/O $0.22/GB + $2.20/M calls | same metering | $0.132/GB over 1 GB |
| Upstash Redis | $0 | $0.25 | $2.00 /M commands | $2.00 /M commands | $0.03/GB over 200 GB |
Where the others win, and why we say so: Firestore's free tier is far more generous than ours at 50k reads a day, and DynamoDB's per-unit read price is lower than ours. The difference shows up in the totals because their units are smaller than a document — and because index-entry reads, a one-read minimum per query, per-region write billing and hourly compute all land on the invoice without appearing on the rate card. Sources: Firestore, DynamoDB, Supabase, Convex, Upstash.
Console
Browse and edit your data live
Explore the key hierarchy, edit documents in place, and see exactly what you're spending. Included with every namespace — nothing to install.
Illustrations of the console, drawn in the page rather than screenshotted — the numbers are an example workload, not live data.
Pricing
Metered in the smallest unit there is
The rates are quoted per million because that is readable. The meter underneath counts single operations and megabyte-hours, so there is no threshold to cross and nothing to round up to.
Free every month, forever
1 GB stored, 100,000 reads and 10,000 writes. Not a trial and not a credit that expires — it applies to every account, every month. On the free plan usage stops at the allowance instead of billing you, so no card is needed to start.
$5
Prepays $6.50 of metered usage. A 1.3× discount, and nothing changes about how you are metered.
$10
Prepays $15 of metered usage. A 1.5× discount, for when the meter is regularly past the smaller pack.
There is no monthly floor, no per-seat charge and no tier that unlocks a feature. Packs are a discount on the same meter, so crossing one changes only the price you already paid — never what your application is allowed to do.
Architecture
Why it can be this cheap
The price is not a promotion. It follows from two decisions about where data lives and how writes agree with each other.
hot tier
Reads come from memory
Recently touched documents are served from an in-memory cache. Concurrent requests for the same key collapse into a single backend fetch — a hundred readers cost one storage read, not a hundred.
That's why cost stays flat as traffic grows: the expensive layer sees a fraction of your actual request volume.
cold tier
Durable storage without always-on servers
Documents live in object storage — that's where both the durability and the low price come from. No cluster to keep running, no replica on standby, nothing that bills you while your project sleeps.
A conventional database keeps machines running for indexes and transaction state. That fixed cost is the floor you pay whether or not anyone uses your app.
The trade-off, stated honestly
JayDB uses optimistic concurrency instead of locks. When you read a document you get its version; when you write, you submit against that version. If someone else wrote first, you get a rejection and retry.
A traditional database keeps a lock manager and transaction coordinator running at all times, provisioned for peak contention. That infrastructure runs whether or not contention ever happens — and you pay for it.
Most app workloads are low-contention. Two users rarely write the same document in the same millisecond. When they do, one retry settles it. JayDB charges for the case you actually have, not the worst case you might.
The limit: under sustained concurrent writes to a single hot key, retries pile up and you want a database that takes locks. This page says so upfront rather than burying it.
Frequently asked questions
Everything you need to know
Clear answers on pricing, concurrency, self-hosting, and architecture.
Is it safe for a static site to talk to the database directly?
Yes — because no shared secret ships to the browser. Your app is a public PKCE client, so instead of embedding an API key, each end-user signs in through your org's OIDC issuer and the browser receives a bearer token scoped to that user. There is nothing confidential in your JavaScript to steal, and one user's token can't act as another. PKCE (proof key) replaces the client secret entirely.
How is this different from “serverless” or a backend-as-a-service?
“Serverless” functions still boot a server per request that you write, deploy, and
debug. A BaaS SDK still runs code you configure and version. jaydb removes the layer
itself: the browser calls a standard HTTP data plane directly, authenticated by an
OIDC token from a per-tenant issuer you didn't build. The only always-on system is
jaydb — and you don't operate that either. If you want a server, you can add one
with an X-JayDB-API-Key; you just never have to start with one.
Which identity providers can my users sign in with?
Google, GitHub, Microsoft, any generic OIDC provider, or a custom OAuth2 provider.
Org admins connect their upstream providers and register browser apps (client ID,
redirect URIs, allowed origins) from the console. Every organization gets its own
issuer at https://<org>.jaydb.com, with discovery metadata and a JWKS
published under it.
Will I be charged unexpectedly on the free plan?
Never. The free plan includes 1 GB of storage, 100,000 reads, and 10,000 writes every month with a hard stop. When you reach the limit, requests return a rate-limit status instead of charging a credit card. No credit card is asked for when signing up.
How does JayDB prevent race conditions and write collisions?
JayDB uses standard HTTP ETag headers for Compare-And-Swap (CAS) optimistic locking.
Every read returns the document's current version tag. When updating, your app passes If-Match: <etag>.
If another user or agent updated the document in the meantime, JayDB rejects the write with 412 Precondition Failed, allowing your client to re-read and retry safely without lock managers.
Can I self-host JayDB on my own infrastructure?
Yes. The core JayDB engine is 100% open source under the MIT license on GitHub. You can embed the Go package directly into your application or run the standalone server binary backed by AWS S3, Cloudflare R2, MinIO, or local disk storage.
Why is JayDB so much cheaper than Firestore or Supabase?
Traditional databases keep always-on compute instances, replication clusters, and lock managers running 24/7, creating a high monthly cost floor. JayDB stores durable cold data in ultra-low-cost object storage (S3) at $0.05/GB-mo and serves hot traffic from an in-memory singleflight cache. When your app is idle, you pay $0.
What languages and frameworks work with JayDB?
Because JayDB is accessed over standard REST HTTP, it works in any language or runtime without installing an SDK: JavaScript, TypeScript, Python, Go, Rust, Ruby, PHP, Next.js, Node.js, Cloudflare Workers, Deno, Bun, and cURL.