← Back to ArticlesLogic Apps

Logic Apps — Integrating with Azure Functions for Custom Logic

Using Azure Functions within Logic Apps workflows to execute custom code, complex transformations, and business logic.

Logic Apps — Integrating with Azure Functions for Custom Logic

Why Combine Logic Apps with Azure Functions?

Azure Logic Apps excels at orchestrating workflows with its visual designer and 400+ connectors. However, some operations require custom code — complex data transformations, business rule evaluation, cryptographic operations, or calling APIs with specific protocols. Azure Functions fills this gap perfectly.

Together they provide:


Architecture

┌─────────────────────────────────────────────────────────────────┐
│                    Logic App Workflow                           │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  ┌──────────┐    ┌──────────────┐    ┌───────────────────────┐  │
│  │ Trigger  │───▶│ Azure        │───▶│ Continue Workflow     │  │
│  │ (HTTP/   │    │ Function     │    │ (Send email, update   │  │
│  │  Queue)  │    │ (Custom code)│    │  database, etc.)      │  │
│  └──────────┘    └──────────────┘    └───────────────────────┘  │
│                         │                                       │
│                         ▼                                       │
│                  ┌──────────────┐                               │
│                  │ Returns JSON │                               │
│                  │ to workflow  │                               │
│                  └──────────────┘                               │
└─────────────────────────────────────────────────────────────────┘

Step 1: Create an Azure Function for Logic Apps

HTTP-Triggered Function (C#)

using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.Logging;
using System.Text.Json;

public class OrderValidator
{
    private readonly ILogger<OrderValidator> _logger;

    public OrderValidator(ILogger<OrderValidator> logger)
    {
        _logger = logger;
    }

    [Function("ValidateOrder")]
    public async Task<IActionResult> Run(
        [HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequest req)
    {
        _logger.LogInformation("Validating order from Logic App");

        var body = await JsonSerializer.DeserializeAsync<OrderRequest>(req.Body);

        var result = new ValidationResult
        {
            IsValid = true,
            Errors = new List<string>()
        };

        // Business rule: minimum order amount
        if (body.TotalAmount < 10)
        {
            result.IsValid = false;
            result.Errors.Add("Minimum order amount is $10");
        }

        // Business rule: max items per order
        if (body.Items?.Count > 50)
        {
            result.IsValid = false;
            result.Errors.Add("Maximum 50 items per order");
        }

        // Business rule: shipping address required for physical items
        if (body.Items?.Any(i => i.IsPhysical) == true && string.IsNullOrEmpty(body.ShippingAddress))
        {
            result.IsValid = false;
            result.Errors.Add("Shipping address required for physical items");
        }

        return new OkObjectResult(result);
    }
}

public class OrderRequest
{
    public string OrderId { get; set; }
    public decimal TotalAmount { get; set; }
    public List<OrderItem> Items { get; set; }
    public string ShippingAddress { get; set; }
}

public class OrderItem
{
    public string ProductId { get; set; }
    public int Quantity { get; set; }
    public bool IsPhysical { get; set; }
}

public class ValidationResult
{
    public bool IsValid { get; set; }
    public List<string> Errors { get; set; }
}

Step 2: Call the Function from Logic Apps

Using the Azure Functions Connector

In the Logic Apps designer:

  1. Add action → search "Azure Functions"
  2. Select your Function App
  3. Choose the function (e.g., ValidateOrder)
  4. Pass the request body from previous steps

Workflow Definition (JSON)

{
  "Call_Validation_Function": {
    "type": "Function",
    "inputs": {
      "function": {
        "id": "/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Web/sites/{functionApp}/functions/ValidateOrder"
      },
      "body": {
        "orderId": "@triggerBody()?['orderId']",
        "totalAmount": "@triggerBody()?['totalAmount']",
        "items": "@triggerBody()?['items']",
        "shippingAddress": "@triggerBody()?['shippingAddress']"
      }
    },
    "runAfter": {}
  },
  "Check_Validation_Result": {
    "type": "If",
    "expression": {
      "equals": ["@body('Call_Validation_Function')?['isValid']", true]
    },
    "actions": {
      "Process_Order": { "type": "Http", "inputs": { "...": "..." } }
    },
    "else": {
      "actions": {
        "Reject_Order": {
          "type": "Response",
          "inputs": {
            "statusCode": 400,
            "body": {
              "error": "validation_failed",
              "errors": "@body('Call_Validation_Function')?['errors']"
            }
          }
        }
      }
    },
    "runAfter": { "Call_Validation_Function": ["Succeeded"] }
  }
}

Step 3: Data Transformation Function

A common use case — transforming data between formats that Logic Apps connectors can't handle natively:

[Function("TransformCsvToJson")]
public async Task<IActionResult> TransformCsv(
    [HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequest req)
{
    using var reader = new StreamReader(req.Body);
    var csvContent = await reader.ReadToEndAsync();

    var lines = csvContent.Split('\n', StringSplitOptions.RemoveEmptyEntries);
    var headers = lines[0].Split(',').Select(h => h.Trim()).ToArray();

    var records = new List<Dictionary<string, string>>();

    for (int i = 1; i < lines.Length; i++)
    {
        var values = lines[i].Split(',');
        var record = new Dictionary<string, string>();

        for (int j = 0; j < headers.Length && j < values.Length; j++)
        {
            record[headers[j]] = values[j].Trim();
        }

        records.Add(record);
    }

    return new OkObjectResult(new { records, count = records.Count });
}

Step 4: Long-Running Function with Durable Functions

For operations that take longer than the Logic Apps HTTP timeout (120 seconds), use the async polling pattern:

[Function("StartLongProcess")]
public async Task<IActionResult> StartProcess(
    [HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequest req,
    [DurableClient] DurableTaskClient client)
{
    var input = await JsonSerializer.DeserializeAsync<ProcessRequest>(req.Body);

    var instanceId = await client.ScheduleNewOrchestrationInstanceAsync(
        "LongRunningOrchestrator", input);

    return client.CreateCheckStatusResponse(req, instanceId);
}

[Function("LongRunningOrchestrator")]
public async Task<ProcessResult> RunOrchestrator(
    [OrchestrationTrigger] TaskOrchestrationContext context)
{
    var input = context.GetInput<ProcessRequest>();

    // Step 1: Validate
    var validation = await context.CallActivityAsync<bool>("ValidateData", input);

    // Step 2: Process (might take minutes)
    var result = await context.CallActivityAsync<ProcessResult>("ProcessData", input);

    // Step 3: Notify
    await context.CallActivityAsync("SendNotification", result);

    return result;
}

Logic Apps can poll the status URL returned by the Durable Function until completion.


Best Practices

PracticeWhy
Use AuthorizationLevel.FunctionSecure the function with a key
Return structured JSONLogic Apps can parse and use in conditions
Keep functions focusedOne function = one responsibility
Handle errors gracefullyReturn error details in response body
Use Managed IdentityAvoid storing function keys in Logic Apps
Set appropriate timeoutsMatch Logic Apps action timeout settings

Summary


Azure Integration Hub — Logic Apps