cors.md

April 22, 2026 ยท View on GitHub

import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem';

Description

The cors Plugin allows you to enable Cross-Origin Resource Sharing (CORS). CORS is an HTTP-header based mechanism which allows a server to specify any origins (domain, scheme, or port) other than its own, and instructs browsers to allow the loading of resources from those origins.

Attributes

NameTypeRequiredDefaultValid valuesDescription
allow_originsstringFalse"*"Comma-separated string of origins to allow CORS, in the format scheme://host:port, for example https://somedomain.com:8081. If you have multiple origins, use , to list them. If allow_credential is set to false, you can use * to allow all origins. If allow_credential is set to true, you can use ** to forcefully allow all origins, but this poses security risks.
allow_methodsstringFalse"*"Comma-separated string of HTTP request methods to allow CORS, for example GET, POST. If allow_credential is set to false, you can use * to allow all methods. If allow_credential is set to true, you can use ** to forcefully allow all methods, but this poses security risks.
allow_headersstringFalse"*"Comma-separated string of HTTP headers allowed in requests when accessing a cross-origin resource. If allow_credential is set to false, you can use * to allow all request headers. If allow_credential is set to true, you can use ** to forcefully allow all request headers, but this poses security risks.
expose_headersstringFalseComma-separated string of HTTP headers that should be made available in response to a cross-origin request. If allow_credential is set to false, you can use * to allow all response headers. If not specified, the Plugin will not modify the Access-Control-Expose-Headers header. See Access-Control-Expose-Headers - MDN for more details.
max_ageintegerFalse5Maximum time in seconds for which the results of a preflight request can be cached. If the time is within this limit, the browser will check the cached result. Set to -1 to disable caching. Note that the maximum value is browser-dependent. See Access-Control-Max-Age for more details.
allow_credentialbooleanFalsefalseWhen set to true, allows requests to include credentials such as cookies. According to CORS specification, if you set this to true, you cannot use * for other CORS attributes.
allow_origins_by_regexarrayFalseRegex to match origins that allow CORS. For example, [".*\.test.com$"] can match all subdomains of test.com. When configured, only domains matching the RegEx will be allowed and allow_origins will be ignored.
allow_origins_by_metadataarrayFalseOrigins to enable CORS referenced from allow_origins set in the Plugin metadata. For example, if "allow_origins": {"EXAMPLE": "https://example.com"} is set in the Plugin metadata, then ["EXAMPLE"] can be used to allow CORS on the origin https://example.com.
timing_allow_originsstringFalseComma-separated string of origins to allow access to the resource timing information. See Timing-Allow-Origin for more details.
timing_allow_origins_by_regexarrayFalseRegex to match origins for enabling access to the resource timing information. For example, [".*\.test.com"] can match all subdomains of test.com. When configured, only domains matching the RegEx will be allowed and timing_allow_origins will be ignored.

:::info IMPORTANT

  1. The allow_credential attribute is sensitive and must be used carefully. If set to true, the default value * of the other attributes will be invalid and they should be specified explicitly.
  2. When using ** you are vulnerable to security risks like CSRF. Make sure that this meets your security levels before using it.

:::

Metadata

NameTypeRequiredDefaultValid valuesDescription
allow_originsobjectFalseA map of keys and allowed origins. The keys are used in the allow_origins_by_metadata attribute and the values are equivalent to the allow_origins attribute of the Plugin.

Examples

The examples below demonstrate how you can configure the cors Plugin for different scenarios.

:::note

You can fetch the admin_key from config.yaml and save to an environment variable with the following command:

admin_key=$(yq '.deployment.admin.admin_key[0].key' conf/config.yaml | sed 's/"//g')

:::

Enable CORS for a Route

The following example demonstrates how to enable CORS on a Route to allow resource loading from a list of origins.

<Tabs groupId="api" defaultValue="admin-api" values={[ {label: 'Admin API', value: 'admin-api'}, {label: 'ADC', value: 'adc'}, {label: 'Ingress Controller', value: 'aic'} ]}>

Create a Route with the cors Plugin:

curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
  -H "X-API-KEY: ${admin_key}" \
  -d '{
    "id": "cors-route",
    "uri": "/anything",
    "plugins": {
      "cors": {
        "allow_origins": "http://sub.domain.com,http://sub2.domain.com",
        "allow_methods": "GET,POST",
        "allow_headers": "headr1,headr2",
        "expose_headers": "ex-headr1,ex-headr2",
        "max_age": 50,
        "allow_credential": true
      }
    },
    "upstream": {
      "nodes": {
        "httpbin.org:80": 1
      },
      "type": "roundrobin"
    }
  }'
