> ## Documentation Index
> Fetch the complete documentation index at: https://grounds-docs-scene-editor-design.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Integrate In-Game Permissions

> Configure the Grounds Velocity plugin or Minestom module and evaluate player permissions from local snapshots.

Use the Grounds permissions modules when a Velocity proxy or Minestom
gameserver needs to evaluate in-game permissions. Each runtime fetches a
complete snapshot through the private REST API at login and evaluates later
checks locally.

## Declare service and event access

Managed applications declare permission-service access, NATS subject access,
and transport on their component in the platform bundle. Use the environment
field supported by the component's chart:

<Tabs>
  <Tab title="Velocity">
    ```yaml theme={null}
    components:
      velocity:
        type: plugin-velocity-base
        chart:
          url: oci://ghcr.io/groundsgg/charts/grounds-velocity
          version: 0.8.0
        image: ghcr.io/groundsgg/velocity
        version: 0.13.0
        services:
          permissions:
            version: v1
            access:
              - capability: snapshot:read
              - capability: catalog:register
                resources:
                  - plugin-permissions
                  - plugin-agones
                  - plugin-chat
        helm:
          env:
            - name: NATS_URL
              valueFrom: shared.nats
        events:
          - subject: permissions.snapshot.invalidated
            dir: sub
    ```
  </Tab>

  <Tab title="Minestom (grounds-gamemode)">
    ```yaml theme={null}
    components:
      minestom-lobby:
        type: gamemode
        chart:
          url: oci://ghcr.io/groundsgg/charts/grounds-gamemode
          version: 0.8.0
        image: ghcr.io/groundsgg/minestom-lobby
        version: 1.4.0
        services:
          permissions:
            version: v1
            access:
              - capability: snapshot:read
        helm:
          kind: lobby
          extraEnv:
            - name: NATS_URL
              valueFrom: shared.nats
        events:
          - subject: permissions.snapshot.invalidated
            dir: sub
    ```
  </Tab>
</Tabs>

`snapshot:read` allows the workload to fetch player snapshots.
`catalog:register` allows it to replace manifests only for the listed source
IDs. These are application-level bundle declarations, not the environment
variables or volumes rendered into the pod.

The platform projection turns `services.permissions.access` into the private
REST endpoint, token-file path, and exact REST authorization. It turns
`events:` into NATS subject authorization enforced by a projected workload
identity. The chart-specific `helm.env` or `helm.extraEnv` entry separately
passes the shared broker endpoint as `NATS_URL`. Declaring an event never
configures `NATS_URL` by itself.

A permissions service declaration without an access entry grants no
permission-service access, even when the event subscription is present.

<Tip>
  Declare only the capabilities and catalog sources your workload uses. The
  platform derives runtime credentials and exact authorization from the service
  and event declarations, while the chart environment selects the transport.
</Tip>

## Configuration

| Variable                                     | Required when enabled | Managed behavior                  | Purpose                                                                |
| -------------------------------------------- | --------------------- | --------------------------------- | ---------------------------------------------------------------------- |
| `PERMISSIONS_SERVICE_URL`                    | Yes                   | Injected                          | Base URL of the permission instance assigned to the workload.          |
| `PERMISSIONS_TOKEN_FILE`                     | Yes                   | Injected                          | Path to the projected token used for private REST requests.            |
| `NATS_URL`                                   | For live invalidation | Declared explicitly by the bundle | Broker endpoint used to receive snapshot invalidations.                |
| `GROUNDS_TOKEN_FILE`                         | When NATS requires it | Injected for managed workloads    | Path to the projected token used for the authorized NATS subscription. |
| `PERMISSIONS_SNAPSHOT_INVALIDATIONS_SUBJECT` | No                    | Default subject                   | Overrides `permissions.snapshot.invalidated`.                          |
| `GROUNDS_PERMISSION_SERVER_TYPE`             | No                    | Runtime-specific default          | Resolves server-type-scoped grants, for example `lobby`.               |
| `GROUNDS_PERMISSION_SERVER_ID`               | No                    | Unset                             | Resolves grants scoped to one server.                                  |
| `GROUNDS_PERMISSION_ENVIRONMENT`             | No                    | Set by the platform when needed   | Resolves environment-scoped grants, such as `stage` or `prod`.         |
| `PERMISSIONS_REFRESH_INTERVAL_SECONDS`       | No                    | `60`                              | Interval for refreshing snapshots of online players.                   |

