# Automation Flow Nodes

URL: https://zerodev.ca/docs/medusa/developer-documentation/nodes

> Create visual trigger, condition, and action nodes for Medusa's automation engine.



Medusa features a visual node-based automation flow editor where administrators create reactive event pipelines. Modules can supply custom action and condition nodes that appear in the canvas palette and execute seamlessly within automation workflows.

Automation nodes reside in `resources/nodes/` and load from every `*.js` file located directly in that directory.

## Base Class Definition

```js title="modules/example/resources/nodes/sendAlert.js"
import { Nodes } from "#medusa/modules";

export class SendAlertNode extends Nodes {
    constructor(medusa) {
        super(medusa, {
            name: "send_alert",
            title: "Send Item Alert",
            category: "Example",
            description: "Dispatches an alert notification to a specified channel.",
            icon: "Bell",
            accent: "indigo",
            hasTarget: true,
            hasSource: true,
            fields: [
                {
                    key: "channel",
                    type: "channel",
                    label: "Destination Channel",
                    default: ""
                },
                {
                    key: "message",
                    type: "text",
                    label: "Alert Message",
                    placeholder: "Item %item_name% was updated.",
                    default: ""
                }
            ],
            branches: []
        });
    }

    async run(context) {
        const channelId = context.text("channel", "");
        const template = context.text("message", "Item updated.");
        if (!channelId) return { success: false };

        const formatted = context.fill(template);
        await this.medusa.discord.post(channelId, { content: formatted });

        return { success: true };
    }
}
```

## Conditional Branching Nodes

Nodes that evaluate conditions define `branches` and return the targeted branch identifier in `run()`:

```js title="modules/example/resources/nodes/checkStock.js"
import { Nodes } from "#medusa/modules";

export class CheckStockNode extends Nodes {
    constructor(medusa) {
        super(medusa, {
            name: "check_stock",
            title: "Check Item Stock",
            category: "Example",
            description: "Evaluates whether an item has available inventory.",
            icon: "Check",
            accent: "emerald",
            fields: [
                { key: "identifier", type: "text", label: "Item Identifier", default: "%item_id%" },
                { key: "minStock", type: "number", label: "Minimum Quantity", default: 1 }
            ],
            branches: [
                { id: "available", label: "In Stock" },
                { id: "empty", label: "Out of Stock" }
            ]
        });
    }

    async run(context) {
        const identifier = context.text("identifier", "");
        const minStock = context.number("minStock", 1);
        const handler = this.medusa.modules.handlers.get(this.module, "items");
        if (!handler) return { success: true, branch: "empty" };

        const item = await handler.getByIdentifier(identifier).catch(() => null);
        if (item && item.quantity >= minStock) {
            return { success: true, branch: "available" };
        }

        return { success: true, branch: "empty" };
    }
}
```

## Constructor Options

The configuration object passed to `super(medusa, meta)` accepts the following properties:

| Property      | Type            | Default      | Description                                                                                                                                                        |
| ------------- | --------------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `name`        | `string`        | Required     | Unique node identifier. Full type key becomes `<module>:<name>`.                                                                                                   |
| `title`       | `string`        | `name`       | Display title shown on the node card in the flow builder.                                                                                                          |
| `category`    | `string`        | `"General"`  | Palette category where this node appears in the node selector.                                                                                                     |
| `description` | `string`        | `""`         | Subtitle describing what the node does.                                                                                                                            |
| `icon`        | `string`        | `"Sparkles"` | Lucide icon identifier (e.g. `"Sparkles"`, `"Bell"`, `"Coins"`, `"Shield"`).                                                                                       |
| `accent`      | `string`        | `"indigo"`   | Tailwind color accent applied to the node header and handles (`indigo`, `violet`, `blue`, `emerald`, `teal`, `rose`, `amber`, `cyan`, `sky`, `fuchsia`, `purple`). |
| `hasTarget`   | `boolean`       | `true`       | When `true`, renders an incoming execution handle on the left.                                                                                                     |
| `hasSource`   | `boolean`       | `true`       | When `true`, renders an outgoing execution handle on the right.                                                                                                    |
| `fields`      | `Array<object>` | `[]`         | List of field configuration inputs rendered inside the node body.                                                                                                  |
| `branches`    | `Array<object>` | `[]`         | List of labeled output branches for conditional flows.                                                                                                             |
| `order`       | `number`        | `0`          | Sort weight within the palette category.                                                                                                                           |

