Docs
API7 GatewayManage traffic and APIsTraffic ManagementConditionally Disable Global Plugins

Conditionally Disable Global Plugins

Skip a global plugin for specific routes using route labels, a serverless-pre-function global rule, and _meta.filter.

Applies to:API7 Gateway 3.10.x

Roles:Platform engineer

Goal

Create a global response-rewrite rule that adds X-Global-Rule: applied to every route by default, then exempt one labeled route from it while a second, unlabeled route keeps receiving the header. A serverless-pre-function global rule reads the api7_disable_global_rules route label into a request variable, and _meta.filter on response-rewrite skips the plugin when that variable matches the label. Two matching approaches are covered: a regex filter on the raw label string, and a list-based filter using the has operator for routes that skip more than one global plugin.

_meta.filter, _meta.priority, _meta.disable, and _meta.error_response are documented, open-source APISIX plugin common configurations available on every plugin, not an API7-only feature; see Plugin common configurations (opens in Apache APISIX docs).

Before you begin

Set the variables used in this guide:

export API7_ADMIN_URL="https://localhost:7443"
export API7_GATEWAY_GROUP_ID="default"   # only the group the quickstart creates; otherwise copy the gateway group's UUID from Gateway Groups in the Dashboard
read -rp "Gateway group name for ADC (if used): " API7_GATEWAY_GROUP_NAME && export API7_GATEWAY_GROUP_NAME
export GATEWAY_URL="http://127.0.0.1:9080"
read -rsp "Dashboard token: " API7_ADMIN_KEY && export API7_ADMIN_KEY && echo
  • Confirm that the control plane and selected gateway group are ready:

    curl -ksS --connect-timeout 3 --max-time 10 -o /dev/null -w '%{http_code}\n' "${API7_ADMIN_URL}/api/gateway_groups" -H "X-API-KEY: ${API7_ADMIN_KEY}"
    curl -ksS --connect-timeout 3 --max-time 10 "${API7_ADMIN_URL}/api/gateway_groups/${API7_GATEWAY_GROUP_ID}/instances" -H "X-API-KEY: ${API7_ADMIN_KEY}" | grep -o '"status": *"Healthy"' | wc -l

    Expected: 200, then 1 or more.

  • The gateway can resolve and reach httpbin.org:443. This is a public demonstration service, so its availability and behavior are outside API7 Gateway's control.

  • curl, grep, and tr are available.

  • For ADC, confirm label-selector support and reserve the working files:

    adc --version
    adc sync --help | grep -- '--label-selector'
    for file in global-plugin-exemption-adc.yaml global-plugin-exemption-cleanup.yaml; do test ! -e "${file}" || { echo "Stop: ${file} already exists"; exit 1; }; done

    ADC resolves --gateway-group by display name; the compatibility Admin API uses the group's UUID. Enter the display name of the group identified by API7_GATEWAY_GROUP_ID.

Global rules cannot carry labels or a name distinct from their ID. The Admin API's PUT /apisix/admin/global_rules/{global_rule_id} request body accepts only a plugins object, and PUT replaces the complete rule — not a merge. If your gateway group already runs serverless-pre-function or response-rewrite as a global rule, applying this guide's PUT overwrites that existing configuration instead of adding to it. Check both before continuing:

curl -ksS --connect-timeout 3 --max-time 10 -w '\nserverless-pre-function: %{http_code}\n' "${API7_ADMIN_URL}/apisix/admin/global_rules/serverless-pre-function?gateway_group_id=${API7_GATEWAY_GROUP_ID}" -H "X-API-KEY: ${API7_ADMIN_KEY}"
curl -ksS --connect-timeout 3 --max-time 10 -w '\nresponse-rewrite: %{http_code}\n' "${API7_ADMIN_URL}/apisix/admin/global_rules/response-rewrite?gateway_group_id=${API7_GATEWAY_GROUP_ID}" -H "X-API-KEY: ${API7_ADMIN_KEY}"

