Vercel Logo

Build Your Own Plugin

Time to fly solo. You've followed the steps, copied the code, fixed the errors. Now you build something the tutorial didn't plan for — YOUR plugin, YOUR API, YOUR problem. This is where "I took a course" becomes "I can build this."

You've built two plugins — Shout (toy) and email (real). You know the plugin folder pattern, how credentials work, how errors should behave. This is graduation: same patterns, no more hand-holding.

Mental Model: Workflow Thinking

You can look at any async problem and see steps, pause points, failure modes. That's the skill — not just using this builder, but thinking in workflows.

For deeper patterns, explore Building AI Agents with Vercel Workflow and Stateful Slack Bots with Vercel Workflow.

Outcome

You'll create a complete plugin for a service you actually use — Slack, Stripe, GitHub, your internal API — and run it in a workflow that does something useful for you.

Fast Track

  1. Deploy the production template (or continue on starter)
  2. Run pnpm create-plugin to scaffold your plugin
  3. Implement the step, test connection, and error handling

Choose Your Path

You've been building on the starter template. For your own plugin, you have three options:

Continue on Starter

Keep building where you are. The starter has everything you need — same patterns, same plugin structure. Good for learning, quick experiments, or if you just want to finish what you started.

The production template includes more plugins to study, a cleaner codebase, and is ready for real use:

Deploy with Vercel

This gives you a fresh Vercel project with Postgres provisioned automatically.

The production template includes additional features like overlay-based UI, public workflow sharing, API keys management, and OG image generation. See the workflow-builder-template repository for the full feature set.

GitHub Template (Clone Locally First)

Want to explore the code before deploying? Create a repo from the template:

Use this template on GitHub

Clone it, poke around, then deploy when ready.

Why This Lesson Matters

Tutorials are passive. You follow steps, things work, you move on. Retention is maybe 20%.

Building YOUR plugin, for YOUR API, when the docs don't map perfectly? That's where friction happens. You'll debug something weird. You'll make a decision the tutorial didn't cover. You'll come out knowing this system instead of just recognizing it.

This is the difference between "I took a course" and "I can build this."

Study These Production Plugins

The production template includes several real-world plugins. Before building your own, study how these handle common patterns:

PluginWhat to Learn
ResendClean credential handling, simple single-action plugin
SlackOAuth-style tokens, message formatting
LinearMultiple actions (create ticket, find issues), API with GraphQL
FirecrawlScraping and search actions, handling complex API responses

Each follows the same folder structure. Read one thoroughly — the patterns repeat.

Plugin Folder Checklist

The production template uses a streamlined structure:

plugins/[your-plugin]/
├── index.ts           → Plugin definition, registration
├── icon.tsx           → Icon component
├── credentials.ts     → Credential type definition
├── test.ts            → Connection test function
└── steps/
    └── [action].ts    → "use step" function with fetchCredentials()

Run pnpm create-plugin to scaffold this structure automatically.

Reflection Prompt

Plan Before You Code

Before you start building, answer these questions for your chosen API: (1) What's the main action your plugin will perform? (2) What credentials does it need? (3) What inputs should the config UI collect? (4) What can fail, and which failures are retryable vs fatal? Write this down — it's your design doc.

Hands-on Exercise

1. Pick your API:

  • Your internal service (recommended) — the real test is integrating something the template doesn't have
  • Stripe (create charge, refund, subscription)
  • Twilio (send SMS, make call)
  • GitHub (create issue, comment on PR)
  • Any API you actually use at work

2. Design before you code:

  • What inputs does the UI need?
  • What can fail? Which failures are retryable?
  • What should the step output for downstream nodes?

3. Build the plugin:

Run pnpm create-plugin to scaffold, then customize. Here's the skeleton you'll fill in:

plugins/[your-plugin]/steps/[action].ts
import "server-only";
 
import { FatalError, RetryableError } from "workflow";
import { fetchCredentials } from "@/lib/credential-fetcher";
import { type StepInput, withStepLogging } from "@/lib/steps/step-handler";
import type { YourCredentials } from "../credentials";
 
type YourActionResult =
  | { success: true; /* your output fields */ }
  | { success: false; error: string };
 
type YourActionInput = StepInput & {
  // Your config fields from index.ts
};
 
async function stepHandler(
  input: YourActionInput,
  credentials: YourCredentials
): Promise<YourActionResult> {
  // 1. Validate credentials
  if (!credentials.API_KEY) {
    throw new FatalError("API_KEY is not configured");
  }
 
  // 2. Call your API
  const response = await fetch("https://your-api.com/endpoint", {
    method: "POST",
    headers: { Authorization: `Bearer ${credentials.API_KEY}` },
    body: JSON.stringify({ /* your payload */ }),
  });
 
  // 3. Handle errors by type
  if (response.status === 401) {
    throw new FatalError("Invalid API key");
  }
  if (response.status === 429 || response.status >= 500) {
    throw new RetryableError(`API returned ${response.status}`);
  }
 
  // 4. Return success
  const data = await response.json();
  return { success: true, /* your output fields */ };
}
 
export async function yourActionStep(
  input: YourActionInput
): Promise<YourActionResult> {
  "use step";
  const credentials = input.integrationId
    ? await fetchCredentials(input.integrationId)
    : {};
  return withStepLogging(input, () => stepHandler(input, credentials));
}
 
export const _integrationType = "your-plugin";

The key sections:

  • Validate credentials exist (FatalError if missing)
  • Call your API
  • Handle errors by type (401 = Fatal, 429/5xx = Retryable)
  • Return success with output for downstream nodes

4. Test the full flow:

  • Add your plugin to a workflow
  • Run it
  • Break it on purpose (bad credentials)
  • Verify retries or fatal errors work as expected
  • Fix it and run successfully

Verify It Works

Your plugin is ready when:

  1. Happy path succeeds — Run your workflow and check the Runs tab:

    {
      "success": true,
      // your output fields from step return
    }
  2. Bad credentials fail fast — Remove or invalidate your API key, run again. You should see FatalError logged immediately, not retry attempts.

  3. Transient errors retry — If your API returns 429 or 503, the workflow should retry with backoff (check the step execution count in logs).

If all three work, you've internalized the pattern. Ship it.

Learn More

Now that you've built real plugins, go deeper with these resources:

ResourceWhat You'll Learn
Workflow SDK DocsComplete API reference, advanced patterns
Vercel Workflow Platform DocsDeployment, observability, production config
Vercel FunctionsThe runtime powering your workflow steps
Fluid ComputeHow Vercel optimizes function execution

Done

  • Picked a real API you actually use
  • Built the plugin folder structure
  • Plugin appears in action grid
  • Workflow runs successfully with your plugin
  • Tested error handling (retries or fatal)
  • Credentials stay out of logs

What You've Learned

You started with a deploy button and ended with a custom integration. Along the way:

  • Keep Your API Fast — Routes return instantly, workflows run in background
  • Pause and Continue — Workflows suspend for webhooks, resume exactly
  • Steps and Flow — Code matches diagram, steps are composable promises
  • Retry and Error Recovery — You control what retries and what stops
  • Step Tracing — Observability comes free, correlation IDs thread everything

Now go build something real. When you do, share it in the Vercel Community — we want to see what you create.