Getting Started

Introduction

Palbase is a backend platform in two halves. Every project has a set of modules — auth, storage, realtime, flags, notify, secrets and analytics — that an app uses with no backend of its own, the way it would use Firebase: sign users in, keep their files, push them messages, read flags, count events. And when a project needs code of its own, it runs on machines built from a GitHub branch on every push, beside Postgres databases of its own — with a TypeScript framework, palbase/backend, that is made for exactly that.

You can use either half without the other. Most projects start with the modules from an app, and add a backend when a rule has to live somewhere an app cannot change it.

A taste

An app signs a user up with the project's publishable key:

// app/signup.ts
import { createClient } from "palbase/web";

const palbase = createClient({ url: "https://<ref>.palbase.studio", publishableKey: "pb_<ref>_c…" });

const { user } = await palbase.auth.signUp({ email: "ada@example.com", password: "a passphrase of fifteen+" });
console.log(user.id);

A server — any server — sends that user an email with the secret key:

// server/welcome.ts
import { createServer } from "palbase/server";

const palbase = createServer({ url: process.env.PALBASE_URL!, secretKey: process.env.PALBASE_SECRET_KEY! });

await palbase.notify.sendEmail({ body: { to: "ada@example.com", subject: "Welcome", text: "Glad you are here." } });

And a backend of your own is a class whose methods are routes, served on a machine:

// src/modules/notes/notes.controller.ts
import { Body, Controller, Database, Post, User, z, type UserT } from "palbase/backend";

const CreateNote = z.object({ title: z.string().min(1).max(200) });
type CreateNote = z.infer<typeof CreateNote>;

@Controller("/notes")
export class NotesController {
  // Every route needs a signed-in user unless it says otherwise; @User() is them.
  @Post("")
  async create(@Body(CreateNote) body: CreateNote, @User() user: UserT): Promise<{ id: string }> {
    const [row] = await Database.$query<{ id: string }>("insert into notes (owner, title) values ($1, $2) returning id", [user.id, body.title]);
    return row;
  }
}

Push it to its branch, and the machine builds it and rolls it out beside the version that serves. The Quickstart does all three, end to end.

What every project has

ModuleWhat it doesFrom an app (publishable key)From a server (secret key)
authUsers, sessions, passwords, passkeys, second factors, magic links, phone codes, social sign-in, test usersWeb AuthAuthentication, Test Users
storageBuckets and files, public reads, signed URLs, image renditionsUploads — through a URL a server signedStorage, Direct Uploads
realtimeChannels over one socket: broadcasts, shared state, presence; a backend's gatewaysWeb Realtime, Web GatewaysRealtime, Gateways, Channels
flagsFeature flags and remote config, per-user valuesWeb FlagsFlags
notifyEmail, push, SMS and WhatsApp; templates, topics, preferencesa user's devices, preferences and topicsNotifications
secretsThe project's secrets, versioned, in two sets—Secrets
analyticsEvents, identifies, page views; summaries, funnels and retentionWeb AnalyticsAnalytics, Reading Analytics
aiThe platform's models: answers, tools, agents, and chats with the members a backend namesThrough a backend's gateway: a chat's members join it (Web Gateways)AI

Each module answers at the project's address, under a path of its own (/auth/…, /storage/…, /flags/…), and each has a contract — the OpenAPI document the SDK's clients are generated from. The SDKs are a convenience over those paths, not a requirement: anything that speaks HTTPS can call them.

And for code of your own:

MachinesYour program, built from a repository's branch and directory on every push; sizes that sleep and sizes that never do; logs, builds and rollback — Machines
DatabasesPostgres 18 with pgvector and PostGIS, backups and point-in-time restore, a read-only SQL console in Studio — Databases
palbase/backendControllers, services and modules in TypeScript, secure by default, with the project's modules and its database built in — Backend SDK

The surfaces

