Flow SDK

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.

artifacts/my-script.ts
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

FieldTypeRequiredDefaultDescription
type"script"requiredArtifact type discriminator.
namestringrequiredDisplay name.
slugstringrequiredURL-safe identifier. Unique per workspace.
descriptionstringoptionalWhat this script does.
timeoutnumberoptional30Max execution seconds (1–300).
memory_mbnumberoptional128Memory limit in MB (64–1024).
tagsstring[]optionalOrganizational 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.

artifacts/enrich-incident.ts
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

FieldTypeRequiredDefaultDescription
type"custom_node"requiredDiscriminator.
namestringrequiredLabel shown on the node in the canvas.
slugstringrequiredUnique slug.
iconstringoptional"Box"Lucide icon name.
colorstringoptional"indigo"Accent color: indigo | purple | green | amber | red | blue
inputsFieldDef[]requiredArray of input field definitions.
outputsFieldDef[]requiredArray of output field definitions.
timeoutnumberoptional30Max execution seconds.

FieldDef

typescript
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.

artifacts/github-push-handler.ts
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:

bash
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

FieldTypeRequiredDefaultDescription
type"webhook_handler"requiredDiscriminator.
namestringrequiredDisplay name.
slugstringrequiredForms part of the URL.
methodsstring[]optional["POST"]Allowed HTTP methods: GET | POST | PUT | PATCH | DELETE.
authenumoptional"key"none | key | hmac | basic | bearer
timeoutnumberoptional10Response timeout in seconds (1–30).
max_body_kbnumberoptional256Max 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.

artifacts/daily-sla-report.ts
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.

artifacts/normalize-pagerduty-incident.ts
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.
Every save creates a new immutable version. You can view and restore any previous version from the Versions tab on the artifact detail page.