Frontend SDK
The frontend bundle must register exactly once with the same plugin ID as manifest.json.
import { definePlugin, registerPlugin } from "@haloforge/plugin-sdk";import { MyPanel } from "./Panel";import "./styles.css";
export default registerPlugin("dev.example.my-plugin", definePlugin({ panel: MyPanel,}));Core Helpers
Section titled “Core Helpers”| Helper | Use |
|---|---|
definePlugin() | Level 1/2 plugin panels and slots |
defineModulePlugin() | Level 0 module plugins |
defineAssistantPlugin() | Level 3 assistant plugins |
registerPlugin() | Register the frontend bundle with HaloForge |
invokePlugin() | Call this plugin’s Rust backend commands |
invokeOtherPlugin() | Call another plugin’s declared commands |
usePluginSettings() | Read and update plugin settings |
useHostTheme() | Read active host theme and tokens |
useHostAI() | Reuse AIChat model and transport integration |
usePluginNavigation() | Sync Level 0 plugin pages with HaloForge Back/Forward |
usePluginWindows() | Ask HaloForge to open plugin routes/resources through the multi-window dispatcher |
pluginCurrentWindow() / usePluginCurrentWindow() | Update or reset the active HaloForge window title |
usePluginWindowTitle() | Keep the native window preview title synced with the current plugin document |
AppSelect | Render HaloForge-styled dropdown/listbox controls |
log() / createPluginLogger() | Write plugin diagnostics into the HaloForge app log |
Calling Rust Commands
Section titled “Calling Rust Commands”import { invokePlugin } from "@haloforge/plugin-sdk";
interface PingResult { ok: boolean; message: string;}
export async function ping() { return invokePlugin<PingResult>("ping", { source: "panel" });}Logging
Section titled “Logging”Use createPluginLogger() for frontend events that should be visible in HaloForge diagnostics, not only in browser DevTools.
import { createPluginLogger } from "@haloforge/plugin-sdk";
const logger = createPluginLogger("image-generation");
await logger.info("Generation started", { model: "gpt-image-2.0", size: "1024x1024", count: 1,});
await logger.error("Generation failed", { status: 502, elapsedMs: 1842, error: "upstream gateway timeout",});HaloForge writes these records to ~/.haloforge/logs/haloforge.log.YYYY-MM-DD. Keep details JSON-serializable and do not include API keys, bearer tokens, full prompts, or raw image/base64 payloads.
Plugin Routes And Windows
Section titled “Plugin Routes And Windows”Use usePluginNavigation() for page changes inside the current plugin window. Use usePluginWindows() when a plugin wants HaloForge to open one of its routes or resources in the best host window.
import { usePluginNavigation, usePluginWindows } from "@haloforge/plugin-sdk";
export function DocumentsPanel() { const navigation = usePluginNavigation(); const windows = usePluginWindows();
function openDetail(id: string) { navigation.pushRoute(`/detail/${id}`, { params: { id } }); }
async function openDocument(path: string) { await windows.openResource(path, { route: "/document", params: { path }, reuseKey: "resource", openMode: "reuse_or_new", }); }}usePluginWindows() combines the call with the plugin manifest window policy. The plugin declares intent; HaloForge owns window creation, focus, reuse, restore, and safety checks.
Native Window Title
Section titled “Native Window Title”Plugins can update the native window title shown in the operating system window preview, such as the Windows taskbar thumbnail. Use this for document-oriented plugins that should show the active file name.
import { usePluginWindowTitle } from "@haloforge/plugin-sdk";
export function MarkdownPanel({ activeModule, document }) { usePluginWindowTitle( document?.name?.trim() || null, { subtitle: "Markdown", enabled: activeModule === "markdown", }, );
return <main>{document?.name ?? "No document"}</main>;}HaloForge formats plugin titles as Title - Subtitle - HaloForge and resets the title when the owning panel unmounts or disables the hook. Hidden or background plugin panels cannot overwrite another plugin’s active window title.
Host-Styled Select
Section titled “Host-Styled Select”Use AppSelect instead of a raw <select> when rendering combo boxes.
import { AppSelect } from "@haloforge/plugin-sdk";
export function ModePicker({ value, onChange }) { return ( <AppSelect value={value} onChange={(event) => onChange(event.target.value)}> <option value="overview">Overview</option> <option value="diagnostics">Diagnostics</option> </AppSelect> );}Private Bridges Are Off Limits
Section titled “Private Bridges Are Off Limits”The SDK may use an internal bridge to talk to the current HaloForge runtime. Plugin source should not read those internals directly.
Avoid:
window.__HF_HOSTwindow.__HALOFORGE_PLUGIN_HOST__invoke("plugin_invoke", ...)Use the SDK helper that matches the feature. If no helper exists, add one to haloforge-plugin-api first.