Railbase
GPTClaude

How plugins work

Pull-only acquisition, signed artifacts, verification, and the subprocess runtime.

Updated

Video guide —watch on YouTube ↗

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.

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:

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/<slug>/… 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.
  • 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.

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.