services:
  - name: cors-service
    routes:
      - name: cors-route
        uris:
          - /anything
        plugins:
          cors:
            allow_origins: "http://sub.domain.com,http://sub2.domain.com"
            allow_methods: "GET,POST"
            allow_headers: "headr1,headr2"
            expose_headers: "ex-headr1,ex-headr2"
            max_age: 50
            allow_credential: true
    upstream:
      type: roundrobin
      nodes:
        - host: httpbin.org
          port: 80
          weight: 1

Synchronize the configuration to APISIX:

adc sync -f adc.yaml

<Tabs groupId="k8s-api" defaultValue="gateway-api" values={[ {label: 'Gateway API', value: 'gateway-api'}, {label: 'APISIX Ingress Controller', value: 'apisix-ingress-controller'} ]}>

apiVersion: v1
kind: Service
metadata:
  namespace: aic
  name: httpbin-external-domain
spec:
  type: ExternalName
  externalName: httpbin.org
---
apiVersion: apisix.apache.org/v1alpha1
kind: PluginConfig
metadata:
  namespace: aic
  name: cors-plugin-config
spec:
  plugins:
    - name: cors
      config:
        allow_origins: "http://sub.domain.com,http://sub2.domain.com"
        allow_methods: "GET,POST"
        allow_headers: "headr1,headr2"
        expose_headers: "ex-headr1,ex-headr2"
        max_age: 50
        allow_credential: true
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  namespace: aic
  name: cors-route
spec:
  parentRefs:
    - name: apisix
  rules:
    - matches:
        - path:
            type: Exact
            value: /anything
      filters:
        - type: ExtensionRef
          extensionRef:
            group: apisix.apache.org
            kind: PluginConfig
            name: cors-plugin-config
      backendRefs:
        - name: httpbin-external-domain
          port: 80

Apply the configuration to your cluster:

kubectl apply -f cors-ic.yaml
apiVersion: apisix.apache.org/v2
kind: ApisixUpstream
metadata:
  namespace: aic
  name: httpbin-external-domain
spec:
  ingressClassName: apisix
  externalNodes:
  - type: Domain
    name: httpbin.org
---
apiVersion: apisix.apache.org/v2
kind: ApisixRoute
metadata:
  namespace: aic
  name: cors-route
spec:
  ingressClassName: apisix
  http:
    - name: cors-route
      match:
        paths:
          - /anything
      upstreams:
      - name: httpbin-external-domain
      plugins:
      - name: cors
        enable: true
        config:
          allow_origins: "http://sub.domain.com,http://sub2.domain.com"
          allow_methods: "GET,POST"
          allow_headers: "headr1,headr2"
          expose_headers: "ex-headr1,ex-headr2"
          max_age: 50
          allow_credential: true

Apply the configuration to your cluster:

kubectl apply -f cors-ic.yaml

Send a request to the Route with an allowed origin:

curl "http://127.0.0.1:9080/anything" -H "Origin: http://sub2.domain.com" -I

You should receive an HTTP/1.1 200 OK response and observe CORS headers:

...
Access-Control-Allow-Origin: http://sub2.domain.com
Access-Control-Allow-Credentials: true
Server: APISIX/3.8.0
Vary: Origin
Access-Control-Allow-Methods: GET,POST
Access-Control-Max-Age: 50
Access-Control-Expose-Headers: ex-headr1,ex-headr2
Access-Control-Allow-Headers: headr1,headr2

Send a request to the Route with an origin that is not allowed:

curl "http://127.0.0.1:9080/anything" -H "Origin: http://sub3.domain.com" -I

You should receive an HTTP/1.1 200 OK response without any CORS headers:

...
Server: APISIX/3.8.0
Vary: Origin

Use RegEx to Match Origin

The following example demonstrates how to use a regular expression to match allowed origins using the allow_origins_by_regex attribute.

<Tabs groupId="api" defaultValue="admin-api" values={[ {label: 'Admin API', value: 'admin-api'}, {label: 'ADC', value: 'adc'}, {label: 'Ingress Controller', value: 'aic'} ]}>

Create a Route with the cors Plugin:

curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
  -H "X-API-KEY: ${admin_key}" \
  -d '{
    "id": "cors-route",
    "uri": "/anything",
    "plugins": {
      "cors": {
        "allow_methods": "GET,POST",
        "allow_headers": "headr1,headr2",
        "expose_headers": "ex-headr1,ex-headr2",
        "max_age": 50,
        "allow_origins_by_regex": [ ".*\\.test.com$" ]
      }
    },
    "upstream": {
      "nodes": {
        "httpbin.org:80": 1
      },
      "type": "roundrobin"
    }
  }'