Expected: both are absent (404). If either already exists, stop and combine its existing functions or plugin configuration with this guide's by hand before applying either PUT — do not overwrite it.

Check the fixed Admin API identifiers for the service and routes before using PUT. Global rules are identified by their plugin name above, so only the service and routes need this ID check (their name fields intentionally differ from their URL IDs):

curl -ksS --connect-timeout 3 --max-time 10 -w '\nservice: %{http_code}\n' "${API7_ADMIN_URL}/apisix/admin/services/global-plugin-exemption-service-id?gateway_group_id=${API7_GATEWAY_GROUP_ID}" -H "X-API-KEY: ${API7_ADMIN_KEY}"
for id in global-plugin-exemption-exempt-id global-plugin-exemption-protected-id; do
  curl -ksS --connect-timeout 3 --max-time 10 -w "\nroute ${id}: %{http_code}\n" "${API7_ADMIN_URL}/apisix/admin/routes/${id}?gateway_group_id=${API7_GATEWAY_GROUP_ID}" -H "X-API-KEY: ${API7_ADMIN_KEY}"
done

Expected: every resource is absent. A 200 response containing a resource means the ID is in use; stop or change every occurrence because PUT replaces that ID. For ADC, continue only if its diff creates exactly this guide's labeled resources.

Steps

Complete steps 1–4 for the Admin API path, or skip them and complete step 5 for ADC. Do not apply both paths.

Step 1: Create the upstream service

curl -ksS --connect-timeout 3 --max-time 15 "${API7_ADMIN_URL}/apisix/admin/services/global-plugin-exemption-service-id?gateway_group_id=${API7_GATEWAY_GROUP_ID}" -X PUT \
  -H "X-API-KEY: ${API7_ADMIN_KEY}" -H "Content-Type: application/json" \
  -d '{
    "name": "global-plugin-exemption-service",
    "labels": {"guide": "global-plugin-exemption"},
    "upstream": {
      "name": "default", "type": "roundrobin", "scheme": "https", "pass_host": "node",
      "nodes": [{"host": "httpbin.org", "port": 443, "weight": 100}]
    }
  }'

Step 2: Register the serverless-pre-function global rule

This global rule reads the matched route's api7_disable_global_rules label and registers it as the api7_disable_global_rules request variable, using the core.ctx.register_var pattern documented in Register Custom Variables (opens in Plugin Hub docs). A route without the label falls back to 1, a value that never matches the response-rewrite filter below.

curl -ksS --connect-timeout 3 --max-time 15 "${API7_ADMIN_URL}/apisix/admin/global_rules/serverless-pre-function?gateway_group_id=${API7_GATEWAY_GROUP_ID}" -X PUT \
  -H "X-API-KEY: ${API7_ADMIN_KEY}" -H "Content-Type: application/json" \
  -d '{
    "plugins": {
      "serverless-pre-function": {
        "phase": "rewrite",
        "functions": [
          "return function(conf, ctx) local core = require \"apisix.core\" core.ctx.register_var(\"api7_disable_global_rules\", function(ctx) local route = ctx.matched_route and ctx.matched_route.value if route and route.labels then return route.labels.api7_disable_global_rules end return 1 end) end"
        ]
      }
    }
  }'

Step 3: Register the response-rewrite global rule with _meta.filter

_meta.filter uses the same [var, "!", operator, value] expression syntax as route matching; see APISIX expressions (opens in Apache APISIX docs). This filter skips response-rewrite only when api7_disable_global_rules matches the regex .*response-rewrite.*:

curl -ksS --connect-timeout 3 --max-time 15 "${API7_ADMIN_URL}/apisix/admin/global_rules/response-rewrite?gateway_group_id=${API7_GATEWAY_GROUP_ID}" -X PUT \
  -H "X-API-KEY: ${API7_ADMIN_KEY}" -H "Content-Type: application/json" \
  -d '{
    "plugins": {
      "response-rewrite": {
        "headers": {"set": {"X-Global-Rule": "applied"}},
        "_meta": {
          "filter": [
            ["api7_disable_global_rules", "!", "~~", ".*response-rewrite.*"]
          ]
        }
      }
    }
  }'

