Add an automation node type
Add an action that flow builders can drop into an automation, such as "Create card" or "Send a Telegram message". The editor's palette and the run engine both pick it up from a registry. Concepts explains how a flow runs around your node.
- Define the node in
src/server/nodes.ts:import { z } from "zod"; import { defineNode } from "@monark/automation/server"; export const publishPostNode = defineNode({ descriptor: { kind: "action", category: "posts", label: "Posts: Publish post", description: "Publish a draft post.", icon: "Send", inputs: [{ id: "in" }], outputs: [{ id: "out" }], outputFields: [{ key: "postId", type: "string", description: "The published post." }], configFields: [{ key: "postId", label: "Post ID", type: "text", required: true }], }, configSchema: z.object({ postId: z.string() }), execute: async (ctx, config) => { ctx.log(`publishing ${config.postId}`); return { postId: config.postId }; }, });configFieldsbuilds the form in the editor;configSchemavalidates it beforeexecuteruns. Labels are plain English and aren't translated. - Check the owner's permission inside
executefor anything privileged. A flow runs as the person who created it, who may have lost access since:if (!ctx.actorUserId || !(await hasPermission(ctx.actorUserId, "posts.publish", ctx.organizationId))) { throw new Error('The automation owner lacks the "posts.publish" permission.'); }hasPermissioncomes from@monark/rbac/server. Throwing fails this step. - Register the module's nodes once, and call the helper in
services/api/src/server.tsnext to the other automation nodes:
The node's type becomesregisterAutomationNodes("posts", { "publish-post": publishPostNode });posts.publish-post. - Open an automation in the editor. The node is in the palette under its category.
Call an outside service with a secret
- Add a secret field:
{ key: "token", label: "API token", type: "secret", required: true }. The editor shows a dropdown of the organization's secret names and stores only the name. - In
execute, resolve it and fail if it's missing:const token = await ctx.getSecret(config.token); if (!token) throw new Error(`No secret named ${config.token}.`); - Make the call with
safeFetchfrom@monark/common/http, which blocks unsafe hosts and times out. - Return only non-sensitive data. Never pass the secret to
ctx.logor include it in the output. See Secrets.
Good to know:
- Each
outputFieldsentry is offered downstream as{{ steps.<node>.<key> }}. - For branching, call
ctx.activateOutputs(["<output id>"]); a node that doesn't activates all its outputs.