Skip to main content
Version: Next

Actions Registry (alpha)

Overview

The Actions Registry Service is a core service designed to provide a distributed registry for actions that can be executed within Backstage backend plugins. This service allows plugins to register reusable actions with well-defined schemas and execution logic, promoting consistency and reusability across the Backstage ecosystem.

Action Structure

Each action registered with the service must conform to the ActionsRegistryActionOptions type, which includes:

Required Properties

  • name: A unique identifier for the action (string)
  • title: A human-readable title for the action (string)
  • description: A detailed description of what the action does (string)
  • schema: Object containing schema definitions
    • input: Function that returns a Zod schema for validating input
    • output: Function that returns a Zod schema for validating output
    • secrets: (optional) Function that returns a Zod schema for validating secrets. See Secrets below.
  • action: The async function that executes the action logic

Optional Properties

  • visibilityPermission: A BasicPermission that controls visibility and access to the action through the permissions framework. See Permissions below.
  • attributes: Object containing behavioral flags:
    • destructive: Boolean indicating if the action modifies or deletes data
    • idempotent: Boolean indicating if running the action multiple times produces the same result
    • readOnly: Boolean indicating if the action only reads data without modifications

Action Context

When an action is executed, it receives a context object (ActionsRegistryActionContext) containing:

  • input: The validated input data matching the defined input schema
  • secrets: The validated secrets data matching the defined secrets schema, or undefined if no secrets schema is declared
  • logger: A LoggerService instance for logging within the action
  • credentials: BackstageCredentials for authentication and authorization

Using the Service

Registering an Action

Here's an example of how to register an action with the Actions Registry Service:

import { ActionsRegistryService } from '@backstage/backend-plugin-api/alpha';

export function registerMyActions(actionsRegistry: ActionsRegistryService) {
// Register a simple read-only action
actionsRegistry.register({
name: 'fetch-user-info',
title: 'Fetch User Information',
description: 'Retrieves user information from the catalog',
schema: {
input: z =>
z.object({
userRef: z.string(),
includeGroups: z.boolean().optional(),
}),
output: z =>
z.object({
user: z.object({
name: z.string(),
email: z.string(),
groups: z.array(z.string()).optional(),
}),
}),
},
attributes: {
readOnly: true,
idempotent: true,
},
action: async ({ input, logger, credentials }) => {
logger.info(`Fetching user info for ${input.userRef}`);

// Perform the action logic here
const user = await fetchUserFromCatalog(input.userRef, credentials);

return {
output: {
user: {
name: user.name,
email: user.email,
groups: input.includeGroups ? user.groups : undefined,
},
},
};
},
});

// Register a destructive action
actionsRegistry.register({
name: 'delete-entity',
title: 'Delete Entity',
description: 'Removes an entity from the catalog',
schema: {
input: z =>
z.object({
entityRef: z.string(),
force: z.boolean().optional(),
}),
output: z =>
z.object({
deletedEntities: z.array(z.string()),
}),
},
attributes: {
destructive: true,
idempotent: false,
},
action: async ({ input, logger, credentials }) => {
logger.warn(`Deleting entity ${input.entityRef}`);

// Perform the deletion logic here
const { deletedEntities } = await deleteEntityFromCatalog(
input.entityRef,
input.force,
credentials,
);

return {
output: deletedEntities,
};
},
});
}

Accessing the Service in a Plugin

To use the Actions Registry Service in your plugin, access it through dependency injection:

import {
createBackendPlugin,
coreServices,
} from '@backstage/backend-plugin-api';
import { actionsRegistryServiceRef } from '@backstage/backend-plugin-api/alpha';

export const myPlugin = createBackendPlugin({
pluginId: 'my-plugin',
register(env) {
env.registerInit({
deps: {
actionsRegistry: actionsRegistryServiceRef,
logger: coreServices.logger,
},
async init({ actionsRegistry, logger }) {
logger.info('Registering actions...');
registerMyActions(actionsRegistry);
logger.info('Actions registered successfully');
},
});
},
});

