# How to Handle CORS in Gin with Custom Configuration: Complete Middleware Guide

> Configure CORS in Gin effectively with custom middleware. Control origins, headers, and methods using gin-contrib/cors for secure and robust web applications.

- Repository: [Gin-Gonic/gin](https://github.com/gin-gonic/gin)
- Tags: how-to-guide
- Published: 2026-02-27

---

**Use the `gin-contrib/cors` package to generate a configurable `gin.HandlerFunc` via `cors.New(config)`, then register it globally or per-group using `router.Use()` to intercept requests, validate origins against your whitelist, and automatically handle OPTIONS preflight responses.**

Handling Cross-Origin Resource Sharing (CORS) properly is essential for any Go web service that accepts browser-based requests. While the `gin-gonic/gin` framework does not ship with built-in CORS functionality, it provides a flexible middleware architecture that makes it straightforward to handle CORS in Gin with custom configuration using the official community package.

## Understanding Gin's Middleware Architecture

Gin processes requests through a **HandlersChain** assembled for every matched route. According to the source code in [`routergroup.go`](https://github.com/gin-gonic/gin/blob/main/routergroup.go), this chain consists of three tiers:

1. **Global middleware** registered with `router.Use(...)` – executes for every incoming request.
2. **Group-level middleware** attached to a `RouterGroup` – executes only for routes sharing that prefix.
3. **Route-specific middleware** supplied during route registration.

CORS is implemented as middleware that intercepts the request before your route handler executes. It inspects the `Origin` header, applies your security policy, sets the necessary response headers (`Access-Control-Allow-Origin`, `Access-Control-Allow-Methods`, etc.), and may short-circuit the chain for preflight OPTIONS requests.

## Installing the Official CORS Package

Gin relies on the `gin-contrib/cors` repository for CORS functionality. Install it alongside Gin:

```bash
go get github.com/gin-gonic/gin
go get github.com/gin-contrib/cors

```

## Implementing Custom CORS Configuration

The `cors.Config` struct in [`cors.go`](https://github.com/gin-gonic/gin/blob/main/cors.go) exposes granular controls for origins, methods, headers, credentials, and preflight caching. You convert this configuration into middleware using `cors.New(config)`, which returns a standard `gin.HandlerFunc`.

### Global CORS Setup

Register CORS middleware at the router level to apply a uniform policy across all endpoints:

```go
package main

import (
	"time"
	"github.com/gin-contrib/cors"
	"github.com/gin-gonic/gin"
)

func main() {
	r := gin.Default()

	corsConfig := cors.Config{
		AllowOrigins:     []string{"https://example.com", "https://api.example.com"},
		AllowMethods:     []string{"GET", "POST", "PUT", "PATCH", "DELETE"},
		AllowHeaders:     []string{"Origin", "Content-Type", "Authorization"},
		ExposeHeaders:    []string{"Content-Length"},
		AllowCredentials: true,
		MaxAge:           12 * time.Hour,
	}

	r.Use(cors.New(corsConfig))

	r.GET("/data", func(c *gin.Context) {
		c.JSON(200, gin.H{"data": "response"})
	})

	r.Run(":8080")
}

```

Key implementation details from [`cors.go`](https://github.com/gin-gonic/gin/blob/main/cors.go):
- `AllowOrigins` supports exact strings, wildcards (`*`), or sub-domain patterns.
- `MaxAge` defines how long browsers may cache preflight results.
- `AllowCredentials: true` permits cookies and authorization headers in cross-origin requests.

### Route Group Specific Policies

Because `RouterGroup.Use()` in [`routergroup.go`](https://github.com/gin-gonic/gin/blob/main/routergroup.go) accepts the same `HandlerFunc` interface, you can attach different CORS configurations to distinct API segments:

```go
api := r.Group("/api")
api.Use(cors.New(cors.Config{
	AllowOrigins: []string{"https://client-app.com"},
	AllowMethods: []string{"GET", "POST"},
}))

admin := r.Group("/admin")
admin.Use(cors.New(cors.Config{
	AllowOrigins:     []string{"https://admin.example.com"},
	AllowMethods:     []string{"GET", "POST", "DELETE"},
	AllowCredentials: true,
}))

```

This approach isolates public API CORS rules from administrative endpoints without code duplication.

### Manual CORS Implementation

For scenarios requiring dynamic origin validation logic that exceeds the `Config` struct capabilities, implement a custom `gin.HandlerFunc`:

```go
import (
	"net/http"
	"strings"
)

func CustomCORSMiddleware() gin.HandlerFunc {
	return func(c *gin.Context) {
		origin := c.Request.Header.Get("Origin")
		
		// Allow any sub-domain of example.com
		if strings.HasSuffix(origin, ".example.com") {
			c.Header("Access-Control-Allow-Origin", origin)
			c.Header("Access-Control-Allow-Methods", "GET,POST,OPTIONS")
			c.Header("Access-Control-Allow-Headers", "Content-Type,Authorization")
			
			if c.Request.Method == http.MethodOptions {
				c.AbortWithStatus(http.StatusNoContent)
				return
			}
		}
		
		c.Next()
	}
}

```

Register this with `r.Use(CustomCORSMiddleware())` exactly as you would the official package.

## Internal Mechanics of CORS Handling

The `gin-contrib/cors` middleware operates as follows, based on the implementation in [`cors.go`](https://github.com/gin-gonic/gin/blob/main/cors.go):

1. **Configuration Parsing**: The `New(config)` function validates your `Config` struct and returns a closure matching the `func(*gin.Context)` signature required by Gin's middleware chain.

2. **Origin Validation**: For each request, the handler extracts the `Origin` header and matches it against `AllowedOrigins`. It supports exact matches, the wildcard `*`, and pattern matching for sub-domains.

3. **Header Injection**: Upon a successful match, it writes the configured CORS headers to the response writer before calling `c.Next()` to proceed to subsequent handlers.

4. **Preflight Termination**: When the request method is `OPTIONS` and the `Access-Control-Request-Method` header is present, the middleware validates the preflight request. If valid, it calls `c.AbortWithStatus(http.StatusNoContent)` (HTTP 204), preventing any downstream route handlers from executing and avoiding unnecessary processing.

## Summary

- Gin's middleware architecture in [`routergroup.go`](https://github.com/gin-gonic/gin/blob/main/routergroup.go) supports CORS at global, group, or route levels via the `Use()` method.
- The official `gin-contrib/cors` package provides a production-ready `Config` struct and `New()` constructor to generate `gin.HandlerFunc` middleware.
- Custom configurations control allowed origins, methods, headers, credentials, and preflight caching duration.
- The middleware automatically handles OPTIONS preflight requests by aborting the chain with status 204, ensuring compliant browser behavior.
- For complex logic, implement manual middleware using `c.Header()` and `c.AbortWithStatus()`.

## Frequently Asked Questions

### Does Gin include built-in CORS middleware?

No. The `gin-gonic/gin` repository intentionally keeps the core framework minimal. CORS functionality resides in the community-maintained `gin-contrib/cors` package, which follows Gin's middleware conventions and integrates seamlessly with the `HandlersChain` system defined in [`routergroup.go`](https://github.com/gin-gonic/gin/blob/main/routergroup.go).

### How do I configure CORS to allow credentials?

Set `AllowCredentials: true` in your `cors.Config` struct. When enabled, the middleware sets the `Access-Control-Allow-Credentials: true` header. You must also specify explicit origins in `AllowOrigins` (wildcard `*` is incompatible with credentials per the CORS specification). Example:

```go
config := cors.Config{
	AllowOrigins:     []string{"https://trusted-site.com"},
	AllowCredentials: true,
}

```

### Can I apply different CORS rules to different API endpoints?

Yes. Use `RouterGroup` instances to scope middleware. As shown in [`routergroup.go`](https://github.com/gin-gonic/gin/blob/main/routergroup.go), each group maintains its own middleware chain. Attach distinct `cors.New(config)` calls to different groups (e.g., `/api` vs `/admin`), or apply middleware directly to individual route definitions for endpoint-specific policies.

### Why do OPTIONS preflight requests return 404 errors?

This occurs when no route handler matches the OPTIONS method, or when CORS middleware is not registered early enough in the chain. Ensure you register `cors.New(config)` globally or within the specific group before defining routes. The middleware intercepts OPTIONS requests and aborts with status 204 before the router attempts to match a non-existent OPTIONS handler.