Step 4: Create the exempt and protected routes

curl -ksS --connect-timeout 3 --max-time 15 "${API7_ADMIN_URL}/apisix/admin/routes/global-plugin-exemption-exempt-id?gateway_group_id=${API7_GATEWAY_GROUP_ID}" -X PUT \
  -H "X-API-KEY: ${API7_ADMIN_KEY}" -H "Content-Type: application/json" \
  -d '{
    "name": "global-plugin-exemption-exempt",
    "labels": {"guide": "global-plugin-exemption", "api7_disable_global_rules": "response-rewrite"},
    "paths": ["/anything/global-exempt"], "methods": ["GET"],
    "service_id": "global-plugin-exemption-service-id"
  }'

curl -ksS --connect-timeout 3 --max-time 15 "${API7_ADMIN_URL}/apisix/admin/routes/global-plugin-exemption-protected-id?gateway_group_id=${API7_GATEWAY_GROUP_ID}" -X PUT \
  -H "X-API-KEY: ${API7_ADMIN_KEY}" -H "Content-Type: application/json" \
  -d '{
    "name": "global-plugin-exemption-protected",
    "labels": {"guide": "global-plugin-exemption"},
    "paths": ["/anything/global-protected"], "methods": ["GET"],
    "service_id": "global-plugin-exemption-service-id"
  }'

Step 5: Apply one complete desired state with ADC

Skip this alternative if you completed steps 1–4. global_rules entries are keyed by plugin name and, like the Admin API, cannot carry labels:

global-plugin-exemption-adc.yaml
global_rules:
  serverless-pre-function:
    phase: rewrite
    functions:
      - |-
        return function(conf, ctx)
            local core = require "apisix.core"
            core.ctx.register_var("api7_disable_global_rules", function(ctx)
                local route = ctx.matched_route and ctx.matched_route.value
                if route and route.labels then
                    return route.labels.api7_disable_global_rules
                end
                return 1
            end)
        end
  response-rewrite:
    headers:
      set:
        X-Global-Rule: applied
    _meta:
      filter:
        - - api7_disable_global_rules
          - "!"
          - "~~"
          - ".*response-rewrite.*"
services:
  - name: global-plugin-exemption-service
    labels: {guide: global-plugin-exemption}
    upstream:
      name: default
      scheme: https
      pass_host: node
      nodes: [{host: httpbin.org, port: 443, weight: 100}]
    routes:
      - name: global-plugin-exemption-exempt
        labels: {guide: global-plugin-exemption, api7_disable_global_rules: response-rewrite}
        uris: [/anything/global-exempt]
        methods: [GET]
      - name: global-plugin-exemption-protected
        labels: {guide: global-plugin-exemption}
        uris: [/anything/global-protected]
        methods: [GET]

Lint, validate, inspect, and apply the reviewed diff. Because global_rules cannot be labeled, --label-selector scopes only the service and routes in this diff — review the global_rules portion directly, since it affects every route in the gateway group that runs response-rewrite:

adc lint -f global-plugin-exemption-adc.yaml
adc validate -f global-plugin-exemption-adc.yaml --backend api7ee --server "${API7_ADMIN_URL}" --token "${API7_ADMIN_KEY}" --gateway-group "${API7_GATEWAY_GROUP_NAME}" --tls-skip-verify --label-selector guide=global-plugin-exemption
adc diff -f global-plugin-exemption-adc.yaml --backend api7ee --server "${API7_ADMIN_URL}" --token "${API7_ADMIN_KEY}" --gateway-group "${API7_GATEWAY_GROUP_NAME}" --tls-skip-verify --label-selector guide=global-plugin-exemption
adc sync -f global-plugin-exemption-adc.yaml --backend api7ee --server "${API7_ADMIN_URL}" --token "${API7_ADMIN_KEY}" --gateway-group "${API7_GATEWAY_GROUP_NAME}" --tls-skip-verify --label-selector guide=global-plugin-exemption

