SuperAPI AI Directory

using-backstage-permissions

Security & auditing

View repository

Updated 10-16 02:24

About this skill

Backstage Permissions Patterns

When to use this skill

  • Plugin needs to hide a button, menu, or page from users without the right permission (frontend)
  • Plugin's route handler needs to authorize the caller before performing an action (backend)
  • Defining new permissions for a plugin
  • Checking permissions per-resource ("can update this component", not "can update components in general")

Choose the right pattern

Declaring permissions

Building... Pattern Reference
A new permission for your plugin createPermission({ name, attributes }) in a *-common library references/declaring.md

Frontend (in a plugin component)

Situation Pattern Reference
Conditionally render UI based on a permission usePermission({ permission }) hook references/frontend.md
Wrap an entire subtree in a permission gate <RequirePermission permission={...}>...</RequirePermission> references/frontend.md

Backend (in a route handler or scheduled task)

Situation Pattern Reference
Authorize a basic action permission coreServices.permissions.authorize([{ permission }], { credentials }) references/backend.md
Authorize per-resource (with conditional decisions) permissions.authorizeConditional([{ permission, resourceRef }], { credentials }) + permissionsRegistry.addResourceType(...) references/resource-permissions.md

Two invocation modes

  • Standalone (mid-development): user is editing an existing plugin. Pick the right reference based on what's being added — defining a permission, gating UI, or authorizing on the backend.
  • Post-scaffold (called by creating-backstage-plugin): when intent describes "only admins can…", "the current user must have permission to…", or any access-control language. Add the relevant patterns to the freshly scaffolded plugin.

Common gotchas

  • Permissions are declared once, used twice. The permission object lives in a *-common (or *-permissions-common) library so both the frontend (usePermission) and the backend (permissions.authorize) import the same definition. Don't redeclare the same permission in two places.
  • The host app must enable the permission framework. Adopters set permission.enabled: true in app-config.yaml and install a PermissionPolicy. If permissions aren't enabled, authorize always returns AuthorizeResult.ALLOW — your plugin code is correct, just unenforced. Document the prerequisite in your plugin README.
  • permissions.authorize returns an array matching the order of requests. For a single permission, destructure const [decision] = await permissions.authorize(...) — and the decision is decision.result === AuthorizeResult.ALLOW, NOT a boolean.
  • For resource permissions, prefer authorizeConditional over authorize when the decision can depend on the resource (e.g. "owner can update"). It returns conditional decisions you translate into a database-level filter, avoiding the need to load every candidate resource into memory.
  • usePermission is async. Returns { allowed, loading, error } — render a <Skeleton /> while loading, otherwise users see UI flicker as access is determined.
  • For backend plugins, coreServices.permissions and coreServices.permissionsRegistry come from @backstage/backend-plugin-api. They're plugin-scoped — your plugin gets a PermissionsService that already knows its own pluginId.