← Back to ArticlesAPI Management

API Management — Policy Fragments: The Complete Guide

Everything you need to know about APIM Policy Fragments — why they exist, how to use them, Named Values integration, drawbacks, and real-world patterns.

API Management — Policy Fragments: The Complete Guide

The Problem Without Policy Fragments

Imagine you have 30 APIs in APIM. Every single one of them needs:

Without policy fragments, you copy-paste the same 80-line XML block into every API policy. Now someone updates the JWT issuer URL — and you're touching 30 policies manually, hoping you don't miss one.

Before Policy Fragments:
┌─────────────────────────────────────────────────────────┐
│  API 1 Policy                                           │
│  ├── JWT Validation (copy-pasted, 40 lines)             │
│  ├── Rate Limiting (copy-pasted, 20 lines)              │
│  └── Error Formatting (copy-pasted, 30 lines)           │
│                                                         │
│  API 2 Policy                                           │
│  ├── JWT Validation (same 40 lines, again)              │
│  ├── Rate Limiting (same 20 lines, again)               │
│  └── Error Formatting (same 30 lines, again)            │
│                                                         │
│  ... repeated for every API ...                         │
└─────────────────────────────────────────────────────────┘

Policy Fragments solve this. They are reusable, named policy snippets stored at the APIM instance level that can be included by reference inside any API or operation policy.


What Is a Policy Fragment?

A Policy Fragment is a named XML policy snippet stored centrally in your APIM instance. Think of it as a function in programming — write it once, call it anywhere.

<!-- This is a Policy Fragment named "validate-jwt-fragment" -->
<validate-jwt header-name="Authorization" failed-validation-httpcode="401">
    <openid-config url="{{JwtIssuerUrl}}/.well-known/openid-configuration" />
    <required-claims>
        <claim name="aud">
            <value>{{JwtAudience}}</value>
        </claim>
    </required-claims>
</validate-jwt>

And then in any API policy, you simply reference it:

<policies>
    <inbound>
        <include-fragment fragment-id="validate-jwt-fragment" />
        <base />
    </inbound>
</policies>

That single line pulls in the entire JWT validation logic — versioned, tested, and maintained in one place.


Why Use Policy Fragments?

1. DRY Principle — Don't Repeat Yourself

The most fundamental reason. When the same policy logic appears in multiple places, every change becomes a multi-API edit with risk of inconsistency.

2. Centralized Governance

Security and compliance teams can own and lock down fragments like JWT validation or IP filtering. API developers include the fragment but cannot alter its internals — they get a safe, pre-approved building block.

3. Easier Maintenance

Update a fragment once. Every API that references it automatically gets the updated behavior on next request evaluation. No deployments, no API-by-API edits.

4. Faster API Onboarding

New APIs get production-grade cross-cutting concerns in seconds:

<policies>
    <inbound>
        <include-fragment fragment-id="validate-jwt-fragment" />
        <include-fragment fragment-id="rate-limit-by-tier-fragment" />
        <include-fragment fragment-id="inject-correlation-id-fragment" />
        <base />
    </inbound>
    <backend>
        <base />
    </backend>
    <outbound>
        <base />
    </outbound>
    <on-error>
        <include-fragment fragment-id="standard-error-format-fragment" />
        <base />
    </on-error>
</policies>

A new API inherits auth, rate limiting, tracing, and error formatting — before the developer writes a single line of business logic.

5. Testability

Fragments can be validated and tested in isolation. When you update the JWT fragment, you test that fragment — not each of the 30 APIs that use it.


Creating a Policy Fragment

Via Azure Portal

  1. Go to Azure Portal → API Management → your instance
  2. Under APIs, click Policy fragments
  3. Click + Create
  4. Give it a meaningful name (validate-jwt, rate-limit-by-tier, etc.)
  5. Paste your XML policy content
  6. Click Create

Via Azure CLI