SurfaceWhat it isDocs
palbase/webThe client for web and JavaScript apps: createClient with the publishable key — auth, the session, the realtime socketWeb SDK
palbase/serverThe client for servers: createServer with a secret key — every module's server calls, and the receiver for the project's webhooksServer SDK
palbase/backendThe framework a machine runs: PalbaseFactory, @Module, @Controller, and Palbase, Database and LogBackend SDK
palgresThe database half of the framework: the schema in TypeScript, typed queries, and migrations that run beside the version that servesSchema, Schema changes
StudioOrganizations, projects and environments; every module's screens; machines, databases, keys and secretsOrganizations

Palbase has SDKs in TypeScript (palbase/web, palbase/server, palbase/backend), Swift and Kotlin. An iOS app takes the Swift SDK, palbase-swift with Swift Package Manager (iOS SDK), and an Android app the Kotlin one, studio.palbase:palbase and an artifact per module (Android SDK). An app on another platform, such as a game engine, calls the modules over HTTPS with the publishable key in the apikey header and the user's access token as Authorization: Bearer …, the same requests the SDKs make.

The TypeScript SDKs are one npm package, palbase, whose parts are its subpaths — the CLI comes with it (npx palbase) — and palgres, which palbase/backend brings: npm install palbase (or bun add palbase). They ship compiled, under the Palbase SDK License (Project Structure).

Organizations, projects and environments

An organization is a team, a project is an app in it, and an environment is the thing that runs: each one a project of the platform's own, with nothing shared with another environment — its own address, its own keys, its own users and files, its own machines and databases. A staging copy of production is a second environment, with its machines built from another branch. Everyone in the organization reaches its projects at their role, owner, admin or member: members, invitations, roles and the audit log are in Organizations.

A project's first environment has the slug main. Every environment answers at one address, named by its ref:

https://<ref>.palbase.studio

The ref is on the environment's Settings, and it never changes. A machine of the environment answers under the app domain — https://<ref>.app.palbase.studio for the one named backend — and a database at <name>-<ref>.db.palbase.studio.

The keys

An environment has publishable keys — each registered app's own, the one the environment was made with, and any you make — and any number of secret keys, on Settings › API keys:

KeyLooks likeRoleWhere it goes
Publishablepb_<ref>_c…anonIn apps: a browser bundle, a mobile app. What it reaches is what an app may do, and the signed-in user's token decides whose data.
Secretpb_<ref>_s…service_roleOn servers only: a machine's secrets, a server's environment. It opens the whole project — every user, every file, every secret.

The ref is in the key, so a key names its project. A secret key is made with a name (Create secret key), shown once, and revoked on its own (Revoke) — give each server its own, and a leaked one is replaced without touching the others. createClient refuses a secret key, and createServer refuses to run in a browser.

Every request to the project carries one of them in the apikey header; a request with none is 401 missing_api_key, and a key of another project is the same as none (401 invalid_api_key). A user's access token, when one is sent, must be one this project issued (401 invalid_token otherwise). Past the project's requests-per-second limit, a request is 429 rate_limited with Retry-After: 1; past its requests for the period, under a quota that refuses, 429 quota_exceeded with Retry-After until the next period.

Note: The project's address answers a browser on any site. A browser app calls it directly, with the publishable key and the signed-in user's token, as Web SDK explains. Your own machine's backend answers cross-origin requests itself, for the origins it names.

How code reaches a machine

One way: a push to the machine's branch.

  1. Connect GitHub — install Palbase Cloud on the account that owns the repository, and connect it to the environment.
  2. Open a machine on a repository, a branch and a directory of it (Cloud › Machines › New machine).
  3. Push. A push that changes the machine's directory is built — its Dockerfile, or what Railpack detects — and rolled out beside the version that serves; it takes traffic once it answers its health check.

There is no deploy command and no tool to install. A release whose machine migrates a database runs the migration itself, beside the version that serves; a release that does not come up never takes a request; and any build that succeeded can be returned to (Builds, Logs & Rollback).