Artifact Types
Every type of artifact you can create in the Flow SDK — scripts, custom nodes, webhook handlers, scheduled jobs, and transforms — with skeletons and API availability.
An artifact is a versioned, deployable unit of custom code. Every artifact runs inside the FlowOS execution sandbox with access to the ctx NodeContext. You create and edit artifacts in SDK → Artifacts using the Monaco editor with full TypeScript IntelliSense.
Script
General-purpose executable. Can be triggered manually, from a workflow Script node, or via the SDK API. Use scripts for one-off data operations, migrations, and backfill jobs.
import type { NodeContext } from '@flowos/sdk'
export async function run(ctx: NodeContext): Promise<unknown> {
ctx.log.info('Script started')
const incidents = await ctx.tables.query('incidents', {
filter: { status: 'open', severity: 'P1' },
})
for (const inc of incidents.data) {
await ctx.tables.update('incidents', inc.id, {
tags: [...(inc.tags ?? []), 'auto-tagged'],
})
}
return { processed: incidents.total }
}Manifest
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| type | "script" | required | — | Artifact type discriminator. |
| name | string | required | — | Display name. |
| slug | string | required | — | URL-safe identifier. Unique per workspace. |
| description | string | optional | — | What this script does. |
| timeout | number | optional | 30 | Max execution seconds (1–300). |
| memory_mb | number | optional | 128 | Memory limit in MB (64–1024). |
| tags | string[] | optional | — | Organizational labels. |
Custom Node
A custom node appears in the Workflow Studio node palette and can be dropped into any workflow canvas. It exposes typed input/output fields defined in its manifest.
import type { NodeContext, CustomNodeInput } from '@flowos/sdk'
interface Input {
incidentId: string
enrichmentLevel: 'basic' | 'full'
}
interface Output {
enriched: boolean
ciCount: number
riskScore: number
}
export async function run(ctx: NodeContext, input: CustomNodeInput<Input>): Promise<Output> {
const incident = await ctx.tables.find('incidents', input.incidentId)
const cis = incident.cmdb_items ?? []
const riskScore = cis.length * 10 + (incident.severity === 'P1' ? 50 : 20)
await ctx.tables.update('incidents', incident.id, {
custom_fields: { ...incident.custom_fields, risk_score: riskScore },
})
return { enriched: true, ciCount: cis.length, riskScore }
}Manifest
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| type | "custom_node" | required | — | Discriminator. |
| name | string | required | — | Label shown on the node in the canvas. |
| slug | string | required | — | Unique slug. |
| icon | string | optional | "Box" | Lucide icon name. |
| color | string | optional | "indigo" | Accent color: indigo | purple | green | amber | red | blue |
| inputs | FieldDef[] | required | — | Array of input field definitions. |
| outputs | FieldDef[] | required | — | Array of output field definitions. |
| timeout | number | optional | 30 | Max execution seconds. |
FieldDef
interface FieldDef {
name: string
label: string
type: 'string' | 'number' | 'boolean' | 'object' | 'array' | 'date'
required?: boolean
default?: unknown
description?: string
enum?: string[] // restrict to specific values
expression?: boolean // allow {{expression}} binding
}Webhook Handler
Receives inbound HTTP requests from external systems. Each handler is assigned a unique stable URL. Use for GitHub push events, Stripe webhooks, PagerDuty callbacks, or any other inbound integration.
import type { NodeContext, WebhookRequest, WebhookResponse } from '@flowos/sdk'
export async function handle(ctx: NodeContext, req: WebhookRequest): Promise<WebhookResponse> {
// Verify signature
const sig = req.headers['x-hub-signature-256'] as string
const secret = await ctx.secrets.get('GITHUB_WEBHOOK_SECRET')
if (!ctx.crypto.verifyHmacSha256(req.rawBody, secret, sig)) {
return { status: 401, body: { error: 'Invalid signature' } }
}
const { repository, commits, pusher } = req.body as {
repository: { name: string }
commits: { id: string; message: string }[]
pusher: { name: string }
}
ctx.log.info('Push received', { repo: repository.name, commits: commits.length })
await ctx.events.publish('github.push', {
repo: repository.name,
pusher: pusher.name,
commitCount: commits.length,
commitIds: commits.map(c => c.id),
})
return { status: 200, body: { received: true } }
}Webhook URL
Each handler is reachable at:
https://<workspace>.flowos.io/api/webhooks/<handler-slug>?key=<signing-key>The key query parameter is an optional additional HMAC verification layer. Rotate it in SDK → Artifacts → [handler] → Security.
Manifest
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| type | "webhook_handler" | required | — | Discriminator. |
| name | string | required | — | Display name. |
| slug | string | required | — | Forms part of the URL. |
| methods | string[] | optional | ["POST"] | Allowed HTTP methods: GET | POST | PUT | PATCH | DELETE. |
| auth | enum | optional | "key" | none | key | hmac | basic | bearer |
| timeout | number | optional | 10 | Response timeout in seconds (1–30). |
| max_body_kb | number | optional | 256 | Max request body size in KB. |
Scheduled Job
Runs on a cron schedule. Equivalent to a Script artifact but with built-in schedule config. See Scheduled Jobs for full cron reference.
import type { NodeContext, ScheduledJobPayload } from '@flowos/sdk'
export async function run(ctx: NodeContext, payload: ScheduledJobPayload): Promise<void> {
ctx.log.info('Daily SLA report starting', { scheduledAt: payload.scheduledAt })
const breached = await ctx.tables.query('incidents', {
filter: { 'sla.breached': true, status: ['open', 'investigating'] },
})
await ctx.notify.broadcast({
to: { role: 'itsm-manager' },
channel: 'email',
template: 'daily-sla-report',
variables: { breachedCount: breached.total, incidents: breached.data.slice(0, 10) },
})
}Transform
A pure data mapping function used by the Data Transformer in Integration Studio. Receives an input object and returns a transformed output. No side effects — ctx.tables, ctx.http, etc. are not available.
import type { TransformContext } from '@flowos/sdk'
interface PagerDutyIncident {
id: string
title: string
urgency: 'low' | 'high'
status: string
created_at: string
}
export function transform(input: PagerDutyIncident, ctx: TransformContext) {
return {
title: input.title,
source: 'pagerduty',
external_id: input.id,
severity: input.urgency === 'high' ? 'P1' : 'P2',
status: input.status === 'triggered' ? 'open' : 'investigating',
created_at: input.created_at,
}
}Artifact Lifecycle
- •Draft — saved but not yet deployed. Editable freely.
- •Active — deployed to one or more environments. Immutable at the active version.
- •Deprecated — still executable but flagged for removal. Warning shown in canvas/scheduler.
- •Archived — not executable. History preserved.