## Node Field Types

Fields declared inside `fields` define controls displayed on the canvas:

| Field Type   | Control Rendered                                                          | Read With                           |
| ------------ | ------------------------------------------------------------------------- | ----------------------------------- |
| `"text"`     | Single-line text input (`mono: true` for code style)                      | `context.text(key, fallback)`       |
| `"textarea"` | Multi-line text input                                                     | `context.text(key, fallback)`       |
| `"number"`   | Numeric input                                                             | `context.number(key, fallback)`     |
| `"select"`   | Dropdown menu with an `options` array                                     | `context.data[key]`                 |
| `"switch"`   | Toggle switch                                                             | `context.bool(key)`                 |
| `"duration"` | Numeric input paired with a unit dropdown (`unitKey`, `unitDefault`)      | `context.durationMs(key, unitKey)`  |
| `"channel"`  | Channel picker for the flow's server                                      | `context.data[key]`                 |
| `"role"`     | Role picker for the flow's server                                         | `context.data[key]`                 |
| `"category"` | Category picker for the flow's server                                     | `context.data[key]`                 |
| `"server"`   | Picker of the other servers the editor can manage                         | `context.server(context.data[key])` |
| `"bot"`      | Picker of the bots that are in the flow's server (empty = the flow's bot) | `context.useBot(context.data[key])` |
| `"flow"`     | Picker of the other flows in the server                                   | `context.invoke(context.data[key])` |

Pickers never fall back to text inputs; an empty list shows an empty state such as "No roles found".

## Execution Context

The `context` object provided to `run(context)` supplies runtime data, trigger parameters, and helper methods:

| Property / Method                        | Return Type            | Description                                                                                                     |
| ---------------------------------------- | ---------------------- | --------------------------------------------------------------------------------------------------------------- |
| `context.guildId`                        | `string`               | Id of the server the node is acting in.                                                                         |
| `context.guild`                          | `Guild`                | The server the node is acting in. A Switch Server node changes it.                                              |
| `context.member`, `context.user`         | `GuildMember`, `User`  | Who triggered the flow. Empty for schedules and webhooks.                                                       |
| `context.channel`                        | `TextChannel`          | The routed channel, or the trigger's channel.                                                                   |
| `context.message`                        | `Message`              | The trigger message for message, reaction and message context menu triggers.                                    |
| `context.client`, `context.bot`          | `Client`, `ClientUser` | The bot running this part of the flow.                                                                          |
| `context.flow`, `context.node`           | `object`               | The stored flow and this node.                                                                                  |
| `context.data`                           | `object`               | Raw field values configured on this node instance.                                                              |
| `context.tokens`                         | `object`               | All `%tokens%` of the run.                                                                                      |
| `context.text(key, fallback)`            | `string`               | Reads a field with token interpolation.                                                                         |
| `context.number(key, fallback)`          | `number`               | Reads a numeric field with token interpolation.                                                                 |
| `context.bool(key)`                      | `boolean`              | Reads a switch field.                                                                                           |
| `context.durationMs(amountKey, unitKey)` | `number`               | Converts a duration field to milliseconds.                                                                      |
| `context.resolve(value)`                 | `string`               | Replaces tokens in any text.                                                                                    |
| `context.set(key, value)`                | `void`                 | Stores a token for later nodes.                                                                                 |
| `context.resolveMember(spec)`            | `Promise<GuildMember>` | Resolves a member from an id, mention or token; defaults to the node's `target` field or the triggering member. |
| `context.resolveMessage(reference)`      | `Promise<Message>`     | Resolves a message link or id; empty returns the trigger message.                                               |
| `context.server(serverId)`               | `Promise<Guild>`       | Another authorized server as seen by the current bot, or `null`.                                                |
| `context.route(channel)`                 | `void`                 | Sends later Embed and Send Message nodes to this channel.                                                       |
| `context.useBot(key)`                    | `Promise<Bot>`         | Runs later nodes as another bot that is in the server.                                                          |
| `context.invoke(flowId)`                 | `Promise<any>`         | Runs another enabled flow in the same server and returns its result.                                            |

`run()` returns `{ success: true }`, `{ branch: "<branch id>" }` to follow one branch, or `{ stop: true }` to end the run. Throwing an error marks the run as failed at this node and continues on the node's `error` handle if it has one.