az apim policy-fragment create \
  --resource-group "my-rg" \
  --service-name "my-apim" \
  --name "validate-jwt-fragment" \
  --value '<validate-jwt header-name="Authorization" failed-validation-httpcode="401">
    <openid-config url="{{JwtIssuerUrl}}/.well-known/openid-configuration"/>
  </validate-jwt>'

Via ARM/Bicep

resource policyFragment 'Microsoft.ApiManagement/service/policyFragments@2022-08-01' = {
  name: '${apimService.name}/validate-jwt-fragment'
  properties: {
    value: '''
      <validate-jwt header-name="Authorization" failed-validation-httpcode="401">
        <openid-config url="{{JwtIssuerUrl}}/.well-known/openid-configuration"/>
      </validate-jwt>
    '''
    description: 'Validates JWT bearer tokens against our identity provider'
    format: 'xml'
  }
}

Using Named Values Inside Policy Fragments

Named Values (formerly called Properties) are APIM's key-value store — a secure place for configuration, secrets, and Key Vault references. They are the perfect companion to Policy Fragments because they externalize configuration without hardcoding it inside fragment XML.

How Named Values Work

Policy Fragment XML
      │
      │  uses {{NamedValueKey}}
      ▼
Named Value Store ──► Plain text value     (e.g., issuer URLs, audience)
                  ──► Secret value         (e.g., API keys — masked in portal)
                  ──► Key Vault reference  (e.g., certificates, tokens)

APIM substitutes {{NamedValueKey}} at runtime with the actual value. The fragment XML never contains the real secret.

Creating Named Values

Via Portal:

  1. Go to APIM → Named values → + Add
  2. Set Name, Display name, Type (Plain / Secret / Key Vault), Value

Via Azure CLI:

# Plain value
az apim nv create \
  --resource-group "my-rg" \
  --service-name "my-apim" \
  --named-value-id "JwtIssuerUrl" \
  --display-name "JWT Issuer URL" \
  --value "https://login.microsoftonline.com/your-tenant-id/v2.0" \
  --secret false

# Secret value
az apim nv create \
  --resource-group "my-rg" \
  --service-name "my-apim" \
  --named-value-id "BackendApiKey" \
  --display-name "Backend API Key" \
  --value "super-secret-key-here" \
  --secret true

Via Bicep with Key Vault:

resource namedValue 'Microsoft.ApiManagement/service/namedValues@2022-08-01' = {
  name: '${apimService.name}/JwtIssuerUrl'
  properties: {
    displayName: 'JWT Issuer URL'
    value: 'https://login.microsoftonline.com/${tenantId}/v2.0'
    secret: false
  }
}

resource namedValueSecret 'Microsoft.ApiManagement/service/namedValues@2022-08-01' = {
  name: '${apimService.name}/BackendApiKey'
  properties: {
    displayName: 'Backend API Key'
    secret: true
    keyVault: {
      secretIdentifier: 'https://my-keyvault.vault.azure.net/secrets/BackendApiKey'
    }
  }
}

Consuming Named Values in a Fragment

<!-- Fragment: validate-jwt-fragment -->
<!-- Uses Named Values for all environment-specific config -->
<validate-jwt 
    header-name="Authorization" 
    failed-validation-httpcode="401"
    failed-validation-error-message="Access token is missing or invalid.">
    
    <openid-config url="{{JwtIssuerUrl}}/.well-known/openid-configuration" />
    
    <audiences>
        <audience>{{JwtAudience}}</audience>
    </audiences>
    
    <required-claims>
        <claim name="roles" match="any">
            <value>{{AllowedRole}}</value>
        </claim>
    </required-claims>
</validate-jwt>
<!-- Fragment: call-backend-fragment -->
<!-- Backend URL and API key from Named Values -->
<set-header name="X-Api-Key" exists-action="override">
    <value>{{BackendApiKey}}</value>
</set-header>
<set-backend-service base-url="{{BackendServiceUrl}}" />
<!-- Fragment: inject-tracing-fragment -->
<!-- Environment name and version from Named Values -->
<set-header name="X-Correlation-Id" exists-action="skip">
    <value>@(Guid.NewGuid().ToString())</value>
