GUIDE 15 / 28 · NATIVE 2.0.7

Actions & data contracts

Route buttons to backend handlers, validate payloads and make repeated clicks predictable.

From a button to a handler#

A button supplies pluginId, actionId and optional data to the native bridge. The manager checks that the plugin exists and is enabled, creates its context, then awaits onAction. It uses onButtonAction only if onAction is absent. Register buttons with stable IDs; button IDs identify UI entries while action IDs identify behavior.

A sidebar button with openTabId opens that tab and invokes its action. Implement the opening/refresh action even when the tab already contains content. The custom tab action bar passes a tabId object; sidebar actions can have undefined data.

Validate and dispatch explicitly#

JavaScript
module.exports = {
  onAction(actionId, context, data) {
    switch (actionId) {
      case 'refresh':
        if (data && typeof data.tabId !== 'string') throw new Error('Invalid tabId');
        context.ui.setTabContent('status', '<p>Ready</p>');
        return { refreshed: true };
      case 'reset':
        context.saveConfig({ count: 0 });
        return { count: 0 };
      default: throw new Error('Unknown action: ' + actionId);
    }
  }
};

Whitelist actions and fields. Do not treat arbitrary action data as a local path, JavaScript expression or destination URL. Prefer values already checked by the plugin's configuration logic.

Success, errors and busy states#

Thrown errors are logged and returned as a failure object. Returning a value yields a success object with that result. With no action handler, the manager logs the action and returns success without meaningful work. The app's action wrapper converts failure responses into UI notifications.

Native tab action buttons disable their own loading action temporarily, but this is not a global transaction lock. Other entry points can invoke the same action. Keep a module-level busy flag for expensive operations, and make actions idempotent where repeated clicks should have the same effect.

Recorder controls and unsupported operations#

The plugin context does not expose startRecording, saveReplay, deleteClip or an arbitrary native-command sender. An onRecordingStart hook observes the event; it does not create a public recording-control API. Avoid reaching through the tab's DOM into the application's private bridge.

Use clip metadata from hooks and native-declared action buttons for plugin features. If a feature requires a new recorder API, implement and validate that API in the native application first rather than inventing a context method in a plugin.

Implementation reference: PluginManager.executePluginAction; App.tsx; src/native/backend.ts · native 2.0.7.