The integration is enabled when both `PERMISSIONS_SERVICE_URL` and
`PERMISSIONS_TOKEN_FILE` are present. Supplying only one fails startup. Managed
workloads receive both values from the platform; do not override them to call a
central instance or an instance assigned to another project.

The client reads the projected token file for every request, so routine token
rotation does not require a workload restart. Never copy the token into a
manifest, environment variable, or log message. The bundle declares access and
the broker endpoint; the platform projection injects the resulting token-file
paths. Put only token-file paths in configuration, never token values.

## Velocity

Install the Velocity permissions plugin with your proxy. At proxy startup it
registers the REST snapshot client, loads player snapshots during login,
refreshes online players on the configured interval, and exposes the local
`Permissions` service to your plugin code.

Use the shared API for an unscoped check:

```kotlin theme={null}
import gg.grounds.permissions.Permissions
import java.util.UUID

fun canModerateChat(permissions: Permissions, playerId: UUID): Boolean =
    permissions.hasPermission(playerId, "network.chat.moderate")
```

Pass an explicit scope only when your code must override the runtime's default
scope. The configured environment, server type, and server ID form that default
scope:

```kotlin theme={null}
import gg.grounds.permissions.PermissionCheckScope
import gg.grounds.permissions.Permissions
import java.util.UUID

fun canUseLobbyCommand(permissions: Permissions, playerId: UUID): Boolean =
    permissions.hasPermission(
        playerId,
        "network.lobby.command.use",
        PermissionCheckScope.serverType("lobby"),
    )
```

## Minestom

Add the Minestom permissions module to the Grounds module runtime. The module
registers `Permissions` in `GroundsServerContext.services` while it is
installed. Discover and select its `grounds.permissions` provider, then resolve
the service from the code that handles your player action.

```kotlin theme={null}
import gg.grounds.modules.require
import gg.grounds.permissions.Permissions
import gg.grounds.runtime.GroundsServerContext
import gg.grounds.runtime.core.GroundsServer
import java.util.UUID

fun main() {
    GroundsServer.builder()
        .discoverProviders()
        .useProvider("grounds.permissions")
        .start()
}

fun canJoinQueue(ctx: GroundsServerContext, playerId: UUID): Boolean {
    val permissions = ctx.services.require<Permissions>()
    return permissions.hasPermission(playerId, "network.queue.join")
}
```

The Minestom module uses the configured server type. If you do not set
`GROUNDS_PERMISSION_SERVER_TYPE`, it uses the Grounds server type from the
module context.

## Snapshot lifecycle and outages

1. The runtime fetches a snapshot through the private runtime REST API during
   player login.
2. For an online player, the runtime receives a NATS invalidation containing
   only the schema version and Minecraft UUID.
3. The runtime force-fetches the authoritative snapshot through private REST
   and atomically replaces its local snapshot after a successful response.
4. A failed invalidation refresh keeps the previous snapshot. Once
   `refreshAfter` passes, the periodic sweep retries the assigned permission
   instance while that snapshot remains usable.
5. Snapshot expiry remains the fail-closed boundary: an expired snapshot makes
   every check return `false`.
6. If login cannot obtain a valid snapshot, the runtime denies that login.

The invalidation carries no roles, grants, permissions, username, or project
identifier. It is a prompt to re-read state, not an authorization result.
Receiving it does not replace the independently authorized `snapshot:read`
REST request. Events for offline players do not trigger a fetch because their
next login loads the current snapshot.

If NATS is unavailable during startup or reconnect, the runtime continues to
serve from valid local snapshots. Login loading, the periodic `refreshAfter`
sweep, and expiry provide recovery without making event transport part of the
authorization decision.

This fail-closed behavior prevents a temporary service outage from granting
unverified permissions. Handle a `false` result as an ordinary authorization
denial rather than retrying a remote call.

## Next steps

* [Register your permission nodes](/reference/plugins/in-game-permissions/permission-catalog)
* [Understand the permission runtime topology](/reference/plugins/in-game-permissions/infrastructure)
* [Manage roles and player access](/reference/plugins/in-game-permissions/administration)
