grounds.yaml, and a connection your code reads from the environment.
The model for you
Messaging on Grounds follows one rule: you only get what you declare.- You add an
events:block togrounds.yamllisting each subject and whether you publish to it, subscribe to it, or both. - When you push, the platform records that declaration against your app and injects a connection — a
NATS_URLand a short-lived identity token — into your pod. - When your app connects, the broker grants it permissions derived from your declaration. If you declared
dir: pubonmatch.ended, you can publish that subject and nothing else; you can’t subscribe to it, and you can’t touch subjects you never asked for.
If your app has no
events: block, it’s simply not a bus participant — no NATS_URL, no token, no connection. Messaging is opt-in per app.Declaring events
Add anevents: block to your grounds.yaml. Each entry is a subject plus a direction:
grounds.yaml
| Field | What it means |
|---|---|
subject | The NATS subject. Must be lowercase, start with a letter, and contain only letters, digits, dots, and the NATS wildcards * (one token) and > (everything below). |
dir | pub, sub, or both. Defaults to both. Controls which permissions you’re granted for that subject. |
The connection you get
After a push with anevents: block, your pod has two values injected into its environment:
| Variable | What it is |
|---|---|
NATS_URL | The broker your app should connect to. The platform sets the right value for where your app landed — you don’t choose it. |
GROUNDS_TOKEN_FILE | Path to a short-lived identity token file your client presents when connecting. The platform rotates it for you (~1h, picked up live — no pod restart). |
- Grounds SDK (recommended)
- Raw jnats
The SDK reads the injected environment and presents the token for you:You never touch the URL or the token. Connect, subscribe to subjects you declared
sub/both on, publish to subjects you declared pub/both on.A freshly-changed
events: block can take up to about a minute to take effect after a push, because the permission lookup is briefly cached. If a new subscription doesn’t work immediately, give it a moment and reconnect.Subject isolation
Subjects are plain, lowercase strings on a shared bus. Two apps that both publish a bareconfig.updated are publishing the same subject — so today, pick subject names that are unlikely to collide (prefix them with your app or domain, e.g. mobrush.results rather than results).
The permission layer keeps you from reading or writing subjects you didn’t declare — that’s enforced and is the real security boundary. What isn’t automatic yet is namespacing: a richer per-project / per-target subject prefix that would make collisions structurally impossible is designed but not wired into the bus you connect to. Until it ships, naming discipline is your isolation against name clashes within the shared bus.
The deny-by-default permission model — you can only touch subjects you declared — is the shipped behavior of the security layer. Automatic per-tenant subject prefixing is the part that’s still on the roadmap. Don’t rely on prefix-based isolation existing yet; rely on declaring narrowly and naming your subjects distinctly.
What works today
Be realistic about maturity before you design around this.Cross-server within one server set — proven
Two Minecraft servers behind the same bus relay messages by both declaring compatible subjects. This is verified end to end (a chat / direct-message relay between two Paper servers over one NATS), and it’s the working reference for cross-server coordination.
Cross-app between separate pushes — early
Two independently pushed apps doing a full publish-then-consume roundtrip through the supported push flow has not been demonstrated. The permission scoping is wired and the security model is sound, but no app to bus to app message has been confirmed flowing the whole way. Treat it as roadmap.
Declaring events on your pushed app — works
Declaring events on your pushed app — works
Your
grounds.yaml events: block is parsed, carried through the push, and recorded against your app’s identity. Your pod gets NATS_URL and the token. This is real today for any pushed app that declares events.Publishing from your own app — works at the infra level
Publishing from your own app — works at the infra level
A pushed app declaring
dir: pub (for example a gamemode publishing mobrush.results) is wired to publish. The permission set grants exactly that, and the connection is scoped correctly. The publish path is the more mature direction.Subscribing to and consuming another app's events — not yet proven e2e
Subscribing to and consuming another app's events — not yet proven e2e
No two independently-pushed apps have been shown to complete a publish-then-consume roundtrip through the supported flow. The internal debug tooling that reads the bus is not a pushed app and doesn’t count as proof of the developer path. If your design depends on app B reliably consuming app A’s events, that’s unproven today — build a fallback or check in with the platform team before committing to it.
Persistence / replay of past events — not available
Persistence / replay of past events — not available
The bus delivers live messages to connected subscribers. There is no retained-history or replay stream you can rely on yet — an app that wasn’t connected when a message was published won’t see it later.
Limits and caveats
- Subjects are flat strings. Names are your only collision protection today; prefix them. Automatic per-tenant prefixing is roadmap.
- Opt-in only. No
events:block means no bus access — there’s no implicit connection. - Token rotates. Re-read
GROUNDS_TOKEN_FILEon each connect (or use the SDK) so rotation stays transparent; a connect-once raw client holds its original scope until it reconnects. - Declaration changes lag briefly. Allow up to ~a minute after a push for a changed
events:block to take effect. - No replay. Only live messages reach connected subscribers; there’s no history fetch you can depend on.
- Cross-app delivery is unproven. Publishing works; another pushed app reliably consuming it does not, yet.
Related
Manifest reference
Every field of
grounds.yaml, including where the events: block sits in your app definition.Pushes
How a push goes from upload to a running pod — the step that injects your connection.
Observability
Where to find your deployment logs and metrics when you’re debugging what your app sent or received.
Platform test environment
Spin up an isolated environment to iterate on a component that uses the bus.
