Subchapter 22.6
references/flow.mdMarkdown20 KBView on GitHub
Build and manage workflows through the Admin GraphQL API and run store operations with Shopify CLI. Use the CLI guidance for setup/execution and Admin guidance for ordinary resource queries.
Use unstable. Reads and writes require approved write_automations access and a user context. Ask for the store domain when needed to run an operation.
shopify store auth --store example.myshopify.com --scopes write_automationsFollow CLI execution rules. Instructions or draft JSON do not authorize store changes; creating a draft does not authorize activation. Before a mutation, state the target store/workflow.
Use the bundled Admin unstable schema for operation details and validation. If it lacks a Flow operation, check against the public unstable Admin reference (opens in a new tab) instead.
| Read | Purpose |
|---|---|
automationWorkflows | Search by name, status:ACTIVE/status:INACTIVE, or trigger:"<task ID>", combined with AND. |
automationWorkflow(id:, version:) | Read a workflow; omit version for latest draft, or main if no draft exists. |
automationTasks | Search by text and optional taskType. |
automationTask(id:) | Look up a returned versioned task GID. |
automationTemplates | Search template titles and descriptions by text. |
automationTemplate(id:) | Read the chosen template’s configuration. |
automationEnvironmentPaths | Find data paths with required rootType and natural-language query. |
automationFlowFileParse(flowFileContent:) | Parse complete .flow contents into name and configuration without saving. |
Task/workflow connections support forward/backward pagination and reverse, up to 100 results per page. Templates use connection pagination too. Environment search defaults to 10, maximum 40. Select pageInfo { hasNextPage endCursor } for forward searches.
Selection sets that match the schema:
query FindTasks($query: String!) {
automationTasks(first: 10, query: $query) {
nodes { id taskId version label taskType state connector { name installed installUrl } }
pageInfo { hasNextPage endCursor }
}
}query ConfigureTask($id: ID!) {
automationTask(id: $id) {
taskId version outputPortIds
configFields { ...FlowTaskConfigField }
returnFields { name description type typeDefinitions }
}
}query ReadWorkflow($id: ID!, $version: String) {
automationWorkflow(id: $id, version: $version) {
id name version versionState activeVersion adminApiVersion tags note configuration
validationErrors { code message stepId configFieldId }
}
}Search templates with nodes { id title description }, then fetch configuration through automationTemplate(id:) for the chosen template.
| Mutation | Arguments |
|---|---|
automationWorkflowCreate | Required name, configuration; optional tags, note. |
automationWorkflowUpdate | Required id, version; optional name, configuration, tags, note. |
automationWorkflowActivate / automationWorkflowDeactivate | Required id, version. |
automationWorkflowDelete | Required id; deletes the whole workflow. |
Select userErrors { field message code } on every mutation. Read the returned workflow’s ID/version, activation state, and validationErrors { code message stepId configFieldId } where available. Delete returns deletedAutomationWorkflowId.
mutation CreateWorkflow($name: String!, $configuration: JSON!) {
automationWorkflowCreate(name: $name, configuration: $configuration) {
automationWorkflow {
id
version
activeVersion
validationErrors { code message stepId configFieldId }
}
userErrors { field message code }
}
}Put the configuration object directly under the variables’ configuration key. Write the operation and variables to files, then execute:
shopify store execute \
--store example.myshopify.com \
--version unstable \
--query-file /tmp/flow-operation.graphql \
--variable-file /tmp/flow-variables.json \
--allow-mutations \
--jsonOmit --allow-mutations for reads.
For automationFlowFileParse, pass the original .flow contents, including the checksum; do not decode the export yourself. A malformed or unsupported file fails with a GraphQL error rather than a null result.
Check connectors, tasks, resource IDs, and timezone before saving on another store. These APIs do not expose export, history, audit events, manual runs, or validation without saving.
Look up tasks and paths only when creating a workflow or changing its logic. Listing, renaming, activating, deactivating, and deleting need only the operations above.
MAIN task versions. Read chosen tasks’ configFields, outputPortIds, and returnFields. Use their GraphQL id for lookup; write taskId/version into task_id/task_version. Installation is connector.installed; task states are MAIN, DEPRECATED, or DRAFT.Read the first task/environment page. Refine off-target searches; paginate relevant results with after: endCursor when a suitable match is missing and hasNextPage is true. Keep query, filters, and schema context unchanged while following a cursor. Stop once enough verified information is available.
Request config fields’ shared and type-specific metadata: validations, options, Liquid/secrets support, allowed resource/path types, query filters, units, and defaults. Validations include allowed values, numeric ranges, and JavaScript regex patterns/flags; subfield identifies the value part checked.
In the installed skill, append the relevant definitions from assets/flow/task-config-fields.graphql to the query and spread FlowTaskConfigField under configFields. For validation-only queries include only the validation fragment. With MCP, select the equivalent metadata using the Admin schema.
Flow loads the trigger fields used by the workflow. Get Data is for additional records. Prefer dedicated tasks over API requests and conditions/Sum/Count over Run Code where suitable. If discovery is unavailable, use supplied task/template definitions and identify gaps; do not guess metadata.
automationEnvironmentPaths searches Admin API types, not app-defined types. For app-defined trigger/action outputs, read the task’s returnFields { name type typeDefinitions } and verify paths against those definitions. An empty environment search does not mean an app field is unavailable.
Pass the Admin type supplied by the trigger/earlier action as rootType, such as Order, rather than runtime names such as order or getOrderData. Search from the root available in this workflow: finding a field under Customer does not establish a standalone customer variable in an order workflow.
query FindFields($workflowId: ID, $version: String) {
automationEnvironmentPaths(
rootType: "Order"
query: "customer email"
workflowId: $workflowId
version: $version
first: 10
) {
nodes { path description leafType pathElementsTypes searchCategories }
pageInfo { hasNextPage endCursor }
}
}For existing workflows, pass workflowId and the version being edited; omit both for new workflows. version requires workflowId. Batch independent searches with aliases, one root/query per field.
dataTypeFilter accepts ALL, LIST, SCALAR, SCALAR_IN_LIST, or SCALAR_OR_SCALAR_IN_LIST. Start with ALL for unknown shapes; use LIST for loop inputs and SCALAR for paths without lists. Optional scalarTypes names scalars/enums; it cannot be empty or combined with LIST. Match task allowedTypes against results.
Translate merchant words into a few candidate searches: cost → totalPrice, subtotalPrice, currentTotalPrice; location → defaultAddress, shippingAddress, country; purchases → lineItems, product, variant, quantity; discounts → discountCode, discountApplications; stock → inventoryLevel, inventoryQuantity; timing → createdAt, processedAt. Read descriptions to distinguish meanings such as original/current totals. Interpret path, leafType, pathElementsTypes, and searchCategories together: scalar leaves can sit inside lists. SCALAR_IN_LIST needs list operators; LIST needs iteration; ALL alone can require checking nested lists. Segment types omit full list/nullability signatures.
Search discovers schema paths, not records or step scope. Confirm the trigger/earlier action supplies the root on the same branch. Action outputs depend on return metadata, configuration, and the step’s actual output name; preserve names and verify suffixes for repeated tasks. Search does not establish Run Code fields, analytics columns, or patches. Results such as order.metafield need arguments configured before use.
Runtime paths are direct camelCase fields, without edges/nodes/node. Output names commonly follow the step, such as getOrderData, sendAdminApiRequest, count, sum, and runCode; repeated tasks can take numeric suffixes. ForEach item variables exist only within loop_body, never through after or another branch; order.lineItems commonly binds lineItemsForeachitem, and a getOrderData list binds getOrderDataForeachitem. For unknown-path errors, recheck the relevant schema/type definitions, root, list traversal, outputs, and patches; leave unverifiable fields unfinished.
Read the workflow JSON schema (opens in a new tab) before authoring and validate structure against it. The installed skill bundles it at assets/flow/workflow_structure_schema.json. Task values, links/ports, scope, and other server rules need separate checks.
__metadata.version at 0.1. Include root.steps, root.links, and root.patched_fields. Workflow name, tags, and definition note are separate mutation arguments.order_created, add_vip_tags, and add_review_tags. Distinguish repeated uses of the same task by purpose. Include discovered task_id, task_version, uppercase task_type, and config_field_values. Omit step name, or match the task label with an optional (n) suffix. Preserve IDs on edits; a step ID reused for a different task is rejected, so replacing a task needs a new ID and updated links. The server lays out steps itself and keeps each existing step’s description and note by step ID.from_step_id, from_port_id, to_step_id, to_port_id. Reference real steps and discovered output ports: ordinarily output, conditions true/false, ForEach loop_body/after; destinations use input.order.id without Liquid. Follow field metadata and preserve unrelated null/default values.For example, Add order tags can use {"config_field_id":"order_id","value":"order.id"} and {"config_field_id":"tags","value":"[\"vip\"]"} after checking metadata. Bundled examples are assets/flow/order-created-add-order-tags.json and assets/flow/order-created-conditional-tags.json; verify versions and connector availability before execution.
Conditions use Flow’s DSL in the string condition config field, with paths/literals and parentheses. Reads omit a blank condition; omit the entry rather than sending an empty string, which is rejected. Operators:
==, !=, >, >=, <, <=.start_with?, end_with?, include? and their not_ forms; in?/not_in? for comma-separated literals.empty_or_nil?/not_empty_and_not_nil? for strings; nil?/not_nil? for other scalars.any?, all?, none?; combinations: &&, ||.String comparisons ignore case. include? and the other string operators test one string, never a list; test list membership with any?, all?, or none? over the items. Use numeric/boolean literals and ISO 8601 dates. in?/not_in? take one quoted string: a JSON array such as '["1","2"]', which is the form reads return, or a comma-separated list such as '1,2,3'. Numbers compare numerically. Use one kind of logical operator at the top level, either && or ||; split mixed AND/OR logic into separate conditions. Do not put Liquid or JavaScript in conditions.
(order.totalPrice > 100)
(order.note include? 'urgent')
(order.name in? '#1001,#1002')
(order.customer.firstName not_empty_and_not_nil?)
(order.tags any? |tags_item| (tags_item == 'vip'))
(order.lineItems any? |lineItems_item| (lineItems_item.product.tags any? |tags_item| (tags_item == 'presale')))Use Liquid only in fields whose metadata supports it. Flow’s Liquid data model differs from theme Liquid, and the theme Liquid validator does not check Flow paths or scope. Output scalar properties and iterate object lists with {% for item in order.lineItems %}. Do not serialize whole Flow objects with | json, index lists with [0], or use .first, .last, .empty?, or .size shortcuts. Count projected scalars: {{ order.lineItems.id | size }}. Assign filtered values before if comparisons; escape scalar values correctly in JSON.
Liquid supports date_minus/date_plus with day, week, month, or year units, for example {{ scheduledAt | date_minus: "1 day" }}; these are not condition operators. Look up the shop timezone. Hash filters include md5, sha1, sha256, blake3, hmac_sha1, hmac_sha256; use supported secret handling for HMAC keys.
Use root.patched_fields for fields with arguments. Look up signatures and reuse equivalent patches:
{"field":"metafield","arguments":{"namespace":"custom","key":"loyalty_tier"},"handle":"loyaltyTier","patched_type":"CUSTOMER","merchant_configured":true}The handle becomes customer.loyaltyTier.value. Variant patches use PRODUCT_VARIANT. Preserve verified existing types; distinguish metaobject definition IDs from entry IDs/handles using task metadata. Use the Custom Data guidance when creating or changing definitions is part of the work.
DTSTART and IANA timezone. Decoded form: DTSTART;TZID=America/Toronto:YYYYMMDDTHHMMSS\nRRULE:FREQ=DAILY;INTERVAL=1;WKST=MO. Replace placeholders with a future start and preserve the newline through both JSON layers. Intervals range from 10 minutes to one year; follow field constraints.[0]. Use discovered queryFilters, presets, sort/limit rules; unsupported filters can be ignored and broaden results. Check resource-specific search/ID formats, quote values with spaces, and use ISO dates.listpath, a verified list path without Liquid braces. loop_body enters iteration; after runs after completion. Verify item names/scope.adminApiVersion, which can differ from the unstable endpoint used to manage it. Its config value is a JSON-encoded object with mutation name and JSON argument text in blob, e.g. {"name":"customerUpdate","blob":"{\"input\":{\"id\":\"{{ order.customer.id }}\",\"note\":\"Reviewed\"}}"}. Serialize that object as the config string; it is not a full GraphQL operation. Begin/edit/commit calls need separate ordered actions with verified returned IDs; use ForEach after when commit must follow all iterations.[["Content-Type","application/json"]]. Identify missing endpoints/credentials in the draft and keep the workflow inactive until they are filled in.input is a GraphQL query against Flow’s environment; script is synchronous JavaScript exporting default function main(input); output_schema is SDL defining Output. For example, select order { lineItems { quantity } }, return {total: input.order.lineItems.reduce((n, item) => n + item.quantity, 0)}, and define type Output { total: Int! }. Verify paths/output names, e.g. runCode.total. Admin/Functions validators do not check it. No async, promises, timers, fetch, or imports. The clock is fixed; Date.now()/new Date() are not current time. Supply a timestamp matching the requirement through the input query.rows list; loop before reading row fields. Verify columns for the exact query through metadata, a template, or results. Generic task return fields and the Admin schema do not establish columns.Fetch current configuration before editing logic. Configuration is a full replacement: preserve unrelated steps, links, patches, names, and manual edits. Metadata-only edits need ID/version and relevant metadata.
Update requires the version being edited. Metadata/draft edits retain it; replacing main configuration creates a draft and returns its version. Use returned versions. On stale-version errors, reread and reapply if still appropriate. Draft versions can stay unchanged across edits, so reread just before replacement to preserve concurrent work.
Omitted/null tags preserves them; [] clears them; a list replaces them. Omitted note preserves it; explicit null clears it. Send only requested metadata changes.
NO_ACTIVATION_WITH_WORKFLOW_VALIDATION_ERRORS, read/fix workflow errors before retrying; for INVALID_WORKFLOW_VERSION_STATE, reread current definitions.activeVersion, which can differ from latest draft. It cancels that version’s runs; an executing action can finish. An inactive version does not stop another active version. Confirm activeVersion is null before reporting the workflow is off.versionState (DRAFT, MAIN, SUPERSEDED) describes definitions independently of activation. A definition is active when its version equals nullable activeVersion. Active main can coexist with a newer inactive draft. Workflow status filters describe overall activation.
Check transport failures, GraphQL errors, userErrors, and workflow validationErrors separately. Saving does not establish readiness to activate. ENTITY_ALREADY_LOCKED means another operation holds the workflow: wait briefly, reread, and retry once if still needed. If a timeout/connection failure leaves the outcome unclear, read store state before retrying. Do not repeat creation automatically, delete a resource as compensation, or report activation without the returned activeVersion. Check deletion’s returned deletedAutomationWorkflowId and userErrors.
Report what changed, workflow ID/version, whether the result is draft JSON, a saved draft, or active, and remaining errors/setup. State what was validated. Order, draft order, product, and customer created triggers can be tested from the resource’s More actions → Run Flow Automation menu; this executes real actions.