Skip to Content

next-workflow-builder

DevKit AI Builder Template DocumentationMarketplace
CTRL K
Demo
CTRL K
  • DevKit 
  • AI Builder Template 
    • Introduction
    • Getting Started
    • Configuration
    • Plugins
      • Overview
      • HTTP Request
      • Condition
      • Loop
      • Merge
      • Database Query
      • Run Workflow
      • Run Workflows in Sequence
      • Switch
    • Creating Plugins
    • API Reference
    • CLI Reference
    • Components
    • Database
    • Authentication
    • Deployment
    • Architecture
    • Contributing
    • MCP Server
  • Marketplace
  • Introduction
  • Getting Started
  • Configuration
  • Plugins
    • Overview
    • HTTP Request
    • Condition
    • Loop
    • Merge
    • Database Query
    • Run Workflow
    • Run Workflows in Sequence
    • Switch
  • Creating Plugins
  • API Reference
  • CLI Reference
  • Components
  • Database
  • Authentication
  • Deployment
  • Architecture
  • Contributing
  • MCP Server

On This Page

  • Scaffolding
  • Required files
  • index.ts - Plugin definition
  • Plugin definition fields
  • Action definition fields
  • icon.tsx - SVG icon
  • credentials.ts - Credential types
  • Step files
  • steps/do-thing.ts
  • Step file conventions
  • Connection test
  • test.ts
  • Custom API routes
  • index.ts (routes section)
  • routes/webhook.ts
  • Register and discover
  • Complete example
Question? Give us feedback Edit this page 
DocumentationCreating Plugins

Creating Plugins

This guide walks through building a complete integration plugin from scratch.

Scaffolding

Use the CLI to generate a plugin skeleton:

npx nwb create-plugin

This creates a new directory under plugins/ with all required files.

Alternatively, create the files manually following the structure below.

Required files

index.ts - Plugin definition

The main file defines the plugin and registers it:

import type { IntegrationPlugin } from "next-workflow-builder/plugins"; import { registerIntegration } from "next-workflow-builder/plugins"; import { MyServiceIcon } from "./icon"; const myServicePlugin: IntegrationPlugin = { type: "my-service", label: "My Service", description: "Connect to My Service API", icon: MyServiceIcon, formFields: [ { id: "apiKey", label: "API Key", type: "password", placeholder: "sk-...", configKey: "apiKey", envVar: "MY_SERVICE_API_KEY", helpText: "Get your key from ", helpLink: { text: "my-service.com", url: "https://my-service.com/keys", }, }, ], testConfig: { getTestFunction: async () => { const { testMyService } = await import("./test"); return testMyService; }, }, actions: [ { slug: "do-thing", label: "Do Thing", description: "Performs an action via My Service", category: "My Service", stepFunction: "doThingStep", stepImportPath: "do-thing", configFields: [ { key: "input", label: "Input", type: "template-textarea", placeholder: "Enter input or use {{NodeName.field}}...", rows: 4, required: true, }, ], outputFields: [ { field: "result", description: "The output result" }, ], }, ], }; registerIntegration(myServicePlugin); export default myServicePlugin;

Plugin definition fields

FieldTypeRequiredDescription
typestringYesUnique integration type slug (e.g. "my-service")
labelstringYesDisplay name shown in the UI
descriptionstringYesShort description for the connection picker
iconReact.ComponentTypeYesSVG icon component
formFieldsFormField[]YesCredential fields shown when adding a connection
testConfigobjectNoLazy-loaded connection test function
actionsPluginAction[]YesActions this plugin provides
routesRouteMetadata[]NoCustom API route definitions
dependenciesRecord<string, string>NoNPM dependencies for code generation

Action definition fields

FieldTypeRequiredDescription
slugstringYesUnique action slug within this plugin
labelstringYesDisplay name
descriptionstringYesWhat this action does
categorystringYesCategory for UI grouping
stepFunctionstringYesExported function name in the step file
stepImportPathstringYesFile name under steps/ (no extension)
configFieldsActionConfigField[]YesConfiguration fields for this action
outputFieldsOutputField[]NoOutput fields available for downstream templates
outputConfigOutputDisplayConfigNoHow to display output in the runs panel
codegenTemplatestringNoCustom code generation template