</set-header>
<set-header name="X-Environment" exists-action="override">
    <value>{{EnvironmentName}}</value>
</set-header>
<set-header name="X-Service-Version" exists-action="override">
    <value>{{ServiceVersion}}</value>
</set-header>

Environment Promotion Pattern

This is where Named Values + Fragments really shine. The fragment XML is identical across dev/staging/prod. Only the Named Values differ:

Named Value KeyDevStagingProduction
JwtIssuerUrlhttps://login.microsoftonline.com/dev-tenant/v2.0https://login.microsoftonline.com/stg-tenant/v2.0https://login.microsoftonline.com/prod-tenant/v2.0
BackendApiKeydev-key-abc123stg-key-xyz456kv://prod-keyvault/BackendApiKey
EnvironmentNamedevelopmentstagingproduction

Real-World Fragment Examples

Fragment 1: JWT Validation

<!-- Fragment name: validate-jwt-fragment -->
<validate-jwt 
    header-name="Authorization" 
    failed-validation-httpcode="401"
    failed-validation-error-message="Unauthorized: Missing or invalid access token.">
    <openid-config url="{{JwtIssuerUrl}}/.well-known/openid-configuration" />
    <audiences>
        <audience>{{JwtAudience}}</audience>
    </audiences>
</validate-jwt>

Fragment 2: Tier-Based Rate Limiting

<!-- Fragment name: rate-limit-by-tier-fragment -->
<set-variable name="tier" value="@{
    var sub = context.Subscription;
    return sub?.Properties?.FirstOrDefault(p => p.Key == "tier")?.Value?.ToString() ?? "free";
}" />

<choose>
    <when condition="@(context.Variables.GetValueOrDefault<string>('tier') == 'enterprise')">
        <!-- No rate limiting for enterprise -->
    </when>
    <when condition="@(context.Variables.GetValueOrDefault<string>('tier') == 'pro')">
        <rate-limit-by-key calls="10000"
                           renewal-period="3600"
                           counter-key="@(context.Subscription.Id)" />
    </when>
    <when condition="@(context.Variables.GetValueOrDefault<string>('tier') == 'basic')">
        <rate-limit-by-key calls="1000"
                           renewal-period="3600"
                           counter-key="@(context.Subscription.Id)" />
    </when>
    <otherwise>
        <!-- Free tier -->
        <rate-limit-by-key calls="100"
                           renewal-period="3600"
                           counter-key="@(context.Subscription.Id)" />
    </otherwise>
</choose>

Fragment 3: Standard Error Response

<!-- Fragment name: standard-error-format-fragment -->
<!-- Place in <on-error> section -->
<set-header name="Content-Type" exists-action="override">
    <value>application/json</value>
</set-header>
<set-body>@{
    return new JObject(
        new JProperty("error", new JObject(
            new JProperty("code", context.Response.StatusCode),
            new JProperty("message", context.LastError?.Message ?? "An unexpected error occurred."),
            new JProperty("correlationId", context.Variables.GetValueOrDefault<string>("correlationId", "unknown")),
            new JProperty("timestamp", DateTime.UtcNow.ToString("o"))
        ))
    ).ToString();
}</set-body>

Fragment 4: IP Allowlist Check

<!-- Fragment name: ip-allowlist-fragment -->
<!-- Named Value "AllowedCidrRanges" holds comma-separated CIDRs -->
<set-variable name="clientIp" value="@(context.Request.IpAddress)" />
<set-variable name="isAllowed" value="@{
    var allowedRanges = "{{AllowedCidrRanges}}".Split(',');
    var clientIp = context.Variables.GetValueOrDefault<string>("clientIp");
    // Simple IP prefix check — extend with proper CIDR logic as needed
    return allowedRanges.Any(range => clientIp.StartsWith(range.Trim()));
}" />
<choose>
    <when condition="@(!context.Variables.GetValueOrDefault<bool>('isAllowed'))">
        <return-response>
            <set-status code="403" reason="Forbidden" />
            <set-body>{"error": "access_denied", "message": "Your IP is not permitted."}</set-body>
        </return-response>
    </when>
