Skip to main content

DSL Overview

The FlowForger DSL lets you write Power Automate flows as TypeScript classes with decorators. A transformer reads the class at compile time and produces a FlowIR document, which the emitter turns into Logic Apps/Power Automate clientdata.json.

The class is never executed as JavaScript. The transformer reads its source, so only the constructs described in these pages have a meaning: TypeScript that has no Power Automate equivalent is ignored or reported (see Diagnostics).

Structure​

A flow is a TypeScript class decorated with @Flow, containing one trigger method, one @Action() method with the flow logic, and an optional constructor for flow-level configuration.

/**
* Says hello. This comment becomes the flow's description.
*/
@Flow('MyFlow')
class MyFlow {
@HttpTrigger({ method: 'POST' })
trigger() {}

@Action()
async run(ctx: FlowContext) {
// Build the greeting (this comment becomes the action's description)
await ctx.compose('Greeting', { message: 'Hello from FlowForger' });
}

constructor(ctx: FlowContext) {
ctx.flow.metadata = { schemaVersion: '1.0.0.0' };
}
}

Rules​

A few rules follow from the class being read rather than run:

  • One trigger method, one @Action() method. Only the first @Action() method is compiled (DSL019).
  • await every action. An action call without await is left out of the compiled flow (DSL017).
  • No return. A return has no effect; the following actions still run (DSL018). End a run with await ctx.terminate(...). throw, try/catch, break and continue are ignored too (DSL020); errors are handled with scopes and @runAfter (see Control Flow).
  • No constants above the class. A const declared outside the class is not part of the flow: reading it compiles to the text @NAME, with no warning. Put configuration in flow parameters in the constructor and read it with ctx.parameters('Name').
  • Parameter names match exactly. ctx.parameters('SiteUrl') must use the exact key (including case) from ctx.flow.parameters; anything else is reported as DSL015.
  • Don't capture an action's result. const x = await ctx.http(...) does not work (DSL032). Read an earlier action with ctx.body('Name') or ctx.outputs('Name').
  • Constructor at the bottom. By convention the constructor is the last member of the class, and generated DSL puts it there.
  • Valid TypeScript only. A syntax error stops compile, run, push and the debuggers (DSL051), because TypeScript would otherwise silently recover and the half-parsed code would compile into broken expressions. Note that optional chaining with brackets is ?.['value'] in TypeScript, not ?['value'].

Run flowforger validate <flow.ff.ts> to check all of this before you push.

Decorators​

  • @Flow(nameOrOptions) — Marks a class as a flow definition. Accepts either a string name or a FlowOptions object:

    • name — the flow's name
    • description? — optional description
    • workflowId? — Dataverse workflow GUID, used by flowforger push to identify the target flow. Set automatically by flowforger pull, and stamped into the file by push the first time it creates the flow — so you rarely write it by hand. Without it, push falls back to matching on name
    @Flow({ name: 'ProcessOrders', description: 'Process new orders', workflowId: '11111111-1111-1111-1111-111111111111' })
    class ProcessOrders { /* ... */ }
  • @HttpTrigger(options?) — HTTP request trigger. See Triggers.

  • @ManualTrigger(options?) — Manual (button/PowerApp) trigger.

  • @RecurrenceTrigger(options) — Scheduled trigger.

  • @ConnectorTrigger(options) — Trigger off a connector event (e.g. "when an item is created" in SharePoint).

  • @Action() — Marks the method containing the flow's action logic. A flow has exactly one.

Comments and Descriptions​

Comments carry over to Power Automate:

  • The JSDoc comment above the class is the flow's description, unless @Flow({ description }) sets one. A file-level JSDoc comment is used when the class has none. The flow description has no length limit.
  • A // comment directly above a statement becomes that action's description (the note on its card in the designer). So does a @description tag. Free text in a /** ... */ comment is not a description.
  • Power Automate limits action and trigger descriptions to 256 characters. A longer comment is emitted as a 256-character excerpt, and the full text is kept in the action's metadata (flowforgerDescription), so converting the flow back to the DSL restores it. If the note is edited in the designer, the edited text wins.

JSDoc tags such as @action, @runAfter and @runtimeConfig set things TypeScript has no syntax for; see Annotations.

Flow Context​

Every trigger and action method receives a FlowContext object (ctx) that provides:

  • Reference functions (ctx.body(), ctx.outputs(), ctx.triggerBody(), ctx.variables(), ...) — see Actions
  • Built-in actions (ctx.http(), ctx.compose(), ctx.response(), ...) — see Actions
  • Control flow support (native if/for/while) — see Control Flow
  • Typed connector access (ctx.connectors.sharepoint.GetItems(...), ctx.connectors.dataverse.ListRecords(...), ...) — see Connectors
  • Runtime expression evaluation (ctx.eval()) and expression helper functions (ctx.utcNow(), ctx.formatDateTime(), ctx.guid(), ...) — see Expressions
  • Flow-level configuration via ctx.flow

Flow Configuration (ctx.flow)​

ctx.flow is a FlowConfig object you set up in the constructor. It replaces hand-written JSON for metadata, connection references, parameters, and child flow definitions:

constructor(ctx: FlowContext) {
// Logic Apps schema/content version info
ctx.flow.metadata = { schemaVersion: '1.0.0.0' };

// Connector connection references used by the flow
ctx.flow.connectionReferences = {
'shared_sharepointonline': {
apiId: '/providers/Microsoft.PowerApps/apis/shared_sharepointonline',
connectionReferenceLogicalName: 'cr_sharepoint',
},
};

// Flow parameters (e.g. environment variables)
ctx.flow.parameters = {
'SiteUrl': { type: 'String', defaultValue: 'https://contoso.sharepoint.com' },
};

// Name-based child workflow references, resolved by ctx.callWorkflow(name, 'FriendlyName', ...)
ctx.flow.childFlows = {
GenerateDocument: {
workflowId: 'fa05dee0-12d5-f011-8544-7c1e523655f2',
description: 'Generates a Word document',
},
};
}

FlowConfig fields:

  • metadata — FlowMetadataConfig: schemaVersion, contentVersion, $schema
  • workflowMetadata? — arbitrary workflow-level metadata (creator, provisioning method, etc.), preserved verbatim
  • connectionReferences — map of connection reference name → ConnectionReferenceConfig (apiId, connectionReferenceLogicalName?, connectionName?, runtimeSource?, impersonation?)
  • parameters — map of parameter name → FlowParameterConfig (type, defaultValue?, allowedValues?, metadata?). A parameter with metadata.schemaName is backed by an environment variable: with a Dataverse connection, flowforger run reads its current value from the environment instead of the defaultValue
  • childFlows — map of friendly name → ChildFlowDefinitionConfig (workflowId, description?, parameters?, dslPath?) for calling child workflows by name instead of GUID (see Actions — ctx.callWorkflow)

File Convention​

FlowForger DSL files use the .ff.ts extension.

Next Steps​