Permissions

Actions can optionally declare a visibilityPermission to control visibility and access through the Backstage permissions framework. The visibilityPermission must be a BasicPermission (not a resource permission). When set, the action is only visible in listings and accessible by callers who are authorized.

When accessed via the Actions Service or the /.backstage/actions/v1/... HTTP endpoints, actions that are denied by the permission policy are filtered from list results and return a 404 Not Found on invocation, as if they don't exist.

Permissions declared on actions are automatically registered with the PermissionsRegistryService so they appear in the permission policy system.

Adding a Permission to an Action

import { createPermission } from '@backstage/plugin-permission-common';

// Define a permission for your action
const myDeletePermission = createPermission({
name: 'my-plugin.actions.deleteEntity',
attributes: { action: 'delete' },
});

actionsRegistry.register({
name: 'delete-entity',
title: 'Delete Entity',
description: 'Removes an entity from the catalog',
visibilityPermission: myDeletePermission,
schema: {
input: z => z.object({ entityRef: z.string() }),
output: z => z.object({ deleted: z.boolean() }),
},
action: async ({ input }) => {
// action logic
return { output: { deleted: true } };
},
});

Actions without a visibilityPermission field remain visible and accessible by all callers, preserving backwards compatibility.

Secrets

Actions can declare a secrets schema to request external credentials from the end user, such as API tokens, personal access tokens, or other sensitive values that are not part of Backstage's own authentication system. Secrets are kept separate from the input schema so they never appear in tool definitions or LLM context when actions are exposed as MCP tools.

Declaring a Secrets Schema

Add a secrets function to the schema object alongside input and output. It works the same way as the input schema, receiving the Zod instance and returning a Zod object schema:

actionsRegistry.register({
name: 'create-issue',
title: 'Create GitHub Issue',
description: 'Creates an issue in a GitHub repository',
schema: {
input: z =>
z.object({
repo: z.string(),
title: z.string(),
body: z.string().optional(),
}),
output: z =>
z.object({
issueUrl: z.string(),
}),
secrets: z =>
z.object({
githubToken: z
.string()
.describe('GitHub Personal Access Token with repo scope'),
}),
},
attributes: {
destructive: false,
},
action: async ({ input, secrets, credentials }) => {
const octokit = new Octokit({ auth: secrets.githubToken });

const { data } = await octokit.issues.create({
owner: input.repo.split('/')[0],
repo: input.repo.split('/')[1],
title: input.title,
body: input.body,
});

return { output: { issueUrl: data.html_url } };
},
});

The secrets field in the action context is fully typed based on the declared schema. Actions without a secrets schema receive undefined for the secrets field.

How Secrets Flow Through the System

Secrets are validated against the Zod schema the same way input is validated. If secrets are required but not provided, or if they fail validation, the action returns an InputError. If secrets are provided to an action that does not declare a secrets schema, the request is also rejected.

The secrets schema is included in the action metadata returned by the list endpoint, so callers can discover which secrets an action requires before invoking it.

Best Practices

Naming Conventions

  • Use kebab-case: Action names should be in kebab-case (e.g., fetch-user-info, create-repository)
  • Be Descriptive: Choose names that clearly describe what the action does
  • Avoid Redundancy: Don't include plugin names in action names since the plugin context is separate
  • Use Verbs: Start action names with verbs that describe the operation (e.g., fetch, create, delete, update)

Action Attributes Reference

AttributeTypeDefaultDescription
destructivebooleantrueIndicates the action modifies or deletes data. Use with caution.
idempotentbooleanfalseIndicates the action can be run multiple times with the same result
readOnlybooleanfalseIndicates the action only reads data without making modifications

These attributes help consumers of actions understand their behavior and implement appropriate safeguards, retries, or optimizations based on the action's characteristics.