Overview
Plugins allow you to extend AutoMFlows’s capabilities by creating custom node types with your own functionality. You can create nodes for specific automation tasks, integrate external services, or add specialized workflow behaviors.
Easy to Build Simple file structure and clear interfaces
TypeScript Support Full type safety with TypeScript
Hot Reload Automatically reload on changes
Share & Reuse Easily share plugins with the community
Plugin Structure
A plugin is a directory in the plugins/ folder with the following files:
your-plugin/
plugin.json # Plugin manifest (required)
handler.js # Node handlers (required)
handler.ts # TypeScript source (optional)
config.tsx # Frontend config component (optional)
icon.svg # Custom icon (optional)
README.md # Plugin documentation (optional)
The only required files are plugin.json and handler.js. All other files are optional but recommended for better functionality and documentation.
Quick Start
Create plugin directory
Create a new folder in the plugins/ directory: mkdir plugins/my-plugin
cd plugins/my-plugin
Create plugin manifest
Create plugin.json to define your plugin metadata: {
"name" : "my-plugin" ,
"version" : "1.0.0" ,
"description" : "My custom AutoMFlows plugin" ,
"author" : "Your Name" ,
"nodes" : [
{
"type" : "myplugin.customAction" ,
"label" : "Custom Action" ,
"category" : "Custom" ,
"icon" : "🎯" ,
"description" : "Performs a custom action" ,
"handlerPath" : "handler.js" ,
"defaultData" : {
"field1" : "default value"
}
}
]
}
Create node handler
Create handler.ts to implement your node logic: import { BaseNode } from '@automflows/shared' ;
import { NodeHandler } from '../../backend/src/nodes/base' ;
import { ContextManager } from '../../backend/src/engine/context' ;
export class CustomActionHandler implements NodeHandler {
async execute ( node : BaseNode , context : ContextManager ) : Promise < void > {
const page = context . getPage ();
if ( ! page ) {
throw new Error ( 'No page available' );
}
// Your custom logic here
console . log ( 'Executing custom action:' , node . data );
}
}
export default {
'myplugin.customAction' : CustomActionHandler ,
} ;
Build and test
Compile TypeScript to JavaScript: # From plugin directory
npx tsc handler.ts --target ES2020 --module commonjs
Restart the backend server to load your plugin: # From project root
npm run dev:backend
Plugin Manifest
The plugin.json file defines your plugin’s metadata and available nodes.
Manifest Fields
Field Type Required Description namestring Yes Unique plugin identifier (lowercase, no spaces) versionstring Yes Semantic version (e.g., “1.0.0”) descriptionstring Yes Brief description of the plugin authorstring No Plugin author name nodesarray Yes Array of node definitions
Node Definition Fields
Field Type Required Description typestring Yes Unique node type identifier (use plugin name prefix) labelstring Yes Display name shown in the UI categorystring Yes Category name for grouping in the sidebar iconstring Yes Emoji or icon identifier descriptionstring No Node description handlerPathstring Yes Path to handler file relative to plugin root configComponentPathstring No Path to React config component defaultDataobject No Default values for node data fields
Always use a namespace prefix for node types (e.g., myplugin.action) to avoid conflicts with other plugins or core nodes.
Creating Node Handlers
Handlers implement the business logic for your nodes. They must implement the NodeHandler interface.
Handler Interface
interface NodeHandler {
execute ( node : BaseNode , context : ContextManager ) : Promise < void >;
}
Here’s a complete example from the example-plugin:
import { BaseNode } from '@automflows/shared' ;
import { NodeHandler } from '../../backend/src/nodes/base' ;
import { ContextManager } from '../../backend/src/engine/context' ;
interface FillFormNodeData {
fields : Array <{
selector : string ;
selectorType ?: 'css' | 'xpath' ;
value : string ;
timeout ?: number ;
}>;
}
export class FillFormHandler implements NodeHandler {
async execute ( node : BaseNode , context : ContextManager ) : Promise < void > {
const data = node . data as FillFormNodeData ;
const page = context . getPage ();
if ( ! page ) {
throw new Error ( 'No page available. Ensure Open Browser node is executed first.' );
}
if ( ! data . fields || ! Array . isArray ( data . fields ) || data . fields . length === 0 ) {
throw new Error ( 'Fields array is required for Fill Form node' );
}
// Fill each field
for ( const field of data . fields ) {
if ( ! field . selector ) {
throw new Error ( 'Selector is required for each field' );
}
const timeout = field . timeout || 30000 ;
const selector = field . selector ;
const value = field . value || '' ;
try {
if ( field . selectorType === 'xpath' ) {
await page . locator ( `xpath= ${ selector } ` ). fill ( value , { timeout });
} else {
await page . fill ( selector , value , { timeout });
}
} catch ( error : any ) {
throw new Error ( `Failed to fill field with selector " ${ selector } ": ${ error . message } ` );
}
}
}
}
export default {
'example.fillForm' : FillFormHandler ,
} ;
Utility Node Example
Some nodes don’t need to execute any browser actions:
import { BaseNode } from '@automflows/shared' ;
import { NodeHandler } from '../../backend/src/nodes/base' ;
import { ContextManager } from '../../backend/src/engine/context' ;
export class ShortcutHandler implements NodeHandler {
async execute ( _node : BaseNode , _context : ContextManager ) : Promise < void > {
// Shortcut is a utility node - it doesn't execute anything
// This handler exists only to satisfy the plugin interface
return ;
}
}
export default {
'shortcut.shortcut' : ShortcutHandler ,
} ;
Context Manager API
The ContextManager provides access to the workflow execution context:
Browser & Page Methods
Method Return Type Description getPage()Page | nullGet the current Playwright Page object getBrowser()Browser | nullGet the current Playwright Browser object setPage(page)voidSet the current page setBrowser(browser)voidSet the current browser
Variable & Data Methods
Method Return Type Description getVariable(name)anyGet a workflow variable setVariable(name, value)voidSet a workflow variable getData(key)anyGet data from previous nodes setData(key, value)voidSet data for next nodes
Example Usage
async execute ( node : BaseNode , context : ContextManager ): Promise < void > {
// Get the browser page
const page = context . getPage ();
// Get a workflow variable
const username = context . getVariable ( 'username' );
// Store data for next nodes
context.setData( 'extractedText' , await page.textContent( '.result' ));
// Set a workflow variable
context.setVariable( 'status' , 'completed' );
}
Frontend Config Components
Create custom React components for node configuration in the UI:
import { PluginConfigComponentProps } from '../../frontend/src/plugins/types' ;
export default function MyNodeConfig ({ node , onChange } : PluginConfigComponentProps ) {
return (
< div className = "space-y-4" >
< div >
< label className = "block text-sm font-medium mb-1" > Selector </ label >
< input
type = "text"
className = "w-full px-3 py-2 border rounded"
value = { node . data . selector || '' }
onChange = { ( e ) => onChange ( 'selector' , e . target . value ) }
placeholder = "Enter CSS selector"
/>
</ div >
< div >
< label className = "block text-sm font-medium mb-1" > Timeout (ms) </ label >
< input
type = "number"
className = "w-full px-3 py-2 border rounded"
value = { node . data . timeout || 30000 }
onChange = { ( e ) => onChange ( 'timeout' , parseInt ( e . target . value )) }
/>
</ div >
</ div >
);
}
Best Practices
Error Handling
Type Safety
Naming
Documentation
Always validate inputs and provide clear error messages: if ( ! data . selector ) {
throw new Error ( 'Selector is required for this node' );
}
try {
await page . click ( data . selector );
} catch ( error : any ) {
throw new Error ( `Failed to click element: ${ error . message } ` );
}
Define interfaces for your node data: interface MyNodeData {
selector : string ;
value : string ;
timeout ?: number ;
}
async execute ( node : BaseNode , context : ContextManager ): Promise < void > {
const data = node . data as MyNodeData ;
// TypeScript now knows the shape of data
}
Use descriptive, unique node type identifiers
Always prefix with plugin name: myplugin.nodeType
Use camelCase for node types
Use Title Case for display labels
{
"type" : "myplugin.fillForm" , // ✓ Good
"label" : "Fill Form" , // ✓ Good
}
Include a README.md with:
Plugin description
Installation instructions
Node documentation
Usage examples
Configuration options
Testing Your Plugin
Validate manifest
Ensure plugin.json is valid JSON:
Build handler
Compile TypeScript if using .ts files: npx tsc handler.ts --target ES2020 --module commonjs
Restart backend
Restart the backend server to load your plugin: Check server logs for plugin loading messages.
Verify in UI
Refresh the frontend
Check that your nodes appear in the sidebar under the specified category
Drag a node onto the canvas
Test the node configuration
Test execution
Create a workflow using your custom node and execute it to verify functionality.
Example Plugins
AutoMFlows includes several example plugins you can reference:
example-plugin Fill Form and Scroll To nodes demonstrating form automation and scrolling
shortcut Shortcut node for keyboard navigation between nodes (utility node example)
comment-box Comment Box node for adding annotations to workflows
set-config-node Set Config node for dynamic configuration management
Troubleshooting
Plugin not loading
Checklist:
Verify plugin.json is valid JSON (use jq or JSON validator)
Check that handler file path is correct
Review server logs for error messages
Ensure handler exports match node types in manifest
Handler not found
// ✓ Correct export format
export default {
'myplugin.nodeType' : MyHandler ,
} ;
// ✗ Incorrect - missing default export
export const handlers = {
'myplugin.nodeType' : MyHandler ,
};
Node not appearing in UI
Check that plugin loaded successfully in server logs
Verify node category is set correctly in manifest
Refresh the frontend after plugin loads
Clear browser cache if necessary
TypeScript errors
Ensure you have the correct imports:
import { BaseNode } from '@automflows/shared' ;
import { NodeHandler } from '../../backend/src/nodes/base' ;
import { ContextManager } from '../../backend/src/engine/context' ;
Sharing Your Plugin
To share your plugin with others:
Create repository
Create a Git repository with your plugin code: cd plugins/my-plugin
git init
git add .
git commit -m "Initial plugin commit"
Add documentation
Include a comprehensive README.md with installation and usage instructions.
Publish
Push to GitHub, GitLab, or any Git hosting service: git remote add origin < your-repo-ur l >
git push -u origin main
Installation instructions
Users can install your plugin by cloning it into their plugins/ directory: cd plugins
git clone < your-repo-ur l > my-plugin
npm run dev:backend # Restart to load plugin
Advanced Topics
Multiple Nodes in One Plugin
You can define multiple nodes in a single plugin:
{
"name" : "my-plugin" ,
"nodes" : [
{
"type" : "myplugin.action1" ,
"label" : "Action 1" ,
"handlerPath" : "handler.js"
},
{
"type" : "myplugin.action2" ,
"label" : "Action 2" ,
"handlerPath" : "handler.js"
}
]
}
export class Action1Handler implements NodeHandler {
async execute ( node : BaseNode , context : ContextManager ) : Promise < void > {
// Action 1 logic
}
}
export class Action2Handler implements NodeHandler {
async execute ( node : BaseNode , context : ContextManager ) : Promise < void > {
// Action 2 logic
}
}
export default {
'myplugin.action1' : Action1Handler ,
'myplugin.action2' : Action2Handler ,
} ;
Plugin Dependencies
If your plugin requires external npm packages:
Create a package.json in your plugin directory
Install dependencies
Import and use them in your handler
{
"name" : "my-plugin" ,
"version" : "1.0.0" ,
"dependencies" : {
"axios" : "^1.6.0"
}
}
import axios from 'axios' ;
export class ApiCallHandler implements NodeHandler {
async execute ( node : BaseNode , context : ContextManager ) : Promise < void > {
const response = await axios . get ( node . data . url );
context . setData ( 'apiResponse' , response . data );
}
}
Next Steps
Node Types Learn about built-in node types
Workflow Engine Understand how workflows execute
MCP Server Build AI-powered workflow automation
API Reference Explore the full API