Expected: lint and validation succeed; the diff creates the two labeled global rules plus the labeled service and its two labeled routes; sync affects only that reviewed set.

Optional: switch to list-based label matching

Both matching approaches read the same route label, but this variant returns a list of plugin names instead of a raw string and uses the has operator for exact membership instead of a regex.

Complex regex expressions can incur more processing overhead than the list-based approach.

Replace the Step 2 serverless-pre-function and Step 3 response-rewrite bodies with:

curl -ksS --connect-timeout 3 --max-time 15 "${API7_ADMIN_URL}/apisix/admin/global_rules/serverless-pre-function?gateway_group_id=${API7_GATEWAY_GROUP_ID}" -X PUT \
  -H "X-API-KEY: ${API7_ADMIN_KEY}" -H "Content-Type: application/json" \
  -d '{
    "plugins": {
      "serverless-pre-function": {
        "phase": "rewrite",
        "functions": [
          "return function(conf, ctx) local core = require \"apisix.core\" core.ctx.register_var(\"api7_disable_global_rules\", function(ctx) local route = ctx.matched_route and ctx.matched_route.value if route and route.labels then local value = route.labels.api7_disable_global_rules if value then local disable_plugins = {} for plugin_name in string.gmatch(value, \"[^,]+\") do local trimmed_name = string.gsub(plugin_name, \"^%s*(.-)%s*$\", \"%1\") if trimmed_name and trimmed_name ~= \"\" then table.insert(disable_plugins, trimmed_name) end end return disable_plugins end end return 1 end) end"
        ]
      }
    }
  }'

curl -ksS --connect-timeout 3 --max-time 15 "${API7_ADMIN_URL}/apisix/admin/global_rules/response-rewrite?gateway_group_id=${API7_GATEWAY_GROUP_ID}" -X PUT \
  -H "X-API-KEY: ${API7_ADMIN_KEY}" -H "Content-Type: application/json" \
  -d '{
    "plugins": {
      "response-rewrite": {
        "headers": {"set": {"X-Global-Rule": "applied"}},
        "_meta": {
          "filter": [
            ["api7_disable_global_rules", "!", "has", "response-rewrite"]
          ]
        }
      }
    }
  }'

The equivalent ADC global_rules block:

global-plugin-exemption-adc.yaml
global_rules:
  serverless-pre-function:
    phase: rewrite
    functions:
      - >-
        return function(conf, ctx)
            local core = require "apisix.core"
            core.ctx.register_var("api7_disable_global_rules", function(ctx)
                local route = ctx.matched_route and ctx.matched_route.value
                if route and route.labels then
                    local value = route.labels.api7_disable_global_rules
                    if value then
                        local disable_plugins = {}
                        for plugin_name in string.gmatch(value, "[^,]+") do
                            local trimmed_name = string.gsub(plugin_name, "^%s*(.-)%s*$", "%1")
                            if trimmed_name and trimmed_name ~= "" then
                                table.insert(disable_plugins, trimmed_name)
                            end
                        end
                        return disable_plugins
                    end
                end
                return 1
            end)
        end
  response-rewrite:
    headers:
      set:
        X-Global-Rule: applied
    _meta:
      filter:
        - - api7_disable_global_rules
          - "!"
          - has
          - response-rewrite

Use this variant when a route needs to skip more than one global plugin: set api7_disable_global_rules to a comma-separated list, such as response-rewrite,prometheus.

Validate

Send a request to the exempt route:

EXEMPT_RESPONSE=$(curl -sS --connect-timeout 3 --max-time 15 -D - -o /dev/null "${GATEWAY_URL}/anything/global-exempt" | tr -d '\r')
if printf '%s\n' "${EXEMPT_RESPONSE}" | grep -qi '^X-Global-Rule:'; then echo 'Unexpected global rule'; exit 1; else echo 'Global rule skipped'; fi