icon.tsx - SVG icon

export function MyServiceIcon({ className }: { className?: string }) { return ( <svg className={className} viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" > {/* Your SVG paths */} </svg> ); }

credentials.ts - Credential types

Define a type for the environment variables your plugin uses:

export type MyServiceCredentials = { MY_SERVICE_API_KEY?: string; };

Step files

Each action needs a corresponding step file under steps/. Steps run server-side during workflow execution.

steps/do-thing.ts

import "server-only"; import { fetchCredentials, getErrorMessage, type StepInput, withStepLogging, } from "next-workflow-builder/server"; import type { MyServiceCredentials } from "../credentials"; type DoThingResult = { result: string; }; export type DoThingInput = StepInput & { input: string; integrationId?: string; }; async function stepHandler( input: { input: string }, credentials: MyServiceCredentials ): Promise<DoThingResult> { const apiKey = credentials.MY_SERVICE_API_KEY; if (!apiKey) { throw new Error("MY_SERVICE_API_KEY is not configured."); } const response = await fetch("https://api.my-service.com/do-thing", { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${apiKey}`, }, body: JSON.stringify({ input: input.input }), }); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${await response.text()}`); } const data = await response.json(); return { result: data.result }; } export async function doThingStep( input: DoThingInput ): Promise<DoThingResult> { "use step"; const credentials = input.integrationId ? await fetchCredentials(input.integrationId) : {}; return withStepLogging(input, () => stepHandler(input, credentials)); } doThingStep.maxRetries = 0; export const _integrationType = "my-service";

Step file conventions

  • Always import "server-only" to prevent client bundling
  • Use "use step" directive to mark the function as a workflow step entry point
  • Wrap with withStepLogging for execution history tracking
  • Export _integrationType for the codegen system
  • Set maxRetries on the step function (typically 0)
  • Separate stepHandler from the exported entry point for portability
  • Prefer fetch over SDK dependencies to reduce supply chain attack surface

Connection test

The test file validates credentials when a user clicks “Test” in the connection dialog:

test.ts

export async function testMyService( credentials: Record<string, string> ) { const apiKey = credentials.MY_SERVICE_API_KEY; if (!apiKey) { return { success: false, error: "MY_SERVICE_API_KEY is required" }; } try { const response = await fetch("https://api.my-service.com/ping", { headers: { Authorization: `Bearer ${apiKey}` }, }); if (!response.ok) { return { success: false, error: `HTTP ${response.status}` }; } return { success: true }; } catch (error) { return { success: false, error: String(error) }; } }

The test function is lazy-loaded via testConfig.getTestFunction to avoid bundling server-only code on the client.

Custom API routes

Plugins can define custom API route handlers:

index.ts (routes section)

const myPlugin: IntegrationPlugin = { // ... routes: [ { path: "/my-service/webhook", methods: ["POST"], handler: "myServiceWebhook", handlerImportPath: "routes/webhook", }, ], };

routes/webhook.ts

import type { RouteHandler } from "next-workflow-builder"; export const myServiceWebhook: RouteHandler = async (route, ctx) => { const body = await route.request.json(); // Handle webhook... return new Response(JSON.stringify({ ok: true }), { headers: { "Content-Type": "application/json" }, }); };

Plugin routes are registered when plugins are imported at runtime.

Register and discover

After creating your plugin files:

  1. Add an import to plugins/index.ts:

    import "./my-service";
  2. Run the discovery script to regenerate registry files:

    npx nwb discover-plugins

The discovery script imports plugins/index.ts to populate the registry, then generates type definitions, step registry, display configs, and codegen templates.

Complete example

See the Firecrawl plugin  in the example app for a complete reference implementation with multiple actions, connection testing, and step handlers.

Last updated on March 12, 2026
SwitchAPI Reference

© 2026 All rights reserved.

Product by David Sanchez