# Railbase — full documentation > Railbase is a self-hosted backend platform that ships as a single binary: authentication, an embedded single-file NoSQL document store (Vault), REST auto-CRUD APIs, realtime subscriptions, multi-tenancy, RBAC, file storage, scheduled jobs, i18n and an admin console — with no external database to operate. Commercial business plugins (helpdesk, inventory, accounting, CMS, document translation and more) install at runtime from a built-in marketplace as signed, licensed artifacts. The core is free to download and run on Linux, macOS and Windows; plugins are per-seat licenses. # Introduction _Section: Getting Started · Updated: 2026-06-07 · Source: https://railbase.app/learn/introduction.md_ > What Railbase is, how plugins are sold, and where railbase.app fits in. **Railbase is a single Go binary you self-host.** It gives you a production-grade backend — auth, a typed data layer, REST + realtime APIs, jobs, an admin console — out of one executable with no external database to operate. The core is free to download and run. The capabilities that turn it into a *product* — accounting, inventory, procurement, helpdesk, and so on — are **plugins**: paid subscriptions you buy and install at runtime from a marketplace built into the admin. There is no rebuild and no third-party code to vet; the core pulls a signed, licensed artifact and runs it. ![The railbase.app home page](/docs/ys-home.png "railbase.app — download the free Railbase core and browse the plugin marketplace.") ## The three pieces | Piece | What it is | Who runs it | |---|---|---| | **Railbase core** | The free single binary you download and host | You | | **Plugins** | Paid, signed add-ons installed at runtime | You (bought via the marketplace) | | **railbase.app** | The vendor's licensing, distribution & billing server | The vendor (this site) | You run the core. **railbase.app is the source of truth** for the catalog, prices, your licenses, and payments — your instance syncs with it to discover plugins, complete purchases, fetch signed artifacts, and keep licenses fresh. You never download a plugin file by hand, and your server never stores card data. ![The Railbase plugin storefront on railbase.app](/docs/ys-plugins.png "The storefront catalog: each plugin is a paid, per-seat subscription your instance installs at runtime.") > [!NOTE] > railbase.app is a secondary resource for day-to-day work. **Everything you can do on > this site — browse, buy, install, manage billing — you can also do from inside > your own Railbase admin.** See [Installing plugins](installing-plugins). ## What you'll find in these docs - **[Quickstart](quickstart)** — download, run, and install your first plugin in a few minutes. - **Core Concepts** — the [architecture](architecture), [how plugins work](how-plugins-work), and [licensing & seats](licensing-and-seats). - **Guides** — day-to-day tasks like [installing plugins](installing-plugins) and [managing billing](managing-billing). - **Self-Hosting** — [deploying](deployment), [backups](backups-and-restore), and [security](security) for production. - **Reference** — the [CLI](cli) and [environment variables](environment-variables). ## What Railbase is *not* > [!IMPORTANT] > In the **hosted distribution** model, third-party plugins are acquired only > through the built-in marketplace as signed, verified artifacts — there is no > "sideload an untrusted binary from disk" path. (First-party plugins can also be > registered into a self-hosted build at compile time; that's a build you control, > not a downloaded binary.) See [How plugins work](how-plugins-work) and > [Installing plugins](installing-plugins). --- # Quickstart _Section: Getting Started · Updated: 2026-06-10 · Source: https://railbase.app/learn/quickstart.md_ > Download, run, create an admin, and install your first plugin. This guide takes you from nothing to a running Railbase with a paid plugin installed. It takes a few minutes and assumes a local machine; production hardening is covered in [Deployment](deployment). ## 1. Download the binary Grab the build for your platform from the [download page](../download). It's a single executable — there is nothing else to install. On macOS / Linux, make it runnable: ```bash chmod +x ./railbase ``` ## 2. Create the first admin Create your superuser before starting the server. The email is a positional argument; you'll be prompted for a password (twice). The command creates the data store on first use, and the vault won't unlock without a password — locally, set **`RAILBASE_DEV=true`** to use the built-in development key: ```bash RAILBASE_DEV=true ./railbase admin create you@example.com ``` > [!IMPORTANT] > The vault is a single file opened by **one process at a time**. Commands that > open it (`admin`, `backup`, `jobs`, …) can't run while `serve` holds the lock — > they exit with *"vault: file is locked by another process"*. Create the admin > first (as here), or stop the server for a moment. On a running instance you can > also create the first admin from the **`/_/bootstrap`** setup wizard instead. ## 3. Run it ```bash RAILBASE_DEV=true ./railbase serve ``` With no other flags, Railbase: - listens on **`http://localhost:8095`** - keeps its data store at **`./pb_data/railbase.vault`** (a single encrypted file) - serves the admin console at **`http://localhost:8095/_/`** > [!IMPORTANT] > In production you set a real `RAILBASE_VAULT_PASSWORD` instead of > `RAILBASE_DEV=true`. A bare `serve` with neither exits with *"no vault > password configured"*. See [Installation](installation#development-vs-production). > [!TIP] > Use a different port or data directory with flags: `RAILBASE_DEV=true > ./railbase serve --addr :9000 --data-dir ./demo`. See the [CLI reference](cli). Now open **`http://localhost:8095/_/`** and sign in. (Two-factor authentication is self-service — enroll it under your account settings once you're in.) ```walkthrough target: admin fresh: true title: Sign in to the admin console steps: - say: Open the admin console. On a fresh instance you land on the sign-in screen. do: navigate value: /_/login expect: { text: Railbase admin } - say: Enter the admin email you just created. do: fill on: { label: Email } value: admin@example.com - say: And the password. do: fill on: { label: Password } value: AdminP@ss123 - say: Sign in. do: click on: { role: button, name: Sign in } expect: { text: Collections } - say: And you're in. This is your home base — collections, logs, and settings, all one click away. do: navigate value: /_/ expect: { text: Audit events } - say: Your collections, audit events, and live request rates, all at a glance. do: hover on: { text: Collections, first: true } minDwellSec: 2 ``` ## 4. Open the marketplace The in-app marketplace is **built in and on by default** — there's nothing to enable. Open **Marketplace** in your admin (`/_/`) and you're browsing the live catalogue from railbase.app. > [!NOTE] > The marketplace always pulls from railbase.app — the catalogue, prices, and > checkout are served there. To add a plugin to a build you compile yourself > instead, see [Installing plugins](installing-plugins). ## 5. Buy or trial, and install a plugin Open the Marketplace and pick a plugin. The flow is entirely in-product: 1. **Browse** — the catalogue, prices, and per-seat terms are synced live from railbase.app. 2. **Buy or try free** — checkout is embedded right in the page; enter your card in the secure payment form (served by railbase.app; **card data never touches your server**). Where a plugin offers a trial, **Try free** starts a time-boxed trial with no card. 3. **Install** — your instance pulls the plugin, **verifies it's authentic and unmodified**, and starts it as a managed process. No download, no file to copy, no redeploy. The plugin's tables, routes, and jobs are live the moment it registers. > [!NOTE] > Pricing is **per seat**, where a seat is a user holding a *billable* role. > Viewers and self-service users are free. See [Licensing & seats](licensing-and-seats). ## Next steps - Understand the moving parts in [Architecture](architecture). - Learn the full install/update/uninstall flow in [Installing plugins](installing-plugins). - Take it to production with [Deployment](deployment) and [Backups](backups-and-restore). --- # Installation _Section: Getting Started · Updated: 2026-06-10 · Source: https://railbase.app/learn/installation.md_ > Platforms, the binary, flags, and the on-disk layout. Railbase ships as a single statically-linked executable. There is no installer, no runtime to provision, and no database server to stand up. ## Get the binary Download the build for your OS/architecture from the [download page](../download). Builds are produced for the common Linux, macOS, and Windows targets. ![The Railbase download page](/docs/ys-download.png "One signed binary per platform — no installer and no database to provision.") ```bash # macOS / Linux chmod +x ./railbase ./railbase version # prints version, build info, installed plugins ``` On Windows, run `railbase.exe serve` from a terminal. ## Running the server ```bash ./railbase serve [flags] ``` The flags you'll use most (each also has an environment-variable equivalent; precedence is **flag > env > default**): | Flag | Env | Default | Purpose | |---|---|---|---| | `--addr` | `RAILBASE_HTTP_ADDR` | `:8095` | HTTP listen address | | `--data-dir` | `RAILBASE_DATA_DIR` | `./pb_data` | Data directory | | `--vault-path` | `RAILBASE_VAULT_PATH` | `/railbase.vault` | Data file location | | `--vault-password` | `RAILBASE_VAULT_PASSWORD` | — | Unlock password (**required in production**) | | `--log-level` | `RAILBASE_LOG_LEVEL` | `info` | `debug` · `info` · `warn` · `error` | See the full list in [Environment variables](environment-variables) and the [CLI reference](cli). ## On-disk layout A fresh `serve` creates a `pb_data/` directory: ```text pb_data/ ├── railbase.vault # your data — a single encrypted, MVCC document store ├── railbase.vault.lock# single-process lock (see the CLI note below) ├── .secret # auto-generated master key (back this up; guard it) ├── .audit_seal_key # signing keys for the tamper-evident audit chain ├── .authority_seal_key ├── storage/ # uploaded files (File()/Files() fields) ├── logs/ # structured application logs └── backups/ # local snapshots written by `railbase backup` ``` The lock file enforces **one process per vault**: CLI commands that open the data file can't run while the server is up — see the [CLI reference](cli). There is **no external database**. Data lives in the vault file; read [Data & multi-tenancy](data-and-tenancy) for the details. > [!CAUTION] > The `.secret` file is the master key for your data. If you lose it you lose > access to the vault. Include it in your backups and keep it out of version > control. ## Development vs. production - **Development** — opt into the development key so you can iterate without managing secrets. The vault refuses to unlock with no password configured, so enable the dev fallback explicitly with **`RAILBASE_DEV=true`** (or `runtime.dev: true` in `railbase.yaml`): ```bash RAILBASE_DEV=true ./railbase serve ``` Without it, `serve`/`migrate` exit with *"no vault password configured (set RAILBASE_VAULT_PASSWORD, RAILBASE_VAULT_PASSWORD_FILE, or RAILBASE_DEV=true)"*. - **Production** — set a real vault password and turn on production mode: ```bash export RAILBASE_PROD=true export RAILBASE_VAULT_PASSWORD_FILE=/run/secrets/railbase-vault ./railbase serve --addr :8095 --data-dir /var/lib/railbase ``` In production an empty vault password is a hard error — Railbase refuses to start with the dev key. Full guidance is in [Deployment](deployment) and [Security](security). ## Upgrading the binary To upgrade the core, replace the executable with a newer build and restart — your `pb_data/` is forward-compatible and migrations apply automatically on boot. You can also upgrade in place from the admin; see [Updating](updating). --- # Railbase vs PocketBase _Section: Why Railbase · Updated: 2026-06-11 · Source: https://railbase.app/learn/pocketbase-alternative.md_ > The same single-binary, self-hosted simplicity as PocketBase — plus commercial business modules you install at runtime. PocketBase and Railbase start from the same idea: a backend that ships as **one self-hosted binary** instead of a cloud account and a pile of services. If you love that model but need ready-made business features, here's how they compare. ## At a glance | | PocketBase | Railbase | |---|---|---| | Shape | Single Go binary | Single Go binary | | Database | Embedded SQLite | Embedded single-file store (Vault) | | License | Open-source (MIT), free | Free to self-host; commercial plugins | | Auth, REST, realtime, files, admin UI | Yes | Yes | | Multi-tenancy + RBAC | Rules-based | Built into the core | | Ready business apps | — | Marketplace: helpdesk, inventory, accounting, CMS… | ## When PocketBase is the better choice PocketBase is **open-source and completely free**, with a polished developer experience and a large community. If you want a minimal backend for a side project or micro-SaaS, prefer MIT-licensed source you can fork, and are happy to build product features yourself, PocketBase is hard to beat. ## When teams pick Railbase Railbase keeps the single-binary, self-host-anywhere feel, then adds the layers most products eventually need: - **Multi-tenancy and RBAC** in the core, not bolted on. - An **admin SPA** plus a public site frontend out of the box. - A **plugin marketplace** — buy a helpdesk, inventory, accounting (GL/AP/AR), CMS or translation module and install it **at runtime**, self-hosted, with no rebuild and no third-party cloud. In short: choose PocketBase when you want the leanest open-source backend and will build features yourself; choose Railbase when you want that same operational simplicity **plus** commercial business modules you run on your own server. ## FAQ ### Is Railbase open source like PocketBase? No. PocketBase is MIT-licensed open source. The Railbase core is closed-source but **free to self-host** — you download and run the binary at no cost; only the optional business plugins are paid (per-seat licenses). ### Does Railbase use SQLite like PocketBase? No. Railbase persists everything in Vault — an embedded, single-file NoSQL document store. The operational model is the same as PocketBase's SQLite file: one data file next to one binary, and a backup is a file copy. ### Does Railbase support multi-tenancy? Yes. Tenant isolation and role-based access control are part of the core, not an add-on — every collection, API call and plugin is tenant-scoped. ### Can I extend Railbase with my own code? Yes. `railbase init` scaffolds a site you build on with schema definitions, hooks, scheduled jobs and the TypeScript SDK — and compiled plugins from the marketplace install at runtime without a rebuild. ## Get started [Download Railbase](../download) · [browse the plugins](../plugins) --- # A self-hosted Supabase alternative _Section: Why Railbase · Updated: 2026-06-11 · Source: https://railbase.app/learn/supabase-alternative.md_ > Trade a multi-container Postgres stack for one self-hosted binary — with auth, realtime, tenancy and business modules included. Supabase is a powerful, open-source backend built on PostgreSQL. The catch for self-hosters: running it yourself means operating a **stack of services** (Postgres, GoTrue, PostgREST, Realtime, Storage, Kong, Studio…) via Docker Compose. Railbase takes the opposite bet — **one binary, one data file** — and adds ready business apps. ## At a glance | | Supabase | Railbase | |---|---|---| | Database | PostgreSQL (full SQL) | Embedded single-file store (Vault) | | Self-hosting | Multi-container Docker stack | One binary, one file | | Auth, realtime, storage, REST | Yes | Yes | | SQL ecosystem / extensions / pgvector | Yes | — | | Ready business apps | — | Marketplace: helpdesk, inventory, accounting… | | Hosting | Cloud + self-host | Self-host (free core) | ## When Supabase is the better choice If you need the **full PostgreSQL ecosystem** — raw SQL, extensions, pgvector, a managed cloud with autoscaling, and a large catalogue of tooling — Supabase is an excellent, open-source choice. Teams standardised on Postgres will feel at home. ## When teams pick Railbase - **Zero-ops self-hosting:** no container orchestration — copy one binary and run it. A backup is a single file. - **Batteries included:** auth, multi-tenancy, RBAC, realtime, files, scheduler, mailer and an admin SPA in the box. - **Commercial modules:** install a helpdesk, inventory or accounting plugin at runtime instead of building it. Pick Supabase when Postgres and SQL flexibility are central to your app. Pick Railbase when you want the **simplest possible self-hosted backend** and ready-to-run business modules over raw database power. > [!NOTE] > Prefer a managed cloud? Supabase offers one today; Railbase is self-host-first. > If you'd want hosted Railbase, [tell us](../support). ## FAQ ### Is Railbase a drop-in replacement for Supabase? No. Railbase covers the same backend essentials — auth, REST API, realtime, file storage, an admin UI — but its data layer is an embedded single-file document store (Vault), not PostgreSQL. SQL queries, Postgres extensions and pgvector don't carry over; collections and access rules do. ### Can I self-host Railbase for free? Yes. The Railbase core is free to self-host: one binary, one data file, no usage metering. Commercial plugins (helpdesk, inventory, accounting, CMS…) are licensed per seat from the marketplace. ### Does Railbase support realtime subscriptions like Supabase? Yes. Realtime change events are built into the core and exposed to both the REST/SDK surface and plugins — no separate Realtime service to run. ### How do I migrate data from Supabase to Railbase? Export your Postgres tables to JSON or CSV, then import them into Railbase collections via the admin console or the auto-CRUD REST API. Relational schemas flatten into document collections, so review foreign-key joins first — embedded documents or reference fields replace them. ## Get started [Download Railbase](../download) · [browse the plugins](../plugins) --- # A self-hosted Firebase alternative _Section: Why Railbase · Updated: 2026-06-11 · Source: https://railbase.app/learn/firebase-alternative.md_ > Own your data on your own server instead of a proprietary Google cloud — with the app-backend essentials and business modules built in. Firebase made real-time apps easy — but it's **Google-hosted, proprietary, and can't be self-hosted**, and usage-based pricing can surprise you at scale. Railbase offers the same app-backend essentials while keeping everything on **infrastructure you control**. ## At a glance | | Firebase | Railbase | |---|---|---| | Hosting | Google cloud only | Self-host, anywhere | | Data ownership | Proprietary; data in Google | Your server, one file | | Database | Firestore (NoSQL) | Embedded document store | | Auth, realtime, files | Yes | Yes | | Pricing | Usage-based (can spike) | Free core + per-seat plugin licenses | | Lock-in | High | Low — own the binary + data | ## When Firebase is the better choice If you want a fully-managed, scale-to-zero serverless backend, are deep in the Google/GCP ecosystem, and don't want to run any servers, Firebase is mature and convenient. ## When teams pick Railbase - **Own your data and your bill:** self-hosted, no vendor cloud, no per-read/write metering. - **No lock-in:** it's your binary and your single data file — move it anywhere. - **More than infrastructure:** ready business modules (helpdesk, inventory, accounting, CMS) you install and run yourself. Choose Firebase if a managed Google backend fits and you accept the lock-in. Choose Railbase to **own your stack end-to-end** and add business apps without a third party. ## FAQ ### Can Firebase be self-hosted? No — Firebase runs only in Google's cloud. If self-hosting and data ownership are requirements, you need a different backend: Railbase runs as a single binary on any Linux, macOS or Windows server you control. ### Does Railbase have usage-based pricing like Firebase? No. There is no per-read/per-write metering. The Railbase core is free to self-host; commercial plugins are flat per-seat licenses, so the bill doesn't move with traffic. ### Does Railbase offer realtime updates like Firestore? Yes. Realtime change subscriptions are built into the core, alongside auth, file storage and the REST API — the app-backend essentials Firebase is known for, on your own server. ### Where is my data stored with Railbase? In a single `.vault` file on your server — an embedded NoSQL document store. Backups, migrations between machines and audits are file-level operations you perform yourself; no third party holds your data. ## Get started [Download Railbase](../download) · [browse the plugins](../plugins) --- # Railbase vs Retool _Section: Why Railbase · Updated: 2026-06-11 · Source: https://railbase.app/learn/retool-alternative.md_ > Retool builds internal UIs over data you already have; Railbase is the self-hosted backend itself — and ships business apps too. Retool and Railbase get compared a lot, but they solve **different halves** of the problem. Retool is a low-code builder for **internal tools and admin UIs on top of data and APIs you already run**. Railbase is the **backend itself** — data, auth, APIs, tenancy — and it ships end-user and admin UIs plus business modules. ## At a glance | | Retool | Railbase | |---|---|---| | Primary role | Internal-tool / admin UI builder | Backend platform | | Provides the data layer? | No — connects to yours | Yes — storage, auth, REST, realtime | | Hosting | Cloud + self-host (paid tiers) | Self-host (free core) | | Pricing | Per-user | Free core + per-seat plugin licenses | | App audience | Internal-facing | Public site frontend + admin SPA | | Ready business apps | Build them screen by screen | Marketplace: helpdesk, inventory, accounting… | ## When Retool is the better choice If you already have databases and APIs and just need to **assemble internal dashboards and admin panels quickly**, Retool's drag-and-drop builder and large integration catalogue are excellent. ## When teams pick Railbase - You need the **backend**, not just a UI over one: data model, auth, multi-tenancy, REST, realtime. - You want **end-user-facing** apps, not only internal tools. - You want **ready business modules** (helpdesk, inventory, accounting) you self-host, not panels you rebuild per screen. The two can even complement each other — but if you're standing up a product backend from scratch and want business apps included, Railbase covers the whole stack on your own server. ## FAQ ### Does Railbase replace Retool? Only if what you actually need is the backend. Retool builds internal UIs over data sources you already operate; Railbase **is** the data source — storage, auth, REST, realtime, tenancy — and ships its own admin and site UIs. Teams replace Retool with Railbase when the "internal tool" is really a whole app. ### Can I build internal tools with Railbase? Yes. Every collection gets auto-generated CRUD, the admin SPA manages data, users and operations out of the box, and the UI component kit + TypeScript SDK cover custom screens — without a per-user builder license. ### Can Retool connect to Railbase? Yes. Railbase exposes a standard REST API with token auth, so Retool (or any UI builder) can sit on top of it like any other API resource. ### How does pricing differ? Retool charges per user of the builder/apps. Railbase's core is free to self-host; you only pay per-seat licenses for the commercial plugins you install (helpdesk, inventory, accounting, CMS…). ## Get started [Download Railbase](../download) · [browse the plugins](../plugins) --- # Architecture _Section: Core Concepts · Updated: 2026-06-10 · Source: https://railbase.app/learn/architecture.md_ > The core, out-of-process plugins, the vault data store, and the marketplace. Railbase is one binary that supervises a small set of moving parts. Understanding them makes the install, update, and licensing flows obvious. ## The big picture ```text your server ┌───────────────────────────────────────────────┐ │ Railbase core (the binary) │ │ ├── HTTP server :8095 │ │ │ / public assets (optional) │ │ │ /_/ admin console (SPA) │ │ │ /_pm marketplace / plugin manager │ │ │ /papi/... reverse-proxy to plugins │ │ ├── vault single-file data store │ │ └── plugin manager │ │ ├── plugin A (subprocess) │ │ └── plugin B (subprocess) │ └───────────────────────────────────────────────┘ │ syncs with (catalog, licenses, ▼ artifacts, payments) railbase.app — vendor licensing & distribution ``` ## The core The core is the always-on process. It owns the HTTP server, the data store, auth, the job runner, and the admin console. It boots in well under a second and runs happily on a small VPS. Key routes: - **`/_/`** — the admin console (a single-page app embedded in the binary). - **`/_pm`** — the marketplace / plugin-manager console (on by default; `RAILBASE_PLUGIN_MANAGER=0` disables). - **`/papi//…`** — a reverse proxy the core uses to route requests to a running plugin. ## Plugins run out of process A plugin is **a separate signed binary that the core runs as a subprocess** — it is *not* compiled into Railbase. When you install a plugin, the plugin manager: 1. launches it with a private port and short-lived tokens, 2. waits for it to **register** (a loopback handshake announcing its slug, the collections it owns, the capabilities it `provides`/`consumes`, its minimum core version, and the roles it declares), 3. supervises it — health-checks it, restarts it on crash, and proxies `/papi//…` traffic to it. Because plugins are separate processes, a misbehaving plugin can't take the core down, and the core can stop, update, or remove one without a redeploy. The full lifecycle is in [How plugins work](how-plugins-work). ## The vault data store Persistence is a single encrypted file (`railbase.vault`) managed by an in-process, MVCC document engine. No PostgreSQL, no connection strings, no sidecar. Each plugin gets its own namespaced collections; multi-tenant deployments use a file per tenant. See [Data & multi-tenancy](data-and-tenancy). ## railbase.app: the vendor plane Your instance is a *client* of railbase.app, the vendor's server. It reaches out to: - **discover** plugins and prices (catalog) and the vendor's signing key (pubkey), - **complete a purchase** (payment runs on railbase.app; your server is never a card processor), - **download** the signed artifact for a plugin you're licensed for, - **keep licenses fresh** via a periodic license check (renewals, revocations). The trust anchor is a **pinned public key**: the core only runs an artifact whose signature verifies against the key it has pinned for the vendor. This is why acquisition is marketplace-only — see [How plugins work](how-plugins-work) and [Licensing & seats](licensing-and-seats). --- # How plugins work _Section: Core Concepts · Updated: 2026-06-08 · Source: https://railbase.app/learn/how-plugins-work.md_ > Pull-only acquisition, signed artifacts, verification, and the subprocess runtime. Plugins are how Railbase becomes a product. This page explains the **hosted distribution** model: how a signed plugin gets from the marketplace onto your server at runtime, and how it runs once it's there. > [!NOTE] > This is the runtime/distribution path — the built-in marketplace, on by default, > pulling from railbase.app. To add a plugin to a build you compile yourself > instead, register the plugin's Go module at build time; see > [Installing plugins](installing-plugins). ## Acquisition is pull-only In the hosted model, a plugin enters your instance one way: the core **pulls** it from the marketplace. There is no "install from file", no upload field, and no way to point a *marketplace-managed* slot at a binary on disk. (Build-time registration is a separate, compile-time path — it doesn't go through the manager at all.) When you install a plugin, the core runs a four-step exchange with railbase.app: ```text catalog → grant → download → verify ``` 1. **catalog** — fetch the plugin's entry: its current version, minimum core version, content hash (sha256), signature, and the vendor's public key. 2. **grant** — present your license; the vendor returns a short-lived download token for the version you're entitled to. 3. **download** — fetch the artifact bytes using that token. 4. **verify** — refuse to run it unless **all three** hold: - the sha256 matches the catalog, - the catalog's public key equals the key your instance has **pinned** for the vendor, - the Ed25519 signature validates against that pinned key. > [!IMPORTANT] > The pinned public key is the trust anchor. An artifact that isn't signed by the > key you trust never executes — even if a network path is compromised. This is > the whole reason sideloading is disabled: every running plugin is one the core > cryptographically verified. ## How a plugin runs A verified artifact is launched as a **managed subprocess**, not loaded into the core's address space. The plugin manager: - starts the process with a private port and short-lived `ADMIN_TOKEN` / `DATA_TOKEN` credentials, - waits for it to **register** over loopback, and - **supervises** it: a periodic health probe restarts a crashed or hung plugin (with a restart budget), and traffic to `/papi//…` is reverse-proxied to the live process. Running out of process means a faulty plugin degrades to *that plugin being unavailable* — it cannot crash the core or corrupt another plugin's data. ## The register handshake On startup a plugin announces itself to the core. The handshake carries: | Field | Meaning | |---|---| | `slug` | The plugin's identifier | | `url` | Where the core proxies its API | | `colls` | The collections (tables) it owns | | `provides` / `consumes` | Capabilities it offers to / needs from other plugins | | `min_core` | The minimum core version it requires | | `roles` | The RBAC roles + permissions it declares (and which are *billable*) | Two consequences worth knowing: - **Compatibility is enforced.** If a plugin's `min_core` is newer than your core, the core refuses to register it and tells you to update. See [Updating](updating). - **The core doesn't hardcode plugin roles.** A plugin *carries* its roles and teaches them to the core at install time, which is what makes per-seat billing work without a core rebuild. See [Licensing & seats](licensing-and-seats). ## Why not compile plugins in, or load `.so` files? Out-of-process subprocesses avoid the two classic plugin traps: native `.so`/plugin loading is platform-locked and ABI-fragile across Go versions, and a single monolithic binary can't isolate faults. Signed subprocesses give you cross-platform artifacts, crash isolation, and runtime install/update/remove — without trusting unverified code. > [!NOTE] > There *is* a developer-only path to launch a local, unsigned plugin binary for > building plugins. It is admin-gated and separate from the marketplace; it has > no role in normal operation and is not how you obtain plugins. --- # Licensing & seats _Section: Core Concepts · Updated: 2026-06-07 · Source: https://railbase.app/learn/licensing-and-seats.md_ > Per-seat pricing by billable role, signed license tokens, expiry, and revocation. Paid plugins are licensed **per seat**, billed monthly or yearly. This page explains what a seat is, what a license is, and how your instance enforces it. ## What counts as a seat A seat is **a user who holds a *billable* role** in a plugin — not every user. - **Billable** — operator- and approver-style roles that *do* the work (post a journal entry, approve a requisition, manage stock). - **Free** — viewers, self-service users, and public/customer roles never consume a seat. Each plugin **declares its own roles** and marks which are billable; the core learns them when the plugin registers (see [How plugins work](how-plugins-work)). `seats(plugin)` is simply the number of users with at least one billable role in that plugin. A plugin's detail page on this site lists which roles are billable. ![A plugin detail page showing billable and free roles](/docs/ys-plugin-gl.png "Each plugin lists its billable (seat) roles and its free roles — here, General Ledger bills gl.accountant and gl.controller per seat, while gl.viewer is free.") > [!NOTE] > Seats are counted **per plugin**. A user who operates two plugins is a seat in > each. This is why adding a plugin to an existing team is priced on that > plugin's billable users, not your whole user base. ## What a license is On payment, the vendor issues a short, signed **license token** bound to your project. On the wire it looks like: ```text lic1.. ``` The payload is deliberately tiny — `{ project, plugin, seats, roles, kid, iat, exp }` — so it can be stored and passed around cheaply. Your instance verifies the Ed25519 signature against the **pinned vendor public key** and enforces the seat count and expiry locally. No call to the vendor is needed to *check* a license; verification is offline and fast. ## Expiry, renewal, and grace Your instance periodically re-checks the license with the vendor (a *heartbeat*). This is how renewals, seat changes, and **revocations** propagate without you doing anything: - A renewed license rotates in automatically. - A revoked or lapsed license gates the plugin **off** until it's restored — callers get a *402 Payment Required* with a hint pointing at the marketplace. - A short grace window covers an in-flight renewal (e.g. a webhook still settling) so a payment retry over a weekend doesn't suspend production. ## Key rotation The vendor can rotate its signing key; the token's `kid` (key id) records which key signed it. New licenses are signed by the active key, and your instance re-pins the current key over the heartbeat — so a rotation self-heals on the next license refresh. ## Managing your subscription Change seats, switch billing cycle, or cancel from **Manage billing** in either the in-app marketplace or your [account](/account) on this site — both open the vendor's secure billing portal. Details in [Managing billing](managing-billing). --- # Data & multi-tenancy _Section: Core Concepts · Updated: 2026-06-11 · Source: https://railbase.app/learn/data-and-tenancy.md_ > The single-file vault store, collections, and the file-per-tenant model. Railbase has no external database. All of your data lives in a single encrypted file managed by an in-process engine. This keeps operations trivial: one process, one file to back up. ## The vault The store is an encrypted, MVCC **document store** kept in one file — `pb_data/railbase.vault` by default. "MVCC" means readers never block writers, so the single-file model still serves concurrent traffic well. The file is encrypted at rest with a master key (`pb_data/.secret`) and unlocked at boot with your vault password (see [Installation](installation) and [Security](security)). What this means for you: - **No connection string, no DB server, no migrations service.** Schema migrations apply automatically on boot. - **Backups are a file copy.** A snapshot is a byte-exact copy of the `.vault` file — see [Backups & restore](backups-and-restore). - **Keep it on local disk.** Use the VPS's local disk, not a network mount, for correct file locking and durability. - **A single document can be up to 4 MiB.** Documents larger than a storage page are split across pages transparently; past 4 MiB a write is rejected with an explicit error. Store large payloads as [files](rest-api#files), not document fields. (The REST create endpoint additionally caps a request body at 1 MiB.) ## Collections Data is organized into **collections** (think tables). The core owns the system collections (users, sessions, audit log, settings); each plugin owns its own, namespaced by slug (e.g. an inventory plugin's collections are prefixed so they never collide with another plugin's). When you uninstall a plugin its collections are left dormant rather than deleted, so reinstalling restores your data. A separate, backup-gated *purge* permanently removes them — see [Installing plugins](installing-plugins). ## Multi-tenancy For deployments that serve multiple isolated organizations, Railbase uses **row-level tenancy inside the one vault file**. Tenants are registered in the built-in `_tenants` collection — from the CLI: ```bash ./railbase tenant create acme ./railbase tenant list ``` A collection opts into tenancy with the `.Tenant()` mixin in the [schema](schema). Its rows then carry a `tenant_id` column, and the scope comes from the request: a call with an `X-Tenant: ` header (the [TypeScript SDK](typescript-sdk)'s `rb.setTenant(id)`) writes and reads only that tenant's rows — the value is stamped on insert and filtered on read. Requests without the header operate at **site scope**. Roles can likewise be granted per tenant ([RBAC](authentication)). > [!TIP] > A single-tenant deployment is just the default case: no tenant setup, no > `X-Tenant` header, no `.Tenant()` mixins required. Reach for multi-tenancy > only when you actually host separate organizations on one instance. Note that > backups operate on the whole vault file — i.e. on all tenants at once. --- # Project setup _Section: Building with Railbase · Updated: 2026-06-10 · Source: https://railbase.app/learn/project-setup.md_ > Scaffold a Railbase app, understand the layout, and run the dev loop. Railbase isn't only something you run — it's something you **build on**. You define your data model in Go, and Railbase gives you a REST + realtime API, auth, an admin console, and a typed client for free. This section is the developer track. ## Scaffold a project ```bash railbase init myapp --template basic \ --railbase-source /path/to/railbase # pre-release: pin the local core checkout cd myapp go mod tidy ``` > [!IMPORTANT] > **Pre-release:** `github.com/railbase/railbase` isn't published yet, so > `--railbase-source` (or `RAILBASE_LOCAL_PATH`) is **required**. It writes > **both** `replace` directives into the generated `go.mod` — one for the core > and one for the (also-unpublished) `github.com/railbase/vault` it depends on, > assuming a `vault/` checkout next to the railbase one. `replace` directives > are not inherited from dependencies, so if your vault checkout lives > elsewhere and `go mod tidy` fails with *"github.com/railbase/vault@v0.0.0 … > 404"*, point the second replace at the right path: > > ```text > replace github.com/railbase/vault => /path/to/vault > ``` `railbase init` generates a complete, buildable project: ```text myapp/ ├── cmd/myapp/main.go # entry point (your binary) ├── schema/main.go # your data model (Go DSL) ├── pb_hooks/example.pb.js# server-side JS hooks ├── railbase.yaml # project config ├── webembed/ # optional embedded SPA ├── Makefile ├── go.mod └── pb_data/ # vault + .secret (created on init) ``` The templates layer on each other: `auth-starter` = `basic` + auth, `fullstack` = `auth-starter` + a web frontend. > [!TIP] > The project directory and Go module both default to the `` you pass. > Override the module path with `--module github.com/you/myapp` while keeping the > directory name. ## The entry point `cmd/myapp/main.go` is a thin `main` that hands control to Railbase and gives you a hook to extend the app before the server starts: ```go package main import ( _ "myapp/schema" // blank import: its init() registers your collections "github.com/go-chi/chi/v5" "github.com/railbase/railbase/pkg/railbase" "github.com/railbase/railbase/pkg/railbase/cli" "github.com/railbase/railbase/pkg/railbase/hooks" ) func main() { cli.ExecuteWith(func(app *railbase.App) { // Runs after the app is built, before Run() starts. Safe here: // Go hooks (the registry is lazy). app.GoHooks().OnRecordBeforeCreate("posts", func(c *hooks.Context, ev *hooks.RecordEvent) error { // ... validate, mutate ev.Record, etc. return nil // or an error / hooks.ErrReject → 400 }) // Jobs / JobsStore / Realtime / EventBus are wired during // App.Run and are nil at this point — register against them // from OnBeforeServe, which fires after every subsystem is up: app.OnBeforeServe(func(r chi.Router) { app.Jobs().Register("report.generate", generateReport) app.EventBus().Subscribe("record.changed", 256, onRecordChanged) r.Get("/api/myapp/stats", statsHandler(app)) }) }) } ``` Useful `app` seams: `OnBeforeServe(func(chi.Router))`, `GoHooks()`, `ServeStaticFS(path, fs)`, `Jobs()`, `JobsStore()`, `Realtime()`, `EventBus()`, `Pool()`. Everything except `GoHooks`/`OnBeforeServe`/ `ServeStaticFS` returns nil before `Run` — touch those only from inside an `OnBeforeServe` callback (or later, e.g. in hooks and handlers). ## First run ```bash go build ./cmd/myapp ./myapp migrate diff initial_schema # generate the first migration from your DSL ./myapp migrate up # apply it ./myapp serve # http://localhost:8095 (admin at /_/) ``` The scaffolded `railbase.yaml` ships with `runtime.dev: true`, so these commands unlock the vault with the development key out of the box — no `RAILBASE_VAULT_PASSWORD` needed. (Remove that line and set a real password before deploying; see [Installation](installation#development-vs-production).) If you ever clear it and forget, `migrate`/`serve` exit with *"no vault password configured"* — re-add it or prefix the command with `RAILBASE_DEV=true`. Create an admin with `./myapp admin create you@example.com` and sign in. See [Defining your schema](schema) next. ## The dev loop For active development, `railbase dev` runs the backend and a frontend dev server under one Ctrl-C, waits for `/readyz`, and (optionally) regenerates the TypeScript SDK on schema changes: ```bash railbase dev --web ./web --web-cmd "npm run dev" \ --watch-schema ./schema --sdk-out web/src/client ``` Logs are interleaved and prefixed `[api]` / `[web]`. --- # Defining your schema _Section: Building with Railbase · Updated: 2026-06-10 · Source: https://railbase.app/learn/schema.md_ > Declare collections, fields, relations, and access rules with the Go DSL. Your data model is Go code. You declare **collections** (think tables) and their fields with a fluent builder, register them from an `init()`, and Railbase generates the storage, the REST API, the realtime topics, and the typed client from that one definition. ## A collection ```go package schema import s "github.com/railbase/railbase/pkg/railbase/schema" var Posts = s.Collection("posts"). Field("title", s.Text().Required().MaxLen(200)). Field("body", s.RichText()). Field("status", s.Status("draft", "published").Default("draft")). Field("author", s.Relation("_users").Required().CascadeDelete()). Index("idx_status", "status"). ListRule("status = 'published' || @request.auth.id != ''"). ViewRule("status = 'published' || @request.auth.id != ''"). CreateRule("@request.auth.id != ''"). UpdateRule("@request.auth.id = author"). DeleteRule("@request.auth.id = author") func init() { s.Register(Posts) } ``` `id`, `created`, and `updated` are added automatically — don't declare them. ## Fields Beyond the primitives — `Text() Number() Bool() Date() Email() URL() JSON() Select(...) MultiSelect(...) File() Files() Relation(t) Relations(t) Password() RichText()` — the DSL ships a large library of **domain types** that carry validation and rendering: `Currency() Finance() Money… Address() Tel() PersonName() Slug() SequentialCode() Status(...) Priority() Rating() Tags() Country() Percentage() Coordinates() IBAN() Duration()` and more. Chain modifiers on any field: ```go s.Finance().Required().Min("0.01") // money — use Finance, never Number().Float() s.Slug().From("title").Unique() s.SequentialCode().Prefix("PO-").Pad(5) // PO-00001, PO-00002, … s.Text().FTS() // full-text searchable ``` > [!IMPORTANT] > Use `Finance()` for monetary values, not `Number().Float()` — floats lose > precision on money. The domain types exist so you don't reinvent these rules. ## Indexes & constraints ```go .Index("idx_status", "status") .UniqueIndex("idx_po_number", "tenant_id", "po_number") ``` Row-level **checks** (invariant expressions evaluated on every write) are defined per collection in the admin schema editor; the Go DSL's `.Checks(...)` seam exists but its `CheckSpec` argument type isn't exported from `pkg/railbase/schema` yet. ## Access rules Every collection has five rules — `ListRule`, `ViewRule`, `CreateRule`, `UpdateRule`, `DeleteRule` — written in a small expression language (`@request.auth.id != ''`, `@request.auth.id = author`, `status = 'published'`). > [!CAUTION] > **Secure by default.** An operation with **no rule set is locked** (server-only), > not public. Call `.PublicRules()` or set explicit rules to open an endpoint. > Forgetting a rule fails closed, never open. ## Mixins Common behaviours attach with one call: | Mixin | Adds | |---|---| | `.Tenant()` | `tenant_id` + row-level isolation for multi-tenant apps | | `.SoftDelete()` | soft delete + restore (records hidden, not destroyed) | | `.Audit()` | append to the tamper-evident audit log on every change | > [!NOTE] > **End-user sign-in: use the built-in `_users`.** The core guarantees a built-in > auth-collection named **`_users`** (email, password_hash, verified, token_key, > last_login_at, name) — it is the default owner for API tokens / SCIM and the > target of `/api/collections/_users/auth-signup` and `auth-with-password`. You > don't declare it; relate to it with `s.Relation("_users")`. To own its shape, > register your own `s.AuthCollection("_users")` and the core uses yours instead. > > The auth endpoints exist for **registered** auth collections only — nothing > registers a plain `users` (no underscore), so `/api/collections/users/auth-*` > returns 404 unless your schema declares it; prefer the built-in `_users` (or a > distinct name like `staff`) to avoid confusion with the pre-`_users` holdover. > `.AuthCollection()` and `.Tenant()` can't be combined — the validator > rejects it. After editing the schema, generate and apply a migration — see [Migrations](migrations). --- # Migrations _Section: Building with Railbase · Updated: 2026-06-04 · Source: https://railbase.app/learn/migrations.md_ > Turn schema changes into versioned migrations and apply them. Your Go schema is the source of truth. Migrations are how that truth becomes durable, versioned changes to the data store. Railbase generates them by diffing your DSL against the last applied schema. ## Generate a migration After you change `schema/`, generate a migration from the diff: ```bash railbase migrate diff add_posts_status ``` This writes `migrations/NNNN_add_posts_status.up.sql`. User migrations are numbered from **1000** up (system migrations occupy the lower range). If the diff is empty you'll see *"schema unchanged — no migration emitted."* > [!NOTE] > If a change can't be made safely automatically (for example a type change that > could lose data), `migrate diff` refuses and tells you to hand-write the > migration. It also emits a guarded backfill placeholder for `NOT NULL` additions > so you can't apply an unsafe step by accident. ## Apply migrations ```bash railbase migrate up # apply all pending, then snapshot the schema railbase migrate status # VERSION · NAME · STATUS(applied|pending) · SOURCE ``` Migrations also **apply automatically on boot**, so a freshly-deployed binary brings its data store up to date on `serve` without a manual step. ## Rolling back ```text railbase migrate down → not implemented (refuses) ``` There is no automatic down-migration. To reverse a change, write a new forward migration that undoes it — explicit and reviewable beats a magic rollback. And before anything risky, take a snapshot: ```bash railbase backup ``` See [Backups & restore](backups-and-restore). ## Where migrations live ```text migrations/ ├── 1000_initial_schema.up.sql ├── 1001_add_posts_status.up.sql └── … ``` Commit the `migrations/` directory to source control alongside `schema/` — that pair is the full, replayable history of your data model. --- # The REST API _Section: Building with Railbase · Updated: 2026-06-10 · Source: https://railbase.app/learn/rest-api.md_ > Auto-generated CRUD endpoints with filtering, sorting, expansion, and paging. Every collection you define gets a complete REST API automatically — no controllers to write. The endpoints are protected by the [access rules](schema) on the collection. ## Endpoints ```text GET /api/collections/{name}/records list POST /api/collections/{name}/records create POST /api/collections/{name}/records/batch batch write GET /api/collections/{name}/records/{id} view PATCH /api/collections/{name}/records/{id} update DELETE /api/collections/{name}/records/{id} delete POST /api/collections/{name}/records/{id}/restore restore (soft-delete) ``` ## Querying a list The list endpoint takes query parameters: | Param | Example | Purpose | |---|---|---| | `page` / `perPage` | `?page=2&perPage=50` | Offset pagination | | `filter` | `?filter=status='published' && views>100` | Expression filter | | `sort` | `?sort=-created,title` | Multi-key sort (`-` = descending) | | `expand` | `?expand=author` | Inline related records | | `q` | `?q=railbase` | Full-text search over `FTS()` fields | | `cursor` | `?cursor=…` | Cursor pagination (`nextCursor` in the response) | | `count` | `?count=exact` | Fill `totalItems`/`totalPages`: `exact` · `estimate` · `cap[:N]` | ```bash curl "http://localhost:8095/api/collections/posts/records?filter=status='published'&sort=-created&expand=author&count=exact" ``` The list response is enveloped: ```json { "page": 1, "perPage": 30, "totalItems": 142, "totalPages": 5, "items": [ { "id": "…", "title": "…", "expand": { "author": { } } } ] } ``` > [!NOTE] > Counting is opt-in: without `count=…`, `totalItems` and `totalPages` are > `null` — a deliberate default, since an exact count is a full scan on large > collections. ## Batch writes `POST /api/collections/{name}/records/batch` takes up to 200 operations and returns a per-op result list; `atomic: true` makes it all-or-nothing: ```json { "atomic": true, "ops": [ { "action": "create", "data": { "name": "Tech" } }, { "action": "update", "id": "abc123", "data": { "name": "Life" } }, { "action": "delete", "id": "def456" } ] } ``` ## Files File fields (`File()` / `Files()` in the [schema](schema)) are populated after the record exists, not inline in the create JSON: ```text POST /api/collections/{name}/records/{id}/files/{field} multipart, part name "file" DELETE /api/collections/{name}/records/{id}/files/{field}/{filename} GET /api/files/{collection}/{record_id}/{field}/{filename}?token=…&expires=… ``` The upload response (and the record's field value) carries a **signed download URL** — the token is the auth, so the GET needs no `Authorization` header and expires after a few minutes; re-read the record for a fresh one. ## Authentication Send a bearer token from one of the [auth endpoints](authentication): ```bash curl http://localhost:8095/api/collections/posts/records \ -H "Authorization: Bearer " ``` The current identity is available in rules as `@request.auth.*` and via `GET /api/auth/me`. ## URL compatibility Railbase is wire-compatible with PocketBase-style clients: everything is served under `/api/…` in PB-compatible shapes, and that's the default and the v1 ship target (mode **`strict`**). A `compat.mode` knob exists for the planned native surface — set it at runtime (`railbase config set compat.mode '"both"'`) or via `RAILBASE_PB_COMPAT`, and read the active mode from `GET /api/_compat-mode`: - `strict` — PocketBase shapes only, under `/api/…` (default) - `native` — Railbase-native shapes under `/v1/…` (reserved) - `both` — both prefixes; shape resolved per request (migration aid) > [!NOTE] > The native `/v1/…` route surface is **not mounted yet** — today the modes only > influence shape details such as the realtime payload framing. Build against > `/api/…`. > [!TIP] > You rarely call these by hand — generate a typed client with `railbase generate > sdk` and let it do CRUD, auth, and realtime for you. See the > [TypeScript SDK](typescript-sdk). --- # Authentication & identity _Section: Building with Railbase · Updated: 2026-06-10 · Source: https://railbase.app/learn/authentication.md_ > Auth collections, passwords, OAuth, API tokens, and RBAC roles. Authentication is built in. You declare which collections are sign-in capable, and Railbase mounts the endpoints, hashes passwords, issues tokens, and enforces your [access rules](schema). ## Auth collections The core guarantees a built-in **`_users`** auth collection — end users sign up and sign in against it with no schema work on your part. Add further auth collections in the schema when you need separate identity pools: ```go var Staff = s.AuthCollection("staff") // injects email, password_hash, verified, … func init() { s.Register(Staff) } ``` You can have several (`_users`, `staff`, `customers`) — each has its own sign-in endpoints and an isolated email namespace. Operator/admin accounts (created with `railbase admin create`) are separate from these app auth collections. See the `_users` note in [Defining your schema](schema) before naming one `users`. ## Endpoints Each auth collection gets, under `/api/collections/{name}/`: ```text POST auth-signup POST auth-with-password POST auth-refresh POST auth-logout POST request-verification POST confirm-verification POST request-password-reset POST confirm-password-reset POST request-otp POST auth-with-otp GET auth-with-oauth2/{provider} + /callback ``` Plus TOTP, WebAuthn, and (where configured) LDAP/SAML. The signed-in identity is at `GET /api/auth/me`. Responses carry `{ token, record }`. ## OAuth providers Configure providers with environment variables — Google, GitHub, Apple, Microsoft are built in: ```ini RAILBASE_OAUTH_GOOGLE_CLIENT_ID=… RAILBASE_OAUTH_GOOGLE_CLIENT_SECRET=… RAILBASE_OAUTH_GITHUB_CLIENT_ID=… RAILBASE_OAUTH_GITHUB_CLIENT_SECRET=… ``` ## API tokens For server-to-server access, mint long-lived bearer tokens: ```bash railbase auth token create --owner --collection _users \ --name "ci-bot" --ttl 720h railbase auth token list [--owner ] [--all] railbase auth token rotate [--ttl 720h] railbase auth token revoke ``` `--collection` is the owner's **auth collection** and defaults to the built-in `_users` — a token minted against a non-existent collection authenticates but can't load its owner. > [!IMPORTANT] > The raw token is shown **once**, on create/rotate, and can't be recovered. Copy > it then; if you lose it, rotate. ## Roles & permissions (RBAC) Authorization is role-based, in two scopes — **site** (global) and **tenant**. Manage roles from the CLI: ```bash railbase role create site editor --desc "Can edit content" railbase role grant site editor posts.update railbase role assign _users/ site editor railbase role list-for _users/ ``` A subject is `/` — `_users/` for app users (the built-in collection), `_admins/` for operator accounts. The `system_admin` site role is immutable. In the [plugin model](building-plugins), plugins **declare their own roles** at install time — the core doesn't hardcode them, which is what makes per-seat billing by role work. See [Licensing & seats](licensing-and-seats). --- # Realtime _Section: Building with Railbase · Updated: 2026-06-10 · Source: https://railbase.app/learn/realtime.md_ > Subscribe to record changes over WebSocket or SSE. Railbase pushes record changes to subscribed clients in real time over WebSocket (or Server-Sent Events). Subscriptions respect the same access rules as the REST API — you only receive events for records you could read. ## Connect and subscribe The WebSocket endpoint is `GET /api/realtime/ws` (subprotocol `railbase.v1`). Subscribe by sending a JSON frame with the topics you care about: ```json { "action": "subscribe", "topics": ["posts/*", "posts/abc123", "users/me"] } ``` You can `subscribe` / `unsubscribe` at any time without reconnecting. For SSE, open `GET /api/realtime?topics=posts/*,orders/create` instead — the topics ride in the query string, and the connection also speaks the PocketBase-SDK dialect (a `PB_CONNECT` hello with a `clientId`, then `POST /api/realtime` to update that client's subscriptions). Both transports require an authenticated caller. ## Topics Topics are `/` or `/`: | Topic | Matches | |---|---| | `posts/*` | every create/update/delete on `posts` | | `posts/create` | only new `posts` | | `posts/abc123` | changes to one record | Verbs are `create`, `update`, and `delete`. ## Events The server pushes one frame per matching record event; `data` carries the verb and the record: ```json { "event": "posts/create", "id": "", "data": { "action": "create", "record": { "id": "abc123", "title": "…" } } } ``` plus control frames (`railbase.subscribed`, `pong`, `error`). Over SSE the same triple maps onto the `event:` / `id:` / `data:` frame fields. > [!NOTE] > A subscription is filtered by the collection's **ListRule**, evaluated per > subscriber — so realtime can't leak records a user couldn't list over REST. > Subscribe-time `filter:` / `expand:` are not supported yet; subscribe to a topic > and read details over REST if you need expansion. ## From the SDK The generated [TypeScript client](typescript-sdk) wraps all of this: ```ts const sub = rb.realtime.subscribe({ topics: ["posts/*"] }, (evt) => { console.log(evt.event, evt.data); }); // later: sub.unsubscribe() ``` ## Publishing your own topics Server-side (Go), plugins and apps can publish domain events on the bus: ```go app.Realtime().Publish("orders.shipped", payload, tenantID) ``` Subscribers receive them the same way as record events. --- # TypeScript SDK _Section: Building with Railbase · Updated: 2026-06-10 · Source: https://railbase.app/learn/typescript-sdk.md_ > Generate a fully-typed client for your collections, auth, and realtime. Railbase generates a TypeScript client from your schema, so your frontend gets typed CRUD, auth, and realtime with autocomplete — no hand-written API layer and no drift between client and server. ## Generate the client ```bash railbase generate sdk --out ./web/src/client ``` This emits a self-contained client — an `index.ts` entry, `types.ts`, `zod.ts`, a `collections/.ts` per collection, plus `auth.ts`, `realtime.ts`, schema-independent modules (`account.ts`, `tenants.ts`, `notifications.ts`, `i18n.ts`, `stripe.ts`, `contact.ts`, `errors.ts`) and a `_meta.json` used for drift detection. It's a **full regenerate** each time; commit the output and never hand-edit it. > [!TIP] > Add `--check` in CI to fail the build when the committed client drifts from the > schema (`railbase generate sdk --check` exits non-zero on mismatch). Or let > `railbase dev --watch-schema` regenerate it as you code. ## Use it ```ts import { createRailbaseClient } from "./client"; export const rb = createRailbaseClient({ baseURL: "", // same-origin in prod; "http://localhost:8095" in dev storage: window.localStorage, // persist the bearer token across reloads storageKey: "rb_token", }); // Typed CRUD — `posts` is generated from your collection const { items } = await rb.posts.list({ filter: "status='published'", sort: "-created" }); const post = await rb.posts.create({ title: "Hello", status: "draft" }); // Auth — a `Auth` helper per auth collection. The built-in // `_users` is always generated: const { token, record } = await rb._usersAuth.signinWithPassword({ identity: "you@example.com", password: "…", }); await rb._usersAuth.signup({ email, password, passwordConfirm }); const me = await rb.me(); // Realtime rb.realtime.subscribe({ topics: ["posts/*"] }, (evt) => console.log(evt)); ``` The client stores the token in the `storage` you pass and attaches it to every request; call `rb.setToken()` / `rb.setTenant()` to change identity at runtime. ## Other generators ```bash railbase generate openapi --out openapi.json # OpenAPI 3.1 spec railbase generate schema-json --out schema.json # machine-readable schema (for tooling) ``` > [!NOTE] > The SDK generator targets TypeScript. Other languages can consume the OpenAPI > spec above. --- # Hooks & custom logic _Section: Building with Railbase · Updated: 2026-06-10 · Source: https://railbase.app/learn/hooks.md_ > Run server-side logic on record events — in JavaScript or Go. Hooks let you run logic when records change — validate input, derive fields, send notifications, enforce invariants. Write them in JavaScript for quick iteration, or in Go for type safety and performance. ## JavaScript hooks Drop a `*.pb.js` file in `pb_hooks/` (next to where you run the binary; override with `RAILBASE_HOOKS_DIR` or `hooks.dir` in `railbase.yaml`). It's loaded by an embedded JS runtime at boot and hot-reloaded on save: ```js // pb_hooks/posts.pb.js $app.onRecordBeforeCreate("posts").bindFunc((e) => { const title = (e.record.title || "").trim(); if (!title) throw new Error("title required"); // abort with 400 e.record.title = title; // mutate before write e.next(); }); $app.onRecordAfterCreate("posts").bindFunc((e) => { console.log("post created:", e.record.id); e.next(); }); ``` Events available: `onRecordBeforeCreate` / `AfterCreate`, `…BeforeUpdate` / `AfterUpdate`, `…BeforeDelete` / `AfterDelete`. The `$app` binding also exposes: ```js $app.routerAdd("GET", "/api/hello/{name}", (e) => e.json(200, { hello: e.pathParam("name") })); // chi-style {param} paths $app.cronAdd("nightly", "0 3 * * *", () => { /* … */ }); $app.onRequest((e) => { /* every request */ e.next(); }); ``` > [!NOTE] > A **Before** hook that throws aborts the write with a 400. An **After** hook > throwing is logged but does not undo the committed write. Each handler is capped > at ~5 seconds of wall-clock time. Rename a file to `*.disabled` to skip it. ## Go hooks For compiled, typed logic, register from your `main` (see [Project setup](project-setup)). The handler types live in `pkg/railbase/hooks`; a handler receives a context plus the event, reads/mutates `ev.Record` (a `map[string]any`), and returns `nil` to continue or an error (or `hooks.ErrReject`) to refuse the write with a 400: ```go import "github.com/railbase/railbase/pkg/railbase/hooks" cli.ExecuteWith(func(app *railbase.App) { app.GoHooks().OnRecordBeforeCreate("posts", func(c *hooks.Context, ev *hooks.RecordEvent) error { title, _ := ev.Record["title"].(string) if title == "" { return fmt.Errorf("title required") // → 400 at the REST layer } ev.Record["title"] = strings.TrimSpace(title) // mutations persist return nil }) }) ``` Before-hooks run synchronously and may mutate the record; after-hooks run async, after commit. Pass `""` as the collection to hook every collection. Use `c.Ctx` for timeouts and outbound calls. ## Which to use - **JavaScript** — fast iteration, no rebuild, hot-reload. Great for validation, small derivations, glue. - **Go** — type safety, access to the full `app` API, no per-call interpreter overhead. Reach for it for anything performance-sensitive or complex. For background work that shouldn't block a request, use [Jobs & cron](jobs-and-cron) instead of doing it inline in a hook. --- # Jobs & cron _Section: Building with Railbase · Updated: 2026-06-10 · Source: https://railbase.app/learn/jobs-and-cron.md_ > Run background work and scheduled tasks with the built-in queue. Railbase has a built-in background job runner and scheduler — no Redis, no separate worker process. Use it for anything that shouldn't block a request: sending email, generating documents, sweeping expired data. ## Define a job Register a handler under a **kind** at boot, then enqueue work against that kind at runtime. The registry (and the store) come up during `App.Run`, so register from inside `OnBeforeServe` — calling `app.Jobs()` directly in the `ExecuteWith` callback returns nil (the subsystem doesn't exist yet): ```go import "github.com/railbase/railbase/pkg/railbase/jobs" cli.ExecuteWith(func(app *railbase.App) { app.OnBeforeServe(func(_ chi.Router) { // Register: maps a "kind" to a handler. j.Payload is the raw // JSON the enqueuer passed. app.Jobs().Register("email.welcome", func(ctx context.Context, j *jobs.Job) error { return sendWelcome(ctx, j.Payload) }) }) }) // Enqueue (e.g. from a hook or REST handler — any time after boot): _, err := app.JobsStore().Enqueue(ctx, "email.welcome", map[string]any{"user_id": id}, jobs.EnqueueOptions{Delay: 30 * time.Second}) ``` Jobs retry on failure (default 5 attempts). Return `jobs.ErrPermanent` to fail without retrying. ## Inspect jobs from the CLI ```bash railbase jobs list railbase jobs show railbase jobs run-now railbase jobs cancel railbase jobs recover # requeue jobs stuck in "running" ``` > [!NOTE] > Like every command that opens the vault, these need exclusive access to the > data file — stop the server first, or you'll get *"vault: file is locked by > another process"*. To inspect and manage jobs on a **running** instance, use > the admin console instead (see below). ## Schedule recurring work Persisted schedules are managed with `railbase cron` (standard 5-field cron expressions): ```bash railbase cron upsert nightly-report "0 3 * * *" report.generate railbase cron list railbase cron run-now nightly-report railbase cron disable nightly-report ``` You can also schedule from a JavaScript hook: ```js $app.cronAdd("nightly", "0 3 * * *", () => { /* … */ }); ``` > [!TIP] > Scheduled backups are just a built-in job kind — you can manage them here or > from the admin's Backups screen. See [Backups & restore](backups-and-restore). The admin console shows the schedule table and lets you run, enable/disable, or edit entries — see [Operating from the admin](operations). --- # The UI kit _Section: Building with Railbase · Updated: 2026-06-04 · Source: https://railbase.app/learn/ui-kit.md_ > Use Railbase's shadcn-style component kit in your own frontend. The same component library that builds the Railbase admin is available to your frontend. It's a shadcn-style kit — you **copy the source** into your project and own it, rather than installing an opaque dependency. > [!TIP] > Every component has its own reference page in the **UI Components** section — > e.g. [Button](ui-button), [Select](ui-select), [Data table](ui-QDatatable). This > page covers the kit as a whole: how to pull components in and wire it up. ## From the CLI ```bash railbase ui list # available components railbase ui peers # the `npm install …` line for peer deps railbase ui init # scaffold styles.css, cn.ts, primitives into your project railbase ui add button input form # copy specific components (+ their local deps) ``` `ui add` resolves local dependencies automatically — adding `form` pulls in the `label` it uses. Run `ui init` once before `ui add`. ## From the HTTP registry The kit is also served over HTTP at `/api/_ui/*`, so a build script can pull sources directly from a running instance: ```bash curl http://localhost:8095/api/_ui/styles.css > src/styles.css curl http://localhost:8095/api/_ui/cn.ts > src/lib/ui/cn.ts curl http://localhost:8095/api/_ui/components/button/source > src/lib/ui/button.tsx ``` Other endpoints: `/api/_ui/manifest`, `/api/_ui/registry`, `/api/_ui/components`, `/api/_ui/components/{name}`, `/api/_ui/primitives`, `/api/_ui/peers`. > [!NOTE] > "Copy, don't install" means upgrades are explicit: you re-copy a component when > you want its newer version, and your local edits are never clobbered by a > package bump. The kit ships its own theme tokens (`styles.css`) and a `cn()` > helper. This pairs naturally with the [TypeScript SDK](typescript-sdk): generate a typed client for your data, drop in the UI kit for the interface, and you have a full-stack app against your Railbase backend. --- # Building plugins _Section: Building with Railbase · Updated: 2026-06-07 · Source: https://railbase.app/learn/building-plugins.md_ > How plugins are structured, registered, and distributed (advanced). Plugins are how functionality is packaged and sold on top of Railbase. This page is an overview of the plugin model for developers; it's an advanced topic — most apps are built directly on the core (see the rest of this section). ## A plugin is a Go module with a `Register(app)` entry point A plugin is a Go module that declares collections, verbs, and roles and exposes a `Register(app)` function. It ships in two forms: - **Build-time (today):** an embedder imports the module and calls `Register(app)` in their `main`, compiling the plugin into their app binary. This is how first-party plugins are installed into a self-hosted build right now — see [Installing plugins](installing-plugins). - **Distribution (hosted):** the same module is built as a standalone artifact, signed, and run by the core's plugin manager as a managed **subprocess** it talks to over HTTP — giving crash isolation, cross-platform artifacts, and runtime install/update/remove without rebuilding. That runtime is described in [How plugins work](how-plugins-work). A plugin: - declares its **collections** with the same [schema DSL](schema) your app uses, - registers them with the core and reads/writes through the core's REST API under a service-identity token the plugin manager mints on install, - exposes its own HTTP **verbs**, which the core reverse-proxies at `/papi//…`, - declares the **roles** it needs (the core learns them at install — it doesn't hardcode plugin roles), which is what drives per-seat billing. ## The register handshake On startup a plugin announces itself to the core over loopback. The handshake carries: ```text slug, url, colls[], provides[], consumes{}, subscribes[], min_core, roles[], requires{}, actions[] ``` The core enforces `min_core` (it refuses a plugin that needs a newer core) and learns the plugin's collections, capabilities, and roles. A small harness reduces a plugin's entry point to a few lines — register the schema, declare verbs and roles, and serve — handling the handshake, the service token, the health probe, and the verb mux for you. ## Inter-plugin communication - **Capabilities** — a plugin `provides` a named capability (e.g. a stock provider) and another `consumes` it; the core mediates and runs detach hooks before a provider is removed. - **Events** — plugins publish and subscribe to bus topics for loosely-coupled workflows. - **Atomic reservations** — for cross-process consistency without a shared database transaction, the core offers a conditional decrement/release primitive (`/api/_tx/reserve` · `/api/_tx/release`). ## Distribution Plugins reach customers only through the marketplace, as **signed artifacts**: 1. The artifact is published to railbase.app (the vendor's distribution server) with its version, content hash, and Ed25519 signature. 2. A customer buys a subscription; railbase.app issues a `lic1` license. 3. The customer's core pulls the artifact, verifies sha256 + signature against the **pinned vendor key**, and runs it. There is no sideloading — see [How plugins work](how-plugins-work) and [Licensing & seats](licensing-and-seats) for the full trust and billing model. > [!NOTE] > Authoring and publishing plugins to the marketplace is a vendor/partner process. > If you want to distribute a plugin, get in touch via [Support](../support). --- # Accordion _Section: UI Components · Updated: 2026-06-04 · Source: https://railbase.app/learn/ui-accordion.md_ > A vertically stacked set of headings that each reveal a section of content. A vertically stacked set of headings that each reveal a section of content. ## Installation ```bash railbase ui add accordion ``` > [!NOTE] > `railbase ui add` also copies shared primitives — they ship alongside this component automatically. ## Usage ```tsx import { Accordion, AccordionItem, AccordionTrigger, AccordionContent } from "@/lib/ui/accordion"; Is it accessible? Yes. It follows the WAI-ARIA pattern. ``` ## Anatomy Exported parts: `Accordion` · `AccordionItem` · `AccordionTrigger` · `AccordionContent` --- # Alert _Section: UI Components · Updated: 2026-06-04 · Source: https://railbase.app/learn/ui-alert.md_ > A callout for drawing attention to a short, important message. A callout for drawing attention to a short, important message. ## Installation ```bash railbase ui add alert ``` Peer dependencies: ```bash npm install class-variance-authority ``` ## Usage ```tsx import { Alert, AlertTitle, AlertDescription } from "@/lib/ui/alert"; Heads up! You can add components to your app. ``` ## Anatomy Exported parts: `alertVariants` · `Alert` · `AlertTitle` · `AlertDescription` --- # Alert Dialog _Section: UI Components · Updated: 2026-06-04 · Source: https://railbase.app/learn/ui-alert-dialog.md_ > A modal dialog that interrupts the user with an important confirmation. A modal dialog that interrupts the user with an important confirmation. ## Installation ```bash railbase ui add alert-dialog ``` > [!NOTE] > `railbase ui add` also copies the `button` component and shared primitives — they ship alongside this component automatically. ## Usage ```tsx import { AlertDialog, AlertDialogTrigger, AlertDialogContent, AlertDialogHeader, AlertDialogTitle, AlertDialogDescription, AlertDialogFooter, AlertDialogCancel, AlertDialogAction } from "@/lib/ui/alert-dialog"; Delete Are you sure? This cannot be undone. Cancel Continue ``` ## Anatomy Exported parts: `AlertDialog` · `AlertDialogTrigger` · `AlertDialogPortal` · `AlertDialogOverlay` · `AlertDialogContent` · `AlertDialogHeader` · `AlertDialogFooter` · `AlertDialogTitle` · `AlertDialogDescription` · `AlertDialogAction` · `AlertDialogCancel` --- # Aspect Ratio _Section: UI Components · Updated: 2026-06-04 · Source: https://railbase.app/learn/ui-aspect-ratio.md_ > Constrains its content to a fixed width-to-height ratio. Constrains its content to a fixed width-to-height ratio. ## Installation ```bash railbase ui add aspect-ratio ``` ## Usage ```tsx import { AspectRatio } from "@/lib/ui/aspect-ratio"; Cover ``` --- # Avatar _Section: UI Components · Updated: 2026-06-04 · Source: https://railbase.app/learn/ui-avatar.md_ > An image element with a text fallback for a user or entity. An image element with a text fallback for a user or entity. ## Installation ```bash railbase ui add avatar ``` ## Usage ```tsx import { Avatar, AvatarImage, AvatarFallback } from "@/lib/ui/avatar"; ME ``` ## Anatomy Exported parts: `Avatar` · `AvatarImage` · `AvatarFallback` --- # Badge _Section: UI Components · Updated: 2026-06-04 · Source: https://railbase.app/learn/ui-badge.md_ > A small label for status, counts, or categories. A small label for status, counts, or categories. ## Installation ```bash railbase ui add badge ``` Peer dependencies: ```bash npm install class-variance-authority ``` ## Usage ```tsx import { Badge } from "@/lib/ui/badge"; New Beta Error ``` ## Anatomy Exported parts: `badgeVariants` · `Badge` --- # Breadcrumb _Section: UI Components · Updated: 2026-06-04 · Source: https://railbase.app/learn/ui-breadcrumb.md_ > Shows the path to the current page in a hierarchy. Shows the path to the current page in a hierarchy. ## Installation ```bash railbase ui add breadcrumb ``` > [!NOTE] > `railbase ui add` also copies shared primitives — they ship alongside this component automatically. ## Usage ```tsx import { Breadcrumb, BreadcrumbList, BreadcrumbItem, BreadcrumbLink, BreadcrumbSeparator, BreadcrumbPage } from "@/lib/ui/breadcrumb"; Home Settings ``` ## Anatomy Exported parts: `Breadcrumb` · `BreadcrumbList` · `BreadcrumbItem` · `BreadcrumbLink` · `BreadcrumbPage` · `BreadcrumbSeparator` · `BreadcrumbEllipsis` --- # Button _Section: UI Components · Updated: 2026-06-04 · Source: https://railbase.app/learn/ui-button.md_ > A clickable button with variants and sizes; also exposes `buttonVariants` for styling links. A clickable button with variants and sizes; also exposes `buttonVariants` for styling links. ## Installation ```bash railbase ui add button ``` Peer dependencies: ```bash npm install class-variance-authority ``` > [!NOTE] > `railbase ui add` also copies shared primitives — they ship alongside this component automatically. ## Usage ```tsx import { Button } from "@/lib/ui/button"; ``` ## Anatomy Exported parts: `buttonVariants` · `Button` --- # Calendar _Section: UI Components · Updated: 2026-06-04 · Source: https://railbase.app/learn/ui-calendar.md_ > A date-field calendar for selecting single dates or ranges. A date-field calendar for selecting single dates or ranges. ## Installation ```bash railbase ui add calendar ``` > [!NOTE] > `railbase ui add` also copies the `button` component — they ship alongside this component automatically. ## Usage ```tsx import { Calendar } from "@/lib/ui/calendar"; const [date, setDate] = useState(); ``` --- # Card _Section: UI Components · Updated: 2026-06-04 · Source: https://railbase.app/learn/ui-card.md_ > A surface that groups related content and actions. A surface that groups related content and actions. ## Installation ```bash railbase ui add card ``` ## Usage ```tsx import { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter } from "@/lib/ui/card"; Project Deploy in one click. Card body. Footer. ``` ## Anatomy Exported parts: `Card` · `CardHeader` · `CardTitle` · `CardDescription` · `CardContent` · `CardFooter` --- # Carousel _Section: UI Components · Updated: 2026-06-04 · Source: https://railbase.app/learn/ui-carousel.md_ > A slideshow for cycling through images or cards (embla-carousel). A slideshow for cycling through images or cards (embla-carousel). ## Installation ```bash railbase ui add carousel ``` Peer dependencies: ```bash npm install embla-carousel ``` > [!NOTE] > `railbase ui add` also copies the `button` component — they ship alongside this component automatically. ## Usage ```tsx import { Carousel, CarouselContent, CarouselItem, CarouselPrevious, CarouselNext } from "@/lib/ui/carousel"; Slide 1 Slide 2 ``` ## Anatomy Exported parts: `useCarousel` · `Carousel` · `CarouselContent` · `CarouselItem` · `CarouselPrevious` · `CarouselNext` --- # Chart _Section: UI Components · Updated: 2026-06-04 · Source: https://railbase.app/learn/ui-chart.md_ > A set of composable chart components (bar, line, area, pie, …) with tooltips and legends. A set of composable chart components (bar, line, area, pie, …) with tooltips and legends. ## Installation ```bash railbase ui add chart ``` Peer dependencies: ```bash npm install @preact/signals ``` ## Usage ```tsx import { ChartContainer, BarChart } from "@/lib/ui/chart"; ``` ## Anatomy Exported parts: `chartFiltersSignal` · `addChartFilter` · `removeChartFilter` · `toggleChartFilter` · `clearChartFilters` · `isChartFilterActive` · `applyChartFilters` · `ChartTooltip` · `ChartTooltipContent` · `ChartLegend` · `ChartLegendContent` · `ChartStyle` · `BarChart` · `LineChart` · `AreaChart` · `PieChart` · `DonutChart` · `ScatterChart` · `RadarChart` · `RadialBarChart` · `Treemap` · `FunnelChart` · `ComposedChart` · `ChartContainer` · `ChartFilterBar` --- # Checkbox _Section: UI Components · Updated: 2026-06-04 · Source: https://railbase.app/learn/ui-checkbox.md_ > A control that toggles between checked and unchecked. A control that toggles between checked and unchecked. ## Installation ```bash railbase ui add checkbox ``` > [!NOTE] > `railbase ui add` also copies shared primitives — they ship alongside this component automatically. ## Usage ```tsx import { Checkbox } from "@/lib/ui/checkbox"; const [checked, setChecked] = useState(false);
``` --- # Collapsible _Section: UI Components · Updated: 2026-06-04 · Source: https://railbase.app/learn/ui-collapsible.md_ > An interactive section that expands and collapses its content. An interactive section that expands and collapses its content. ## Installation ```bash railbase ui add collapsible ``` > [!NOTE] > `railbase ui add` also copies shared primitives — they ship alongside this component automatically. ## Usage ```tsx import { Collapsible, CollapsibleTrigger, CollapsibleContent } from "@/lib/ui/collapsible"; Toggle Hidden content. ``` ## Anatomy Exported parts: `Collapsible` · `CollapsibleTrigger` · `CollapsibleContent` · `CollapsibleCtx` --- # Combobox _Section: UI Components · Updated: 2026-06-04 · Source: https://railbase.app/learn/ui-combobox.md_ > An autocomplete input — a Popover wrapping a Command list (composed, not a single tag). An autocomplete input — a Popover wrapping a Command list (composed, not a single tag). ## Installation ```bash railbase ui add combobox ``` > [!NOTE] > `railbase ui add` also copies the `command`, `popover` components — they ship alongside this component automatically. ## Usage ```tsx import { Popover, PopoverTrigger, PopoverContent, Command, CommandInput, CommandList, CommandEmpty, CommandGroup, CommandItem } from "@/lib/ui/combobox"; Select framework… No results. Next.js Astro ``` ## Anatomy Exported parts: `Popover` · `PopoverTrigger` · `PopoverContent` · `Command` · `CommandInput` · `CommandList` · `CommandEmpty` · `CommandGroup` · `CommandItem` · `CommandSeparator` --- # Command _Section: UI Components · Updated: 2026-06-04 · Source: https://railbase.app/learn/ui-command.md_ > A fast, composable command menu / command palette. A fast, composable command menu / command palette. ## Installation ```bash railbase ui add command ``` ## Usage ```tsx import { Command, CommandInput, CommandList, CommandEmpty, CommandGroup, CommandItem } from "@/lib/ui/command"; No results found. Calendar Search ``` ## Anatomy Exported parts: `Command` · `CommandInput` · `CommandList` · `CommandEmpty` · `CommandGroup` · `CommandSeparator` · `CommandItem` · `CommandShortcut` --- # Context Menu _Section: UI Components · Updated: 2026-06-04 · Source: https://railbase.app/learn/ui-context-menu.md_ > A menu shown on right-click of an element. A menu shown on right-click of an element. ## Installation ```bash railbase ui add context-menu ``` > [!NOTE] > `railbase ui add` also copies shared primitives — they ship alongside this component automatically. ## Usage ```tsx import { ContextMenu, ContextMenuTrigger, ContextMenuContent, ContextMenuItem } from "@/lib/ui/context-menu"; Right-click here Back Reload ``` ## Anatomy Exported parts: `ContextMenu` · `ContextMenuTrigger` · `ContextMenuContent` · `ContextMenuItem` · `ContextMenuCheckboxItem` · `ContextMenuRadioGroup` · `ContextMenuRadioItem` · `ContextMenuLabel` · `ContextMenuSeparator` · `ContextMenuShortcut` · `ContextMenuGroup` · `ContextMenuPortal` · `ContextMenuSubTriggerChevron` --- # Drawer _Section: UI Components · Updated: 2026-06-04 · Source: https://railbase.app/learn/ui-drawer.md_ > A panel that slides in from an edge of the screen. A panel that slides in from an edge of the screen. ## Installation ```bash railbase ui add drawer ``` > [!NOTE] > `railbase ui add` also copies shared primitives — they ship alongside this component automatically. ## Usage ```tsx import { Drawer, DrawerTrigger, DrawerContent, DrawerHeader, DrawerTitle, DrawerDescription, DrawerFooter, DrawerClose } from "@/lib/ui/drawer"; Open Title Description. Close ``` ## Anatomy Exported parts: `Drawer` · `DrawerTrigger` · `DrawerClose` · `DrawerPortal` · `DrawerOverlay` · `DrawerContent` · `DrawerHeader` · `DrawerFooter` · `DrawerTitle` · `DrawerDescription` --- # Dropdown Menu _Section: UI Components · Updated: 2026-06-04 · Source: https://railbase.app/learn/ui-dropdown-menu.md_ > A menu of actions or options triggered by a button. A menu of actions or options triggered by a button. ## Installation ```bash railbase ui add dropdown-menu ``` > [!NOTE] > `railbase ui add` also copies shared primitives — they ship alongside this component automatically. ## Usage ```tsx import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuItem } from "@/lib/ui/dropdown-menu"; Open My account Profile Sign out ``` ## Anatomy Exported parts: `DropdownMenu` · `DropdownMenuTrigger` · `DropdownMenuContent` · `DropdownMenuItem` · `DropdownMenuCheckboxItem` · `DropdownMenuRadioGroup` · `DropdownMenuRadioItem` · `DropdownMenuLabel` · `DropdownMenuSeparator` · `DropdownMenuShortcut` · `DropdownMenuGroup` · `DropdownMenuPortal` · `DropdownMenuSub` · `DropdownMenuSubTrigger` · `DropdownMenuSubContent` --- # Form _Section: UI Components · Updated: 2026-06-04 · Source: https://railbase.app/learn/ui-form.md_ > Accessible form fields with labels, descriptions, and validation messages. Accessible form fields with labels, descriptions, and validation messages. ## Installation ```bash railbase ui add form ``` > [!NOTE] > `railbase ui add` also copies the `label` component and shared primitives — they ship alongside this component automatically. ## Usage ```tsx import { Form, FormField, FormItem, FormLabel, FormControl, FormDescription, FormMessage } from "@/lib/ui/form";
Email We'll never share it.
``` ## Anatomy Exported parts: `Form` · `FormField` · `useFormField` · `FormItem` · `FormLabel` · `FormControl` · `FormDescription` · `FormMessage` --- # Hover Card _Section: UI Components · Updated: 2026-06-04 · Source: https://railbase.app/learn/ui-hover-card.md_ > A card that appears when hovering over a trigger (for previews). A card that appears when hovering over a trigger (for previews). ## Installation ```bash railbase ui add hover-card ``` > [!NOTE] > `railbase ui add` also copies shared primitives — they ship alongside this component automatically. ## Usage ```tsx import { HoverCard, HoverCardTrigger, HoverCardContent } from "@/lib/ui/hover-card"; @railbase The Railbase team. ``` ## Anatomy Exported parts: `HoverCard` · `HoverCardTrigger` · `HoverCardContent` --- # Input _Section: UI Components · Updated: 2026-06-04 · Source: https://railbase.app/learn/ui-input.md_ > A single-line text input. A single-line text input. ## Installation ```bash railbase ui add input ``` ## Usage ```tsx import { Input } from "@/lib/ui/input"; ``` --- # Input OTP _Section: UI Components · Updated: 2026-06-04 · Source: https://railbase.app/learn/ui-input-otp.md_ > A segmented input for one-time passcodes. A segmented input for one-time passcodes. ## Installation ```bash railbase ui add input-otp ``` > [!NOTE] > `railbase ui add` also copies shared primitives — they ship alongside this component automatically. ## Usage ```tsx import { InputOTP, InputOTPGroup, InputOTPSlot } from "@/lib/ui/input-otp"; ``` ## Anatomy Exported parts: `InputOTP` · `InputOTPGroup` · `InputOTPSlot` · `InputOTPSeparator` · `OtpField` --- # Item _Section: UI Components · Updated: 2026-06-04 · Source: https://railbase.app/learn/ui-item.md_ > A flexible list-row layout: media, content, and actions with variants. A flexible list-row layout: media, content, and actions with variants. ## Installation ```bash railbase ui add item ``` Peer dependencies: ```bash npm install class-variance-authority ``` ## Usage ```tsx import { Item, ItemMedia, ItemContent, ItemTitle, ItemDescription, ItemActions } from "@/lib/ui/item"; Title Description. ``` ## Anatomy Exported parts: `Item` · `ItemMedia` · `ItemContent` · `ItemTitle` · `ItemDescription` · `ItemActions` · `ItemFooter` · `ItemGroup` · `ItemSeparator` --- # Label _Section: UI Components · Updated: 2026-06-04 · Source: https://railbase.app/learn/ui-label.md_ > An accessible label associated with a form control. An accessible label associated with a form control. ## Installation ```bash railbase ui add label ``` ## Usage ```tsx import { Label } from "@/lib/ui/label"; ``` --- # Menubar _Section: UI Components · Updated: 2026-06-04 · Source: https://railbase.app/learn/ui-menubar.md_ > A desktop-style menu bar with menus, items, and submenus. A desktop-style menu bar with menus, items, and submenus. ## Installation ```bash railbase ui add menubar ``` > [!NOTE] > `railbase ui add` also copies shared primitives — they ship alongside this component automatically. ## Usage ```tsx import { Menubar, MenubarMenu, MenubarTrigger, MenubarContent, MenubarItem } from "@/lib/ui/menubar"; File New Open ``` ## Anatomy Exported parts: `Menubar` · `MenubarMenu` · `MenubarTrigger` · `MenubarContent` · `MenubarItem` · `MenubarCheckboxItem` · `MenubarRadioGroup` · `MenubarRadioItem` · `MenubarLabel` · `MenubarSeparator` · `MenubarShortcut` · `MenubarGroup` · `MenubarPortal` · `MenubarSubTriggerChevron` --- # Navigation Menu _Section: UI Components · Updated: 2026-06-04 · Source: https://railbase.app/learn/ui-navigation-menu.md_ > A site navigation menu with dropdown content panels. A site navigation menu with dropdown content panels. ## Installation ```bash railbase ui add navigation-menu ``` Peer dependencies: ```bash npm install class-variance-authority ``` ## Usage ```tsx import { NavigationMenu, NavigationMenuList, NavigationMenuItem, NavigationMenuTrigger, NavigationMenuContent } from "@/lib/ui/navigation-menu"; Products …links… ``` ## Anatomy Exported parts: `NavigationMenu` · `NavigationMenuList` · `NavigationMenuItem` · `navigationMenuTriggerStyle` · `NavigationMenuTrigger` · `NavigationMenuContent` · `NavigationMenuLink` · `NavigationMenuIndicator` · `NavigationMenuViewport` --- # Pagination _Section: UI Components · Updated: 2026-06-04 · Source: https://railbase.app/learn/ui-pagination.md_ > Page navigation with previous/next and page links. Page navigation with previous/next and page links. ## Installation ```bash railbase ui add pagination ``` Peer dependencies: ```bash npm install class-variance-authority ``` > [!NOTE] > `railbase ui add` also copies the `button` component — they ship alongside this component automatically. ## Usage ```tsx import { Pagination, PaginationContent, PaginationItem, PaginationPrevious, PaginationLink, PaginationEllipsis, PaginationNext } from "@/lib/ui/pagination"; 1 ``` ## Anatomy Exported parts: `Pagination` · `PaginationContent` · `PaginationItem` · `PaginationLink` · `PaginationPrevious` · `PaginationNext` · `PaginationEllipsis` --- # Password _Section: UI Components · Updated: 2026-06-04 · Source: https://railbase.app/learn/ui-password.md_ > A password input with strength scoring and a generator (Zod schema included). A password input with strength scoring and a generator (Zod schema included). ## Installation ```bash railbase ui add password ``` Peer dependencies: ```bash npm install zod ``` > [!NOTE] > `railbase ui add` also copies the `input` component — they ship alongside this component automatically. ## Usage ```tsx import { PasswordInput } from "@/lib/ui/password"; setPw(e.currentTarget.value)} /> // passwordSchema, scorePassword(), generatePassword() are also exported ``` ## Anatomy Exported parts: `passwordSchema` · `scorePassword` · `generatePassword` · `PasswordInput` --- # Phone _Section: UI Components · Updated: 2026-06-04 · Source: https://railbase.app/learn/ui-phone.md_ > An international phone input with country detection and E.164 output. An international phone input with country detection and E.164 output. ## Installation ```bash railbase ui add phone ``` > [!NOTE] > `railbase ui add` also copies the `button`, `command`, `input`, `popover` components — they ship alongside this component automatically. ## Usage ```tsx import { PhoneInput } from "@/lib/ui/phone"; // toE164(), isValidE164(), formatNational() helpers are exported ``` ## Anatomy Exported parts: `COUNTRIES` · `isoToFlag` · `formatNational` · `detectCountry` · `toE164` · `splitE164` · `PhoneInput` · `isValidE164` --- # Popover _Section: UI Components · Updated: 2026-06-04 · Source: https://railbase.app/learn/ui-popover.md_ > Floating content anchored to a trigger. Floating content anchored to a trigger. ## Installation ```bash railbase ui add popover ``` > [!NOTE] > `railbase ui add` also copies shared primitives — they ship alongside this component automatically. ## Usage ```tsx import { Popover, PopoverTrigger, PopoverContent } from "@/lib/ui/popover"; Open Popover body. ``` ## Anatomy Exported parts: `Popover` · `PopoverTrigger` · `PopoverAnchor` · `PopoverContent` --- # Progress _Section: UI Components · Updated: 2026-06-04 · Source: https://railbase.app/learn/ui-progress.md_ > A horizontal progress bar. A horizontal progress bar. ## Installation ```bash railbase ui add progress ``` ## Usage ```tsx import { Progress } from "@/lib/ui/progress"; ``` --- # QDatatable _Section: UI Components · Updated: 2026-06-04 · Source: https://railbase.app/learn/ui-QDatatable.md_ > Railbase's schema-driven data table — search, sort, paginate, select, and export over a collection. Railbase's schema-driven data table — search, sort, paginate, select, and export over a collection. ## Installation ```bash railbase ui add QDatatable ``` > [!NOTE] > `railbase ui add` also copies the `badge`, `button`, `chart`, `checkbox`, `dropdown-menu`, `input`, `select`, `skeleton`, `sonner`, `table` components — they ship alongside this component automatically. ## Usage ```tsx import { QDatatable } from "@/lib/ui/QDatatable"; ``` --- # QEditableForm _Section: UI Components · Updated: 2026-06-04 · Source: https://railbase.app/learn/ui-QEditableForm.md_ > A schema-agnostic record form with a create mode (edit all fields) and an edit mode (per-field click-to-edit). A schema-agnostic record form with a create mode (edit all fields) and an edit mode (per-field click-to-edit). ## Installation ```bash railbase ui add QEditableForm ``` > [!NOTE] > `railbase ui add` also copies the `button` component — they ship alongside this component automatically. ## Usage ```tsx import { QEditableForm } from "@/lib/ui/QEditableForm"; save(name, value)} /> ``` --- # QEditableList _Section: UI Components · Updated: 2026-06-04 · Source: https://railbase.app/learn/ui-QEditableList.md_ > An inline-editable list with add/remove rows and a searchable combobox picker. An inline-editable list with add/remove rows and a searchable combobox picker. ## Installation ```bash railbase ui add QEditableList ``` > [!NOTE] > `railbase ui add` also copies the `button`, `checkbox`, `command`, `input`, `popover`, `select`, `tooltip` components — they ship alongside this component automatically. ## Usage ```tsx import { QEditableList } from "@/lib/ui/QEditableList"; ``` --- # Radio Group _Section: UI Components · Updated: 2026-06-04 · Source: https://railbase.app/learn/ui-radio-group.md_ > A set of mutually-exclusive radio options. A set of mutually-exclusive radio options. ## Installation ```bash railbase ui add radio-group ``` > [!NOTE] > `railbase ui add` also copies shared primitives — they ship alongside this component automatically. ## Usage ```tsx import { RadioGroup, RadioGroupItem } from "@/lib/ui/radio-group";
``` ## Anatomy Exported parts: `RadioGroup` · `RadioGroupItem` --- # Resizable _Section: UI Components · Updated: 2026-06-04 · Source: https://railbase.app/learn/ui-resizable.md_ > Resizable, draggable panel layouts. Resizable, draggable panel layouts. ## Installation ```bash railbase ui add resizable ``` ## Usage ```tsx import { ResizablePanelGroup, ResizablePanel, ResizableHandle } from "@/lib/ui/resizable"; Left Right ``` ## Anatomy Exported parts: `ResizablePanelGroup` · `ResizablePanel` · `ResizableHandle` --- # Scroll Area _Section: UI Components · Updated: 2026-06-04 · Source: https://railbase.app/learn/ui-scroll-area.md_ > A scrollable region with styled scrollbars. A scrollable region with styled scrollbars. ## Installation ```bash railbase ui add scroll-area ``` ## Usage ```tsx import { ScrollArea, ScrollBar } from "@/lib/ui/scroll-area"; …long content… ``` ## Anatomy Exported parts: `ScrollArea` · `ScrollBar` --- # Select _Section: UI Components · Updated: 2026-06-04 · Source: https://railbase.app/learn/ui-select.md_ > A dropdown for choosing one option from a list. A dropdown for choosing one option from a list. ## Installation ```bash railbase ui add select ``` > [!NOTE] > `railbase ui add` also copies shared primitives — they ship alongside this component automatically. ## Usage ```tsx import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem } from "@/lib/ui/select"; ``` ## Anatomy Exported parts: `Select` · `SelectTrigger` · `SelectValue` · `SelectContent` · `SelectItem` · `SelectSeparator` · `SelectLabel` · `SelectGroup` · `SelectScrollUpButton` · `SelectScrollDownButton` --- # Separator _Section: UI Components · Updated: 2026-06-04 · Source: https://railbase.app/learn/ui-separator.md_ > A visual divider between content. A visual divider between content. ## Installation ```bash railbase ui add separator ``` ## Usage ```tsx import { Separator } from "@/lib/ui/separator";
Top
Bottom
``` --- # Sheet _Section: UI Components · Updated: 2026-06-04 · Source: https://railbase.app/learn/ui-sheet.md_ > A dialog that slides in from a screen edge (side drawer). A dialog that slides in from a screen edge (side drawer). ## Installation ```bash railbase ui add sheet ``` Peer dependencies: ```bash npm install class-variance-authority ``` > [!NOTE] > `railbase ui add` also copies shared primitives — they ship alongside this component automatically. ## Usage ```tsx import { Sheet, SheetTrigger, SheetContent, SheetHeader, SheetTitle, SheetDescription } from "@/lib/ui/sheet"; Open Edit profile Make changes, then save. ``` ## Anatomy Exported parts: `Sheet` · `SheetTrigger` · `SheetClose` · `SheetPortal` · `SheetOverlay` · `SheetContent` · `SheetHeader` · `SheetFooter` · `SheetTitle` · `SheetDescription` --- # Sidebar _Section: UI Components · Updated: 2026-06-04 · Source: https://railbase.app/learn/ui-sidebar.md_ > A full application sidebar system: provider, collapsible rail, groups, and menus. A full application sidebar system: provider, collapsible rail, groups, and menus. ## Installation ```bash railbase ui add sidebar ``` Peer dependencies: ```bash npm install class-variance-authority ``` > [!NOTE] > `railbase ui add` also copies the `button`, `input`, `separator`, `sheet`, `skeleton`, `tooltip` components and shared primitives — they ship alongside this component automatically. ## Usage ```tsx import { SidebarProvider, Sidebar, SidebarContent, SidebarGroup, SidebarMenu, SidebarMenuItem, SidebarMenuButton, SidebarInset, SidebarTrigger } from "@/lib/ui/sidebar"; Home {/* page content */} ``` ## Anatomy Exported parts: `useSidebar` · `SidebarProvider` · `Sidebar` · `SidebarTrigger` · `SidebarRail` · `SidebarInset` · `SidebarInput` · `SidebarHeader` · `SidebarFooter` · `SidebarSeparator` · `SidebarContent` · `SidebarGroup` · `SidebarGroupLabel` · `SidebarGroupContent` · `SidebarMenu` · `SidebarMenuItem` · `SidebarMenuAction` · `SidebarMenuButton` · `SidebarMenuSub` · `SidebarMenuSubItem` · `SidebarMenuSubButton` · `SidebarMenuSkeleton` --- # Skeleton _Section: UI Components · Updated: 2026-06-04 · Source: https://railbase.app/learn/ui-skeleton.md_ > A placeholder shimmer shown while content loads. A placeholder shimmer shown while content loads. ## Installation ```bash railbase ui add skeleton ``` ## Usage ```tsx import { Skeleton } from "@/lib/ui/skeleton"; ``` --- # Slider _Section: UI Components · Updated: 2026-06-04 · Source: https://railbase.app/learn/ui-slider.md_ > A draggable control for selecting a value from a range. A draggable control for selecting a value from a range. ## Installation ```bash railbase ui add slider ``` > [!NOTE] > `railbase ui add` also copies shared primitives — they ship alongside this component automatically. ## Usage ```tsx import { Slider } from "@/lib/ui/slider"; ``` --- # Sonner _Section: UI Components · Updated: 2026-06-04 · Source: https://railbase.app/learn/ui-sonner.md_ > Toast notifications — mount `` once, then call `toast()` anywhere. Toast notifications — mount `` once, then call `toast()` anywhere. ## Installation ```bash railbase ui add sonner ``` Peer dependencies: ```bash npm install @preact/signals ``` > [!NOTE] > `railbase ui add` also copies shared primitives — they ship alongside this component automatically. ## Usage ```tsx import { Toaster } from "@/lib/ui/sonner"; // app root: // anywhere: toast("Saved"); toast.error("Something went wrong"); ``` ## Anatomy Exported parts: `dismissToast` · `toast` · `Toaster` --- # Switch _Section: UI Components · Updated: 2026-06-04 · Source: https://railbase.app/learn/ui-switch.md_ > An on/off toggle. An on/off toggle. ## Installation ```bash railbase ui add switch ``` > [!NOTE] > `railbase ui add` also copies shared primitives — they ship alongside this component automatically. ## Usage ```tsx import { Switch } from "@/lib/ui/switch"; const [on, setOn] = useState(false); ``` --- # Table _Section: UI Components · Updated: 2026-06-04 · Source: https://railbase.app/learn/ui-table.md_ > A semantic data table with header, body, and footer parts. A semantic data table with header, body, and footer parts. ## Installation ```bash railbase ui add table ``` ## Usage ```tsx import { Table, TableHeader, TableRow, TableHead, TableBody, TableCell } from "@/lib/ui/table"; NameEmail Adaada@x.com
``` ## Anatomy Exported parts: `Table` · `TableHeader` · `TableBody` · `TableFooter` · `TableRow` · `TableHead` · `TableCell` · `TableCaption` --- # Tabs _Section: UI Components · Updated: 2026-06-04 · Source: https://railbase.app/learn/ui-tabs.md_ > Layered sections of content shown one panel at a time. Layered sections of content shown one panel at a time. ## Installation ```bash railbase ui add tabs ``` > [!NOTE] > `railbase ui add` also copies shared primitives — they ship alongside this component automatically. ## Usage ```tsx import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/lib/ui/tabs"; Account Password Account panel. Password panel. ``` ## Anatomy Exported parts: `Tabs` · `TabsList` · `TabsTrigger` · `TabsContent` --- # Textarea _Section: UI Components · Updated: 2026-06-04 · Source: https://railbase.app/learn/ui-textarea.md_ > A multi-line text input. A multi-line text input. ## Installation ```bash railbase ui add textarea ``` ## Usage ```tsx import { Textarea } from "@/lib/ui/textarea";