Expected: the command prints Global rule skipped.

Send a request to the protected route:

PROTECTED_RESPONSE=$(curl -sS --connect-timeout 3 --max-time 15 -D - -o /dev/null "${GATEWAY_URL}/anything/global-protected" | tr -d '\r')
printf '%s\n' "${PROTECTED_RESPONSE}" | grep -qi '^X-Global-Rule: applied$'

Expected: the assertion succeeds. The response-rewrite global rule remains active for every route that does not carry the exemption label.

Roll back / Clean up

For the Admin API path, delete the routes and service, then the two global rules — since a global rule cannot be partially reverted, only delete it if this guide's Before-you-begin check found it absent; otherwise restore its previous complete configuration instead of deleting it:

for id in global-plugin-exemption-protected-id global-plugin-exemption-exempt-id; do
  curl -ksS --connect-timeout 3 --max-time 15 -X DELETE "${API7_ADMIN_URL}/apisix/admin/routes/${id}?gateway_group_id=${API7_GATEWAY_GROUP_ID}" -H "X-API-KEY: ${API7_ADMIN_KEY}"
done
curl -ksS --connect-timeout 3 --max-time 15 -X DELETE "${API7_ADMIN_URL}/apisix/admin/services/global-plugin-exemption-service-id?gateway_group_id=${API7_GATEWAY_GROUP_ID}" -H "X-API-KEY: ${API7_ADMIN_KEY}"
curl -ksS --connect-timeout 3 --max-time 15 -X DELETE "${API7_ADMIN_URL}/apisix/admin/global_rules/response-rewrite?gateway_group_id=${API7_GATEWAY_GROUP_ID}" -H "X-API-KEY: ${API7_ADMIN_KEY}"
curl -ksS --connect-timeout 3 --max-time 15 -X DELETE "${API7_ADMIN_URL}/apisix/admin/global_rules/serverless-pre-function?gateway_group_id=${API7_GATEWAY_GROUP_ID}" -H "X-API-KEY: ${API7_ADMIN_KEY}"

For ADC, review the diff carefully before syncing an empty state, since global_rules is not scoped by --label-selector:

global-plugin-exemption-cleanup.yaml
global_rules: {}
services: []
adc lint -f global-plugin-exemption-cleanup.yaml
adc diff -f global-plugin-exemption-cleanup.yaml --backend api7ee --server "${API7_ADMIN_URL}" --token "${API7_ADMIN_KEY}" --gateway-group "${API7_GATEWAY_GROUP_NAME}" --tls-skip-verify --label-selector guide=global-plugin-exemption

Confirm the diff removes only the two global rules this guide created and the labeled service and routes before syncing:

adc sync -f global-plugin-exemption-cleanup.yaml --backend api7ee --server "${API7_ADMIN_URL}" --token "${API7_ADMIN_KEY}" --gateway-group "${API7_GATEWAY_GROUP_NAME}" --tls-skip-verify --label-selector guide=global-plugin-exemption
rm -f global-plugin-exemption-adc.yaml global-plugin-exemption-cleanup.yaml

Troubleshoot

  • The exempt route still receives X-Global-Rule: confirm the route's api7_disable_global_rules label value and the filter's regex or list value match exactly, and that both global rules were applied to the same gateway group as the route.
  • The protected route stops receiving X-Global-Rule: a route without the label falls back to the variable value 1, which does not match either filter; if it does, verify no other route or global rule sets api7_disable_global_rules unexpectedly.
  • PUT to a global rule returns 400: the request body accepts only plugins; remove any id, name, or labels field.
  • A PUT to serverless-pre-function or response-rewrite removed unrelated logic: this is expected — PUT replaces the complete global rule. Restore the prior configuration from your own backup or version control; this guide does not retain it.
  • ADC proposes changes to unrelated routes or global rules: stop and verify the gateway-group display name and the complete desired-state file; review the global_rules portion of the diff directly, since it is not scoped by guide=global-plugin-exemption.