services:
  - name: cors-service
    routes:
      - name: cors-route
        uris:
          - /anything
        plugins:
          cors:
            allow_methods: "GET,POST"
            allow_headers: "headr1,headr2"
            expose_headers: "ex-headr1,ex-headr2"
            max_age: 50
            allow_origins_by_regex:
              - ".*\\.test.com$"
    upstream:
      type: roundrobin
      nodes:
        - host: httpbin.org
          port: 80
          weight: 1

Synchronize the configuration to APISIX:

adc sync -f adc.yaml

<Tabs groupId="k8s-api" defaultValue="gateway-api" values={[ {label: 'Gateway API', value: 'gateway-api'}, {label: 'APISIX Ingress Controller', value: 'apisix-ingress-controller'} ]}>

apiVersion: v1
kind: Service
metadata:
  namespace: aic
  name: httpbin-external-domain
spec:
  type: ExternalName
  externalName: httpbin.org
---
apiVersion: apisix.apache.org/v1alpha1
kind: PluginConfig
metadata:
  namespace: aic
  name: cors-regex-plugin-config
spec:
  plugins:
    - name: cors
      config:
        allow_methods: "GET,POST"
        allow_headers: "headr1,headr2"
        expose_headers: "ex-headr1,ex-headr2"
        max_age: 50
        allow_origins_by_regex:
          - ".*\\.test.com$"
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  namespace: aic
  name: cors-route
spec:
  parentRefs:
    - name: apisix
  rules:
    - matches:
        - path:
            type: Exact
            value: /anything
      filters:
        - type: ExtensionRef
          extensionRef:
            group: apisix.apache.org
            kind: PluginConfig
            name: cors-regex-plugin-config
      backendRefs:
        - name: httpbin-external-domain
          port: 80

Apply the configuration to your cluster:

kubectl apply -f cors-ic.yaml
apiVersion: apisix.apache.org/v2
kind: ApisixUpstream
metadata:
  namespace: aic
  name: httpbin-external-domain
spec:
  ingressClassName: apisix
  externalNodes:
  - type: Domain
    name: httpbin.org
---
apiVersion: apisix.apache.org/v2
kind: ApisixRoute
metadata:
  namespace: aic
  name: cors-route
spec:
  ingressClassName: apisix
  http:
    - name: cors-route
      match:
        paths:
          - /anything
      upstreams:
      - name: httpbin-external-domain
      plugins:
      - name: cors
        enable: true
        config:
          allow_methods: "GET,POST"
          allow_headers: "headr1,headr2"
          expose_headers: "ex-headr1,ex-headr2"
          max_age: 50
          allow_origins_by_regex:
            - ".*\\.test.com$"

Apply the configuration to your cluster:

kubectl apply -f cors-ic.yaml

Send a request to the Route with an allowed origin:

curl "http://127.0.0.1:9080/anything" -H "Origin: http://a.test.com" -I

You should receive an HTTP/1.1 200 OK response and observe CORS headers:

...
Access-Control-Allow-Origin: http://a.test.com
Server: APISIX/3.8.0
Vary: Origin
Access-Control-Allow-Methods: GET,POST
Access-Control-Max-Age: 50
Access-Control-Expose-Headers: ex-headr1,ex-headr2
Access-Control-Allow-Headers: headr1,headr2

Send a request with an origin that does not match the pattern:

curl "http://127.0.0.1:9080/anything" -H "Origin: http://a.test2.com" -I

You should receive an HTTP/1.1 200 OK response without any CORS headers:

...
Server: APISIX/3.8.0
Vary: Origin

Configure Origins in Plugin Metadata

The following example demonstrates how to configure allowed origins in Plugin metadata and reference them in the cors Plugin using allow_origins_by_metadata.

<Tabs groupId="api" defaultValue="admin-api" values={[ {label: 'Admin API', value: 'admin-api'}, {label: 'ADC', value: 'adc'}, {label: 'Ingress Controller', value: 'aic'} ]}>

Configure Plugin metadata for the cors Plugin:

curl "http://127.0.0.1:9180/apisix/admin/plugin_metadata/cors" -X PUT \
  -H "X-API-KEY: ${admin_key}" \
  -d '{
    "allow_origins": {
      "key_1": "https://domain.com",
      "key_2": "https://sub.domain.com,https://sub2.domain.com",
      "key_3": "*"
    }
  }'

Create a Route with the cors Plugin using allow_origins_by_metadata:

curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
  -H "X-API-KEY: ${admin_key}" \
  -d '{
    "id": "cors-route",
    "uri": "/anything",
    "plugins": {
      "cors": {
        "allow_methods": "GET,POST",
        "allow_headers": "headr1,headr2",
        "expose_headers": "ex-headr1,ex-headr2",
        "max_age": 50,
        "allow_origins_by_metadata": ["key_1"]
      }
    },
    "upstream": {
      "nodes": {
        "httpbin.org:80": 1
      },
      "type": "roundrobin"
    }
  }'
plugin_metadata:
  cors:
    allow_origins:
      key_1: "https://domain.com"
      key_2: "https://sub.domain.com,https://sub2.domain.com"
      key_3: "*"
services:
  - name: cors-service
    routes:
      - name: cors-route
        uris:
          - /anything
        plugins:
          cors:
            allow_methods: "GET,POST"
            allow_headers: "headr1,headr2"
            expose_headers: "ex-headr1,ex-headr2"
            max_age: 50
            allow_origins_by_metadata:
              - "key_1"
    upstream:
      type: roundrobin
      nodes:
        - host: httpbin.org
          port: 80
          weight: 1

Synchronize the configuration to APISIX:

adc sync -f adc.yaml

Update your GatewayProxy manifest to configure the Plugin metadata:

apiVersion: apisix.apache.org/v1alpha1
kind: GatewayProxy
metadata:
  namespace: aic
  name: apisix-config
spec:
  provider:
    type: ControlPlane
    controlPlane:
      # your control plane connection configuration
  pluginMetadata:
    cors:
      allow_origins:
        key_1: "https://domain.com"
        key_2: "https://sub.domain.com,https://sub2.domain.com"
        key_3: "*"

<Tabs groupId="k8s-api" defaultValue="gateway-api" values={[ {label: 'Gateway API', value: 'gateway-api'}, {label: 'APISIX Ingress Controller', value: 'apisix-ingress-controller'} ]}>

Create the Route with allow_origins_by_metadata:

apiVersion: v1
kind: Service
metadata:
  namespace: aic
  name: httpbin-external-domain
spec:
  type: ExternalName
  externalName: httpbin.org
---
apiVersion: apisix.apache.org/v1alpha1
kind: PluginConfig
metadata:
  namespace: aic
  name: cors-metadata-plugin-config
spec:
  plugins:
    - name: cors
      config:
        allow_methods: "GET,POST"
        allow_headers: "headr1,headr2"
        expose_headers: "ex-headr1,ex-headr2"
        max_age: 50
        allow_origins_by_metadata:
          - "key_1"
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  namespace: aic
  name: cors-route
spec:
  parentRefs:
    - name: apisix
  rules:
    - matches:
        - path:
            type: Exact
            value: /anything
      filters:
        - type: ExtensionRef
          extensionRef:
            group: apisix.apache.org
            kind: PluginConfig
            name: cors-metadata-plugin-config
      backendRefs:
        - name: httpbin-external-domain
          port: 80

Apply the configuration to your cluster:

kubectl apply -f gatewayproxy.yaml -f cors-ic.yaml

Create the Route with allow_origins_by_metadata:

apiVersion: apisix.apache.org/v2
kind: ApisixUpstream
metadata:
  namespace: aic
  name: httpbin-external-domain
spec:
  ingressClassName: apisix
  externalNodes:
  - type: Domain
    name: httpbin.org
---
apiVersion: apisix.apache.org/v2
kind: ApisixRoute
metadata:
  namespace: aic
  name: cors-route
spec:
  ingressClassName: apisix
  http:
    - name: cors-route
      match:
        paths:
          - /anything
      upstreams:
      - name: httpbin-external-domain
      plugins:
      - name: cors
        enable: true
        config:
          allow_methods: "GET,POST"
          allow_headers: "headr1,headr2"
          expose_headers: "ex-headr1,ex-headr2"
          max_age: 50
          allow_origins_by_metadata:
            - "key_1"

Apply the configuration to your cluster:

kubectl apply -f gatewayproxy.yaml -f cors-ic.yaml

Send a request to the Route with an allowed origin:

curl "http://127.0.0.1:9080/anything" -H "Origin: https://domain.com" -I

You should receive an HTTP/1.1 200 OK response and observe CORS headers:

...
Access-Control-Allow-Origin: https://domain.com
Server: APISIX/3.8.0
Vary: Origin
Access-Control-Allow-Methods: GET,POST
Access-Control-Max-Age: 50
Access-Control-Expose-Headers: ex-headr1,ex-headr2
Access-Control-Allow-Headers: headr1,headr2

Send a request with an origin not in the metadata:

curl "http://127.0.0.1:9080/anything" -H "Origin: http://a.test2.com" -I

You should receive an HTTP/1.1 200 OK response without any CORS headers:

...
Server: APISIX/3.8.0
Vary: Origin