Azure Logic Apps
Shared Workflow Routing
- Use the shared workflow spec for deterministic multi-plugin routing:
workflows/multi-plugin-workflows.md. - Apply the trigger phrases, handoff contracts, auth prerequisites, validation checkpoints, and stop conditions before escalating to the next plugin.
1. Azure Logic Apps Overview
Azure Logic Apps is a cloud-based integration platform for automating workflows and orchestrating business processes across hundreds of services. It uses a declarative JSON-based Workflow Definition Language (WDL) and supports both visual designer and code-first development.
Standard vs Consumption comparison:
| Feature | Standard (Single-Tenant) | Consumption (Multi-Tenant) |
|---|---|---|
| Hosting | Azure Functions runtime, dedicated | Shared multi-tenant infrastructure |
| Scaling | Plan-based (WS1/WS2/WS3) or ASE v3 | Auto per-execution |
| Pricing | Fixed compute + storage | Pay per execution + connector call |
| VNet integration | Full VNet, private endpoints | Limited (ISE deprecated) |
| Local development | VS Code + Azure Logic Apps extension | Portal or VS Code (limited) |
| Workflows per app | Multiple workflows in one resource | One workflow per resource |
| Stateless workflows | Supported | Not supported |
| Built-in connectors | Run in-process (no connector fee) | Run in shared infrastructure |
| Storage | Azure Blob + Queue (configurable) | Microsoft-managed |
| Deployment | ZIP deploy, ARM, Bicep, CLI | ARM, Bicep, CLI |
Logic Apps vs Power Automate decision matrix:
| Criteria | Logic Apps | Power Automate |
|---|---|---|
| Target user | Integration engineer / developer | Citizen developer / business user |
| Definition format | JSON (Workflow Definition Language) | Visual designer / Power Fx |
| B2B / EDI | Full (AS2, X12, EDIFACT) | Not supported |
| Deployment model | ARM/Bicep/CLI/DevOps/GitHub Actions | Solution export/import |
| Source control | Git-native JSON files | Solution packages |
| Local development | VS Code extension with Azurite | Not supported |
| Custom connectors | OpenAPI + code | OpenAPI (limited) |
| Pricing | Consumption or fixed compute | Per-user or per-flow license |
| Enterprise integration | Integration accounts, maps, schemas | Basic connectors only |
| Governance | Azure Policy, RBAC, managed identity | Power Platform DLP, Entra |
When to use Logic Apps: Enterprise integration scenarios, B2B/EDI, complex error handling with retry policies, VNet-isolated workloads, git-based CI/CD, multi-workflow applications, high-throughput stateless processing.
When to use Power Automate: Business user self-service automation, approval flows, desktop automation (RPA), simple cloud-to-cloud integrations, Dataverse-centric workflows.
2. Hosting Models
2.1 Standard (Single-Tenant)
Standard Logic Apps run on the Azure Functions runtime with a dedicated Workflow Service Plan.
Key characteristics:
- Multiple workflows in a single Logic App resource
- Full VNet integration and private endpoints
- Stateful and stateless workflow types
- Built-in connectors run in-process (faster, no per-call connector fee)
- Local development with VS Code + Azure Logic Apps (Standard) extension
- Storage: Azure Blob Storage + Azure Queue Storage for run state
- Supports deployment slots for staged rollouts
- Can run in App Service Environment (ASE) v3
Workflow Service Plans:
| Plan | vCPU | Memory | Price Tier |
|---|---|---|---|
| WS1 | 1 | 3.5 GB | Standard |
| WS2 | 2 | 7 GB | Standard |
| WS3 | 4 | 14 GB | Standard |
Standard project structure:
my-logic-app/
├── host.json # Runtime configuration
├── local.settings.json # Local environment variables
├── connections.json # Connection references
├── parameters.json # Shared parameters
├── .vscode/
│ ├── extensions.json
│ ├── launch.json
│ ├── settings.json
│ └── tasks.json
├── workflow1/
│ └── workflow.json # Workflow definition
├── workflow2/
│ └── workflow.json
├── Artifacts/
│ ├── Maps/ # XSLT/Liquid transformation maps
│ └── Schemas/ # XSD validation schemas
└── lib/
└── custom/ # Custom .NET assemblies
host.json for Standard:
{
"version": "2.0",
"extensionBundle": {
"id": "Microsoft.Azure.Functions.ExtensionBundle.Workflows",
"version": "[1.*, 2.0.0)"
}
}
local.settings.json:
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
"FUNCTIONS_WORKER_RUNTIME": "node",
"WORKFLOWS_SUBSCRIPTION_ID": "",
"WORKFLOWS_TENANT_ID": "",
"WORKFLOWS_RESOURCE_GROUP_NAME": "",
"WORKFLOWS_LOCATION_NAME": ""
}
}
2.2 Consumption (Multi-Tenant)
Consumption Logic Apps run on shared multi-tenant infrastructure with automatic scaling.
Key characteristics:
- One workflow per Logic App resource
- Automatic scaling per execution
- Pay per action execution and connector call
- Portal-based designer (primary) or VS Code (limited)
- ISE (Integration Service Environment) is deprecated — migrate to Standard
- Built-in integration with Azure Monitor and Log Analytics
Pricing components:
| Component | Cost |
|---|---|
| Action executions | ~$0.000025/action |
| Standard connector | ~$0.000125/call |
| Enterprise connector | ~$0.001/call |
| Integration account (Basic) | ~$X/month |
| Data retention | Included (90-day run history) |
3. Workflow Definition Language (WDL)
Logic Apps workflows are defined in JSON using WDL. The schema provides a declarative way to describe triggers, actions, conditions, and data flow.
Top-level workflow.json structure:
{
"definition": {
"$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#",
"contentVersion": "1.0.0.0",
"parameters": {},
"triggers": {},
"actions": {},
"outputs": {}
},
"kind": "Stateful"
}
The kind property (Standard only) can be "Stateful" or "Stateless".
3.1 Action Types
HTTP — make HTTP requests to external APIs:
"Call_External_API": {
"type": "Http",
"inputs": {
"method": "POST",
"uri": "https://api.example.com/orders",
"headers": {
"Content-Type": "application/json",
"Authorization": "Bearer @{parameters('apiToken')}"
},
"body": {
"orderId": "@{triggerBody()?['id']}",
"amount": "@{triggerBody()?['total']}"
},
"retryPolicy": {
"type": "exponential",
"count": 4,
"interval": "PT7S",
"minimumInterval": "PT5S",
"maximumInterval": "PT1H"
}
},
"runAfter": {}
}
APIConnection — call managed connectors (SharePoint, SQL, Dynamics, etc.):
"Get_SharePoint_Items": {
"type": "ApiConnection",
"inputs": {
"host": {
"connection": {
"name": "@parameters('$connections')['sharepointonline']['connectionId']"
}
},
"method": "get",
"path": "/datasets/@{encodeURIComponent('https://contoso.sharepoint.com/sites/hr')}/tables/@{encodeURIComponent('Employees')}/items"
},
"runAfter": {}
}
Compose — create or transform data:
"Build_Response_Object": {
"type": "Compose",
"inputs": {
"status": "processed",
"timestamp": "@{utcNow()}",
"orderId": "@{triggerBody()?['id']}"
},
"runAfter": {}
}
Parse JSON — parse and validate JSON with schema:
"Parse_Order": {
"type": "ParseJson",
"inputs": {
"content": "@triggerBody()",
"schema": {
"type": "object",
"properties": {
"id": { "type": "string" },
"amount": { "type": "number" },
"customer": { "type": "string" }
},
"required": ["id", "amount"]
}
},
"runAfter": {}
}
Initialize Variable / Set Variable:
"Initialize_Counter": {
"type": "InitializeVariable",
"inputs": {
"variables": [
{
"name": "counter",
"type": "integer",
"value": 0
}
]
},
"runAfter": {}
},
"Increment_Counter": {
"type": "IncrementVariable",
"inputs": {
"name": "counter",
"value": 1
},
"runAfter": { "Initialize_Counter": ["Succeeded"] }
}
Condition (If):
"Check_Amount": {
"type": "If",
"expression": {
"and": [
{
"greater": [
"@body('Parse_Order')?['amount']",
1000
]
}
]
},
"actions": {
"Approve_Large_Order": { "type": "Compose", "inputs": "Approved", "runAfter": {} }
},
"else": {
"actions": {
"Auto_Process": { "type": "Compose", "inputs": "Auto", "runAfter": {} }
}
},
"runAfter": { "Parse_Order": ["Succeeded"] }
}
Switch:
"Route_By_Region": {
"type": "Switch",
"expression": "@body('Parse_Order')?['region']",
"cases": {
"US": {
"case": "US",
"actions": {
"Process_US": { "type": "Compose", "inputs": "US processing", "runAfter": {} }
}
},
"EU": {
"case": "EU",
"actions": {
"Process_EU": { "type": "Compose", "inputs": "EU processing", "runAfter": {} }
}
}
},
"default": {
"actions": {
"Process_Default": { "type": "Compose", "inputs": "Default processing", "runAfter": {} }
}
},
"runAfter": { "Parse_Order": ["Succeeded"] }
}
ForEach — iterate over arrays:
"Process_Each_Item": {
"type": "Foreach",
"foreach": "@body('Get_Items')?['value']",
"actions": {
"Transform_Item": {
"type": "Compose",
"inputs": {
"name": "@{items('Process_Each_Item')?['name']}",
"processed": true
},
"runAfter": {}
}
},
"operationOptions": "Sequential",
"runAfter": { "Get_Items": ["Succeeded"] }
}
Until — loop until condition met:
"Poll_Status": {
"type": "Until",
"expression": "@equals(body('Check_Status')?['status'], 'complete')",
"limit": {
"count": 60,
"timeout": "PT1H"
},
"actions": {
"Check_Status": {
"type": "Http",
"inputs": {
"method": "GET",
"uri": "https://api.example.com/status/@{body('Start_Job')?['jobId']}"
},
"runAfter": {}
},
"Wait_30s": {
"type": "Wait",
"inputs": { "interval": { "count": 30, "unit": "Second" } },
"runAfter": { "Check_Status": ["Succeeded"] }
}
},
"runAfter": { "Start_Job": ["Succeeded"] }
}
Scope — group actions for error handling (try/catch pattern):
"Try_Scope": {
"type": "Scope",
"actions": {
"Risky_Action": {
"type": "Http",
"inputs": { "method": "POST", "uri": "https://api.example.com/process" },
"runAfter": {}
}
},
"runAfter": {}
},
"Catch_Scope": {
"type": "Scope",
"actions": {
"Log_Error": {
"type": "Compose",
"inputs": "Error: @{result('Try_Scope')}",
"runAfter": {}
}
},
"runAfter": {
"Try_Scope": ["Failed", "TimedOut"]
}
}
Terminate — end workflow with status:
"Terminate_Failed": {
"type": "Terminate",
"inputs": {
"runStatus": "Failed",
"runError": {
"code": "VALIDATION_ERROR",
"message": "Order validation failed: @{body('Validate')?['error']}"
}
},
"runAfter": { "Validate": ["Failed"] }
}
Response — return HTTP response (Request trigger only):
"Send_Response": {
"type": "Response",
"kind": "Http",
"inputs": {
"statusCode": 200,
"headers": { "Content-Type": "application/json" },
"body": {
"status": "accepted",
"id": "@{body('Create_Record')?['id']}"
}
},
"runAfter": { "Create_Record": ["Succeeded"] }
}
Inline Code (Standard, JavaScript):
"Transform_Data": {
"type": "JavaScriptCode",
"inputs": {
"code": "var items = workflowContext.actions.Get_Items.outputs.body.value;\nvar result = items.filter(i => i.active).map(i => ({ id: i.id, name: i.name.toUpperCase() }));\nreturn result;"
},
"runAfter": { "Get_Items": ["Succeeded"] }
}
3.2 runAfter Property
The runAfter property controls action execution order and supports four status values:
| Status | Meaning |
|---|---|
Succeeded |
Previous action completed successfully |
Failed |
Previous action failed |
Skipped |
Previous action was skipped |
TimedOut |
Previous action timed out |
"runAfter": {
"Previous_Action": ["Succeeded"]
}
For error handling (catch block):
"runAfter": {
"Try_Scope": ["Failed", "TimedOut", "Skipped"]
}
For always-run (finally block):
"runAfter": {
"Try_Scope": ["Succeeded", "Failed", "TimedOut", "Skipped"]
}
4. Triggers
4.1 Built-in Triggers
Request (HTTP webhook) — receive inbound HTTP calls:
"triggers": {
"When_a_HTTP_request_is_received": {
"type": "Request",
"kind": "Http",
"inputs": {
"schema": {
"type": "object",
"properties": {
"orderId": { "type": "string" },
"amount": { "type": "number" },
"customer": { "type": "string" }
},
"required": ["orderId"]
},
"method": "POST"
}
}
}
Recurrence — scheduled execution:
"triggers": {
"Recurrence": {
"type": "Recurrence",
"recurrence": {
"frequency": "Day",
"interval": 1,
"startTime": "2024-01-01T08:00:00Z",
"timeZone": "Eastern Standard Time",
"schedule": {
"hours": ["8", "12", "17"],
"weekDays": ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"]
}
}
}
}
Service Bus — message queue/topic trigger:
"triggers": {
"When_messages_are_available_in_queue": {
"type": "ServiceProvider",
"inputs": {
"parameters": {
"queueName": "orders",
"isSessionsEnabled": false
},
"serviceProviderConfiguration": {
"connectionName": "serviceBus",
"operationId": "receiveQueueMessages",
"serviceProviderId": "/serviceProviders/serviceBus"
}
}
}
}
Azure Blob Storage — blob created/modified:
"triggers": {
"When_a_blob_is_added_or_modified": {
"type": "ServiceProvider",
"inputs": {
"parameters": {
"path": "incoming/{name}",
"connectionName": "azureBlob"
},
"serviceProviderConfiguration": {
"connectionName": "AzureBlob",
"operationId": "whenABlobIsAddedOrModified",
"serviceProviderId": "/serviceProviders/AzureBlob"
}
}
}
}
Event Hubs — stream processing:
"triggers": {
"When_events_are_available_in_Event_Hub": {
"type": "ServiceProvider",
"inputs": {
"parameters": {
"eventHubName": "orders",
"consumerGroup": "$Default"
},
"serviceProviderConfiguration": {
"connectionName": "eventHub",
"operationId": "receiveEvents",
"serviceProviderId": "/serviceProviders/eventHub"
}
}
}
}
4.2 Managed Connector Triggers
Managed connector triggers use the ApiConnection type and poll at configured intervals:
- SharePoint — when an item is created or modified
- Dataverse — when a row is added, modified, or deleted
- SQL Server — when an item is created
- Dynamics 365 — when a record is created or updated
- Office 365 Outlook — when a new email arrives
- Salesforce — when a record is created or modified
4.3 Trigger Conditions
Filter trigger execution with conditions:
"triggers": {
"manual": {
"type": "Request",
"kind": "Http",
"inputs": { "schema": {} },
"conditions": [
{
"expression": "@greater(triggerBody()?['amount'], 100)"
}
]
}
}
4.4 SplitOn for Array Triggers
Process each item in an array payload as a separate run:
"triggers": {
"manual": {
"type": "Request",
"kind": "Http",
"inputs": { "schema": {} },
"splitOn": "@triggerBody()?['items']"
}
}
5. Connectors
5.1 Built-in vs Managed
| Category | Built-in (Standard) | Managed |
|---|---|---|
| Execution | In-process | Shared Microsoft infrastructure |
| Latency | Low (ms) | Higher (network hop) |
| Pricing | Included in plan | Per-call fee |
| VNet access | Yes (in-process) | Via VNet integration / gateway |
| Examples | HTTP, Service Bus, Blob, Event Hubs, SQL, Azure Functions | SharePoint, Dynamics 365, SAP, Salesforce |
5.2 Built-in Connectors (Standard — In-Process)
| Connector | Use Case |
|---|---|
| HTTP / HTTP + Webhook | Call any REST API |
| Request / Response | Expose workflow as API |
| Recurrence / Schedule | Time-based triggers |
| Service Bus | Queue/topic messaging |
| Azure Blob Storage | File triggers and operations |
| Azure Queue Storage | Queue messaging |
| Event Hubs | Stream processing |
| Azure Functions | Call Azure Functions |
| Inline Code | Execute JavaScript in-workflow |
| Liquid / XSLT | Template transformations |
| XML / Flat File | XML operations, flat file encoding/decoding |
| IBM MQ | Enterprise messaging |
| Azure Cosmos DB | NoSQL database operations |
| Azure Table Storage | Table storage CRUD |
| SFTP / FTP | File transfer |
| SQL Server | Database operations |
| Batch | Message batching |
| DB2 | IBM DB2 database |
5.3 Managed Connector Tiers
Standard connectors (~$0.000125/call): SQL, SharePoint, Office 365, Dynamics 365, Azure AD, OneDrive, Teams, SFTP-SSH
Premium connectors (~$0.001/call): SAP, IBM 3270, IBM MQ, ServiceNow, Salesforce, Oracle DB, Adobe Sign
Enterprise connectors: SAP (specific), IBM (specific) — higher per-call fees
5.4 On-Premises Data Gateway
For accessing on-premises data sources (SQL Server, file shares, Oracle, SAP, etc.):
- Install gateway on a Windows server in the on-premises network
- Register gateway in Azure (resource in a resource group)
- Create API connection referencing the gateway
- Configure high availability with gateway clusters
5.5 Custom Connectors
Create custom connectors from OpenAPI (Swagger) definitions:
- Supports OpenAPI 2.0 (Swagger) and 3.0
- Authentication: OAuth 2.0, API Key, Basic, Certificate
- Deployed per Logic App (Standard) or per subscription (Consumption)
- Can include custom code for request/response transformation
6. Integration Accounts & B2B
Integration accounts enable B2B enterprise integration with trading partners, agreements, schemas, and maps.
6.1 Integration Account Tiers
| Tier | Partners | Agreements | Maps | Schemas | Assemblies |
|---|---|---|---|---|---|
| Free | 25 | 10 | 25 | 25 | 10 |
| Basic | 500 | 500 | 500 | 500 | 25 |
| Standard | 1000+ | 1000+ | 2000 | 2000 | 50 |
6.2 Linking to Logic App
Consumption: Set the integrationAccount property on the Logic App resource.
Standard: Link via app settings:
{
"Values": {
"WORKFLOWS_INTEGRATION_ACCOUNT_ID": "/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Logic/integrationAccounts/{name}"
}
}
6.3 Trading Partners
Partners represent business entities with identities:
{
"name": "Contoso",
"partnerType": "B2B",
"content": {
"b2b": {
"businessIdentities": [
{ "qualifier": "AS2Identity", "value": "CONTOSO" },
{ "qualifier": "ZZ", "value": "CONTOSO-X12" }
]
}
}
}
Identity qualifiers:
| Protocol | Qualifier | Example |
|---|---|---|
| AS2 | AS2Identity | Company AS2 ID |
| X12 | ZZ | Mutually defined |
| X12 | 01 | DUNS number |
| X12 | 14 | EAN/UCC |
| EDIFACT | ZZZ | Mutually defined |
6.4 Agreements
AS2 Agreement settings:
- Message signing (SHA-256, SHA-384, SHA-512)
- Message encryption (AES-128, AES-192, AES-256, DES3)
- MDN (Message Disposition Notification): sync/async, signed/unsigned
- Compression (gzip)
X12 Agreement settings:
- Interchange control number (ISA)
- Functional group (GS) settings
- Transaction set (ST) validation
- Acknowledgment (TA1, 997, 999)
- Envelope and separator overrides
EDIFACT Agreement settings:
- UNB interchange settings
- UNG group settings
- Character set and separators
- Acknowledgment (CONTRL)
6.5 Schemas and Maps
Schemas (XSD): Upload XSD files for message validation in workflows:
"Validate_X12": {
"type": "XmlValidation",
"inputs": {
"content": "@body('Decode_X12')",
"integrationAccount": {
"schema": { "name": "PurchaseOrder.xsd" }
}
}
}
Maps (XSLT/Liquid): Transform messages between formats:
"Transform_Order": {
"type": "Xslt",
"inputs": {
"content": "@body('Validate_X12')",
"integrationAccount": {
"map": { "name": "X12-to-Internal.xslt" }
}
}
}
6.6 Certificates
- Public certificates: Verify partner signatures
- Private certificates: Sign outbound messages, decrypt inbound
- Store private keys in Azure Key Vault
- Reference in AS2 agreement signing/encryption settings
7. Error Handling & Reliability
7.1 Retry Policies
Configure retry behavior per action:
"retryPolicy": {
"type": "exponential",
"count": 4,
"interval": "PT7S",
"minimumInterval": "PT5S",
"maximumInterval": "PT1H"
}
| Type | Behavior |
|---|---|
none |
No retries |
fixed |
Fixed interval between retries |
exponential |
Exponential backoff with jitter |
Default is exponential with 4 retries if not specified.
7.2 Scope-Based Try/Catch Pattern
Use Scope actions with runAfter for structured error handling:
{
"actions": {
"Try": {
"type": "Scope",
"actions": {
"Call_API": {
"type": "Http",
"inputs": { "method": "POST", "uri": "https://api.example.com/process" },
"runAfter": {}
},
"Save_Result": {
"type": "ApiConnection",
"inputs": { "...": "..." },
"runAfter": { "Call_API": ["Succeeded"] }
}
},
"runAfter": {}
},
"Catch": {
"type": "Scope",
"actions": {
"Get_Error_Details": {
"type": "Compose",
"inputs": "@result('Try')",
"runAfter": {}
},
"Send_Alert": {
"type": "ApiConnection",
"inputs": { "...": "send error notification" },
"runAfter": { "Get_Error_Details": ["Succeeded"] }
}
},
"runAfter": {
"Try": ["Failed", "TimedOut"]
}
},
"Finally": {
"type": "Scope",
"actions": {
"Cleanup": {
"type": "Compose",
"inputs": "Cleanup completed",
"runAfter": {}
}
},
"runAfter": {
"Try": ["Succeeded", "Failed", "TimedOut", "Skipped"],
"Catch": ["Succeeded", "Failed", "TimedOut", "Skipped"]
}
}
}
}
7.3 Concurrency Control
Limit parallel run instances:
"triggers": {
"manual": {
"type": "Request",
"kind": "Http",
"inputs": {},
"operationOptions": "SingleInstance"
}
}
Or set explicit concurrency on actions:
"Process_Each_Item": {
"type": "Foreach",
"foreach": "@body('Get_Items')",
"runtimeConfiguration": {
"concurrency": {
"repetitions": 20
}
}
}
7.4 Secure Inputs/Outputs
Hide sensitive data from run history:
"Call_Auth_API": {
"type": "Http",
"inputs": { "...": "..." },
"runtimeConfiguration": {
"secureData": {
"properties": ["inputs", "outputs"]
}
}
}
7.5 Idempotency
For triggers that may fire duplicates:
- Use
SplitOnwith deduplication in downstream actions - Check for existing records before creating (query → conditional create)
- Use Service Bus session-based message processing for ordered, exactly-once delivery
- Set concurrency control to
SingleInstancefor singleton execution
8. Workflow Expressions
8.1 Expression Syntax
- Inline in strings:
"Hello @{triggerBody()?['name']}" - Full value:
"@triggerBody()?['name']" - Literal @: Escape with
@@
8.2 Function Categories
String functions:
| Function | Example |
|---|---|
concat(a, b) |
@concat('Order-', triggerBody()?['id']) |
substring(str, start, length) |
@substring(body('Get')?['name'], 0, 10) |
replace(str, old, new) |
@replace(body('Get')?['text'], '\n', ' ') |
toLower(str) / toUpper(str) |
@toLower(triggerBody()?['email']) |
trim(str) |
@trim(triggerBody()?['input']) |
indexOf(str, search) |
@indexOf(body('Get')?['name'], '@') |
length(str) |
@length(triggerBody()?['description']) |
startsWith(str, prefix) |
@startsWith(body('Get')?['code'], 'ERR') |
endsWith(str, suffix) |
@endsWith(body('Get')?['file'], '.pdf') |
split(str, delimiter) |
@split(body('Get')?['tags'], ',') |
guid() |
@guid() |
Collection functions:
| Function | Example |
|---|---|
length(array) |
@length(body('Get_Items')?['value']) |
contains(collection, value) |
@contains(body('Get')?['roles'], 'admin') |
empty(collection) |
@empty(body('Get_Items')?['value']) |
first(array) |
@first(body('Get_Items')?['value']) |
last(array) |
@last(body('Get_Items')?['value']) |
skip(array, count) |
@skip(body('Get_Items')?['value'], 10) |
take(array, count) |
@take(body('Get_Items')?['value'], 5) |
union(array1, array2) |
@union(body('List_A'), body('List_B')) |
intersection(a1, a2) |
@intersection(body('List_A'), body('List_B')) |
Logical functions:
| Function | Example |
|---|---|
if(expr, true, false) |
@if(equals(body('Check')?['status'], 'ok'), 'Pass', 'Fail') |
equals(a, b) |
@equals(triggerBody()?['type'], 'urgent') |
and(a, b) |
@and(greater(body('Get')?['amount'], 100), equals(body('Get')?['approved'], true)) |
or(a, b) |
@or(equals(body('Get')?['status'], 'new'), equals(body('Get')?['status'], 'pending')) |
not(expr) |
@not(empty(body('Get_Items')?['value'])) |
greater(a, b) |
@greater(body('Get')?['count'], 0) |
less(a, b) |
@less(body('Get')?['retries'], 3) |
Conversion functions:
| Function | Example |
|---|---|
int(value) |
@int(triggerBody()?['quantity']) |
float(value) |
@float(triggerBody()?['price']) |
string(value) |
@string(body('Get')?['count']) |
bool(value) |
@bool(triggerBody()?['active']) |
json(value) |
@json(body('Get_Text')) |
xml(value) |
@xml(body('Get_JSON')) |
base64(value) |
@base64(body('Get_File')) |
base64ToString(b64) |
@base64ToString(triggerBody()?['content']) |
Date/Time functions:
| Function | Example |
|---|---|
utcNow() |
@utcNow() |
utcNow(format) |
@utcNow('yyyy-MM-dd') |
addDays(timestamp, days) |
@addDays(utcNow(), -7) |
addHours(timestamp, hours) |
@addHours(utcNow(), 2) |
addMinutes(timestamp, mins) |
@addMinutes(utcNow(), 30) |
formatDateTime(ts, fmt) |
@formatDateTime(utcNow(), 'yyyy-MM-ddTHH:mm:ssZ') |
ticks(timestamp) |
@ticks(utcNow()) |
dayOfWeek(timestamp) |
@dayOfWeek(utcNow()) |
dayOfMonth(timestamp) |
@dayOfMonth(utcNow()) |
Workflow context functions:
| Function | Description |
|---|---|
trigger() |
Full trigger output |
triggerBody() |
Trigger body content |
triggerOutputs() |
Trigger outputs including headers |
actions('name') |
Full output of named action |
body('name') |
Body of named action |
actionBody('name') |
Alias for body() |
actionOutputs('name') |
Full outputs of action |
parameters('name') |
Get parameter value |
variables('name') |
Get variable value |
result('scope') |
Array of results from all actions in a scope |
workflow() |
Workflow metadata (name, id, run id) |
item() |
Current item in ForEach |
items('forEach') |
Current item in named ForEach |
iterationIndexes('forEach') |
Current index in named ForEach |
8.3 Common Expression Patterns
Null-safe property access (use ? operator):
@triggerBody()?['address']?['city']
Conditional value:
@if(equals(triggerBody()?['priority'], 'high'), 'Expedite', 'Standard')
Date arithmetic (7 days ago formatted):
@addDays(utcNow(), -7, 'yyyy-MM-dd')
String interpolation:
@{concat('Order-', triggerBody()?['orderId'], '-', utcNow('yyyyMMdd'))}
Coalesce pattern (first non-null):
@coalesce(triggerBody()?['preferredName'], triggerBody()?['firstName'], 'Unknown')
Array length check:
@greater(length(body('Get_Items')?['value']), 0)
Extract filename from path:
@last(split(triggerBody()?['filePath'], '/'))
8.4 Inline Code Action (Standard)
For complex transformations, use JavaScript inline code:
"Transform_Data": {
"type": "JavaScriptCode",
"inputs": {
"code": "var items = workflowContext.actions.Get_Items.outputs.body.value;\nvar grouped = {};\nitems.forEach(function(item) {\n var key = item.category;\n if (!grouped[key]) grouped[key] = [];\n grouped[key].push(item);\n});\nreturn grouped;"
}
}
Access workflow context via workflowContext.trigger.outputs and workflowContext.actions.<name>.outputs.
9. Deployment & CI/CD
9.1 ARM Templates
Consumption Logic App:
{
"type": "Microsoft.Logic/workflows",
"apiVersion": "2019-05-01",
"name": "[parameters('logicAppName')]",
"location": "[parameters('location')]",
"properties": {
"state": "Enabled",
"definition": { "...WDL..." },
"parameters": {
"$connections": {
"value": {
"office365": {
"connectionId": "[resourceId('Microsoft.Web/connections', 'office365')]",
"connectionName": "office365",
"id": "[subscriptionResourceId('Microsoft.Web/locations/managedApis', parameters('location'), 'office365')]"
}
}
}
}
}
}
Standard Logic App:
{
"type": "Microsoft.Web/sites",
"apiVersion": "2022-09-01",
"name": "[parameters('logicAppName')]",
"location": "[parameters('location')]",
"kind": "functionapp,workflowapp",
"identity": { "type": "SystemAssigned" },
"properties": {
"serverFarmId": "[resourceId('Microsoft.Web/serverfarms', parameters('planName'))]",
"siteConfig": {
"appSettings": [
{ "name": "APP_KIND", "value": "workflowApp" },
{ "name": "AzureWebJobsStorage", "value": "[parameters('storageConnectionString')]" },
{ "name": "FUNCTIONS_EXTENSION_VERSION", "value": "~4" },
{ "name": "FUNCTIONS_WORKER_RUNTIME", "value": "node" },
{ "name": "WEBSITE_NODE_DEFAULT_VERSION", "value": "~18" }
]
}
}
}
9.2 Bicep
Consumption:
resource logicApp 'Microsoft.Logic/workflows@2019-05-01' = {
name: logicAppName
location: location
properties: {
state: 'Enabled'
definition: json(loadTextContent('workflow.json')).definition
parameters: {}
}
}
Standard:
resource appServicePlan 'Microsoft.Web/serverfarms@2022-09-01' = {
name: planName
location: location
sku: { name: 'WS1', tier: 'WorkflowStandard' }
kind: 'elastic'
properties: { isSpot: false }
}
resource logicApp 'Microsoft.Web/sites@2022-09-01' = {
name: logicAppName
location: location
kind: 'functionapp,workflowapp'
identity: { type: 'SystemAssigned' }
properties: {
serverFarmId: appServicePlan.id
siteConfig: {
appSettings: [
{ name: 'APP_KIND', value: 'workflowApp' }
{ name: 'AzureWebJobsStorage', value: storageConnectionString }
{ name: 'FUNCTIONS_EXTENSION_VERSION', value: '~4' }
{ name: 'FUNCTIONS_WORKER_RUNTIME', value: 'node' }
]
}
}
}
9.3 Azure CLI
Consumption Logic App:
# Create
az logic workflow create \
--resource-group myRg \
--name my-logic-app \
--definition @workflow.json \
--location eastus
# Show
az logic workflow show --resource-group myRg --name my-logic-app
# Update definition
az logic workflow update \
--resource-group myRg \
--name my-logic-app \
--definition @workflow.json
# List runs
az logic workflow run list \
--resource-group myRg \
--workflow-name my-logic-app \
--filter "status eq 'Failed'"
# Delete
az logic workflow delete --resource-group myRg --name my-logic-app --yes
Standard Logic App:
# Create the Logic App resource
az logicapp create \
--resource-group myRg \
--name my-standard-app \
--storage-account mystorageaccount \
--plan my-workflow-plan \
--runtime-version ~4
# Deploy workflows (ZIP deploy)
cd my-logic-app-project
zip -r deploy.zip . -x ".vscode/*" "local.settings.json" ".git/*"
az logicapp deployment source config-zip \
--resource-group myRg \
--name my-standard-app \
--src deploy.zip
# Show
az logicapp show --resource-group myRg --name my-standard-app
# Configure app settings
az logicapp config appsettings set \
--resource-group myRg \
--name my-standard-app \
--settings "KEY=value"
# List app settings
az logicapp config appsettings list \
--name my-standard-app --resource-group myRg --output table
# Delete app settings
az logicapp config appsettings delete \
--name my-standard-app --resource-group myRg \
--setting-names KEY1 KEY2
# Remove VNet integration
az logicapp vnet-integration remove \
--name my-standard-app --resource-group myRg
9.4 GitHub Actions CI/CD
name: Deploy Logic App (Standard)
on:
push:
branches: [main]
workflow_dispatch:
env:
LOGIC_APP_NAME: 'my-standard-app'
RESOURCE_GROUP: 'my-rg'
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Login to Azure
uses: azure/login@v2
with:
creds: ${{ secrets.AZURE_CREDENTIALS }}
- name: Build project
run: |
zip -r deploy.zip . \
-x ".vscode/*" \
-x "local.settings.json" \
-x ".git/*" \
-x ".github/*" \
-x "*.md"
- name: Deploy to Logic App
uses: azure/functions-action@v1
with:
app-name: ${{ env.LOGIC_APP_NAME }}
package: 'deploy.zip'
- name: Verify deployment
run: |
az logicapp show \
--resource-group ${{ env.RESOURCE_GROUP }} \
--name ${{ env.LOGIC_APP_NAME }} \
--query "state" -o tsv
9.5 Azure DevOps Pipeline
trigger:
branches:
include:
- main
paths:
include:
- 'src/logic-apps/**'
pool:
vmImage: 'ubuntu-latest'
variables:
logicAppName: 'my-standard-app'
resourceGroup: 'my-rg'
azureSubscription: 'MyAzureConnection'
stages:
- stage: Build
jobs:
- job: BuildArtifact
steps:
- task: ArchiveFiles@2
inputs:
rootFolderOrFile: 'src/logic-apps'
includeRootFolder: false
archiveType: 'zip'
archiveFile: '$(Build.ArtifactStagingDirectory)/deploy.zip'
- publish: '$(Build.ArtifactStagingDirectory)/deploy.zip'
artifact: logic-app
- stage: Deploy
dependsOn: Build
jobs:
- deployment: DeployLogicApp
environment: 'production'
strategy:
runOnce:
deploy:
steps:
- task: AzureCLI@2
inputs:
azureSubscription: $(azureSubscription)
scriptType: 'bash'
scriptLocation: 'inlineScript'
inlineScript: |
az logicapp deployment source config-zip \
--resource-group $(resourceGroup) \
--name $(logicAppName) \
--src $(Pipeline.Workspace)/logic-app/deploy.zip
9.6 VS Code Local Development
- Install Azure Logic Apps (Standard) extension
- Install Azurite extension for local storage emulation
- Start Azurite: Ctrl+Shift+P → "Azurite: Start"
- Open Logic App project folder
- Right-click
workflow.json→ "Open in Designer" - Debug: F5 (uses
host.jsonandlocal.settings.json) - Test Request triggers at
http://localhost:7071/api/<workflow>/triggers/manual/invoke - Deploy: right-click project → "Deploy to Logic App"
9.7 Connection Parameterization
Separate connection details per environment using parameters.json and connections.json:
parameters.json:
{
"sqlConnectionString": {
"type": "string",
"value": ""
}
}
connections.json (parameterized):
{
"serviceProviderConnections": {
"sql": {
"parameterValues": {
"connectionString": "@appsetting('SQL_CONNECTION_STRING')"
},
"serviceProvider": {
"id": "/serviceProviders/sql"
}
}
}
}
9.8 Deployment Slots (Standard)
Standard Logic Apps support deployment slots for staged rollouts and zero-downtime swaps.
# Create deployment slot
az logicapp deployment slot create \
--name my-standard-app --resource-group myRg --slot staging
# List deployment slots
az logicapp deployment slot list \
--name my-standard-app --resource-group myRg --output table
# Swap slots (zero-downtime)
az logicapp deployment slot swap \
--name my-standard-app --resource-group myRg \
--slot staging --target-slot production
# Deploy to slot
az logicapp deployment source config-zip \
--name my-standard-app --resource-group myRg \
--src deploy.zip --slot staging
# Configure slot-specific settings
az logicapp config appsettings set \
--name my-standard-app --resource-group myRg \
--slot staging --settings "Environment=Staging"
9.9 Custom Domains & SSL (Standard)
Bind custom hostnames and SSL certificates to Standard Logic Apps.
# Add custom hostname
az logicapp config hostname add \
--hostname api.contoso.com \
--webapp-name my-standard-app --resource-group myRg
# List hostnames
az logicapp config hostname list \
--webapp-name my-standard-app --resource-group myRg --output table
# Bind SSL certificate
az logicapp config ssl bind \
--certificate-thumbprint <thumbprint> --ssl-type SNI \
--name my-standard-app --resource-group myRg
9.10 Backup & Restore (Standard)
Create and restore backups for Standard Logic Apps using Azure Storage.
# Create backup
az logicapp config backup create \
--container-url <sas-url> \
--webapp-name my-standard-app --resource-group myRg
# List backups
az logicapp config backup list \
--webapp-name my-standard-app --resource-group myRg --output table
# Restore from backup
az logicapp config backup restore \
--container-url <sas-url> \
--webapp-name my-standard-app --resource-group myRg
9.11 Batch Operations (Standard)
Bulk management operations for Standard Logic App workflows and runs.
# Batch cancel stuck runs
RUNS=$(az rest --method GET \
--url "https://management.azure.com/subscriptions/<sub-id>/resourceGroups/<rg>/providers/Microsoft.Web/sites/<app>/hostruntime/runtime/webhooks/workflow/api/management/workflows/<workflow>/runs?api-version=2022-03-01&\$filter=status eq 'Running'" \
| jq -r '.value[].name')
for RUN_ID in $RUNS; do
az rest --method POST \
--url "https://management.azure.com/subscriptions/<sub-id>/resourceGroups/<rg>/providers/Microsoft.Web/sites/<app>/hostruntime/runtime/webhooks/workflow/api/management/workflows/<workflow>/runs/$RUN_ID/cancel?api-version=2022-03-01"
done
# List all workflows in Standard Lo
…(truncated)