GUIDE 12 / 28 · NATIVE 2.0.7

Plugin buttons & panels

Build sidebar actions and custom HTML panels, including persistence, icon names and safe dynamic content.

Button locations and routing#

PropertyUse
id, labelStable registration key and visible action label.
locationsidebar_header, sidebar_actions or custom_tab. Custom tab action buttons belong in the tab’s actions array.
actionIdPassed to onAction (or its legacy fallback).
openTabIdOpens the matching tab; sidebar clicks can both open a tab and invoke the action.
icon, tooltipOptional symbol and hover text.
variantdefault, primary, danger, accent in the type contract. Styling varies by location; custom tab explicitly differentiates danger and accent.

A tab action bar supplies { tabId: tab.id } as action data. Do not assume a sidebar click supplies the same object. The handler should accept missing data and verify IDs.

Register and update a panel#

JavaScript
module.exports = {
  onInit(context) {
    context.ui.registerTab({
      id: 'status', title: 'Status', icon: 'terminal',
      actions: [{ id: 'refresh', label: 'Refresh',
        location: 'custom_tab', actionId: 'refresh' }]
    });
    context.ui.registerButton({
      id: 'open-status', label: 'Status', icon: 'terminal',
      location: 'sidebar_actions', actionId: 'refresh', openTabId: 'status'
    });
    context.ui.setTabContent('status', '<h3>Ready</h3><p>Plugin loaded.</p>');
  },
  onAction(actionId, context) {
    if (actionId !== 'refresh') throw new Error('Unknown action');
    context.ui.setTabContent('status', '<p>Updated: ' + new Date().toISOString() + '</p>');
  }
};

Register first, then update content. Content updates to an unknown tab are ignored. Use short, semantic HTML and the native action bar to run backend work.

HTML content and escaping#

Custom tab content is inserted with React dangerouslySetInnerHTML into the main interface. It is not an iframe and has no isolated iframe sandbox. Script tags inserted this way are not a supported event-binding mechanism. Use declared actions for interaction rather than inline handlers.

JavaScript
function escapeHTML(value) {
  return String(value).replace(/[&<>"']/g, character => ({
    '&': '&amp;', '<': '&lt;', '>': '&gt;',
    '"': '&quot;', "'": '&#39;'
  })[character]);
}
// Escape user-controlled names before including them in HTML.
context.ui.setTabContent('status', '<p>' + escapeHTML(clip.name) + '</p>');

Do not insert raw remote response bodies, user-supplied attributes or global CSS selectors into tab content. Keep CSS scoped to a plugin-specific wrapper so it does not alter HydroClips controls.

Icons and persistence#

Sidebar and custom tab mappings include zap, play, sparkles, radio, camera, sliders, terminal, bot, message-square, cloud and refresh. Unrecognized names fall back to Puzzle. The plugin-list avatar has a smaller mapping, so an icon may look different there.

Register/remove calls write the manifest. updateTab and setTabContent update memory and the renderer; reload reconstructs UI from the manifest plus onInit. Recreate dynamic content in onInit or derive it from saved config.

Implementation reference: PluginCustomTab.tsx; HydroLiquidSidebar.tsx; src/main/plugins/PluginManager.ts · native 2.0.7.