About footprint-workflows
Footprint Workflows is a skill for authoring and modifying Datex Studio footprint-workflow configurations in Footprint. It guides developers through binding a low-code TypeScript body to a platform-defined extension point, validating it, and publishing it on a feature branch.
Key features
- 01Binds workflows to platform-defined extension points
- 02Uses metadata to discover slot parameters
- 03Authors low-code TypeScript workflow bodies
- 04Validates and upserts configurations with `dxs`
Use cases
- 01Implementing custom logic at a Footprint processing extension point
- 02Editing a workflow while preserving its configuration and GUID
- 03Checking workflow structure before pushing a configuration
Install
Copy the command and run it in a terminal.
npx skills add https:/ / github.com/ datex/ skills --skill footprint-workflowsAfter installing, type/footprint-workflowsin Claude Code to confirm it loaded.
Bundled filesreferences/footprint-workflows.md
Details
From the project READMEFootprint Workflows
Author or modify a Datex Studio footprint-workflow (configurationTypeId: 23) on a branch — a low-code TypeScript implementation that the Footprint platform invokes at a named extension point in its own processing (before an entity status commits, while cartonizing, while planning allocation, while recommending a location, …). It is the modern replacement for the legacy XAML "Datex Workflow" activities. The platform owns the slot, the GUID, and the input/output contract; you own the body.
References
- ../datex-studio-shared/branch-setup.md — Branch/connection selection (shared across skills)
- references/footprint-workflows.md — Authoritative reference: body shape, the slot binding + workflowsMetadata discovery, the fixed param contract, per-slot field breakdowns, the extension-point catalog, code patterns, CLI lifecycle, pre-flight checklist
- ../datex-studio-shared/configuration-roundtrip.md — The
get -O envelope → jq .json → upsert -Dround-trip and the silent-wipe bug it avoids - ../datex-studio-conventions/file-format.md —
configurationTypeIdtable, code-string editing rules, thereturn;-with-outParams rule - ../datex-studio-conventions/naming-conventions.md —
_workflowsuffix preference; backend types are not bound by the display-name rule - ../datex-studio-conventions/universal-checklist.md — cross-cutting checks every component must pass
- ../datex-studio-runtime/calling-conventions.md — execution-tier rules; workflow runs action-tier (calls actions via
$flows, readsfpds, no$db, no functions) - ../datex-studio-shared/flow-code-patterns.md —
$utils.isDefined, date defaulting, and other flow-code idioms - ../action-creator/references/actions.md — actions are the dispatch target the workflow body calls; same Footprint-server tier
- ../datasource-creator/references/datasources.md —
fpds_*footprint-datasources the workflow reads (action-tier)
Dependencies
requirements-gatheringskill — invoked to produce a requirements brief if one doesn't already exist in context (which slot, what the custom behavior is, what it dispatches to, which package).action-creatorskill — invoked to author the package actions the workflow body dispatches to. Keep the workflow node a thin dispatcher; real logic lives in actions (same Footprint-server tier).function-creatorskill — invoked when the dispatched logic is better expressed as a function wrapped behind an action (workflows can't call functions directly).datasource-creatorskill — invoked when the workflow reads configuration/state through afpds_*footprint-datasource.type-definition-creatorskill — invoked when the dispatch targets need package interfaces/enums (theFootPrintWorkflow.*input/result types are platform-owned and read fromcontexts, not authored).impact-analysisskill — invoked before changing a workflow that other code depends on, or before renaming the actions/datasources it dispatches to; trace$flows.<Package>.<action>call sites rather than grepping inline.
CLI Lifecycle
Workflow authoring goes through dxs configuration — the generic CRUD primitive over every platform configuration type. There is no dxs workflow subcommand and no field-level patching; you build (or fetch + extract) the whole JSON body, edit it, and push the whole thing back. The CLI type identifier is footprintworkflow (lowercase, matches ConfigurationEndpoints.normalize_type output), mapping to configurationTypeId: 23. PublishedMain configs are readonly: true — author on a feature branch.
New workflow — discover the slot from the metadata API, assign a fresh GUID (replacing a legacy workflow instead? reuse its GUID — see step 2):
# 1. Discover the slot: id (= workflowDefinitionId), name, exact inParams/outParams
dxs api GET "/footPrintApiConnections/byName/<connectionName>/workflowsMetadata?applicationId=<branchId>" --raw -O meta.json
jq '.workflowsMetadataJson.workflowDefinitions[] | select(.name=="Cartonization")' meta.json
# 2. Generate a fresh workflowGUID for this new config (new capability; you point callers at it).
# Superseding a legacy workflow on this slot? Reuse ITS GUID instead so existing callers keep working.
python3 -c "import uuid; print(uuid.uuid4())"
# 3. Build body.json: apiSettingName + workflowDefinitionId/Name (step 1) + your fresh GUID,
# the slot's inParams/outParams verbatim, configurationTypeId:23, id:0, your referenceName/title/description/code
# 4. (Optional) read Input/result field shapes — contexts OR meta.json `types`
dxs configuration contexts footprintworkflow -b <branchId> -D body.json
# 5. Validate, THEN upsert — these are two steps, not one. Validate exits 1 when it finds
# errors (read validation_errors, fix body.json, re-run); do not run the upsert on exit 1.
dxs configuration validate footprintworkflow -b <branchId> -D body.json
dxs configuration upsert footprintworkflow -b <branchId> -D body.json
Edit an existing workflow (round-trip — never skip the jq extract):
dxs configuration get footprintworkflow <configId> -b <branchId> -O envelope.json
jq .json envelope.json > body.json # EXTRACT THE INNER BODY (round-trip footgun guard)
# ... edit nodes[0].stepConfig.executeCodeConfig.code ...
dxs configuration validate footprintworkflow -b <branchId> -D body.json # exit 1 = errors found; fix, do not push
dxs configuration upsert footprintworkflow -b <branchId> -D body.json
Round-trip rule (critical)
Never pipe envelope.json directly into dxs configuration upsert — it silently destroys config content (the envelope carries id/jsonString/version/… that upsert -D doesn't expect). Always jq .json envelope.json > body.json first. The envelope also carries Azure app-registration secrets in application.applicationDefinition — the jq .json extract drops them; never commit envelope.json. See ../datex-studio-shared/configuration-roundtrip.md.
Workflow
[Phase 1: Setup + Requirements]
Follow branch-setup.md for branch/connection selection (feature branch — main is readonly)
|
[requirements brief in context?]
YES -> use it NO -> invoke `requirements-gathering`
|
[Phase 2: Identify the platform slot]
Pick the extension point (Cartonization, Entity Status Change (Before Commit),
Allocation Strategy, Recommend* , Barcode Parser, ...). Consult
references/footprint-workflows.md -> Extension-Point Catalog.
If nothing in the platform *calls* the slot -> a workflow config is inert; stop.
|
[Phase 3: Bind to the slot]
Pull the workflowsMetadata API for the connection/branch -> copy the slot's
id (=workflowDefinitionId), name, and inParams/outParams VERBATIM. Never
invent the id/name. workflowGUID is NOT in the metadata — it is the code callers
pass: generate fresh for a NEW workflow, preserve it on edits, and reuse the
legacy GUID when shipping a drop-in replacement for an existing workflow.
|
[Phase 4: Author the body]
Build body.json:
- Slot binding (Phase 3): apiSettingName + workflowDefinitionId/Name +
your workflowGUID + configurationTypeId:23 + start:"step1"
- Fixed param contract: single Input: FootPrintWorkflow.<Slot>InputBaseWL;
out-params exactly as the slot dictates (or [] for before-commit mutation)
- Single ExecuteCodeActivity node; code is a THIN DISPATCHER to package
actions ($flows.<Pkg>.<action>) and fpds reads; map results into
$flow.outParams; action-tier rules (no $db, no functions)
- id:0, accessModifier:public, description <=100 chars, vars/events null
|
[Phase 5: Discover types + validate]
dxs configuration contexts footprintworkflow -b <branchId> -D body.json
-> confirm $types.FootPrintWorkflow.<X>InputBaseWL + result fields you use
dxs configuration validate footprintworkflow -b <branchId> -D body.json
|
[Phase 6: Push]
CREATE -> dxs configuration upsert footprintworkflow -b <branchId> -D body.json
MODIFY -> get -O envelope -> jq .json -> edit -> upsert (round-trip)
|
[Phase 7: Verify (optional)]
Exercise the slot in the running platform; confirm the workflow is invoked and
the result/mutation lands. Re-fetch (jq .json) and diff against body.json.
|
[invoke `post-edit-verification`; then `component-validator`]
Phase Details
Phase 1: Setup + Requirements
- Follow ../datex-studio-shared/branch-setup.md. Never assume a branch ID — confirm with the user. Author on a feature branch; PublishedMain workflow configs are
readonly: true. - Check for a requirements brief in context. The brief should establish: which platform slot, the custom behavior, what package actions/datasources it dispatches to, and the target package. No brief → invoke
requirements-gathering.
Phase 2: Identify the platform slot
A footprint-workflow only runs if the Footprint platform calls its slot. Consult references/footprint-workflows.md → Purpose & When to Use and the Extension-Point Catalog. If the desired behavior isn't triggered by one of the named workflow slots, this is the wrong component type — a plain action/function/datasource is what's needed. If the signature should be yours to design, it's a function/action, not a workflow.
Phase 3: Bind to the slot
The slot binding has two independent parts:
- Slot identity + signature (from the platform). Pull the workflowsMetadata API for the Footprint connection and branch:
Copy the slot'sdxs api GET "/footPrintApiConnections/byName/<connectionName>/workflowsMetadata?applicationId=<branchId>" --raw -O meta.json jq '.workflowsMetadataJson.workflowDefinitions[] | {id, name, inParams, outParams}' meta.jsonid→workflowDefinitionId,name→workflowDefinitionName, and itsinParams/outParamsverbatim.<connectionName>is a real Footprint connection (e.g.DSV), not theapiSettingNamevalue. SetapiSettingNamefrom the branch's Footprint setting. Never invent the id/name. workflowGUID(the code callers pass). It is not in the metadata, so nothing in the catalog dictates it — but callers do: the value reaches the server asProcessingStrategyWorkflowCode/AllocationStrategyWorkflowIdand selects which implementation of the slot runs. Generate a fresh v4 UUID for a genuinely new workflow (python3 -c "import uuid; print(uuid.uuid4())") and then point the callers at it; preserve the existing GUID unchanged when editing (thejq .jsonround-trip keeps it); reuse the legacy GUID when your config is a drop-in replacement for an existing implementation whose callers must not change. Never regenerate on an edit.
Phase 4: Author the body
Build body.json from references/footprint-workflows.md → Minimal Valid Skeleton. Key points:
- File basics per the Pre-Flight Checklist below + universal checklist:
configurationTypeId: 23,id: 0,description≤ 100 chars,accessModifierset, package matches the feature. - Param contract is the platform's, not yours. Single
Inputin-param typedFootPrintWorkflow.<Slot>InputBaseWL; out-params exactly as the slot dictates (one object, a collection, or[]for before-commit mutation). Don't renameInput, add params, or change the out-param shape. - Single
ExecuteCodeActivitynode.start: "step1", nodeid: "step1",decisionConfig: null. Multi-node graphs are possible but unused in the library — single step unless you have a concrete reason. - Code is a thin dispatcher. Translate
$flow.inParams.Inputinto$flows.<Package>.<action>calls andfpdsreads; map results onto$flow.outParams.<Name>. Action-tier rules: no$db, no direct function calls,fpds(not cloud-datasource.json). Declared-outParamsslots usereturn $flow.outParams;, never barereturn;. See references/footprint-workflows.md → Code Patterns. vars/events/fromBaseConfigurationstaynull.
Phase 5: Discover types + validate
dxs configuration contexts footprintworkflow -b <branchId> -D body.json
dxs configuration validate footprintworkflow -b <branchId> -D body.json
contexts returns the $types.FootPrintWorkflow.* IntelliSense surface, and its flowContext echoes your declared signature as a typed IFlow — a quick check that $flow.inParams.Input / $flow.outParams.* resolve to the slot's types. Confirm every field you reference exists (don't hand-roll the platform types). Three discovery sources, by need (see references → Discovering the type surface):
FootPrintWorkflow.*platform types (theInput/result shapes) —contexts, or theworkflowsMetadataAPI'stypesarray (structured JSON, easier to query).$types.<Package>.*package types your dispatch code uses (e.g.$types.Utilities.e_awi_scopes.Cartonization) —dxs configuration nomenclature -b <branchId>(filter with--package/--kind enum/--search; enums list members inconstantValues).
validate catches structural/required-field errors but not a wrong slot binding (workflowDefinitionId/name), a signature that doesn't match the slot, or action-tier violations — walk the Pre-Flight Checklist for those.
Phase 6: Push
# New
dxs configuration upsert footprintworkflow -b <branchId> -D body.json
# Modify-existing (round-trip — never skip the jq extract)
dxs configuration get footprintworkflow <configId> -b <branchId> -O envelope.json
jq .json envelope.json > body.json
# ... edit ...
dxs configuration upsert footprintworkflow -b <branchId> -D body.json
Phase 7: Verify (optional)
Exercise the slot in the running platform — trigger the entity status change / cartonization / allocation / location-recommendation and confirm the workflow is invoked and the result or before-commit mutation lands as intended. If the app isn't available, re-fetch the config (jq .json extract) and diff against body.json to confirm the push landed.
Pre-Flight Checklist
Walk the full list in references/footprint-workflows.md → Pre-Flight Checklist. Fast version:
configurationTypeId: 23; conventional suffix-footprintWorkflow.json.- Slot binding real —
workflowDefinitionId+workflowDefinitionNamecopied from the workflowsMetadata catalog;apiSettingNamematches the branch's Footprint setting. workflowGUIDcorrect — fresh v4 UUID (new workflow, callers repointed), the unchanged existing value (edit), or the legacy workflow's GUID (deliberate drop-in replacement). Never regenerated on an edit.- Param contract matches the slot — single
Inputtyped with the slot's input type (usuallyFootPrintWorkflow.<Slot>InputBaseWL); out-params exactly as the slot dictates (or[]). description≤ 100 chars, non-empty;accessModifierset;id: 0for net-new.- Single
ExecuteCodeActivitynode;startpoints at itsid;vars/events/fromBaseConfigurationnull. - Action-tier compliant code —
$flows.<Pkg>.<action>(no functions, no$db),fpdsreads,$types.FootPrintWorkflow.*,$utils.isDefined; declared-outParams→return $flow.outParams;. - Types verified against
contextsor the metadatatypes. - Validated against the branch.
Common Mistakes
| Mistake | Fix |
|---|---|
Invented or mismatched workflowDefinitionId / workflowDefinitionName | Pull the slot's id/name + inParams/outParams from the workflowsMetadata API and copy verbatim. A wrong/mixed id+name validates clean but never wires. |
Regenerated workflowGUID on an edit, or cloned one from an unrelated config | The GUID is what callers pass to select this workflow (not part of the slot binding). Fresh v4 UUID for a new workflow (python3 -c "import uuid; print(uuid.uuid4())"); preserve unchanged on edits. Copying a GUID is correct in exactly one case — a drop-in replacement for the workflow that owns it. |
Renamed Input, added in-params, or changed the out-param shape | The slot owns the signature. Keep single Input: FootPrintWorkflow.<Slot>InputBaseWL + the slot's exact out-params (or []). Mismatch validates clean, breaks the invoke. |
Hand-wrote interfaces for the Input/result | Read real shapes from dxs configuration contexts footprintworkflow -D body.json; reference $types.FootPrintWorkflow.*. |
Called a function or used $db from the workflow | Action-tier: call actions via $flows.<Pkg>.<action>; wrap function/$db logic behind an action; use fpds, not cloud datasources. |
Bare return; in a slot that declares outParams | Use return $flow.outParams; — bare return; breaks the generated body; Validate misses it, Preview catches it. |
Piped get -O envelope.json straight into upsert -D | jq .json envelope.json > body.json first — the envelope wipes content and carries secrets. |
Raw find/replace on the minified code string | Edit via Python json.load/json.dump; build with \r\n joins; never restructure surrounding JSON. |
| Deep business logic inline in the node | Keep the node a thin dispatcher to package actions; matches the library, stays maintainable. |
| Authored against PublishedMain | Main is readonly: true. Author on a confirmed feature branch (never guess the id). |
After your edit, invoke post-edit-verification to surface description/JSON/schema violations. For a final review, invoke component-validator.