npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@rx-ventures/medusa-flow-builder-plugin

v1.0.0

Published

Shopify-Flow-style visual workflow automation for Medusa v2 — draggable node editor, .flow file import/export, extensible task registry.

Readme

medusa-flow-builder-plugin

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

  • Draggable node editor in the admin dashboard — trigger → conditions → wait → actions.
  • .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 registerTask.
  • 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/[email protected]
                        # — 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 medusa-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 HANDOVER.md for an end-to-end setup walkthrough that assumes zero Medusa experience.

Built-in tasks

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

All action config fields support {{ 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 instrumentation.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" }
  },
})

// Medusa needs a default export for subscribers; a no-op works:
export default async function noop() {}
export const config = { event: "__boot__:noop" }

The task appears in the admin palette immediately. Re-registering the same task_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.

.flow file import / export

The list page has an Import .flow button that accepts Shopify Flow exports. Shopify task_ids are translated to their Medusa equivalents on import (see src/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 flow::wait step, it writes a row to flow_builder_pending_resumption (flow_run_id, step_id, resume_at, next_payload, variables) 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: seconds, minutes, hours, days. 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 .yalc/ directory, with a file: reference in package.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: medusa plugin:publish from @medusajs/[email protected] errors with TypeError: cmd is not a function. As a workaround, run yarn build followed by raw yalc publish — same end result. The bug appears fixed in 2.14.x; we'll switch back to the official command once the consumer app upgrades.

Heads up #2: yalc push only copies plugin files into the consumer's .yalc/ directory — it does not re-resolve transitive dependencies. If you change the plugin's dependencies 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 prepublishOnly script re-runs the build for you, so consumers always get the compiled output. Only the contents of the files whitelist (.medusa/server, .medusa/admin, package.json, README.md, LICENSE) end up in the published tarball — source src/ is not shipped.

Testing

yarn test:unit

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

License

MIT © Rx-Ventures