</choose>

Fragment 5: Correlation ID Injection

<!-- Fragment name: inject-correlation-id-fragment -->
<set-variable name="correlationId" value="@{
    var existing = context.Request.Headers.GetValueOrDefault("X-Correlation-Id", "");
    return string.IsNullOrEmpty(existing) ? Guid.NewGuid().ToString() : existing;
}" />
<set-header name="X-Correlation-Id" exists-action="override">
    <value>@(context.Variables.GetValueOrDefault<string>("correlationId"))</value>
</set-header>

Composing Fragments in an API Policy

A real API policy using multiple fragments looks clean and readable:

<policies>
    <inbound>
        <!-- Authentication -->
        <include-fragment fragment-id="validate-jwt-fragment" />

        <!-- Abuse prevention -->
        <include-fragment fragment-id="ip-allowlist-fragment" />

        <!-- Observability -->
        <include-fragment fragment-id="inject-correlation-id-fragment" />

        <!-- Throttling -->
        <include-fragment fragment-id="rate-limit-by-tier-fragment" />

        <base />
    </inbound>

    <backend>
        <base />
    </backend>

    <outbound>
        <base />
    </outbound>

    <on-error>
        <include-fragment fragment-id="standard-error-format-fragment" />
        <base />
    </on-error>
</policies>

The API policy becomes a declaration of intent, not an implementation. Anyone reading it immediately understands what happens at each stage.


Managing Fragments Programmatically

List All Fragments

az apim policy-fragment list \
  --resource-group "my-rg" \
  --service-name "my-apim" \
  --query "[].{Name:name, Description:properties.description}" \
  --output table

Update a Fragment

az apim policy-fragment create \
  --resource-group "my-rg" \
  --service-name "my-apim" \
  --name "validate-jwt-fragment" \
  --value '<validate-jwt header-name="Authorization" failed-validation-httpcode="401">
    <openid-config url="{{JwtIssuerUrl}}/.well-known/openid-configuration"/>
    <audiences><audience>{{JwtAudience}}</audience></audiences>
  </validate-jwt>'

Note: create also acts as upsert — it will overwrite an existing fragment with the same name.

Delete a Fragment

az apim policy-fragment delete \
  --resource-group "my-rg" \
  --service-name "my-apim" \
  --name "validate-jwt-fragment"

Using the Management REST API

# Get a fragment
curl -X GET \
  "https://management.azure.com/subscriptions/{subId}/resourceGroups/{rg}/providers/Microsoft.ApiManagement/service/{apim}/policyFragments/validate-jwt-fragment?api-version=2022-08-01" \
  -H "Authorization: Bearer $TOKEN"

# Create/update a fragment
curl -X PUT \
  "https://management.azure.com/subscriptions/{subId}/resourceGroups/{rg}/providers/Microsoft.ApiManagement/service/{apim}/policyFragments/validate-jwt-fragment?api-version=2022-08-01" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "properties": {
      "value": "<validate-jwt ...>...</validate-jwt>",
      "description": "Validates JWT tokens against our IdP",
      "format": "xml"
    }
  }'

Fragment Naming Conventions

Adopt consistent naming from day one. A recommended pattern:

{action}-{subject}-fragment

validate-jwt-fragment
rate-limit-by-tier-fragment
inject-correlation-id-fragment
check-ip-allowlist-fragment
format-error-response-fragment
cache-response-fragment
log-to-eventhub-fragment

Alternatively, prefix by concern:

auth-validate-jwt-fragment
auth-check-roles-fragment
observability-inject-correlation-id-fragment
observability-log-request-fragment
security-ip-allowlist-fragment
throttle-by-tier-fragment
throttle-by-client-fragment

Drawbacks and Limitations

Policy Fragments are powerful but not without trade-offs. Know these before adopting them.

1. No Versioning

APIM fragments have no built-in version history. When you update a fragment, the old version is gone. If you need version control, you must manage it externally (Git, CI/CD pipeline, ARM template history).

