Flow Definition Language (FDL)
FDL (Flow Definition Language) is Initfinity's text format for defining flows. It's concise, human-readable, and can be generated by AI or written by hand. Every FDL file can be converted to/from the native FlowDefinition JSON format without loss, apart from three things FDL does not carry: the editor viewport, the node catalog id and the hosted flow link. Those stay in .flow and .flob files.
Basic Structure
#fdl 1
flow "My Flow"
node1(type) field1=value1 field2="string value" @x,y
node2(type) field3=42 @x,y
node1.port -> node2.port
A flow file has three parts:
- Header:
flow "Name"declares the flow name - Nodes: each line declares one node with its type, fields, and optional position
- Edges: connections between node ports using
source.port -> target.port
Version Header
The first line of an FDL file is #fdl 1, the format version. Tools always write it. Readers accept files without it and read them as version 1, so hand-written files and older files keep working. A future incompatible format would start with #fdl 2; a reader that does not know that version stops with a clear error instead of guessing. To older readers the line is just a comment.
Node Syntax
id(typeKey) field=value field2="string" @x,y
| Part | Required | Description |
|---|---|---|
id | Yes | Unique identifier (alphanumeric, hyphens, underscores) |
typeKey | Yes | Node type (e.g. trigger, number, http-fetch) |
field=value | No | Field configuration (space-separated key=value pairs) |
@x,y | No | Position hint (decimals allowed, e.g. @10.5,3); omit to use auto-layout |
Value Quoting Rules
| Value Type | When to Use | Example |
|---|---|---|
| Bare | Alphanumeric + -_./ | GET, upper, true, 42 |
| Double-quoted | Contains spaces or special chars | "https://api.example.com", "a + b" |
| Backtick-fenced | JSON objects, arrays, or multi-line code | `{"key": "val"}` |
Operators containing >, <, = must be quoted: operator=">=".
A bare 42 is a number and a bare true or false is a boolean. To keep a string that looks like one of those, quote it: text="42", flag="true". Tools do this automatically when they write a file.
Escape sequences in double-quoted strings: \", \\, \n, \r, \t and \uXXXX. Any other backslash is kept as written, so "C:\dir" is fine.
Inside a backtick-fenced JSON value, write a literal backtick as \u0060. A value that is valid JSON is stored as that JSON value (`null` is an explicit null); any other backtick text, such as multi-line code, is kept as a string.
Display metadata
Names starting with $ are reserved for node metadata and never clash with a node's own fields:
| Key | Meaning |
|---|---|
$title="Name" | The title shown on the node |
$description="Text" | The node's description |
$style= followed by a JSON object in backticks | Node size, for example `{"width":800,"height":400}` |
A plain title= is also stored as the node title, so nodes such as code or note that have a field called title keep it.
Edge Syntax
#fdl 1
source-node.output-port -> target-node.input-port
Ports are always explicit; there are no defaults. Use the exact port IDs from the node definition.
Edges get a derived id (source-port-target-port). When a file needs a different id or extra edge data such as a label, the writer adds a line after the edges that older readers skip:
#fdl 1
flow "Custom edge"
a(trigger) @0,0
b(logger) @280,0
a.out -> b.in
edgeid a.out -> b.in e-custom
edgeextra a.out -> b.in label="go"
Background
An optional line sets a per-flow canvas background as JSON in backticks:
#fdl 1
background `{"variant":"lines","gap":24,"color":"#101820","image":"/backgrounds/space.jpg"}`
All keys are optional. image is a virtual filesystem path.
Flow id and decorators
The writer adds id= to the header only when the flow's id differs from the one derived from its name: flow "My Flow" id=my-custom-id. Canvas decorators (emoji, text, pins and so on) are written as one JSON array line:
#fdl 1
decorators `[{"id":"d1","type":"decorator","position":{"x":0,"y":0},"data":{"decoratorKind":"text","text":"Note"}}]`
Comments
#fdl 1
# This is a comment (ignored by the parser)
Auto-Layout
If all nodes have position @0,0 (or no positions are specified), the parser automatically applies a left-to-right topological layout with 280px column spacing and 160px row spacing.
Markdown List Markers
Node and edge lines may be prefixed with -, *, or + to allow embedding FDL inside markdown lists:
#fdl 1
- start(trigger) label="Run"
- fetch(http-fetch) url="https://api.example.com" method=GET
- start.out -> fetch.in
Common Node Types
| Node | TypeKey | Key Fields | Ports |
|---|---|---|---|
| Manual trigger | trigger | label | out (trigger) |
| Number | number | value | in, out |
| Text | text | text, mode | in, out |
| Math | math | formula (e.g. "a + b") | a, b → result |
| Comparison | comparison | operator (==,!=,>,<,>=,<=) | a, b → result |
| Condition / branch | condition | default, invert | in → true, false (triggers) |
| HTTP fetch | http-fetch | url, method | in (body), out, status, error |
| JSON path | json-path | path (dot-notation, e.g. user.name) | in, out |
| Logger | logger | level, prefix | in, out |
| Code | code | source, language | in, out |
| CSV parser | csv-parser | delimiter, header | in → out, headers, count, error |
| String transform | string-transform | op (upper,lower,trim,len) | in, out |
| Array filter | array-filter | property, value | in, out |
| Counter | counter | name, step | reset → count |
| Sleep | sleep | duration (ms) | in, out |
| Interval | interval | scheduleMode (interval, preset or cron), interval (ms), count, preset, cronExpression | out (trigger), ticks, index |
| JSON data | json | data | in, out |
Example: HTTP API with Error Handling
#fdl 1
flow "Fetch Todo"
fetch(http-fetch) url="https://jsonplaceholder.typicode.com/todos/1" method=GET
extract(json-path) path=title
display(text)
log(logger)
err-log(logger) prefix="ERROR:"
fetch.out -> extract.in
extract.out -> display.in
display.out -> log.in
fetch.error -> err-log.in
Example: Conditional Branching
#fdl 1
flow "Score Classifier"
score(number) value=75
threshold(number) value=60
check(comparison) operator=">="
branch(condition)
pass-msg(text) text="Pass"
fail-msg(text) text="Fail"
score.out -> check.a
threshold.out -> check.b
check.result -> branch.in
branch.true -> pass-msg.in
branch.false -> fail-msg.in
Example: Filter CSV Rows
#fdl 1
flow "Filter Active Users"
csv-input(text) text="name,active\nAlice,true\nBob,false"
parser(csv-parser)
filter(array-filter) property=active value=true
log(logger)
csv-input.out -> parser.in
parser.out -> filter.in
filter.out -> log.in