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). awaitevery action. An action call withoutawaitis left out of the compiled flow (DSL017).- No
return. Areturnhas no effect; the following actions still run (DSL018). End a run withawait ctx.terminate(...).throw,try/catch,breakandcontinueare ignored too (DSL020); errors are handled with scopes and@runAfter(see Control Flow). - No constants above the class. A
constdeclared 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 withctx.parameters('Name'). - Parameter names match exactly.
ctx.parameters('SiteUrl')must use the exact key (including case) fromctx.flow.parameters; anything else is reported asDSL015. - Don't capture an action's result.
const x = await ctx.http(...)does not work (DSL032). Read an earlier action withctx.body('Name')orctx.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,pushand 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 aFlowOptionsobject:name— the flow's namedescription?— optional descriptionworkflowId?— Dataverse workflow GUID, used byflowforger pushto identify the target flow. Set automatically byflowforger pull, and stamped into the file bypushthe first time it creates the flow — so you rarely write it by hand. Without it,pushfalls back to matching onname
@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@descriptiontag. 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,$schemaworkflowMetadata?— arbitrary workflow-level metadata (creator, provisioning method, etc.), preserved verbatimconnectionReferences— map of connection reference name →ConnectionReferenceConfig(apiId,connectionReferenceLogicalName?,connectionName?,runtimeSource?,impersonation?)parameters— map of parameter name →FlowParameterConfig(type,defaultValue?,allowedValues?,metadata?). A parameter withmetadata.schemaNameis backed by an environment variable: with a Dataverse connection,flowforger runreads its current value from the environment instead of thedefaultValuechildFlows— 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
- Triggers — Trigger types and configuration
- Actions — Built-in actions and reference functions
- Control Flow — Scope, If, ForEach, Switch, loops
- Annotations — JSDoc tags and generated names
- Diagnostics — Every validation rule and how to fix it
- Cloud Semantics — How local runs match the cloud