Overview
Icon for Flow Builder

Flow Builder

Build workflows with a visual node editor

medusa-flow-builder-plugin

Shopify-Flow-style visual workflow automation for Medusa v2.

  • Draggable node editor in the admin dashboard — trigger → conditions → wait → actions.
  • Copy to clipboard.flow file import and export that interchanges with Shopify Flow. Task IDs translate both directions.
  • Extensible task registry — register your own triggers, conditions, and actions from your app with Copy to clipboardregisterTask.
  • Durable WAIT — resumptions survive restarts via a persisted pending-resumption table + 1-minute cron sweeper.
  • Built-in task catalog — 15 triggers, 2 control nodes (wait, condition), 13 actions covering orders, customers, products, webhooks, Slack, Customer.io, and log output.
Status: alpha. API may change. Not yet published to npm — for now, consume via Medusa's local-package workflow (yalc-based).

Install

Once published to npm

yarn add medusa-flow-builder-plugin

Until then — local install via Medusa's plugin CLI

In the plugin repo:

yarn install
yarn build # runs `medusa plugin:build` → writes .medusa/server + .medusa/admin
yalc publish # publishes to your local yalc store
# (the official `medusa plugin:publish` is broken in @medusajs/cli@2.13.3
# — TypeError: cmd is not a function. Raw `yalc publish` does the same job.)

In the consumer Medusa app:

medusa plugin:add medusa-flow-builder-plugin
# Adds `"medusa-flow-builder-plugin": "file:.yalc/medusa-flow-builder-plugin"` to package.json,
# creates .yalc/medusa-flow-builder-plugin/, and writes yalc.lock.
yarn install

Register in your app's Copy to clipboardmedusa-config.ts:

module.exports = defineConfig({
plugins: [
{ resolve: "medusa-flow-builder-plugin" },
],
// …
})

Run the migration so the plugin's tables are created:

yarn medusa db:migrate

Open the admin dashboard → sidebar Extensions → Flow Builder.

New devs: see Copy to clipboardHANDOVER.md for an end-to-end setup walkthrough that assumes zero Medusa experience.

Built-in tasks

Kind Task ID Description Trigger Copy to clipboardmedusa::order::placed Order placed Trigger Copy to clipboardmedusa::order::paid Order paid Trigger Copy to clipboardmedusa::order::fulfilled Order fulfilled Trigger Copy to clipboardmedusa::order::canceled Order canceled Trigger Copy to clipboardmedusa::order::refunded Order refunded Trigger Copy to clipboardmedusa::order::updated Order updated Trigger Copy to clipboardmedusa::customer::created Customer created Trigger Copy to clipboardmedusa::customer::updated Customer updated Trigger Copy to clipboardmedusa::product::created Product created Trigger Copy to clipboardmedusa::product::updated Product updated Trigger Copy to clipboardmedusa::product::deleted Product deleted Trigger Copy to clipboardmedusa::cart::updated Cart updated Trigger Copy to clipboardmedusa::fulfillment::created Fulfillment created Trigger Copy to clipboardmedusa::inventory::level_changed Inventory level changed Trigger Copy to clipboardmedusa::flow::custom_webhook Public inbound webhook (Copy to clipboardPOST /hooks/flow-builder/:token) Control Copy to clipboardflow::wait Pause N seconds/minutes/hours/days before continuing Control Copy to clipboardflow::condition Route through Copy to clipboardif-then-true / Copy to clipboardif-then-false output ports Action Copy to clipboardmedusa::order::add_tags Append tags to order metadata Action Copy to clipboardmedusa::order::remove_tags Remove tags from order metadata Action Copy to clipboardmedusa::order::add_metafield Set a namespaced metafield on an order Action Copy to clipboardmedusa::order::set_metadata Merge keys into Copy to clipboardorder.metadata Action Copy to clipboardmedusa::order::cancel Cancel an order Action Copy to clipboardmedusa::customer::add_tags Append customer tags Action Copy to clipboardmedusa::customer::remove_tags Remove customer tags Action Copy to clipboardmedusa::customer::add_metafield Set a namespaced metafield on a customer Action Copy to clipboardmedusa::product::add_tags Append product tags Action Copy to clipboardmedusa::webhook::post POST JSON to a URL Action Copy to clipboardmedusa::slack::post POST a message to a Slack incoming webhook Action Copy to clipboardmedusa::customerio::send_email Send a Customer.io transactional message (requires Copy to clipboardcustomerio-node installed) Action Copy to clipboardmedusa::flow::log_output Log a templated line to the server logger

All action config fields support Copy to clipboard{{ payload.path.to.field }} template interpolation against the triggering event's payload.

Registering your own tasks

Create a file in your app that runs at boot time — a subscriber, a loader, or Copy to clipboardinstrumentation.ts:

// src/subscribers/register-flow-tasks.ts
import { registerTask } from "medusa-flow-builder-plugin"
registerTask({
task_id: "acme::subscription::pause",
task_version: "0.1",
task_type: "ACTION",
label: "Pause subscription",
description: "Pauses the subscription referenced by the payload.",
category: "Subscription",
config_fields: [
{ id: "subscription_id", label: "Subscription ID", type: "text", required: true },
{ id: "reason", label: "Reason", type: "text" },
],
input_ports: [{ id: "input", label: "" }],
output_ports: [{ id: "output", label: "" }],
async execute(ctx, config) {
const subscriptionService = ctx.container.resolve("subscription_service")
await subscriptionService.pause(config.subscription_id, { reason: config.reason })
return { status: "success" }

The task appears in the admin palette immediately. Re-registering the same Copy to clipboardtask_id overrides the previous definition, including built-ins.

For custom triggers bound to events Medusa doesn't fire natively, register the trigger AND write a subscriber that forwards the event:

import { registerTask, runFlowsForMedusaEvent } from "medusa-flow-builder-plugin"
registerTask({
task_id: "acme::billing::invoice_issued",
task_version: "0.1",
task_type: "TRIGGER",
label: "Invoice issued",
description: "Fires when our billing service issues an invoice.",
category: "Trigger",
config_fields: [],
output_ports: [{ id: "output", label: "" }],
trigger: { medusa_event: "acme.invoice_issued", payload_shape: { invoice_id: "string", amount: "number" } },
})
// In a subscriber:
export default async function ({ event: { data }, container }) {
await runFlowsForMedusaEvent("acme.invoice_issued", data, container)
}
export const config = { event: "acme.invoice_issued" }

The plugin already ships umbrella subscribers for the built-in triggers listed above.

Copy to clipboard.flow file import / export

The list page has an Import .flow button that accepts Shopify Flow exports. Shopify Copy to clipboardtask_ids are translated to their Medusa equivalents on import (see Copy to clipboardsrc/modules/flow_builder/interop/shopify-map.ts). Unmapped tasks are preserved as-is with an "unsupported" note — flows still open in the editor; unmapped steps are skipped at runtime.

Each row in the list page has an Export .flow button. Exports include a matching SHA-256 prefix of the JSON body. Task IDs reverse-translate to Shopify identifiers where a mapping exists, so the file drops back into Shopify Flow cleanly.

Durable WAIT

When a run hits a Copy to clipboardflow::wait step, it writes a row to Copy to clipboardflow_builder_pending_resumption (Copy to clipboardflow_run_id, Copy to clipboardstep_id, Copy to clipboardresume_at, Copy to clipboardnext_payload, Copy to clipboardvariables) and stops that branch. A cron job runs every minute, sweeps due resumptions, and re-enters the runner at the step downstream of the wait. Runs survive restarts.

Units supported: Copy to clipboardseconds, Copy to clipboardminutes, Copy to clipboardhours, Copy to clipboarddays. Resolution is 1 minute; sub-minute waits will fire on the next cron tick.

Local development

Medusa's plugin workflow is yalc-based. Each consuming app gets a copy of the built plugin under its own Copy to clipboard.yalc/ directory, with a Copy to clipboardfile: reference in Copy to clipboardpackage.json — no npm registry needed during development.

git clone https://github.com/Rx-Ventures/medusa-flow-builder-plugin
cd medusa-flow-builder-plugin
yarn install
yarn build # medusa plugin:build
yalc publish # publishes to ~/.yalc
# In your Medusa app:
medusa plugin:add medusa-flow-builder-plugin
yarn install
yarn dev

For the rapid edit-loop while developing the plugin:

yarn dev # medusa plugin:develop — watch + auto-republish

If you don't want the watcher, do it manually after each change:

yarn build && yalc push # rebuild + push to every yalc consumer
Heads up: Copy to clipboardmedusa plugin:publish from Copy to clipboard@medusajs/cli@2.13.3 errors with Copy to clipboardTypeError: cmd is not a function. As a workaround, run Copy to clipboardyarn build followed by raw Copy to clipboardyalc publish — same end result. The bug appears fixed in Copy to clipboard2.14.x; we'll switch back to the official command once the consumer app upgrades.
Heads up #2: Copy to clipboardyalc push only copies plugin files into the consumer's Copy to clipboard.yalc/ directory — it does not re-resolve transitive Copy to clipboarddependencies. If you change the plugin's Copy to clipboarddependencies field, the consumer needs to re-fetch them:

# in the consuming app:
yalc update && yarn install --check-files

Publishing to npm (when ready)

yarn build
npm publish --access public # or `npm publish` for a private/scoped package

The Copy to clipboardprepublishOnly script re-runs the build for you, so consumers always get the compiled output. Only the contents of the Copy to clipboardfiles whitelist (Copy to clipboard.medusa/server, Copy to clipboard.medusa/admin, Copy to clipboardpackage.json, Copy to clipboardREADME.md, Copy to clipboardLICENSE) end up in the published tarball — source Copy to clipboardsrc/ is not shipped.

Testing

yarn test:unit

The suite covers: condition evaluation, template interpolation, WAIT duration parsing, Copy to clipboard.flow import/export round-trip, the Shopify↔Medusa task translation, and the Copy to clipboardregisterTask extensibility hook.

License

MIT © Rx-Ventures

You may also like

Browse all integrations

Build your own

Develop your own custom integration

Build your own integration with our API to speed up your processes. Make your integration available via npm for it to be shared in our Library with the broader Medusa community.

gift card interface

Ready to build your custom commerce setup?