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.
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
- Deploy the production template (or continue on starter)
- Run
pnpm create-pluginto scaffold your plugin - 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.
Deploy Template Fresh (Recommended for Production)
The production template includes more plugins to study, a cleaner codebase, and is ready for real use:
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:
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:
| Plugin | What to Learn |
|---|---|
| Resend | Clean credential handling, simple single-action plugin |
| Slack | OAuth-style tokens, message formatting |
| Linear | Multiple actions (create ticket, find issues), API with GraphQL |
| Firecrawl | Scraping 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.
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:
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:
-
Happy path succeeds — Run your workflow and check the Runs tab:
{ "success": true, // your output fields from step return } -
Bad credentials fail fast — Remove or invalidate your API key, run again. You should see
FatalErrorlogged immediately, not retry attempts. -
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:
| Resource | What You'll Learn |
|---|---|
| Workflow SDK Docs | Complete API reference, advanced patterns |
| Vercel Workflow Platform Docs | Deployment, observability, production config |
| Vercel Functions | The runtime powering your workflow steps |
| Fluid Compute | How 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.
Was this helpful?