Downcity
Plugins

Plugin Actions

Understand command, api, execute, and allowWhenDisabled in plugin actions

Plugin Actions

Plugin actions are the most direct explicit capability surface of a plugin.

An action is mainly built from:

  • allowWhenDisabled
  • command
  • api
  • execute

The core layer is execute

Minimal example:

actions: {
  status: {
    execute: async ({ context }) => {
      return {
        success: true,
        data: {
          rootPath: context.rootPath,
        },
      };
    },
  },
}

execute is responsible for:

  • receiving structured payload
  • running the business logic
  • returning success / data / error / message

command

command handles CLI-side mapping.

It is a good place to define:

  • help text
  • commander arguments and options
  • how CLI input becomes structured payload

api

api handles HTTP-side input mapping.

It is a good place to define:

  • method
  • path
  • how an HTTP request becomes structured payload

allowWhenDisabled

This is one of the easiest plugin-specific details to miss.

It means:

  • this action is still allowed even if the plugin is currently disabled

Typical cases:

  • status
  • install
  • configure
  • models

In other words, the actions you often need precisely because the plugin is not yet fully usable.

Without allowWhenDisabled: true, normal actions on a disabled plugin are blocked.

What context does execute receive

Plugin action execution is designed around the PluginCommandContext shape.

Practically, you can think of it as:

  • a stable minimal execution context for plugin work

It usually includes:

  • cwd
  • rootPath
  • logger
  • config
  • env
  • globalEnv
  • paths
  • pluginConfig

A slightly fuller example

actions: {
  use: {
    allowWhenDisabled: true,
    command: {
      description: "switch the default provider",
      mapInput({ args }) {
        return {
          provider: String(args[0] || ""),
        };
      },
    },
    execute: async ({ payload }) => {
      const provider = String((payload as { provider?: unknown }).provider || "").trim();
      if (!provider) {
        return {
          success: false,
          error: "provider is required",
          message: "provider is required",
        };
      }
      return {
        success: true,
        data: { provider },
      };
    },
  },
}