jstash vs Supabase: when to use each
Both let a web page save each signed-in person’s data without a server of your own. Supabase is a full backend. jstash Apps does one small thing. Here’s the same task done in each, what each one costs you, and when to use which.
Written by jstash, which makes one of the options compared here. Updated 4 October 2026.
Summary
- Queries, shared data, live updates, files or server code: use Supabase. jstash doesn’t do any of these.
- Each person’s own settings, progress or drafts, in a small app, with no policies to write: jstash Apps. Free covers 1 app and 100 users.
- Already signing in with Supabase: keep Supabase Auth and save each person’s JSON in jstash, or add one table with row level security to the project you have. Both work.
- Free plans: Supabase’s is far larger, but pauses projects after a quiet week. jstash’s is small, with hard caps, and quiet apps aren’t paused.
What each one is
Supabase gives each project a full Postgres database, with sign-in, file storage, live updates and server functions around it. Your page reads and writes the database directly, and row level security (RLS) policies decide which rows each person can reach.
jstash Apps keeps private JSON documents for each signed-in person of your app. Your page sends the login token it already has, from Supabase Auth, Clerk, Firebase or Google. jstash checks it, and the request reaches only that person’s documents. Save, load, list and delete, by name. That’s all it does.
The same task, both ways
The task: save one signed-in person’s settings, and load them again on any device they sign in on. Both versions use Supabase Auth for sign-in.
In Supabase
Run this in the SQL Editor. It makes a table with one row per person, turns on RLS, lets only signed-in people reach the table, and adds a policy for each thing they may do. It follows Supabase’s RLS guide.
create table user_settings ( user_id uuid primary key references auth.users on delete cascade, settings jsonb not null ); alter table user_settings enable row level security; revoke all on table user_settings from anon, authenticated; grant select, insert, update on table user_settings to authenticated; create policy "Read own settings" on user_settings for select to authenticated using ((select auth.uid()) = user_id); create policy "Add own settings" on user_settings for insert to authenticated with check ((select auth.uid()) = user_id); create policy "Change own settings" on user_settings for update to authenticated using ((select auth.uid()) = user_id) with check ((select auth.uid()) = user_id);
Then, in the page, with supabase-js:
// supabase is the client your app already has, from createClient()
async function userId() {
const { data } = await supabase.auth.getSession();
if (!data.session) throw new Error('Not signed in');
return data.session.user.id;
}
export async function save(settings) {
const { error } = await supabase
.from('user_settings')
.upsert({ user_id: await userId(), settings });
if (error) throw error;
}
export async function load() {
const { data, error } = await supabase
.from('user_settings')
.select('settings')
.eq('user_id', await userId())
.maybeSingle();
if (error) throw error;
return data?.settings ?? null; // null: nothing saved yet
}
- RLS has to be turned on. Tables made in Supabase’s dashboard have it on by default. Tables made in SQL, like this one, or “through another tool” need the
enable row level securityline. - Grants matter too. In Supabase’s words: “Grants decide whether a role can run an operation on the table at all. Policies decide which rows that operation applies to. Set both for every table you expose.”
- Test the policies. Supabase suggests a test file for each table that checks what each role can and can’t do, run with
supabase test db: “Until the suite passes, you don’t know whether the policies do what you intended.” - Supabase helps you check. Its Security Advisor flags tables in the public schema that don’t have RLS turned on.
Each piece is short and well documented. The catch is that a missing piece doesn’t look like a bug: the app still works. On 1 October 2026, UpGuard reported 16,326 Supabase databases with tables anyone could read, from about 300,000 websites it checked. It notes that tables created through the API, which is how coding agents create them, don’t have RLS turned on by default. The tools aren’t the problem. The rules are one more thing to get right.
In jstash Apps
- Sign in to the jstash dashboard with GitHub and open Apps → New app.
- Choose Supabase and paste your Project URL, from the Connect button or Project Settings → Data API.
- Add every website address your app runs on, like
https://my-app.netlify.app(up to 5). Turn on Allow localhost while you develop. - Copy the starter code from the app’s page. It has your app’s address filled in.
const JSTASH = 'https://api.jstash.app/v1/apps/app_…/me';
// supabase is the client your app already has
async function token() {
const { data } = await supabase.auth.getSession();
if (!data.session) throw new Error('Not signed in');
return data.session.access_token; // Supabase refreshes it for you
}
export async function save(name, value) {
const res = await fetch(`${JSTASH}/${name}`, {
method: 'PUT',
headers: { Authorization: `Bearer ${await token()}` },
body: JSON.stringify(value),
});
if (!res.ok) throw await failure(res);
}
export async function load(name) {
const res = await fetch(`${JSTASH}/${name}`, {
headers: { Authorization: `Bearer ${await token()}` },
});
if (res.ok) return res.json();
const err = await failure(res);
if (err.code === 'DOC_NOT_FOUND') return null; // nothing saved yet
throw err;
}
// jstash errors are JSON: {"error": {"code", "message"}}
async function failure(res) {
const { error } = await res.json()
.catch(() => ({ error: { message: `jstash returned ${res.status}` } }));
return Object.assign(new Error(error.message), { code: error.code });
}
Call load('settings') once someone is signed in, and save('settings', value) when it changes. There’s no table, policy or grant. The one rule is built in, and you can’t turn it off: each request reaches only the signed-in person’s documents.
The trade-off is everything else. You can’t query these documents, share them between people, or look at them yourself. What jstash doesn’t do has the full list.
Side by side
| Topic | Supabase | jstash Apps |
|---|---|---|
| Data model | Postgres tables, with relationships between them. A jsonb column holds JSON. | JSON documents by name, like settings. Up to 200 per person. |
| Queries and joins | Yes. Filter, sort, join and search, from the page or in SQL. | No. Load a document by name, or list one person’s document names. |
| Sharing between users | Yes, as your policies allow. | No. Each document belongs to one person. |
| Realtime | Yes: database changes, broadcast and presence. | No. Load the latest copy when you need it. |
| Security setup | Turn on RLS, set grants, and write a policy for each operation. | One built-in rule: each person reaches only their own documents. You list your website addresses. |
| Sign-in | Supabase Auth, or Clerk, Firebase Auth, Auth0, Cognito or WorkOS. | None of its own. Uses your Supabase Auth, Clerk, Firebase or Google login. |
| Seeing your users’ data | Yes, in the Table Editor, the SQL Editor or your own queries. | No. You see counts, never document contents. |
| Free plan | 2 active projects, a 500 MB database, 50,000 monthly active users and 1 GB of files. | 1 app, up to 100 people, 200 documents of up to 100 KB each per person, and 10 MB of storage shared with your bins. Daily caps. |
| Pausing | Free projects with low activity for a week are paused. You can restore them for up to a year. | Quiet apps aren’t paused. Documents are kept until they’re deleted. |
| Past free | Pro from US$25 a month, with US$10 of compute credits. A spend cap is on by default, but doesn’t cover compute. | Pro: 3 apps, 1,000 people, 1 MB documents, US$5 a month at launch. Invite-only for now. Limits are hard caps; nothing is billed. |
| Files | Yes: Storage, with access policies. | No. JSON only. |
| Server code | Yes: Edge Functions, plus database functions and triggers. | No. jstash doesn’t run your code, and has no webhooks. |
| Backups | Daily, kept 7 days, on Pro. On Free, Supabase suggests exporting your data regularly. | None. A replaced or deleted document can’t be recovered. |
| Lock-in and export | It’s Postgres, so you can dump it with the Supabase CLI. Supabase can also be self-hosted. | No export. Your app moves each person’s documents while they’re signed in. At least 30 days’ notice if jstash shuts down (Terms). |
Supabase facts were checked on 4 October 2026, on Supabase’s own pages. They change, so check its pricing page before you choose. jstash’s limits are all in the Apps guide.
Use Supabase when…
- People share data: a team’s task list, comments, a group chat, a leaderboard.
- You need queries: search, filters, sorting, totals, or records that relate to each other.
- Changes should appear live on other people’s screens, or the same person’s other devices.
- You need to see your users’ data: to help someone, fix a record, run a report or export it.
- You store files: avatars, photos, uploads.
- Some logic can’t run in the browser: payments, webhooks, scheduled jobs.
- You’ll grow: more than 1,000 people saving data, or more than fits in 200 documents a person. Supabase’s free plan alone covers 50,000 monthly active users.
- You want backups, or the option to run it yourself one day.
- You already use Supabase and write policies with confidence. One more table in the project you have is simpler than adding a second service.
Use jstash when…
- Each person’s data is theirs alone: settings, progress, drafts, saved items, game state.
- Your app is a static site or a frontend, and you’d rather not write, test and maintain policies and grants, or run a server.
- It’s small: up to 100 people saving on Free, 1,000 on Pro, each with up to 200 documents.
- You sign in with Clerk, Firebase or Google and don’t want to add a database just for this.
- You want hard caps rather than a meter, and a quiet side project that isn’t paused.
- You want the whole API to fit on one page: save, load, list and delete, with plain
fetch.
Using both
jstash Apps works with Supabase Auth. You can keep Supabase for sign-in and keep each person’s JSON in jstash instead of a table with policies. The jstash code above already does this: token() is the only part that touches Supabase.
- What jstash needs: your Project URL, pasted when you create the app. No Supabase key.
- Signing keys: your project must sign logins with JWT signing keys, using an RSA or elliptic curve key, not the legacy JWT secret. Check Project Settings → JWT Keys in Supabase. Supabase marks the legacy secret “No longer recommended”, and says migrating “does not cause downtime for your application”. jstash’s dashboard tells you if your project still uses the legacy secret.
- The token:
data.session.access_tokenfromsupabase.auth.getSession().data.sessionisnullwhen nobody is signed in. - Anonymous sign-ins from Supabase are refused unless the app turns on Allow anonymous users, which needs Pro.
- Tokens pass through jstash. The token your page sends also works with your Supabase project until it expires, usually within an hour. jstash uses it only to check who’s signed in, and doesn’t store or log it.
- Sign-in still runs on Supabase, so your Supabase plan still applies, including pausing free projects after a week of inactivity.
- Mixing is fine. Shared data can live in Supabase tables with RLS while each person’s settings live in jstash. But if you already write policies for other tables, adding one more is usually simpler.
The full setup is in the Supabase section of the Apps guide.
What jstash doesn’t do
jstash Apps is small on purpose. It isn’t trying to become Supabase.
- No sharing: each document belongs to one person. No teams, roles or collaboration.
- No queries: no search, filters, sorting, indexes or schemas. A save replaces the whole document; there are no partial updates.
- No live sync: changes aren’t pushed to other devices.
- No view of your users’ data: you can’t read, export or edit their documents. You see counts.
- No backups or version history. Keep your own copy of anything that matters.
- No uptime guarantee or service level agreement.
- No files, server code or webhooks, and no SDK: it’s plain
fetch. - Not open source, and it can’t be self-hosted.
- Four logins only: Supabase Auth, Clerk, Firebase Auth and Sign In With Google. GitHub sign-in works only through the first three. No anonymous users on Free.
- Hard caps on every plan: Free has 1 app, 100 people and 10 MB; Pro has 3 apps, 1,000 people and 1 GB. Each app accepts up to 5 exact website addresses, with no wildcards.
- No sensitive data, such as health, genetic or biometric data (Terms).
Which to pick
- Shared data, queries, live updates, files, server code, an admin view, or more than 1,000 people saving: Supabase.
- Each person’s own JSON in a small app, saved from the browser with no policies, grants or server: jstash Apps.
- Signing in with Supabase already: if you’re comfortable with RLS, add the table. If you’d rather not have rules to get right, keep Supabase Auth and use jstash.
- Not sure yet: if your app might soon need anything in the first line, start with Supabase. Moving from jstash later means your app copies each person’s documents while they’re signed in.