Embeddr plugins are React-based modules that can inject UI components, interact with the application state, and perform actions. They are built using Vite and can use the full power of the @embeddr/react-ui component library.
- Loading: Plugins are loaded once at application startup.
- Singleton: Each plugin is treated as a singleton instance.
- React Lifecycle: Components registered by plugins are mounted/unmounted based on UI visibility (e.g., when a tab is selected).
- Persistence: Plugins are not hot-unloaded. State stored in React components will reset on unmount, but state in
localStorageor external stores persists.
- Create File: Create
examples/my_plugin/MyPlugin.tsx. - Define: Export a
PluginDefinitionobject (see Structure below). - Register: Add your plugin to
build-plugins.js. - Build: Run
node build-plugins.js. - Load: The build script automatically copies output to the CLI workspace.
- Verify: Open Embeddr and check the Zen Toolbox or Settings.
A typical plugin consists of a single entry file (e.g., MyPlugin.tsx) that exports a PluginDefinition object.
import type { PluginDefinition, EmbeddrAPI } from "@embeddr/react-ui/types";
const MyComponent: React.FC<{ api: EmbeddrAPI }> = ({ api }) => {
return <div>Hello from My Plugin!</div>;
};
export const MyPlugin: PluginDefinition = {
id: "my.plugin",
name: "My Plugin",
description: "A simple example plugin",
version: "1.0.0",
components: [
{
id: "my-component",
location: "zen-toolbox-tab",
label: "My Tab",
component: MyComponent,
},
],
};
// Register the plugin if loaded in the browser
if (typeof window !== "undefined" && (window as any).Embeddr) {
(window as any).Embeddr.registerPlugin(MyPlugin);
}Plugins are built as IIFE (Immediately Invoked Function Expression) bundles. We use a custom build script (build-plugins.js) that leverages Vite.
To build your plugin, add it to the plugins object in build-plugins.js and run:
node build-plugins.jsThis will generate a dist/your-plugin/index.js and style.css which can be loaded by Embeddr.
Plugins can also include a Python backend to handle API requests, database operations, or other server-side logic.
Create a plugin.py file in your plugin's directory (alongside the built index.js).
from fastapi import APIRouter
def register(router: APIRouter):
@router.get("/hello")
def hello():
return {"message": "Hello from Python!"}The Embeddr backend automatically scans for plugin.py files. If found, it calls the register(router) function.
The router is automatically prefixed with /api/v1/plugins/{plugin_name}.
For example, if your plugin folder is named my-plugin, the route above would be accessible at:
GET /api/v1/plugins/my-plugin/hello
The Arcade plugin demonstrates this by implementing a simple leaderboard system in Python.
If your plugin requires custom ComfyUI nodes (e.g., for specific image processing tasks), these are currently handled as standard ComfyUI Custom Nodes.
- Create a Python file for your node (e.g.,
MyNode.py). - Register it in the
__init__.pyof theembeddr-comfyuipackage (or your own custom node pack). - Your frontend plugin can then use this node in workflows by referencing its internal name (e.g.,
embeddr.MyNode).
Example: The EmbeddrLoadImageID node allows ComfyUI to load images directly from the Embeddr library using their ID.
Every plugin component receives an api prop providing access to the core system.
Access and modify global application state.
global:selectedImage: The currently selected image object.selectImage(image): Select an image programmatically.
generation:workflows: List of available ComfyUI workflows.selectedWorkflow: The currently active workflow.isGenerating: Boolean status of generation.generate(): Trigger the generation process.setWorkflowInput(nodeId, field, value): Update workflow inputs dynamically.
activePanelId: ID of the currently focused panel.isPanelActive(id): Check if a panel is active.
Display notifications to the user.
success(msg)error(msg)info(msg)
backendUrl: Base URL of the API.uploadImage(file, prompt?, parent_ids?): Upload an image to the library.getPluginUrl(path): Get a full URL for plugin assets.
Subscribe to or emit global events.
on(event, listener)emit(event, data)off(event, listener)
Common Events:
generation:startgeneration:completeimage:uploaded
We provide several example plugins to demonstrate different capabilities.
Demonstrates: Complex UI, Canvas manipulation, Drag & Drop, State persistence.
- Key Features:
- Uses
DraggablePanelfor a floating window. - Implements a full image composition tool with layers (Image, Paint, Mask).
- Uses
useLocalStorageto persist workspaces. - Interacts with
api.utils.uploadImageto export compositions. - Uses
api.stores.generation.setWorkflowInputto send images to ComfyUI.
- Uses
Demonstrates: Event listening, Passive interaction.
- Key Features:
- Listens to
generation:startandgeneration:completeevents. - Plays sounds and shows animations based on app state.
- Uses
api.toastfor notifications.
- Listens to
Demonstrates: Full-stack Plugin (React + Python), iframe integration.
- Key Features:
- Frontend: Embeds external content (games) and displays a leaderboard.
- Backend: Implements a
plugin.pywith FastAPI routes to store and retrieve high scores in memory. - Shows how to fetch data from your own plugin's backend.
Demonstrates: External API fetching, Infinite Scroll.
- Key Features:
- Fetches data from the Civitai API.
- Implements infinite scrolling using
IntersectionObserver. - Drag & Drop integration to import images from the web directly into Embeddr.
Demonstrates: Simple action triggers.
- Key Features:
- A simple button that calls
api.stores.generation.generate(). - Shows how to add a component to the
zen-toolbox-actionlocation.
- A simple button that calls
Plugins can import from @embeddr/react-ui to use the same design system as the core app.
Components:
Button,Input,Slider,SwitchDialog,DropdownMenu,TabsScrollArea,CardDraggablePanel(Essential for floating windows)
Hooks:
useLocalStorage: Persist state across reloads.useMediaQuery: Responsive design.
When defining a component, you specify a location.
zen-toolbox-tab: Adds a tab to the main Zen Mode toolbox (left side).zen-toolbox-action: Adds a button/component to the action area (bottom/top of toolbox).zen-sidebar: Adds an item to the sidebar (if available).zen-overlay: Renders directly on top of the UI (use carefully).
Behavioral Notes:
- Multiple Plugins: Multiple plugins can register components to the same location.
- Ordering: Currently, items are rendered in the order they are loaded.
- Cardinality: A single plugin can register multiple components to different (or the same) locations.
- Scoped CSS: Use unique class names or CSS modules if possible (though Tailwind is preferred and supported).
- State Management: Use
React.useStatefor local state anduseLocalStoragefor persistent user preferences. - Error Handling: Wrap async operations (like uploads) in try/catch blocks and use
api.toast.errorto inform the user. - Performance: Avoid heavy computations in the main render loop. Use
useMemoanduseCallback. - Cleanup: If you register global event listeners in
useEffect, always return a cleanup function to remove them.
- Direct DOM Manipulation: Avoid accessing the DOM directly (e.g.,
document.getElementById). Always use React refs. - Blocking the UI: Do not perform synchronous heavy operations. The UI runs on a single thread.
- Hardcoded URLs: Never hardcode API URLs (e.g.,
localhost:8000). Always useapi.utils.backendUrlor relative paths. - Overwriting Global Styles: Be extremely careful with global CSS. Your styles might break the main application layout.
- Ignoring Mobile: Remember that the sidebar and toolbox might be viewed on smaller screens. Test your responsive layout.
- Semantic Versioning: Plugins should follow semantic versioning (major.minor.patch).
- API Stability: The
EmbeddrAPIis currently in Alpha. Breaking changes may occur. - Dependencies: If your plugin relies on specific Python packages, list them clearly in your documentation. There is currently no automated dependency resolution for plugins.
- Locking: We recommend testing your plugin against the specific version of Embeddr you are targeting.
