Back to Blog
6 min read

Building Custom Connectors for Power Automate

When built-in connectors don’t meet your needs, custom connectors let you connect Power Automate to any REST API. This guide covers building, testing, and deploying custom connectors.

Custom Connector Basics

A custom connector defines:

  • API endpoints and operations
  • Authentication method
  • Request/response schemas
  • Triggers and actions

Creating from OpenAPI/Swagger

The easiest approach is importing an OpenAPI specification:

# openapi.yaml
openapi: '3.0.0'
info:
  title: Inventory API
  version: '1.0'
  description: Custom inventory management API

servers:
  - url: https://api.mycompany.com/v1

security:
  - apiKey: []

paths:
  /products:
    get:
      operationId: GetProducts
      summary: Get all products
      parameters:
        - name: category
          in: query
          schema:
            type: string
        - name: inStock
          in: query
          schema:
            type: boolean
      responses:
        '200':
          description: List of products
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Product'

    post:
      operationId: CreateProduct
      summary: Create a new product
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ProductInput'
      responses:
        '201':
          description: Product created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Product'

  /products/{id}:
    get:
      operationId: GetProduct
      summary: Get product by ID
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Product details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Product'

    put:
      operationId: UpdateProduct
      summary: Update a product
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ProductInput'
      responses:
        '200':
          description: Product updated

    delete:
      operationId: DeleteProduct
      summary: Delete a product
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        '204':
          description: Product deleted

components:
  schemas:
    Product:
      type: object
      properties:
        id:
          type: string
        name:
          type: string
        category:
          type: string
        price:
          type: number
        quantity:
          type: integer
        inStock:
          type: boolean
        lastUpdated:
          type: string
          format: date-time

    ProductInput:
      type: object
      required:
        - name
        - category
        - price
      properties:
        name:
          type: string
        category:
          type: string
        price:
          type: number
        quantity:
          type: integer

  securitySchemes:
    apiKey:
      type: apiKey
      name: X-API-Key
      in: header

Authentication Options

API Key

securityDefinitions:
  api_key:
    type: apiKey
    name: X-API-Key
    in: header

# In connector definition
authentication:
  type: apiKey
  apiKey:
    parameterLocation: Header
    name: X-API-Key

OAuth 2.0

securityDefinitions:
  oauth2:
    type: oauth2
    flow: accessCode
    authorizationUrl: https://login.example.com/oauth/authorize
    tokenUrl: https://login.example.com/oauth/token
    scopes:
      read: Read access
      write: Write access

# Connector security configuration
authentication:
  type: oauth2
  oauth2:
    authorizationUrl: https://login.example.com/oauth/authorize
    tokenUrl: https://login.example.com/oauth/token
    refreshUrl: https://login.example.com/oauth/refresh
    scopes:
      - read
      - write
    clientId: '{YOUR_CLIENT_ID}'

Azure AD

authentication:
  type: oauth2
  oauth2:
    authorizationUrl: https://login.microsoftonline.com/{tenant}/oauth2/v2.0/authorize
    tokenUrl: https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token
    refreshUrl: https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token
    scopes:
      - api://{client-id}/.default

Adding Triggers

Webhook Trigger

paths:
  /webhooks:
    x-ms-notification-content:
      schema:
        $ref: '#/components/schemas/WebhookPayload'
      description: Webhook payload

    post:
      operationId: CreateWebhook
      x-ms-trigger: single
      summary: When an item is created
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                callbackUrl:
                  type: string
                  x-ms-notification-url: true
                eventType:
                  type: string
                  enum:
                    - created
                    - updated
                    - deleted
      responses:
        '201':
          description: Webhook created

  /webhooks/{webhookId}:
    delete:
      operationId: DeleteWebhook
      summary: Delete webhook
      parameters:
        - name: webhookId
          in: path
          required: true
          schema:
            type: string
      responses:
        '204':
          description: Webhook deleted

Polling Trigger

paths:
  /items/poll:
    get:
      operationId: PollForNewItems
      x-ms-trigger: batch
      x-ms-trigger-hint: Poll for new items
      summary: When new items are available
      parameters:
        - name: since
          in: query
          x-ms-trigger-metadata: triggerState
          schema:
            type: string
            format: date-time
      responses:
        '200':
          description: New items
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Item'

Policy Templates

Add custom behavior with policies:

<!-- Transform request -->
<set-body template="liquid">
{
    "externalId": "{{body.id}}",
    "displayName": "{{body.name}}",
    "metadata": {
        "source": "PowerAutomate",
        "timestamp": "{{utcNow}}"
    }
}
</set-body>

<!-- Transform response -->
<set-body template="liquid">
{
    "items": [
        {% for item in body.data %}
        {
            "id": "{{item.externalId}}",
            "name": "{{item.displayName}}",
            "created": "{{item.createdAt}}"
        }{% unless forloop.last %},{% endunless %}
        {% endfor %}
    ],
    "count": {{body.total}}
}
</set-body>

<!-- Add headers -->
<set-header name="X-Custom-Header" exists-action="override">
    <value>@(context.Request.Headers.GetValueOrDefault("X-Correlation-Id", Guid.NewGuid().ToString()))</value>
</set-header>

Testing Custom Connectors

Using the Test Panel

# Create test connection
$testParams = @{
    OperationId = "GetProducts"
    Parameters = @{
        category = "Electronics"
        inStock = $true
    }
}

# Execute and verify response
$result = Invoke-CustomConnectorOperation @testParams
$result | Should -Not -BeNullOrEmpty
$result.Count | Should -BeGreaterThan 0

Postman Collection

{
    "info": {
        "name": "Custom Connector Tests"
    },
    "item": [
        {
            "name": "Get Products",
            "request": {
                "method": "GET",
                "header": [
                    {
                        "key": "X-API-Key",
                        "value": "{{apiKey}}"
                    }
                ],
                "url": {
                    "raw": "{{baseUrl}}/products?category=Electronics",
                    "host": ["{{baseUrl}}"],
                    "path": ["products"],
                    "query": [
                        {
                            "key": "category",
                            "value": "Electronics"
                        }
                    ]
                }
            },
            "event": [
                {
                    "listen": "test",
                    "script": {
                        "exec": [
                            "pm.test('Status code is 200', function () {",
                            "    pm.response.to.have.status(200);",
                            "});",
                            "pm.test('Response is array', function () {",
                            "    var jsonData = pm.response.json();",
                            "    pm.expect(jsonData).to.be.an('array');",
                            "});"
                        ]
                    }
                }
            ]
        }
    ]
}

Sharing and Certification

Share Within Organization

# Export connector
Export-PowerAppConnector `
    -ConnectorName "InventoryAPI" `
    -EnvironmentName "Default-{tenant}" `
    -OutputFilePath "./InventoryAPI.zip"

# Import to another environment
Import-PowerAppConnector `
    -FilePath "./InventoryAPI.zip" `
    -EnvironmentName "Production-{tenant}"

Microsoft Certification

For ISV certification, ensure:

certification_requirements:
  documentation:
    - Complete API reference
    - Getting started guide
    - Authentication setup instructions
    - Sample flows

  technical:
    - All operations tested
    - Error responses defined
    - Pagination implemented
    - Rate limiting documented

  compliance:
    - Privacy policy URL
    - Terms of service URL
    - Support contact
    - SLA documentation

Best Practices

Operation Design

best_practices:
  naming:
    - Use clear, descriptive operation IDs
    - Follow verb-noun pattern (GetProducts, CreateOrder)
    - Group related operations

  parameters:
    - Use appropriate parameter types
    - Provide default values where sensible
    - Mark required parameters correctly
    - Add descriptions for all parameters

  responses:
    - Define all possible response codes
    - Include error response schemas
    - Use consistent response structure

Error Handling

responses:
  '400':
    description: Bad Request
    content:
      application/json:
        schema:
          $ref: '#/components/schemas/Error'
  '401':
    description: Unauthorized
  '403':
    description: Forbidden
  '404':
    description: Not Found
  '429':
    description: Rate Limited
    headers:
      Retry-After:
        schema:
          type: integer
  '500':
    description: Internal Server Error

components:
  schemas:
    Error:
      type: object
      properties:
        code:
          type: string
        message:
          type: string
        details:
          type: array
          items:
            type: object
            properties:
              field:
                type: string
              error:
                type: string

Conclusion

Custom connectors extend Power Automate to any API:

  • Import from OpenAPI for quick setup
  • Support multiple authentication methods
  • Enable triggers via webhooks or polling
  • Share across your organization

With custom connectors, there’s no API that Power Automate can’t integrate with.

Resources

Michael John Peña

Michael John Peña

Senior Data Engineer based in Sydney. Writing about data, cloud, and technology.