Baselyra Docs

Baselyra documentation

A self-hosted backend-as-a-service: Postgres with row level security, an auto-generated REST API over your tables, authentication, file storage, realtime and an admin Studio — running as one Node process next to one Postgres container, in a few hundred megabytes of RAM.

The path

Six pages, in this order. It takes about an hour end to end, and step 2 is the one that stops a public table being world-writable.

  1. Install and make the first query

    Clone, run ./scripts/setup.sh, docker compose up -d. Two containers start, migrations apply, and the Studio is on http://127.0.0.1:3130. Then create a table, mint the keys and read a row.

  2. Turn row level security on

    Authorisation is Postgres', not JavaScript's. Every request runs as anon, authenticated or service_role with the verified JWT claims published to the session, and policies decide the rest. A table in public with RLS off is readable and writable by anyone holding the anon key — which is a public key that ships in your frontend.

  3. Learn the REST surface

    Filters, ordering, pagination, one level of embedded resources, upserts and RPC over every table, view and function in public. A PostgREST-compatible subset, so existing client patterns transfer.

  4. Add users

    Email and password, magic links, one-time codes, recovery and email change, plus OAuth sign-in and phone codes over SMS. Refresh tokens rotate and reuse is detected. Read the part about Studio accounts and application users being two unrelated populations.

  5. Store files

    Buckets on local disk with MIME allowlists, per-bucket size limits, signed URLs and Range requests. Policies on storage.objects guard files exactly as they guard a table.

  6. Put it behind a proxy and back it up

    nginx or Apache, TLS, SMTP, pg_dump plus the storage volume, and the security checklist to run before you point a domain at it.

How the pieces fit

One process serves every prefix. It holds two Postgres connection pools: one to the project database your application lives in, and one to the control database that holds Studio accounts and the audit log. Postgres has no cross-database queries without FDW, so a project connection — even one holding the service key, which bypasses RLS entirely — cannot read a Studio password hash.

Your app bl.from('posts') .select('*') .eq('published', true) bl.channel('public:posts') bl.storage.from('avatars') anon key · public by design HTTPS + JWT Baselyra one Node 22 process /auth/v1 /rest/v1 /storage/v1 /realtime/v1 /admin/v1 /ai/v1 / Studio SET LOCAL role public.* auth.* storage.* Postgres 17 RLS decides every row storage volume files on disk
The whole deployment. Two containers. Every user-facing query runs inside a transaction that sets the request role and publishes the verified JWT claims as request.jwt.claims, so the same policies apply through REST, storage and realtime alike.

The Studio

An admin console ships inside the server and is served at /: a data grid with inline editing, a SQL editor with schema-aware autocompletion, user management, a file browser, a live realtime inspector, a per-table API reference, an email template editor and the audit log. It signs in against control.platform_users, which lives in a different database from your data.

127.0.0.1:3130/database Baselyra baselyra_app Overview Database SQL Auth Storage Realtime API AI Email Settings healthy Database / public.posts Insert row id title published created_at 8f13c2a1…4eShipping v0.9true2026-02-11 c07be9d4…81RLS in five policiestrue2026-02-09 2b5a70ff…13Draft: storage notesfalse2026-02-08 91de44c8…a7Import from Firebasetrue2026-01-30 5cc1802e…6fWhy two databasestrue2026-01-27 a4f9013b…2cRealtime re-readsfalse2026-01-22 Policies on public.posts RLS enabled posts_select_published posts_write_own SELECT · anon ALL · authenticated
Studio, Database page. 240px sidebar, 48px top bar, 32px rows, monospace for every identifier and value. The SQL editor runs against the project database only, so select * from control.platform_users fails with relation does not exist — which is the correct answer.

Read by topic

Core

Clients and operations

In one request

The anon key is public and meant to ship in your frontend. It says only which Postgres role the request runs as; the policies decide what that role may read and write.

import { createClient } from '@baselyra/client';

const bl = createClient('https://api.example.com', ANON_KEY);

const { data, error } = await bl
  .from('posts')
  .select('id, title, author:users(name)')
  .eq('published', true)
  .order('created_at', { ascending: false })
  .limit(20);
curl 'https://api.example.com/rest/v1/posts?select=id,title&published=eq.true' \
  -H "apikey: $ANON_KEY"
alter table public.posts enable row level security;

create policy posts_select_published on public.posts
  for select to anon, authenticated
  using (published);

What Baselyra does not do

Stated here rather than discovered on day two. In progress means work has started and nothing is shippable yet — plan as though it does not exist. The full list is on What is not built.

Not availableWhat you have insteadStatus
Edge functionsPostgres functions over /rest/v1/rpc/:fnIn progress
S3 or object storageFiles on the local disk of the host running the appIn progress
Image transformationFiles come back exactly as uploadedIn progress
A project switchercontrol.projects holds one row and every lookup resolves through it, but nothing creates a secondIn progress
A Studio page for webhooks and jobsBoth are configured over /admin/v1/hooks; the console has no screen for them yetIn progress
Apple sign-inSeven other OAuth providers, or email and phoneNot built
Replication and failoverOne Postgres; pg_dump plus the storage volumeNot built

Questions

Is Baselyra a Supabase fork?

No. It is a separate implementation that reuses two of Supabase's public interfaces so client code transfers: a PostgREST-compatible subset for the REST API, and row level security as the authorisation model. The server is one Fastify process with eight runtime dependencies — fastify, five Fastify plugins, pg and nodemailer. No ORM, no query builder, no Redis, no message broker, no sidecar.

Does Baselyra support Google or GitHub sign-in?

Yes. OAuth ships for Google, GitHub, LinkedIn, Facebook, Instagram, TikTok and Envato: you add a client id and secret, and the provider writes a row in auth.identities against the same auth.users record an email sign-up would have created. Phone one-time codes go out over Twilio, Vonage, MessageBird, Amazon SNS, Plivo or a webhook you host. There is no Apple sign-in.

How much memory does Baselyra need?

Roughly 400–600 MB idle across both containers. 1 GB of RAM is enough to start; 2 GB is comfortable. Sizing is on Self-hosting.

Can I move an existing project onto Baselyra?

Tables, rows and users import from Supabase, plain Postgres, Appwrite, Firebase or a pg_dump file. Password hashes survive from Supabase, Postgres and bcrypt SQL dumps, so those users never have to reset. Appwrite and Firebase use hash schemes Baselyra cannot verify, so those users need a password reset — Importing says exactly which, and what to send them.

For language models

The whole documentation is published as plain text so a model can ingest it in one fetch, following the llms.txt convention.

  • /llms.txt — every page with a one-line summary.
  • /llms-full.txt — the entire documentation as one plain-text file.

Every page is a real file with no client-side routing, so any crawler reading HTML gets the same content a browser does.

Edit this page Report a problem

Esc
navigate open Esc close