Mitigation: Store all fragments in Git. Use CI/CD pipelines to deploy — never edit fragments directly in the portal in production.

2. Change Impact Is Invisible

When you update a fragment, every API that uses it is instantly affected. There is no staging, no canary, no gradual rollout within APIM itself. A bug in a fragment is a bug everywhere, simultaneously.

Mitigation: Test fragment changes in a non-production APIM instance first. Add integration tests for APIs that use the fragment.

3. Debugging Is Harder

When an API call fails, the APIM test console and trace show the evaluated policy inline — but it's not always obvious which fragment a particular block came from. You need to mentally map the trace back to the fragment source.

Mitigation: Add comments inside fragments and use trace policies during debugging:

<!-- Fragment: validate-jwt-fragment -->
<trace source="validate-jwt-fragment" severity="information">
    <message>Entering JWT validation fragment</message>
</trace>
<validate-jwt ...>
    ...
</validate-jwt>

4. No Parameters / Dynamic Inputs

Fragments cannot accept parameters. You cannot call a fragment and pass it a value like a function argument. All configuration must come from Named Values, context variables, or subscription properties.

Mitigation: Use set-variable in the calling policy to pre-set context variables that the fragment reads. Document the contract (what variables the fragment expects) in the fragment description.

<!-- Calling policy sets the variable before including the fragment -->
<set-variable name="requiredRole" value="api-consumer" />
<include-fragment fragment-id="check-roles-fragment" />

<!-- Inside check-roles-fragment, read the variable -->
<validate-jwt ...>
    <required-claims>
        <claim name="roles" match="any">
            <value>@(context.Variables.GetValueOrDefault<string>("requiredRole"))</value>
        </claim>
    </required-claims>
</validate-jwt>

5. Not Available in Consumption Tier

Policy Fragments are only available in Developer, Basic, Standard, Premium tiers. If you're on the Consumption tier (serverless), fragments are unavailable and you must copy-paste policies.

6. No Conditional Inclusion Logic

<include-fragment> is always evaluated unconditionally at the point it appears. You cannot skip a fragment based on a runtime condition at the call site — the entire fragment body executes every time.

❌ What you might want to do (but cannot):

<!-- You want: only run the IP check fragment for external clients.
     But include-fragment has no "if" attribute — there is no such syntax. -->
<include-fragment fragment-id="ip-allowlist-fragment" condition="@(isExternalClient)" />

❌ Workaround attempt that still fails — the fragment always runs:

<!-- Setting a variable before the fragment does NOT skip the fragment.
     The fragment body still executes regardless. -->
<set-variable name="skipIpCheck" value="@(context.Variables.GetValueOrDefault<bool>('isExternalClient') == false)" />
<include-fragment fragment-id="ip-allowlist-fragment" />
<!-- The fragment runs unconditionally — skipIpCheck is ignored at the call site. -->

✅ Correct approach — put the condition INSIDE the fragment itself:

<!-- Inside ip-allowlist-fragment: read the skip flag and gate the logic.
     Note: &quot; entities used inside condition="@(...)" to avoid breaking the XML attribute. -->
<choose>
    <when condition="@(!context.Variables.GetValueOrDefault<bool>(&quot;skipIpCheck&quot;))">
        <set-variable name="isAllowed" value="@{
            var allowed = context.Variables.GetValueOrDefault<string>(&quot;AllowedCidrRanges&quot;, string.Empty).Split(',');
            return allowed.Any(r => context.Request.IpAddress.StartsWith(r.Trim()));
        }" />
        <choose>
            <when condition="@(!context.Variables.GetValueOrDefault<bool>(&quot;isAllowed&quot;))">
                <return-response>
                    <set-status code="403" reason="Forbidden" />
                    <set-body>{"error": "access_denied"}</set-body>
                </return-response>
            </when>
        </choose>
    </when>
</choose>

Then the calling policy sets the flag before including the fragment:

<!-- Calling policy: use single quotes inside @() to avoid XML attribute conflict -->
<set-variable name="skipIpCheck" value="@(context.Request.Headers.ContainsKey('X-Internal-Call'))" />
<include-fragment fragment-id="ip-allowlist-fragment" />

The downside: conditional logic bleeds into the fragment, making it less clean. This is a real design tension with fragments — they work best for unconditional cross-cutting concerns.

7. Circular References Are Not Caught Early

If Fragment A includes Fragment B, and Fragment B includes Fragment A, APIM may not catch this at save time. It will typically fail at request evaluation time with a cryptic error.

Mitigation: Keep your fragment dependency graph shallow and acyclic. Document dependencies in fragment descriptions.

8. No Fine-Grained RBAC on Fragments

Access control to policy fragments is at the APIM instance level, not per-fragment. Anyone with Contributor or API Management Service Contributor rights can edit any fragment.

Mitigation: Use dedicated service principals for CI/CD deployments. Restrict portal access for production APIM instances.


Drawbacks Summary Table

DrawbackSeverityMitigation
No built-in versioningHighStore in Git, deploy via CI/CD
Instant global impact on updateHighTest in non-prod first, integration tests
Harder debugging / tracingMediumAdd trace policies, clear naming
No parameters/dynamic inputsMediumUse context variables as a contract
Not in Consumption tierMediumUpgrade tier or accept copy-paste
No conditional inclusion at call siteLowConditional logic inside or around the fragment
No circular reference detectionLowKeep fragment graph flat and documented
No per-fragment RBACLowRestrict portal access, use CI/CD

Best Practices

1. Single Responsibility

Each fragment should do exactly one thing. Don't combine JWT validation and rate limiting into one fragment — split them. This makes fragments independently testable and reusable across different combinations.

2. Self-Contained Named Values

Every configuration value a fragment needs should come from a Named Value (or context variable documented in the fragment description). Fragments should never hardcode environment-specific values.

3. Fail-Safe Defaults

Fragments that check permissions or validate tokens should fail closed. When in doubt, deny. Build in meaningful error messages that help developers debug without revealing internal details.

4. Document the Contract

Use the fragment's description field to state what Named Values it requires and what context variables it reads or sets. Treat it like a function signature.

Fragment: rate-limit-by-tier-fragment

Reads:
  - context.Subscription.Properties["tier"] — Expected: "free" | "basic" | "pro" | "enterprise"

Sets:
  - Nothing (all state is managed internally by the rate-limit-by-key counter)

Named Values required: none

Side effects: Returns HTTP 429 if rate limit exceeded.

5. Deploy via CI/CD, Never Edit in Portal (Production)

Direct portal edits in production are invisible to version history and bypass review. All fragment changes should flow through a Git PR → CI/CD pipeline → APIM deployment.

# Example: Azure DevOps pipeline step to deploy a fragment
- task: AzureCLI@2
  displayName: 'Deploy Policy Fragments'
  inputs:
    azureSubscription: 'my-service-connection'
    scriptType: 'bash'
    scriptLocation: 'inlineScript'
    inlineScript: |
      for file in fragments/*.xml; do
        fragment_name=$(basename "$file" .xml)
        fragment_value=$(cat "$file")
        az apim policy-fragment create \
          --resource-group "$(ResourceGroup)" \
          --service-name "$(ApimName)" \
          --name "$fragment_name" \
          --value "$fragment_value"
      done

Summary

Policy Fragments are one of the most impactful features in Azure APIM for teams managing more than a handful of APIs. They turn repetitive, error-prone copy-paste policy blocks into a governed, reusable library.

Combined with Named Values, they give you environment-portable fragments where the logic is identical across dev/staging/prod and only the configuration differs. This makes promotions safe and auditable.

The key limitations to keep in mind: there is no built-in versioning, no parameters, and changes propagate instantly to all consumers. These are manageable with good CI/CD hygiene and a non-production testing gate — but they require discipline.

When adopted properly, policy fragments let API developers focus on business logic while platform teams maintain a secure, consistent, and observable API gateway layer.