docs/whats_new.md
We are excited to announce the release of Fiber v3! π
In this guide, we'll walk you through the most important changes in Fiber v3 and show you how to migrate your existing Fiber v2 applications to Fiber v3.
Fiber v3 introduces a CLI-powered migration helper. Install the CLI and let it update your project automatically:
go install github.com/gofiber/cli/fiber@latest
fiber migrate --to v3
See the migration guide for more details and options.
Here's a quick overview of the changes in Fiber v3:
Fiber v3 requires Go 1.25 or later. Update your toolchain to 1.25+ before upgrading so the module go directive and standard library features align with the new minimum version.
We have made several changes to the Fiber app, including:
Listen method has been unified with the configuration, allowing for more streamlined setup.Static method has been removed and its functionality has been moved to the static middleware.DisableStartupMessageEnablePrefork (previously Prefork)EnablePrintRoutesListenerNetwork (previously Network)EnabledTrustedProxyCheck has been moved to app.Config.TrustProxy, and TrustedProxies has been moved to TrustProxyConfig.Proxies. Additionally, ProxyHeader must be set to read client IPs from proxy headers (e.g., X-Forwarded-For).XMLDecoder property has been added to allow usage of 3rd-party XML libraries in XML binder.Immutable is enabled and the data still references request or response buffers. Access via c.App().GetString and c.App().GetBytes.Load() logic at runtime, including guard rails for missing or nil view engines so development hot-reload hooks can refresh templates safely.Custom route constraints enable you to define your own validation rules for route parameters.
Use RegisterCustomConstraint to add a constraint type that implements the CustomConstraint interface.
type UlidConstraint struct {
fiber.CustomConstraint
}
func (*UlidConstraint) Name() string {
return "ulid"
}
func (*UlidConstraint) Execute(param string, args ...string) bool {
_, err := ulid.Parse(param)
return err == nil
}
app.RegisterCustomConstraint(&UlidConstraint{})
app.Get("/login/:id<ulid>", func(c fiber.Ctx) error {
return c.SendString("User " + c.Params("id"))
})
app.Use() instead.app.Listen() with tls.Config.app.Listen() with tls.Config.app.Listen() with tls.Config.app.Listen() with tls.Config.Test method has replaced the timeout parameter with a configuration parameter. 0 or lower represents no timeout.Fiber v3 introduces a customizable Ctx interface, allowing developers to extend and modify the context to fit their needs. This feature provides greater flexibility and control over request handling.
The idea behind custom Ctx classes is to give developers the ability to extend the default context with additional methods and properties tailored to the specific requirements of their application. This allows for better request handling and easier implementation of specific logic.
NewWithCustomCtx creates the application and sets the custom context factory at initialization time.
func NewWithCustomCtx(fn func(app *App) CustomCtx, config ...Config) *App
package main
import (
"log"
"github.com/gofiber/fiber/v3"
)
type CustomCtx struct {
fiber.DefaultCtx
}
func (c *CustomCtx) CustomMethod() string {
return "custom value"
}
func main() {
app := fiber.NewWithCustomCtx(func(app *fiber.App) fiber.CustomCtx {
return &CustomCtx{
DefaultCtx: *fiber.NewDefaultCtx(app),
}
})
app.Get("/", func(c fiber.Ctx) error {
customCtx := c.(*CustomCtx)
return c.SendString(customCtx.CustomMethod())
})
log.Fatal(app.Listen(":3000"))
}
This example creates a CustomCtx with an extra CustomMethod and initializes the app with NewWithCustomCtx.
We have added support for configuring the TLS minimum version. This field allows you to set the TLS minimum version for TLSAutoCert and the server listener.
app.Listen(":444", fiber.ListenConfig{TLSMinVersion: tls.VersionTLS12})
We have added native support for automatic certificates management from Let's Encrypt and any other ACME-based providers.
// Certificate manager
certManager := &autocert.Manager{
Prompt: autocert.AcceptTOS,
// Replace with your domain name
HostPolicy: autocert.HostWhitelist("example.com"),
// Folder to store the certificates
Cache: autocert.DirCache("./certs"),
}
app.Listen(":444", fiber.ListenConfig{
AutoCertManager: certManager,
})
MIMEApplicationJavaScript and MIMEApplicationJavaScriptCharsetUTF8 are deprecated. Use MIMETextJavaScript and MIMETextJavaScriptCharsetUTF8 instead.
We have made several changes to the Fiber hooks, including:
OnPreShutdown - Executes before the server starts shutting downOnPostShutdown - Executes after the server has shut down, receives any shutdown errorOnPreStartupMessage - Executes before the startup message is printed, allowing customization of the banner and info entriesOnPostStartupMessage - Executes after the startup message is printed, allowing post-startup logicOnShutdown in favor of the new pre/post shutdown hooksImportant: When using shutdown hooks, ensure app.Listen() is called in a separate goroutine:
// Correct usage
go app.Listen(":3000")
// ... register shutdown hooks
app.Shutdown()
// Incorrect usage - hooks won't work
app.Listen(":3000") // This blocks
app.Shutdown() // Never reached
We have made several changes to the Fiber listen, including:
OnShutdownError and OnShutdownSuccess from ListenConfig in favor of using the OnPostShutdown hook, which receives the shutdown errorapp := fiber.New()
// Before - using ListenConfig callbacks
app.Listen(":3000", fiber.ListenConfig{
OnShutdownError: func(err error) {
log.Printf("Shutdown error: %v", err)
},
OnShutdownSuccess: func() {
log.Println("Shutdown successful")
},
})
// After - using OnPostShutdown hook
app.Hooks().OnPostShutdown(func(err error) error {
if err != nil {
log.Printf("Shutdown error: %v", err)
} else {
log.Println("Shutdown successful")
}
return nil
})
go app.Listen(":3000")
This change simplifies the shutdown handling by consolidating the shutdown callbacks into a single hook that receives the error status.
ListenerNetwork and UnixSocketFileMode// v2 - Requires manual deletion of old file and permissions change
app := fiber.New(fiber.Config{
Network: "unix",
})
os.Remove("app.sock")
app.Hooks().OnListen(func(fiber.ListenData) error {
return os.Chmod("app.sock", 0770)
})
app.Listen("app.sock")
// v3 - Fiber does it for you
app := fiber.New()
app.Listen("app.sock", fiber.ListenConfig{
ListenerNetwork: fiber.NetworkUnix,
UnixSocketFileMode: 0770,
})
TLSConfig to ListenConfig so external providers can supply certificates via GetCertificate. Prefer TLSConfig when configuring TLS; when set, it is cloned and takes precedence over other TLS fields.app := fiber.New()
app.Listen(":443", fiber.ListenConfig{
TLSConfig: &tls.Config{
GetCertificate: func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
return myProvider.Certificate(info.ServerName)
},
},
})
ListenData with versioning, handler, process, and PID metadata, plus dedicated startup message hooks for customization. Check out the Hooks documentation for further details.package main
import (
"fmt"
"os"
"github.com/gofiber/fiber/v3"
)
func main() {
app := fiber.New()
app.Hooks().OnPreStartupMessage(func(sm *fiber.PreStartupMessageData) error {
sm.BannerHeader = "FOOBER " + sm.Version + "\n-------"
// Optional: you can also remove old entries
// sm.ResetEntries()
sm.AddInfo("git-hash", "Git hash", os.Getenv("GIT_HASH"))
sm.AddInfo("prefork", "Prefork", fmt.Sprintf("%v", sm.Prefork), 15)
return nil
})
app.Hooks().OnPostStartupMessage(func(sm *fiber.PostStartupMessageData) error {
if !sm.Disabled && !sm.IsChild && !sm.Prevented {
fmt.Println("startup completed")
}
return nil
})
app.Listen(":5000")
}
We have slightly adapted our router interface
Fiber now ships with a routing adapter (see adapter.go) that understands native Fiber handlers alongside net/http and fasthttp handlers. Route registration helpers accept a required handler argument plus optional additional handlers, all typed as any, and the adapter transparently converts supported handler styles so you can keep using the ecosystem functions you're familiar with.
To align even closer with Express, you can also register handlers that accept the new fiber.Req and fiber.Res helper interfaces. The adapter understands both two-argument (func(fiber.Req, fiber.Res)) and three-argument (func(fiber.Req, fiber.Res, func() error)) callbacks, regardless of whether they return an error. It also accepts Express-style next callbacks that take an error (func(error) or func(error) error). When you include the optional next callback, Fiber wires it to c.Next() for you so middleware continues to behave as expected. Calling next(nil) continues the chain, while passing a non-nil error short-circuits and returns that error. If your handler returns an error, the value returned from the injected next() bubbles straight back to the caller. When your handler omits an error return, Fiber records the result of next() and returns it after your function exits so downstream failures still propagate.
| Case | Handler signature | Notes |
|---|---|---|
| 1 | fiber.Handler | Native Fiber handler. |
| 2 | func(fiber.Ctx) | Fiber handler without an error return. |
| 3 | func(fiber.Req, fiber.Res) error | Express-style request handler with error return. |
| 4 | func(fiber.Req, fiber.Res) | Express-style request handler without error return. |
| 5 | func(fiber.Req, fiber.Res, func() error) error | Express-style middleware with an error-returning next callback and handler error return. |
| 6 | func(fiber.Req, fiber.Res, func() error) | Express-style middleware with an error-returning next callback. |
| 7 | func(fiber.Req, fiber.Res, func()) error | Express-style middleware with a no-argument next callback and handler error return. |
| 8 | func(fiber.Req, fiber.Res, func()) | Express-style middleware with a no-argument next callback. |
| 9 | func(fiber.Req, fiber.Res, func(error)) | Express-style middleware with an error-accepting next callback. |
| 10 | func(fiber.Req, fiber.Res, func(error)) error | Express-style middleware with an error-accepting next callback and handler error return. |
| 11 | func(fiber.Req, fiber.Res, func(error) error) | Express-style middleware with an error-accepting next callback that returns an error. |
| 12 | func(fiber.Req, fiber.Res, func(error) error) error | Express-style middleware with an error-accepting next callback that returns an error and handler error return. |
| 13 | http.HandlerFunc | Standard-library handler function adapted through fasthttpadaptor. |
| 14 | http.Handler | Standard-library handler implementation; pointer receivers must be non-nil. |
| 15 | func(http.ResponseWriter, *http.Request) | Standard-library function handlers via fasthttpadaptor. |
| 16 | fasthttp.RequestHandler | Direct fasthttp handler without error return. |
| 17 | func(*fasthttp.RequestCtx) error | fasthttp handler that returns an error to Fiber. |
RouteChain is a new helper inspired by Express that makes it easy to declare a stack of handlers on the same path, while the existing Route helper stays available for prefix encapsulation.
RouteChain(path string) Register
app.RouteChain("/api").RouteChain("/user/:id?")
.Get(func(c fiber.Ctx) error {
// Get user
return c.JSON(fiber.Map{"message": "Get user", "id": c.Params("id")})
})
.Post(func(c fiber.Ctx) error {
// Create user
return c.JSON(fiber.Map{"message": "User created"})
})
.Put(func(c fiber.Ctx) error {
// Update user
return c.JSON(fiber.Map{"message": "User updated", "id": c.Params("id")})
})
.Delete(func(c fiber.Ctx) error {
// Delete user
return c.JSON(fiber.Map{"message": "User deleted", "id": c.Params("id")})
})
You can find more information about app.RouteChain and app.Route in the API documentation (RouteChain, Route).
Named routes retrieved with app.GetRoute(name) also support route.URL(params) for generating relative URLs directly from the route definition, including parameter substitution for named, wildcard (*), and plus (+) segments.
Domain creates a router scoped to a specific hostname pattern. Routes registered through the returned Router only match requests whose hostname (from c.Hostname()) matches the pattern. When TrustProxy is enabled and the proxy is trusted (as defined by TrustProxyConfig), the hostname may be derived from the X-Forwarded-Host header. Be sure to configure TrustProxyConfig to restrict which proxies are trusted and prevent header spoofing when enabling TrustProxy.
The pattern can contain parameters prefixed with :, accessible via fiber.DomainParam.
Domain routing has zero performance impact on routes that don't use it because the hostname check is applied as a handler wrapper, not a change to the core router.
Note: Because domain filtering happens at handler-execution time, Fiber's
405 Method Not Allowedresponses may advertise methods from domain-scoped routes even when the requesting host does not match. This is a known trade-off of the handler-wrapping approach.When mounting sub-applications via
Domain(...).Use(*fiber.App), routes are cloned at mount time. The same sub-app can safely be mounted on multiple domains, but routes added to the sub-app after mounting will not inherit domain filtering. Register all sub-app routes before mounting.
Domain(host string) Router
app := fiber.New()
// Static domain
app.Domain("api.example.com").Get("/users", func(c fiber.Ctx) error {
return c.SendString("API users")
})
// Domain with parameter
app.Domain(":user.blog.example.com").Get("/", func(c fiber.Ctx) error {
user := fiber.DomainParam(c, "user")
return c.SendString(user + "'s blog")
})
// Domain with groups
api := app.Domain("api.example.com")
v1 := api.Group("/v1")
v1.Get("/posts", listPosts)
// Domain with middleware
admin := app.Domain("admin.example.com")
admin.Use(authMiddleware)
admin.Get("/dashboard", dashboardHandler)
// Mount sub-applications on domain routers
subApp := fiber.New()
subApp.Get("/users", listUsers)
app.Domain("api.example.com").Use("/api", subApp)
// Fallback for unmatched domains
app.Get("/", func(c fiber.Ctx) error {
return c.SendString("default site")
})
Fiber now auto-registers a HEAD route whenever you add a GET route. The generated handler chain matches the GET chain so status codes and headers stay in sync while the response body remains empty, ensuring HEAD clients observe the same metadata as a GET consumer.
app := fiber.New()
app.Get("/health", func(c fiber.Ctx) error {
c.Set("X-Service", "api")
return c.SendString("OK")
})
// HEAD /health reuses the GET middleware chain and returns headers only.
You can still register explicit HEAD handlers for any GET route, and they continue to win when you add them:
app.Head("/health", func(c fiber.Ctx) error {
return c.SendStatus(fiber.StatusNoContent)
})
Prefer to manage HEAD routes yourself? Disable the feature through fiber.Config.DisableHeadAutoRegister:
handler := func(c fiber.Ctx) error {
c.Set("X-Service", "api")
return c.SendString("OK")
}
app := fiber.New(fiber.Config{DisableHeadAutoRegister: true})
app.Get("/health", handler) // HEAD /health now returns 405 unless you add it manually.
Auto-generated HEAD routes appear in tooling such as app.Stack() and cover the same routing scenarios as their GET counterparts, including groups, mounted apps, dynamic parameters, and static file handlers.
We have aligned our method for middlewares closer to Express and now also support the Use of multiple prefixes.
Prefix matching is now stricter: partial matches must end at a slash boundary (or be an exact match). This keeps /api middleware from running on /apiv1 while still allowing /api/:version style patterns that leverage route parameters, optional segments, or wildcards.
Registering a subapp is now also possible via the Use method instead of the old app.Mount method.
// register multiple prefixes
app.Use([]string{"/v1", "/v2"}, func(c fiber.Ctx) error {
// Middleware for /v1 and /v2
return c.Next()
})
// define subapp
api := fiber.New()
api.Get("/user", func(c fiber.Ctx) error {
return c.SendString("User")
})
// register subapp
app.Use("/api", api)
To enable the routing changes above we had to slightly adjust the signature of the Add method.
- Add(method, path string, handlers ...Handler) Router
+ Add(methods []string, path string, handler any, handlers ...any) Router
The app.Test() method now allows users to customize their test configurations:
// Create a test app with a handler to test
app := fiber.New()
app.Get("/", func(c fiber.Ctx) {
return c.SendString("hello world")
})
// Define the HTTP request and custom TestConfig to test the handler
req := httptest.NewRequest(MethodGet, "/", nil)
testConfig := fiber.TestConfig{
Timeout: 0,
FailOnTimeout: false,
}
// Test the handler using the request and testConfig
resp, err := app.Test(req, testConfig)
To provide configurable testing capabilities, we had to change
the signature of the Test method.
- Test(req *http.Request, timeout ...time.Duration) (*http.Response, error)
+ Test(req *http.Request, config ...fiber.TestConfig) (*http.Response, error)
The TestConfig struct provides the following configuration options:
Timeout: The duration to wait before timing out the test. Use 0 for no timeout.FailOnTimeout: Controls the behavior when a timeout occurs:
os.ErrDeadlineExceeded if the test exceeds the Timeout duration.If a custom TestConfig isn't provided, then the following will be used:
testConfig := fiber.TestConfig{
Timeout: time.Second,
FailOnTimeout: true,
}
Note: Using this default is NOT the same as providing an empty TestConfig as an argument to app.Test().
An empty TestConfig is the equivalent of:
testConfig := fiber.TestConfig{
Timeout: 0,
FailOnTimeout: false,
}
SameSite=None, Fiber automatically sets Secure=true as required by RFC 6265bis and modern browsers (Chrome, Firefox, Safari). This ensures compliance with the "None" SameSite policy. See Mozilla docs and Chrome docs for details.Ctx now implements the context.Context interface, replacing the former UserContext helpers.Accept header.context.Context.context.Context.context.Context.HTTP 103 Early Hints status code with Link headers so browsers can preload resources while the final response is being prepared.context.Context. Returns request-scoped value from Locals.context.Context that can be used outside the handler.context.Context returned by Context() for propagating deadlines or values.Bind method.User-Agent request header.Referer request header.Accept-Language request header.Accept-Encoding request header.Content-Type header without parameters.charset parameter from the Content-Type header.Content-Type header is JSON.Content-Type header is form-encoded.Content-Type header is multipart form data.Accept header allows JSON.Accept header allows HTML.Accept header allows XML.Accept header allows text/event-stream.c.Bind().URI() instead.Params with generic types.Query with generic types.Query with generic types.Query with generic types.c.Bind().Body() instead.c.Bind().Cookie() instead.c.Bind().URI() instead.c.Redirect().Route() instead.c.Redirect().Back() instead.c.Bind().Header() instead.Ctx itself now satisfies context.Context; pass c directly where a context.Context is required.SetContext and Context() or context.WithValue on c to store additional request-scoped values.c.ViewBind() for view binding.body interface{} to handlers ...ResFmt.c.Redirect().To() instead.filename* as
specified by RFC 6266 and
RFC 8187.RequestCtx() to access the underlying fasthttp.RequestCtx.SendEarlyHints sends an informational 103 Early Hints response with Link headers based on the provided hints argument. This allows a browser to start preloading assets while the server is still preparing the final response.
hints := []string{"<https://cdn.com/app.js>; rel=preload; as=script"}
app.Get("/early", func(c fiber.Ctx) error {
if err := c.SendEarlyHints(hints); err != nil {
return err
}
return c.SendString("done")
})
Older HTTP/1.1 clients may ignore these interim responses or handle them inconsistently.
In v3, we introduced support for buffered streaming with the addition of the SendStreamWriter method:
func (c Ctx) SendStreamWriter(streamWriter func(w *bufio.Writer)) error
With this new method, you can implement:
app.Get("/sse", func(c fiber.Ctx) error {
c.Set("Content-Type", "text/event-stream")
c.Set("Cache-Control", "no-cache")
c.Set("Connection", "keep-alive")
c.Set("Transfer-Encoding", "chunked")
return c.SendStreamWriter(func(w *bufio.Writer) {
for {
fmt.Fprintf(w, "event: my-event\n")
fmt.Fprintf(w, "data: Hello SSE\n\n")
if err := w.Flush(); err != nil {
log.Print("Client disconnected!")
return
}
}
})
})
You can find more details about this feature in /docs/api/ctx.md.
In v3, we introduced support to silently terminate requests through Drop.
func (c Ctx) Drop() error
With this method, you can:
:::caution While this feature adds the ability to drop connections, it is still highly recommended to use additional measures (such as firewalls, proxies, etc.) to further protect your server endpoints by blocking malicious connections before the server establishes a connection. :::
app.Get("/", func(c fiber.Ctx) error {
if c.IP() == "192.168.1.1" {
return c.Drop()
}
return c.SendString("Hello World!")
})
You can find more details about this feature in /docs/api/ctx.md.
In v3, we introduced a new method to match the Express.js API's res.end() method.
func (c Ctx) End() error
With this method, you can:
return c.End() as an alternative to return nilapp.Use(func (c fiber.Ctx) error {
err := c.Next()
if err != nil {
log.Println("Got error: %v", err)
return c.SendString(err.Error()) // Will be unsuccessful since the response ended below
}
return nil
})
app.Get("/hello", func (c fiber.Ctx) error {
query := c.Query("name", "")
if query == "" {
_ = c.SendString("You don't have a name?")
_ = c.End() // Closes the underlying connection; errors intentionally ignored
return errors.New("No name provided")
}
return c.SendString("Hello, " + query + "!")
})
Fiber v3 introduces a new binding mechanism that simplifies the process of binding request data to structs. The new binding system supports binding from various sources such as URL parameters, query parameters, headers, and request bodies. This unified approach makes it easier to handle different types of request data in a consistent manner.
*multipart.FileHeader, *[]*multipart.FileHeader, and []*multipart.FileHeader field types.Bind().All()) with defined precedence order: (URI -> Body -> Query -> Headers -> Cookies). Learn more.type User struct {
ID int `uri:"id"`
Name string `json:"name"`
Email string `json:"email"`
}
app.Post("/user/:id", func(c fiber.Ctx) error {
var user User
if err := c.Bind().Body(&user); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": err.Error()})
}
return c.JSON(user)
})
In this example, the Bind method is used to bind the request body to the User struct. The Body method of the Bind class performs the actual binding.
Fiber v3 introduces a new shared extractors package that consolidates value extraction utilities previously duplicated across middleware packages. This package provides a unified API for extracting values from headers, cookies, query parameters, form data, and URL parameters with built-in chain/fallback logic and security considerations.
FromAuthHeader(authScheme string): Extract from Authorization header with scheme supportFromCookie(key string): Extract from HTTP cookiesFromParam(param string): Extract from URL path parametersFromForm(param string): Extract from form dataFromHeader(header string): Extract from custom HTTP headersFromQuery(param string): Extract from URL query parametersFromCustom(key string, extractor func(c fiber.Ctx) (string, error)): Define custom extraction logic with metadataChain(extractors ...Extractor): Chain multiple extractors with fallback logicimport "github.com/gofiber/fiber/v3/extractors"
// Extract API key from multiple sources with fallback
apiKeyExtractor := extractors.Chain(
extractors.FromHeader("X-API-Key"),
extractors.FromQuery("api_key"),
extractors.FromCookie("api_key"),
)
app.Use(func(c fiber.Ctx) error {
apiKey, err := apiKeyExtractor.Extract(c)
if err != nil {
return c.Status(401).SendString("API key required")
}
// Use apiKey for authentication
return c.Next()
})
Middleware packages in Fiber v3 now use the shared extractors package instead of maintaining their own extraction logic. This provides:
Fiber v3 enhances the redirect functionality by introducing new methods and improving existing ones. The new redirect methods provide more flexibility and control over the redirection process.
Redirect().To(): Redirects to a specific URL.Redirect().Route(): Redirects to a named route.Redirect().Back(): Redirects to the previous URL.app.Get("/old", func(c fiber.Ctx) error {
return c.Redirect().To("/new")
})
app.Get("/new", func(c fiber.Ctx) error {
return c.SendString("Welcome to the new route!")
})
:::info
The default redirect status code has been updated from 302 Found to 303 See Other to ensure more consistent behavior across different browsers.
:::
The Gofiber client has been completely rebuilt. It includes numerous new features such as Cookiejar, request/response hooks, and more. You can take a look to client docs to see what's new with the client.
The v3 client centralizes common configuration on the client instance and lets you override it per request with client.Config.
You can define base URLs, defaults (headers, cookies, path parameters, timeouts), and toggle path normalization once, while still
using axios-style helpers for each call.
cc := client.New().
SetBaseURL("https://api.service.local").
AddHeader("Authorization", "Bearer <token>").
SetTimeout(5 * time.Second).
SetPathParam("tenant", "acme")
resp, err := cc.Get("/users/:tenant/:id", client.Config{
PathParam: map[string]string{"id": "42"},
Param: map[string]string{"include": "profile"},
DisablePathNormalizing: true,
})
if err != nil {
panic(err)
}
defer resp.Close()
fmt.Println(resp.StatusCode(), resp.String())
client.NewWithHostClient and client.NewWithLBClient allow you to plug existing fasthttp clients directly into Fiber while keeping retries, redirects, and hook logic consistent.Do, DoTimeout, DoDeadline, and CloseIdleConnections, matching the surface area of the wrapped fasthttp transports.Fiber v3 introduces new generic functions that provide additional utility and flexibility for developers. These functions are designed to simplify common tasks and improve code readability.
c.Locals() and the request context.Context, so the same value can be read through middleware FromContext helpers and direct locals access.fiber.Config.PassLocalsToContext is now available to control whether StoreInContext also synchronizes values with request context.Context for Fiber-backed contexts. The default is false for backward compatibility. ValueFromContext continues reading Fiber-backed values from c.Locals().
package main
import (
"strconv"
"github.com/gofiber/fiber/v3"
)
func main() {
app := fiber.New()
app.Get("/convert", func(c fiber.Ctx) error {
value, err := fiber.Convert[int](c.Query("value"), strconv.Atoi, 0)
if err != nil {
return c.Status(fiber.StatusBadRequest).SendString(err.Error())
}
return c.JSON(value)
})
app.Listen(":3000")
}
curl "http://localhost:3000/convert?value=123"
# Output: 123
curl "http://localhost:3000/convert?value=abc"
# Output: "failed to convert: strconv.Atoi: parsing \"abc\": invalid syntax"
package main
import (
"github.com/gofiber/fiber/v3"
)
func main() {
app := fiber.New()
app.Use("/user/:id", func(c fiber.Ctx) error {
// ask database for user
// ...
// set local values from database
fiber.Locals[string](c, "user", "john")
fiber.Locals[int](c, "age", 25)
// ...
return c.Next()
})
app.Get("/user/*", func(c fiber.Ctx) error {
// get local values
name := fiber.Locals[string](c, "user")
age := fiber.Locals[int](c, "age")
// ...
return c.JSON(fiber.Map{"name": name, "age": age})
})
app.Listen(":3000")
}
curl "http://localhost:3000/user/5"
# Output: {"name":"john","age":25}
package main
import (
"github.com/gofiber/fiber/v3"
)
func main() {
app := fiber.New()
app.Get("/params/:id", func(c fiber.Ctx) error {
id := fiber.Params[int](c, "id", 0)
return c.JSON(id)
})
app.Listen(":3000")
}
curl "http://localhost:3000/params/123"
# Output: 123
curl "http://localhost:3000/params/abc"
# Output: 0
package main
import (
"github.com/gofiber/fiber/v3"
)
func main() {
app := fiber.New()
app.Get("/query", func(c fiber.Ctx) error {
age := fiber.Query[int](c, "age", 0)
return c.JSON(age)
})
app.Listen(":3000")
}
curl "http://localhost:3000/query?age=25"
# Output: 25
curl "http://localhost:3000/query?age=abc"
# Output: 0
package main
import (
"github.com/gofiber/fiber/v3"
)
func main() {
app := fiber.New()
app.Get("/header", func(c fiber.Ctx) error {
userAgent := fiber.GetReqHeader[string](c, "User-Agent", "Unknown")
return c.JSON(userAgent)
})
app.Listen(":3000")
}
curl -H "User-Agent: CustomAgent" "http://localhost:3000/header"
# Output: "CustomAgent"
curl "http://localhost:3000/header"
# Output: "Unknown"
Fiber v3 removes the built-in utils directory and now imports utility helpers from the separate github.com/gofiber/utils/v2 module. See the migration guide for detailed replacement steps and examples.
The github.com/gofiber/utils module also introduces new helpers like ParseInt, ParseUint, Walk, ReadFile, and Timestamp.
Fiber v3 introduces a new feature called Services. This feature allows developers to quickly start services that the application depends on, removing the need to manually provision things like database servers, caches, or message brokers, to name a few.
package main
import (
"context"
"github.com/gofiber/fiber/v3"
)
type myService struct {
img string
// ...
}
// Start initializes and starts the service. It implements the [fiber.Service] interface.
func (s *myService) Start(ctx context.Context) error {
// start the service
return nil
}
// String returns a string representation of the service.
// It is used to print a human-readable name of the service in the startup message.
// It implements the [fiber.Service] interface.
func (s *myService) String() string {
return s.img
}
// State returns the current state of the service.
// It implements the [fiber.Service] interface.
func (s *myService) State(ctx context.Context) (string, error) {
return "running", nil
}
// Terminate stops and removes the service. It implements the [fiber.Service] interface.
func (s *myService) Terminate(ctx context.Context) error {
// stop the service
return nil
}
func main() {
cfg := &fiber.Config{}
cfg.Services = append(cfg.Services, &myService{img: "postgres:latest"})
cfg.Services = append(cfg.Services, &myService{img: "redis:latest"})
app := fiber.New(*cfg)
// ...
}
$ go run . -v
_______ __
/ ____(_) /_ ___ _____
/ /_ / / __ \/ _ \/ ___/
/ __/ / / /_/ / __/ /
/_/ /_/_.___/\___/_/ v3.0.0
--------------------------------------------------
INFO Server started on: http://127.0.0.1:3000 (bound on host 0.0.0.0 and port 3000)
INFO Services: 2
INFO π§© [ RUNNING ] postgres:latest
INFO π§© [ RUNNING ] redis:latest
INFO Total handlers count: 2
INFO Prefork: Disabled
INFO PID: 12279
INFO Total process count: 1
fiber.AllLogger[T] interface now has a new generic type parameter T and a method called Logger. This method can be used to get the underlying logger instance from the Fiber logger middleware. This is useful when you want to configure the logger middleware with a custom logger and still want to access the underlying logger instance with the appropriate type.
You can find more details about this feature in /docs/api/log.md.
logger.Config now supports a new field called ForceColors. This field allows you to force the logger to always use colors, even if the output is not a terminal. This is useful when you want to ensure that the logs are always colored, regardless of the output destination.
package main
import "github.com/gofiber/fiber/v3/middleware/logger"
app.Use(logger.New(logger.Config{
ForceColors: true,
}))
The storage interface has been updated to include new subset of methods with WithContext suffix. These methods allow you to pass a context to the storage operations, enabling better control over timeouts and cancellation if needed. This is particularly useful when storage implementations used outside of the Fiber core, such as in background jobs or long-running tasks.
New Methods Signatures:
// GetWithContext gets the value for the given key with a context.
// `nil, nil` is returned when the key does not exist
GetWithContext(ctx context.Context, key string) ([]byte, error)
// SetWithContext stores the given value for the given key
// with an expiration value, 0 means no expiration.
// Empty key or value will be ignored without an error.
SetWithContext(ctx context.Context, key string, val []byte, exp time.Duration) error
// DeleteWithContext deletes the value for the given key with a context.
// It returns no error if the storage does not contain the key,
DeleteWithContext(ctx context.Context, key string) error
// ResetWithContext resets the storage and deletes all keys with a context.
ResetWithContext(ctx context.Context) error
In Fiber v3, many middlewares that previously set values in c.Locals() using string keys (e.g., c.Locals("requestid")) have been updated. To align with Go's context best practices and prevent key collisions, these middlewares now store their specific data in the request's context using unexported keys of custom types.
This means that directly accessing these values via c.Locals("some_string_key") will no longer work for such middleware-provided data.
How to Access Middleware Data in v3:
Each affected middleware now provides dedicated exported functions to retrieve its specific data from the context. You should use these functions instead of relying on string-based lookups in c.Locals().
Examples include:
requestid.FromContext(c)csrf.TokenFromContext(c)csrf.HandlerFromContext(c)session.FromContext(c)basicauth.UsernameFromContext(c)keyauth.TokenFromContext(c)When used with the Logger middleware, the recommended approach is to use the CustomTags feature of the logger, which allows you to call these specific FromContext functions. See the Logger section for more details.
The adaptor middleware has been significantly optimized for performance and efficiency. Key improvements include reduced response times, lower memory usage, and fewer memory allocations. These changes make the middleware more reliable and capable of handling higher loads effectively. Enhancements include the introduction of a sync.Pool for managing fasthttp.RequestCtx instances and better HTTP request and response handling between net/http and fasthttp contexts.
Incoming body sizes now respect the Fiber app's configured BodyLimit (falling back to the default when unset) when running Fiber from net/http through the adaptor, returning 413 Request Entity Too Large for oversized payloads.
| Payload Size | Metric | V2 | V3 | Percent Change |
|---|---|---|---|---|
| 100KB | Execution Time | 1056 ns/op | 588.6 ns/op | -44.25% |
| Memory Usage | 2644 B/op | 254 B/op | -90.39% | |
| Allocations | 16 allocs/op | 5 allocs/op | -68.75% | |
| 500KB | Execution Time | 1061 ns/op | 562.9 ns/op | -46.94% |
| Memory Usage | 2644 B/op | 248 B/op | -90.62% | |
| Allocations | 16 allocs/op | 5 allocs/op | -68.75% | |
| 1MB | Execution Time | 1080 ns/op | 629.7 ns/op | -41.68% |
| Memory Usage | 2646 B/op | 267 B/op | -89.91% | |
| Allocations | 16 allocs/op | 5 allocs/op | -68.75% | |
| 5MB | Execution Time | 1093 ns/op | 540.3 ns/op | -50.58% |
| Memory Usage | 2654 B/op | 254 B/op | -90.43% | |
| Allocations | 16 allocs/op | 5 allocs/op | -68.75% | |
| 10MB | Execution Time | 1044 ns/op | 533.1 ns/op | -48.94% |
| Memory Usage | 2665 B/op | 258 B/op | -90.32% | |
| Allocations | 16 allocs/op | 5 allocs/op | -68.75% | |
| 25MB | Execution Time | 1069 ns/op | 540.7 ns/op | -49.42% |
| Memory Usage | 2706 B/op | 289 B/op | -89.32% | |
| Allocations | 16 allocs/op | 5 allocs/op | -68.75% | |
| 50MB | Execution Time | 1137 ns/op | 554.6 ns/op | -51.21% |
| Memory Usage | 2734 B/op | 298 B/op | -89.10% | |
| Allocations | 16 allocs/op | 5 allocs/op | -68.75% |
The BasicAuth middleware now validates the Authorization header more rigorously and sets security-focused response headers. Passwords must be provided in hashed form (e.g. SHA-256 or bcrypt) rather than plaintext. The default challenge includes the charset="UTF-8" parameter and disables caching. Responses also set a Vary: Authorization header to prevent caching based on credentials. Passwords are no longer stored in the request context. A Charset option controls the value used in the challenge header.
A new HeaderLimit option restricts the maximum length of the Authorization header (default: 8192 bytes).
The Authorizer function now receives the current fiber.Ctx as a third argument, allowing credential checks to incorporate request context.
We are excited to introduce a new option in our caching middleware: Cache Invalidator. This feature provides greater control over cache management, allowing you to define custom conditions for invalidating cache entries.
The middleware now emits Cache-Control headers by default via the new DisableCacheControl flag, increases the default Expiration from 1 minute to 5 minutes, and applies a new MaxBytes limit of 1 MB (previously unlimited).
Additionally, the caching middleware has been optimized to avoid caching non-cacheable status codes, as defined by the HTTP standards. This improvement enhances cache accuracy and reduces unnecessary cache storage usage. Cached responses now include an RFC-compliant Age header, providing a standardized indication of how long a response has been stored in cache since it was originally generated. This enhancement improves HTTP compliance and facilitates better client-side caching strategies.
Cache keys are now redacted in logs and error messages by default, and a DisableValueRedaction boolean (default false) lets you opt out when you need the raw value for troubleshooting.
The default cache key strategy was also hardened. Instead of path-only behavior, keys now use structured request dimensions: method partitioning, path, canonical query string, and selected representation headers (Accept, Accept-Encoding, Accept-Language). This avoids collisions such as /items?id=1 vs /items?id=2 while keeping key generation deterministic. New config fields were added for explicit control: DisableQueryKeys, KeyHeaders, KeyCookies, and DisableVaryHeaders.
As a security/performance default, request body/form values are not part of the default cache key. Cache handling is limited to GET and HEAD requests by default, configurable via the Methods field.
:::note
The deprecated Store and Key options have been removed in v3. Use Storage and KeyGenerator instead.
:::
A new response time middleware measures how long each request takes to process and adds the duration to the response headers.
By default it writes the elapsed time to X-Response-Time, and you can change the header name. A Next hook lets you skip
endpoints such as health checks.
We've made some changes to the CORS middleware to improve its functionality and flexibility. Here's what's new:
Config.AllowPrivateNetwork: This new field is a boolean that allows you to control whether private networks are allowed. This is related to the Private Network Access (PNA) specification from the Web Incubator Community Group (WICG). When set to true, the CORS middleware will allow CORS preflight requests from private networks and respond with the Access-Control-Allow-Private-Network: true header. This could be useful in development environments or specific use cases, but should be done with caution due to potential security risks.We've updated several fields from a single string (containing comma-separated values) to slices, allowing for more explicit declaration of multiple values. Here are the updated fields:
Config.AllowOrigins: Now accepts a slice of strings, each representing an allowed origin.Config.AllowMethods: Now accepts a slice of strings, each representing an allowed method.Config.AllowHeaders: Now accepts a slice of strings, each representing an allowed header.Config.ExposeHeaders: Now accepts a slice of strings, each representing an exposed header.Additionally, panic messages and logs redact misconfigured origins by default, and a DisableValueRedaction flag (default false) lets you reveal them when necessary.
zstd compression alongside gzip, deflate, and brotli.ETag values are now recomputed for compressed payloads so validators remain accurate.Content-Encoding, for range requests or 206 statuses, and when either side sends Cache-Control: no-transform.HEAD requests still negotiate compression so Content-Encoding, Content-Length, ETag, and Vary match a corresponding GET, but the body is omitted.Vary: Accept-Encoding is merged into responses even when compression is skipped, preventing caches from mixing encoded and unencoded variants.BodyLimit through fasthttp WithLimit helpers, including when the compression middleware is active.BodyLimit by using fasthttp MultipartFormWithLimit.The Expiration field in the CSRF middleware configuration has been renamed to IdleTimeout to better describe its functionality. Additionally, the default value has been reduced from 1 hour to 30 minutes.
CSRF now redacts tokens and storage keys by default and exposes a DisableValueRedaction toggle (default false) if you must surface those values in diagnostics.
The CSRF middleware now validates the Sec-Fetch-Site header for unsafe HTTP methods. When present, requests with invalid Sec-Fetch-Site values (not one of "same-origin", "none", "same-site", or "cross-site") are rejected with ErrFetchSiteInvalid. Valid or absent headers proceed to standard origin and token validation checks, providing an early gate to catch malformed requests while maintaining compatibility with legitimate cross-site traffic.
Idempotency middleware now redacts keys by default and offers a DisableValueRedaction configuration flag (default false) to expose them when debugging.
Added support for specifying key length when using encryptcookie.GenerateKey(length). Keys must be base64-encoded and may be 16, 24, or 32 bytes when decoded, supporting AES-128, AES-192, and AES-256 (default).
Custom encryptor and decryptor callbacks now receive the cookie name. The default AES-GCM helpers bind it as additional authenticated data (AAD) so ciphertext cannot be replayed under a different cookie.
Breaking change: Custom encryptor/decryptor hooks now accept the cookie name as their first argument. Update overrides like:
// Before
Encryptor func(value, key string) (string, error)
Decryptor func(value, key string) (string, error)
// After
Encryptor func(name, value, key string) (string, error)
Decryptor func(name, value, key string) (string, error)
The favicon middleware now caps cached favicon assets with a configurable MaxBytes limit (default 1 MiB) and uses a limited reader to guard against oversized files when loading from disk.
The ExcludeVars field has been removed from the EnvVar middleware configuration. When upgrading, remove any references to this field and explicitly list the variables you wish to expose using ExportVars.
The filesystem middleware was removed to reduce confusion with the static middleware. The static middleware now covers the functionality of both. Review the static middleware docs or the migration guide for the updated usage.
The healthcheck middleware has been simplified into a single generic probe handler. No endpoints are registered automatically. Register the middleware on each route you needβusing helpers like healthcheck.LivenessEndpoint, healthcheck.ReadinessEndpoint, or healthcheck.StartupEndpointβand optionally supply a Probe function to determine the service's health. This approach lets you expose any number of health check routes.
Refer to the healthcheck middleware migration guide or the general migration guide to review the changes.
The keyauth middleware was updated to introduce a configurable Realm field for the WWW-Authenticate header.
The old string-based KeyLookup configuration has been replaced with an Extractor field. Use helper functions like keyauth.FromHeader, keyauth.FromAuthHeader, or keyauth.FromCookie to define where the key should be retrieved from. Multiple sources can be combined with keyauth.Chain. See the migration guide below.
New Challenge, Error, ErrorDescription, ErrorURI, and Scope fields allow customizing the WWW-Authenticate header, returning Bearer error details, and specifying required scopes. ErrorURI values are validated as absolute, a default ApiKey challenge is emitted when using non-Authorization extractors, Bearer error values are validated, credentials must conform to RFC 7235 token68 syntax, and scope values are checked against RFC 6750's scope-token format. The header is also emitted only after the status code is finalized.
New helper function called LoggerToWriter has been added to the logger middleware. This function allows you to use 3rd party loggers such as logrus or zap with the Fiber logger middleware without an extra adapter. For example, you can use zap with Fiber logger middleware like this:
Logger configuration now uses Stream instead of Output for the destination writer, so update your logger middleware configuration when migrating to v3.
Custom logger integrations should update any LoggerFunc implementations to the new signature that receives a pointer to the middleware config: func(c fiber.Ctx, data *logger.Data, cfg *logger.Config) error.
package main
import (
"github.com/gofiber/contrib/fiberzap/v2"
"github.com/gofiber/fiber/v3"
"github.com/gofiber/fiber/v3/log"
"github.com/gofiber/fiber/v3/middleware/logger"
)
func main() {
// Create a new Fiber instance
app := fiber.New()
// Create a new zap logger which is compatible with Fiber AllLogger interface
zap := fiberzap.NewLogger(fiberzap.LoggerConfig{
ExtraKeys: []string{"request_id"},
})
// Use the logger middleware with zerolog logger
app.Use(logger.New(logger.Config{
Stream: logger.LoggerToWriter(zap, log.LevelDebug),
}))
// Define a route
app.Get("/", func(c fiber.Ctx) error {
return c.SendString("Hello, World!")
})
// Start server on http://localhost:3000
app.Listen(":3000")
}
:::note
The deprecated TagHeader constant was removed. Use TagReqHeader when you need to log request headers.
:::
In Fiber v3, middleware (like requestid) now stores values in the request context using unexported keys of custom types. This aligns with Go's context best practices to prevent key collisions between packages.
As a result, directly accessing these values using string keys with c.Locals("your_key") or in the logger format string with ${locals:your_key} (e.g., ${locals:requestid}) will no longer work for values set by such middleware.
Recommended Solution: CustomTags
The cleanest and most maintainable way to include these middleware-specific values in your logs is by using the CustomTags option in the logger middleware configuration. This allows you to define a custom function to retrieve the value correctly from the context.
package main
import (
"github.com/gofiber/fiber/v3"
"github.com/gofiber/fiber/v3/middleware/logger"
"github.com/gofiber/fiber/v3/middleware/requestid"
)
func main() {
app := fiber.New()
// Ensure requestid middleware is used before the logger
app.Use(requestid.New())
app.Use(logger.New(logger.Config{
CustomTags: map[string]logger.LogFunc{
"requestid": func(output logger.Buffer, c fiber.Ctx, data *logger.Data, extraParam string) (int, error) {
// Retrieve the request ID using the middleware's specific function
return output.WriteString(requestid.FromContext(c))
},
},
// Use the custom tag in your format string
Format: "[${time}] ${ip} - ${requestid} - ${status} ${method} ${path}\n",
}))
app.Get("/", func(c fiber.Ctx) error {
return c.SendString("Hello, World!")
})
app.Listen(":3000")
}
Alternative: Manually Copying to Locals
If you have existing logging patterns that rely on c.Locals or prefer to manage these values in Locals for other reasons, you can manually copy the value from the context to c.Locals in a preceding middleware:
app.Use(requestid.New()) // Request ID middleware
app.Use(func(c fiber.Ctx) error {
// Manually copy the request ID to Locals
c.Locals("requestid", requestid.FromContext(c))
return c.Next()
})
app.Use(logger.New(logger.Config{
// Now ${locals:requestid} can be used, but CustomTags is generally preferred
Format: "[${time}] ${ip} - ${locals:requestid} - ${status} ${method} ${path}\n",
}))
Both approaches ensure your logger can access these values while respecting Go's context practices.
The Skip is a function to determine if logging is skipped or written to Stream.
app.Use(logger.New(logger.Config{
Skip: func(c fiber.Ctx) bool {
// Skip logging HTTP 200 requests
return c.Response().StatusCode() == fiber.StatusOK
},
}))
app.Use(logger.New(logger.Config{
Skip: func(c fiber.Ctx) bool {
// Only log errors, similar to an error.log
return c.Response().StatusCode() < 400
},
}))
Logger provides predefined formats that you can use by name or directly by specifying the format string.
<details> <summary>Example Usage</summary>app.Use(logger.New(logger.Config{
Format: logger.FormatCombined,
}))
See more in Logger
</details>The limiter middleware uses a new Fixed Window Rate Limiter implementation.
Custom limiter algorithms should now implement the updated limiter.Handler interface, whose New method receives a pointer to the active config: New(cfg *limiter.Config) fiber.Handler.
Limiter now redacts request keys in error paths by default. A new DisableValueRedaction boolean (default false) lets you reveal the raw limiter key if diagnostics require it.
:::note
Deprecated fields Duration, Store, and Key have been removed in v3. Use Expiration, Storage, and KeyGenerator instead.
:::
Monitor middleware is migrated to the Contrib package with PR #1172.
The proxy middleware has been updated to improve consistency with Go naming conventions. The TlsConfig field in the configuration struct has been renamed to TLSConfig. Additionally, the WithTlsConfig method has been removed; you should now configure TLS directly via the TLSConfig property within the Config struct.
The new KeepConnectionHeader option (default false) drops the Connection header unless explicitly enabled to retain it.
proxy.Balancer now accepts an optional variadic configuration: call proxy.Balancer() to use defaults or continue passing a proxy.Config value as before.
The Recover middleware allows customizing the error it returns. Set a PanicHandler in its Config to change the default behavior.
The Session middleware has undergone key changes in v3 to improve functionality and flexibility. While v2 methods remain available for backward compatibility, we now recommend using the new middleware handler for session management.
The session middleware has undergone significant improvements in v3, focusing on type safety, flexibility, and better developer experience.
Extractor Pattern: The string-based KeyLookup configuration has been replaced with a more flexible and type-safe Extractor function pattern.
New Middleware Handler: The New function now returns a middleware handler instead of a *Store. To access the session store, use the Store method on the middleware, or opt for NewStore or NewWithStore for custom store integration.
Manual Session Release: Session instances are no longer automatically released after being saved. To ensure proper lifecycle management, you must manually call sess.Release().
Idle Timeout: The Expiration field has been replaced with IdleTimeout, which handles session inactivity. If the session is idle for the specified duration, it will expire. The idle timeout is updated when the session is saved. If you are using the middleware handler, the idle timeout will be updated automatically.
Absolute Timeout: The AbsoluteTimeout field has been added. If you need to set an absolute session timeout, you can use this field to define the duration. The session will expire after the specified duration, regardless of activity.
Default KeyGenerator: Changed from utils.UUIDv4 to utils.SecureToken, producing base64-encoded tokens instead of UUID format.
For more details on these changes and migration instructions, check the Session Middleware Migration Guide.
The timeout middleware is now configurable. A new Config struct allows customizing the timeout duration, defining a handler that runs when a timeout occurs, and specifying errors to treat as timeouts. The New function now accepts a Config value instead of a duration.
Behavioral changes:
c.Context().Done() and return early.500 Internal Server Error responses.TimeoutErrorWithCode combined with Fiber's Abandon mechanism to ensure complete race-freedom between the middleware, handler goroutine, and context pooling.New Ctx methods for the Abandon mechanism:
Abandon(): Marks the context as abandonedIsAbandoned(): Returns true if the context was abandonedForceRelease(): Releases an abandoned context back to the pool (for advanced use)Migration: Replace calls like timeout.New(handler, 2*time.Second) with timeout.New(handler, timeout.Config{Timeout: 2 * time.Second}).
In v3, Fiber introduced Addons. Addons are additional useful packages that can be used in Fiber.
The Retry addon is a new addon that implements a retry mechanism for unsuccessful network operations. It uses an exponential backoff algorithm with jitter. It calls the function multiple times and tries to make it successful. If all calls are failed, then, it returns an error. It adds a jitter at each retry step because adding a jitter is a way to break synchronization across the client and avoid collision.
<details> <summary>Example</summary>package main
import (
"fmt"
"github.com/gofiber/fiber/v3/addon/retry"
"github.com/gofiber/fiber/v3/client"
)
func main() {
expBackoff := retry.NewExponentialBackoff(retry.Config{})
// Local variables that will be used inside of Retry
var resp *client.Response
var err error
// Retry a network request and return an error to signify to try again
err = expBackoff.Retry(func() error {
client := client.New()
resp, err = client.Get("https://gofiber.io")
if err != nil {
return fmt.Errorf("GET gofiber.io failed: %w", err)
}
if resp.StatusCode() != 200 {
return fmt.Errorf("GET gofiber.io did not return OK 200")
}
return nil
})
// If all retries failed, panic
if err != nil {
panic(err)
}
fmt.Printf("GET gofiber.io succeeded with status code %d\n", resp.StatusCode())
}
To streamline upgrades between Fiber versions, the Fiber CLI ships with a
migrate command:
go install github.com/gofiber/cli/fiber@latest
fiber migrate --to v3
-t, --to string migrate to a specific version, e.g. v3.0.0-f, --force force migration even if already on that version-s, --skip_go_mod skip running go mod tidy, go mod download, and go mod vendorSince we've removed app.Static(), you need to move methods to static middleware like the example below:
// Before
app.Static("/", "./public")
app.Static("/prefix", "./public")
app.Static("/prefix", "./public", Static{
Index: "index.htm",
})
app.Static("*", "./public/index.html")
// After
app.Get("/*", static.New("./public"))
app.Get("/prefix*", static.New("./public"))
app.Get("/prefix*", static.New("./public", static.Config{
IndexNames: []string{"index.htm", "index.html"},
}))
app.Get("*", static.New("./public/index.html"))
:::caution
You have to put * to the end of the route if you don't define static route with app.Use.
:::
We've renamed EnableTrustedProxyCheck to TrustProxy and moved TrustedProxies to TrustProxyConfig.
Important: To use proxy headers like X-Forwarded-For with c.IP(), you must configure all of TrustProxy, ProxyHeader, and a trusted proxy via TrustProxyConfig. If the proxy is not trusted (for example, if you set only ProxyHeader or only TrustProxy without configuring TrustProxyConfig), proxy headers are ignored and c.IP() will return the remote TCP IP instead.
// Before
app := fiber.New(fiber.Config{
// EnableTrustedProxyCheck enables the trusted proxy check.
EnableTrustedProxyCheck: true,
// TrustedProxies is a list of trusted proxy IP ranges/addresses.
TrustedProxies: []string{"0.8.0.0", "127.0.0.0/8", "::1/128"},
})
// After
app := fiber.New(fiber.Config{
// TrustProxy enables the trusted proxy check
TrustProxy: true,
// ProxyHeader specifies which header to read the real client IP from
ProxyHeader: fiber.HeaderXForwardedFor,
// TrustProxyConfig allows for configuring trusted proxies.
TrustProxyConfig: fiber.TrustProxyConfig{
// Proxies is a list of trusted proxy IP ranges/addresses.
Proxies: []string{"0.8.0.0"},
// Trust all loop-back IP addresses (127.0.0.0/8, ::1/128)
Loopback: true,
// Trust Unix domain socket connections
UnixSocket: true,
},
})
For detailed proxy configuration guidance, see the reverse proxy guide.
OnShutdown has been replaced by two hooks: OnPreShutdown and OnPostShutdown.
Use them to run cleanup code before and after the server shuts down. When handling
shutdown errors, register an OnPostShutdown hook and call app.Listen() in a goroutine.
// Before
app.OnShutdown(func() {
// Code to run before shutdown
})
// After
app.Hooks().OnPreShutdown(func() error {
// Code to run before shutdown
return nil
})
The Listen helpers (ListenTLS, ListenMutualTLS, etc.) were removed. Use
app.Listen() with fiber.ListenConfig and a tls.Config when TLS is required.
Options such as ListenerNetwork and UnixSocketFileMode are now configured via
this struct. Prefer TLSConfig when you need full control, or use CertFile and
CertKeyFile for quick TLS setup.
// Before
app.ListenTLS(":3000", "cert.pem", "key.pem")
// After
app.Listen(":3000", fiber.ListenConfig{
CertFile: "./cert.pem",
CertKeyFile: "./key.pem",
})
net/http handlersRoute registration helpers now accept native net/http handlers. Pass an
http.Handler, http.HandlerFunc, or compatible function directly to methods
such as app.Get, Group, or RouteChain and Fiber will adapt it at
registration time. Manual wrapping through the adaptor middleware is no longer
required for these common cases.
:::note Compatibility considerations
Adapted handlers stick to net/http semantics. They do not interact with fiber.Ctx
and are slower than native Fiber handlers because of the extra conversion layer. Use
them to ease migrations, but prefer Fiber handlers in performance-critical paths.
:::
httpHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if _, err := w.Write([]byte("served by net/http")); err != nil {
panic(err)
}
})
app.Get("/", httpHandler)
The signatures for Add and Route have been changed.
To migrate Add you must change the methods in a slice.
// Before
app.Add(fiber.MethodPost, "/api", myHandler)
// After
app.Add([]string{fiber.MethodPost}, "/api", myHandler)
In this release, the Mount method has been removed. Instead, you can use the Use method to achieve similar functionality.
// Before
app.Mount("/api", apiApp)
// After
app.Use("/api", apiApp)
Refer to the route chaining section for details on the new RouteChain helper. The Route function now matches its v2 behavior for prefix encapsulation.
// Before
app.Route("/api", func(apiGrp Router) {
apiGrp.Route("/user/:id?", func(userGrp Router) {
userGrp.Get("/", func(c fiber.Ctx) error {
// Get user
return c.JSON(fiber.Map{"message": "Get user", "id": c.Params("id")})
})
userGrp.Post("/", func(c fiber.Ctx) error {
// Create user
return c.JSON(fiber.Map{"message": "User created"})
})
})
})
// After
app.RouteChain("/api").RouteChain("/user/:id?")
.Get(func(c fiber.Ctx) error {
// Get user
return c.JSON(fiber.Map{"message": "Get user", "id": c.Params("id")})
})
.Post(func(c fiber.Ctx) error {
// Create user
return c.JSON(fiber.Map{"message": "User created"})
});
We introduced a new method that enables rebuilding the route tree stack at runtime. This allows you to add routes dynamically while your application is running and update the route tree to make the new routes available for use.
For more details, refer to the app documentation:
app.Get("/define", func(c fiber.Ctx) error { // Define a new route dynamically
app.Get("/dynamically-defined", func(c fiber.Ctx) error { // Adding a dynamically defined route
return c.SendStatus(http.StatusOK)
})
app.RebuildTree() // Rebuild the route tree to register the new route
return c.SendStatus(http.StatusOK)
})
In this example, a new route is defined, and RebuildTree() is called to ensure the new route is registered and available.
Note: Use this method with caution. It is not thread-safe and can be very performance-intensive. Therefore, it should be used sparingly and primarily in development mode. It should not be invoke concurrently.
RemoveRoute: Removes route by path
RemoveRouteByName: Removes route by name
RemoveRouteFunc: Removes route by a function having *Route parameter
For more details, refer to the app documentation:
Fiber v3 introduces several new features and changes to the Ctx interface, enhancing its functionality and flexibility.
Params with generic types.Query with generic types.Query with generic types.Query with generic types.c.ViewBind() for view binding.In Fiber v3, the Ctx parameter in handlers is now an interface, which means the * symbol is no longer used. Here is an example demonstrating this change:
Before:
package main
import (
"github.com/gofiber/fiber/v2"
)
func main() {
app := fiber.New()
// Route Handler with *fiber.Ctx
app.Get("/", func(c *fiber.Ctx) error {
return c.SendString("Hello, World!")
})
app.Listen(":3000")
}
After:
package main
import (
"github.com/gofiber/fiber/v3"
)
func main() {
app := fiber.New()
// Route Handler without *fiber.Ctx
app.Get("/", func(c fiber.Ctx) error {
return c.SendString("Hello, World!")
})
app.Listen(":3000")
}
Explanation:
In this example, the Ctx parameter in the handler is used as an interface (fiber.Ctx) instead of a pointer (*fiber.Ctx). This change allows for more flexibility and customization in Fiber v3.
The Parser section in Fiber v3 has undergone significant changes to improve functionality and flexibility.
BodyParser: Use c.Bind().Body() instead of c.BodyParser().
// Before
app.Post("/user", func(c *fiber.Ctx) error {
var user User
if err := c.BodyParser(&user); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": err.Error()})
}
return c.JSON(user)
})
// After
app.Post("/user", func(c fiber.Ctx) error {
var user User
if err := c.Bind().Body(&user); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": err.Error()})
}
return c.JSON(user)
})
ParamsParser: Use c.Bind().URI() instead of c.ParamsParser(). Note that the struct tag has changed from params to uri.
// Before
type Params struct {
ID int `params:"id"`
}
app.Get("/user/:id", func(c *fiber.Ctx) error {
var params Params
if err := c.ParamsParser(¶ms); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": err.Error()})
}
return c.JSON(params)
})
// After
type Params struct {
ID int `uri:"id"`
}
app.Get("/user/:id", func(c fiber.Ctx) error {
var params Params
if err := c.Bind().URI(¶ms); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": err.Error()})
}
return c.JSON(params)
})
QueryParser: Use c.Bind().Query() instead of c.QueryParser().
// Before
app.Get("/search", func(c *fiber.Ctx) error {
var query Query
if err := c.QueryParser(&query); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": err.Error()})
}
return c.JSON(query)
})
// After
app.Get("/search", func(c fiber.Ctx) error {
var query Query
if err := c.Bind().Query(&query); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": err.Error()})
}
return c.JSON(query)
})
CookieParser: Use c.Bind().Cookie() instead of c.CookieParser().
// Before
app.Get("/cookie", func(c *fiber.Ctx) error {
var cookie Cookie
if err := c.CookieParser(&cookie); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": err.Error()})
}
return c.JSON(cookie)
})
// After
app.Get("/cookie", func(c fiber.Ctx) error {
var cookie Cookie
if err := c.Bind().Cookie(&cookie); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": err.Error()})
}
return c.JSON(cookie)
})
Fiber v3 enhances the redirect functionality by introducing new methods and improving existing ones. The new redirect methods provide more flexibility and control over the redirection process.
RedirectToRoute: Use c.Redirect().Route() instead of c.RedirectToRoute().
// Before
app.Get("/old", func(c *fiber.Ctx) error {
return c.RedirectToRoute("newRoute")
})
// After
app.Get("/old", func(c fiber.Ctx) error {
return c.Redirect().Route("newRoute")
})
RedirectBack: Use c.Redirect().Back() instead of c.RedirectBack().
// Before
app.Get("/back", func(c *fiber.Ctx) error {
return c.RedirectBack()
})
// After
app.Get("/back", func(c fiber.Ctx) error {
return c.Redirect().Back()
})
Redirect: Use c.Redirect().To() instead of c.Redirect().
// Before
app.Get("/old", func(c *fiber.Ctx) error {
return c.Redirect("/new")
})
// After
app.Get("/old", func(c fiber.Ctx) error {
return c.Redirect().To("/new")
})
The ConfigurableLogger and AllLogger interfaces now use generics. You can specify the underlying logger type when implementing these interfaces. While any can be used for maximum flexibility in some contexts, when retrieving the concrete logger via log.DefaultLogger, you must specify the exact underlying logger type, for example log.DefaultLogger[*MyLogger]().Logger().
Fiber v3 introduces a completely rebuilt client package with numerous new features such as Cookiejar, request/response hooks, and more. Here is a guide to help you migrate from Fiber v2 to Fiber v3.
Import Path:
Update the import path to the new client package.
<details> <summary>Before</summary>import "github.com/gofiber/fiber/v2/client"
import "github.com/gofiber/fiber/v3/client"
Common migrations:
Shared defaults instead of per-call mutation: Move headers and timeouts into the reusable client and override with client.Config when needed.
// Before
status, body, errs := fiber.Get("https://api.example.com/users").
Set("Authorization", "Bearer "+token).
Timeout(5 * time.Second).
String()
if len(errs) > 0 {
return fmt.Errorf("request failed: %v", errs)
}
fmt.Println(status, body)
// After
cli := client.New().
AddHeader("Authorization", "Bearer "+token).
SetTimeout(5 * time.Second)
resp, err := cli.Get("https://api.example.com/users")
if err != nil {
return err
}
defer resp.Close()
fmt.Println(resp.StatusCode(), resp.String())
Body handling: Replace Agent.JSON(...).Struct(&dst) with request bodies through client.Config (or Request.SetJSON) and decode the response via Response.JSON.
// Before
var created user
status, _, errs := fiber.Post("https://api.example.com/users").
JSON(payload).
Struct(&created)
if len(errs) > 0 {
return fmt.Errorf("request failed: %v", errs)
}
fmt.Println(status, created)
// After
cli := client.New()
resp, err := cli.Post("https://api.example.com/users", client.Config{
Body: payload,
})
if err != nil {
return err
}
defer resp.Close()
var created user
if err := resp.JSON(&created); err != nil {
return fmt.Errorf("decode failed: %w", err)
}
fmt.Println(resp.StatusCode(), created)
Path and query parameters: Use the new path/query helpers instead of manually formatting URLs.
<details> <summary>Example</summary>// Before
code, body, errs := fiber.Get(fmt.Sprintf("https://api.example.com/users/%s", id)).
QueryString("active=true").
String()
if len(errs) > 0 {
return fmt.Errorf("request failed: %v", errs)
}
fmt.Println(code, body)
// After
cli := client.New().SetBaseURL("https://api.example.com")
resp, err := cli.Get("/users/:id", client.Config{
PathParam: map[string]string{"id": id},
Param: map[string]string{"active": "true"},
})
if err != nil {
return err
}
defer resp.Close()
fmt.Println(resp.StatusCode(), resp.String())
Agent helpers: Agent.Bytes, AcquireAgent, and Agent.Parse have been removed. Reuse a client.Client instance (or pool requests/responses directly) and access response data through the new typed helpers.
// Before
agent := fiber.AcquireAgent()
status, body, errs := agent.Get("https://api.example.com/users").Bytes()
fiber.ReleaseAgent(agent)
if len(errs) > 0 {
return fmt.Errorf("request failed: %v", errs)
}
var users []user
if err := fiber.Parse(body, &users); err != nil {
return fmt.Errorf("parse failed: %w", err)
}
fmt.Println(status, len(users))
// After
cli := client.New()
resp, err := cli.Get("https://api.example.com/users")
if err != nil {
return err
}
defer resp.Close()
var users []user
if err := resp.JSON(&users); err != nil {
return fmt.Errorf("decode failed: %w", err)
}
fmt.Println(resp.StatusCode(), len(users))
:::tip
If you need pooling, use client.AcquireRequest, client.AcquireResponse, and their corresponding release functions around a long-lived client.Client instead of the removed agent pool.
:::
Fiber-level shortcuts: The fiber.Get, fiber.Post, and similar top-level helpers are no longer exposed from the main module. Use the client package equivalents (client.Get, client.Post, etc.) which call the shared default client (or pass your own client instance for custom defaults).
// Before
status, body, errs := fiber.Get("https://api.example.com/health").String()
if len(errs) > 0 {
return fmt.Errorf("request failed: %v", errs)
}
fmt.Println(status, body)
// After
resp, err := client.Get("https://api.example.com/health")
if err != nil {
return err
}
defer resp.Close()
fmt.Println(resp.StatusCode(), resp.String())
:::note
The client.Get/client.Post helpers use client.C() (the default shared client). For custom defaults, construct a client with client.New() and invoke its methods instead.
:::
| Description | v2 | v3 |
|---|---|---|
| Import | github.com/gofiber/fiber/v2 | github.com/gofiber/fiber/v3/client |
| Client Concept | *fiber.Agent | *client.Client + *client.Request |
| Response Concept | (code int, body []byte, errs []error) | (*client.Response, error) |
| Description | v2 | v3 |
|---|---|---|
| Create Agent/Client | fiber.AcquireAgent() | client.New() |
| Get from pool | fiber.AcquireAgent() | client.AcquireRequest() |
| Release | fiber.ReleaseAgent(a) | client.ReleaseRequest(req) |
| With fasthttp.Client | - | client.NewWithClient(c) |
| With HostClient | - | client.NewWithHostClient(hc) |
| With LBClient | - | client.NewWithLBClient(lb) |
| Get Request object | a.Request() | c.R() |
| Default client | - | client.C() |
| Replace default | - | client.Replace(c) |
| Description | v2 | v3 (Client) | v3 (Request) |
|---|---|---|---|
| GET | fiber.Get(url) | c.Get(url, cfg...) | req.Get(url) |
| POST | fiber.Post(url) | c.Post(url, cfg...) | req.Post(url) |
| PUT | fiber.Put(url) | c.Put(url, cfg...) | req.Put(url) |
| PATCH | fiber.Patch(url) | c.Patch(url, cfg...) | req.Patch(url) |
| DELETE | fiber.Delete(url) | c.Delete(url, cfg...) | req.Delete(url) |
| HEAD | fiber.Head(url) | c.Head(url, cfg...) | req.Head(url) |
| OPTIONS | - | c.Options(url, cfg...) | req.Options(url) |
| Custom | - | c.Custom(url, method, cfg...) | req.Custom(url, method) |
| Description | v2 | v3 |
|---|---|---|
| Set URL | req.SetRequestURI(url) | req.SetURL(url) |
| Get URL | req.URI().String() | req.URL() |
| Set Method | req.Header.SetMethod(method) | req.SetMethod(method) |
| Set Base URL | - | c.SetBaseURL(url) |
| Description | v2 | v3 |
|---|---|---|
| Parse Request | a.Parse() | Not needed |
| Execute (bytes) | a.Bytes() β (code, body, errs) | req.Send() β (*Response, error) |
| Execute (string) | a.String() | resp.String() |
| Execute (struct) | a.Struct(&v) | resp.JSON(&v) / resp.XML(&v) |
| Status Code | Return value code | resp.StatusCode() |
| Status Text | - | resp.Status() |
| Body (bytes) | Return value body | resp.Body() |
| Response Header | resp.Header.Peek(key) | resp.Header(key) |
| All Headers | resp.Header.VisitAll(fn) | resp.Headers() |
| Cookies | - | resp.Cookies() |
| Save to file | - | resp.Save(path) |
| Close | - | resp.Close() |
| Description | v2 | v3 (Client) | v3 (Request) |
|---|---|---|---|
| Set Header | a.Set(k, v) | c.SetHeader(k, v) | req.SetHeader(k, v) |
| Add Header | a.Add(k, v) | c.AddHeader(k, v) | req.AddHeader(k, v) |
| Multiple Headers | - | c.SetHeaders(map) | req.SetHeaders(map) |
| Bytes variants | a.SetBytesK/V/KV() | - | - |
| Description | v2 | v3 (Client) | v3 (Request) |
|---|---|---|---|
| User-Agent | a.UserAgent(ua) | c.SetUserAgent(ua) | req.SetUserAgent(ua) |
| Referer | a.Referer(ref) | c.SetReferer(ref) | req.SetReferer(ref) |
| Content-Type | a.ContentType(ct) | - | req.SetHeader("Content-Type", ct) |
| Host | a.Host(host) | - | req.SetHeader("Host", host) |
| Connection Close | a.ConnectionClose() | - | req.SetHeader("Connection", "close") |
| Description | v2 | v3 (Client) | v3 (Request) |
|---|---|---|---|
| Set Cookie | a.Cookie(k, v) | c.SetCookie(k, v) | req.SetCookie(k, v) |
| Multiple | a.Cookies(k1, v1, ...) | c.SetCookies(map) | req.SetCookies(map) |
| With Struct | - | c.SetCookiesWithStruct(v) | req.SetCookiesWithStruct(v) |
| Cookie Jar | - | c.SetCookieJar(jar) | - |
| Description | v2 | v3 (Client) | v3 (Request) |
|---|---|---|---|
| Query String | a.QueryString(qs) | - | - |
| Add Param | - | c.AddParam(k, v) | req.AddParam(k, v) |
| Set Param | - | c.SetParam(k, v) | req.SetParam(k, v) |
| With Struct | - | c.SetParamsWithStruct(v) | req.SetParamsWithStruct(v) |
| Description | v2 | v3 (Client) | v3 (Request) |
|---|---|---|---|
| Set Path Param | - | c.SetPathParam(k, v) | req.SetPathParam(k, v) |
| Multiple | - | c.SetPathParams(map) | req.SetPathParams(map) |
| With Struct | - | c.SetPathParamsWithStruct(v) | req.SetPathParamsWithStruct(v) |
| Description | v2 | v3 |
|---|---|---|
| Body (bytes) | a.Body(body) | req.SetRawBody(body) |
| Body (string) | a.BodyString(body) | req.SetRawBody([]byte(body)) |
| Body Stream | a.BodyStream(r, size) | - |
| JSON | a.JSON(v) | req.SetJSON(v) |
| XML | a.XML(v) | req.SetXML(v) |
| CBOR (NEW) | - | req.SetCBOR(v) |
| Description | v2 | v3 |
|---|---|---|
| Create Args | fiber.AcquireArgs() | Direct on Request |
| Send Form | a.Form(args) | req.SetFormData(k, v) |
| Add Form Data | args.Set(k, v) | req.AddFormData(k, v) |
| With Map | - | req.SetFormDataWithMap(map) |
| With Struct | - | req.SetFormDataWithStruct(v) |
| Description | v2 | v3 |
|---|---|---|
| Multipart Form | a.MultipartForm(args) | Automatic |
| Boundary | a.Boundary(b) | req.SetBoundary(b) |
| Send File | a.SendFile(f, field...) | req.AddFile(path) |
| Multiple Files | a.SendFiles(...) | req.AddFiles(files...) |
| With Reader | - | req.AddFileWithReader(name, r) |
| FileData | a.FileData(files...) | req.AddFiles(files...) |
| Description | v2 | v3 (Client) | v3 (Request) |
|---|---|---|---|
| Timeout | a.Timeout(d) | c.SetTimeout(d) | req.SetTimeout(d) |
| Max Redirects | a.MaxRedirectsCount(n) | Via Config | req.SetMaxRedirects(n) |
| TLS Config | a.TLSConfig(cfg) | c.SetTLSConfig(cfg) | - |
| Skip Verify | a.InsecureSkipVerify() | Via tls.Config | - |
| Certificates | - | c.SetCertificates(...) | - |
| Root Cert | - | c.SetRootCertificate(path) | - |
| Description | v2 | v3 |
|---|---|---|
| JSON Encoder | a.JSONEncoder(fn) | c.SetJSONMarshal(fn) |
| JSON Decoder | a.JSONDecoder(fn) | c.SetJSONUnmarshal(fn) |
| XML Encoder | - | c.SetXMLMarshal(fn) |
| XML Decoder | - | c.SetXMLUnmarshal(fn) |
| CBOR (NEW) | - | c.SetCBORMarshal/Unmarshal(fn) |
| Description | v2 | v3 |
|---|---|---|
| Basic Auth | a.BasicAuth(user, pass) | Via Header (Base64) |
| Description | v2 | v3 |
|---|---|---|
| Debug | a.Debug(w...) | c.Debug() |
| Disable Debug | - | c.DisableDebug() |
| Logger | - | c.SetLogger(logger) |
| Retry | a.RetryIf(fn) | c.SetRetryConfig(cfg) |
| Description | v2 | v3 |
|---|---|---|
| Reuse Agent | a.Reuse() | Use pool |
| Reset Client | - | c.Reset() |
| Dest Buffer | a.Dest(dest) | - |
| Feature | v3 API |
|---|---|
| Request Hooks | c.AddRequestHook(fn) |
| Response Hooks | c.AddResponseHook(fn) |
| Proxy | c.SetProxyURL(url) |
| Context | req.SetContext(ctx) |
| Dial Function | c.SetDial(fn) |
| Raw Request | req.RawRequest |
| Raw Response | resp.RawResponse |
Agent β v3 separate Client, Request, Response[]error β v3 single error(code, body, errs) β v3 *Response object:param supportiter.Seq2 for collections*Bytes* methods goneFiber v3 removes the in-repo utils package in favor of the external github.com/gofiber/utils/v2 module.
- import "github.com/gofiber/fiber/v2/utils"
+ import "github.com/gofiber/utils/v2"
| v2 function | v3 replacement |
|---|---|
AssertEqual | removed; use testing libraries like github.com/stretchr/testify/assert |
ToLowerBytes | utils.ToLowerBytes |
ToUpperBytes | utils.ToUpperBytes |
TrimRightBytes | utils.TrimRight |
TrimLeftBytes | utils.TrimLeft |
TrimBytes | utils.Trim |
EqualFoldBytes | utils.EqualFold |
UUID | utils.UUID |
UUIDv4 | utils.UUIDv4 |
FunctionName | utils.FunctionName |
GetArgument | utils.GetArgument |
IncrementIPRange | utils.IncrementIPRange |
ConvertToBytes | utils.ConvertToBytes |
CopyString | utils.CopyString |
CopyBytes | utils.CopyBytes |
ByteSize | utils.ByteSize |
ToString | utils.ToString |
UnsafeString | utils.UnsafeString |
UnsafeBytes | utils.UnsafeBytes |
GetString | removed; use utils.ToString or the standard library |
GetBytes | removed; use utils.CopyBytes or []byte(s) |
ImmutableString | removed; strings are already immutable |
GetMIME | utils.GetMIME |
ParseVendorSpecificContentType | utils.ParseVendorSpecificContentType |
StatusMessage | utils.StatusMessage |
IsIPv4 | utils.IsIPv4 |
IsIPv6 | utils.IsIPv6 |
ToLower | utils.ToLower |
ToUpper | utils.ToUpper |
TrimLeft | strings.TrimLeft |
Trim | strings.Trim |
TrimRight | strings.TrimRight |
EqualFold | strings.EqualFold |
StartTimeStampUpdater | utils.StartTimeStampUpdater (new utils.Timestamp provides the current value) |
// v2
import oldutils "github.com/gofiber/fiber/v2/utils"
func demo() {
b := oldutils.TrimBytes([]byte(" fiber "))
id := oldutils.UUIDv4()
s := oldutils.GetString([]byte("foo"))
}
// v3
import (
"github.com/gofiber/utils/v2"
"strings"
)
func demo() {
s := utils.TrimSpace(" fiber ")
id := utils.UUIDv4()
str := utils.ToString([]byte("foo"))
t := strings.TrimRight("bar ", " ")
}
The github.com/gofiber/utils/v2 module also introduces new helpers like ParseInt, ParseUint, Walk, ReadFile, and Timestamp.
Change: In Fiber v2, some middlewares set data in c.Locals() using string keys (e.g., c.Locals("requestid")). In Fiber v3, to align with Go's context best practices and prevent key collisions, these middlewares now store their specific data in the request's context using unexported keys of custom types.
Impact: Directly accessing these middleware-provided values via c.Locals("some_string_key") will no longer work.
Migration Action:
The ContextKey configuration option has been removed from all middlewares. Values are no longer stored under user-defined keys. You must update your code to use the dedicated exported functions provided by each affected middleware to retrieve its data from the context.
Examples of new helper functions to use:
requestid.FromContext(c)csrf.TokenFromContext(c)csrf.HandlerFromContext(c)session.FromContext(c)basicauth.UsernameFromContext(c)keyauth.TokenFromContext(c)For logging these values:
The recommended approach is to use the CustomTags feature of the Logger middleware, which allows you to call these specific FromContext functions. Refer to the Logger section in "What's New" for detailed examples.
:::note
If you were manually setting and retrieving your own application-specific values in c.Locals() using string keys, that functionality remains unchanged. This change specifically pertains to how Fiber's built-in (and some contrib) middlewares expose their data.
:::
The Authorizer callback now receives the current request context. Update custom
functions from:
Authorizer: func(user, pass string) bool {
// v2 style
return user == "admin" && pass == "secret"
}
to:
Authorizer: func(user, pass string, _ fiber.Ctx) bool {
// v3 style with access to the Fiber context
return user == "admin" && pass == "secret"
}
Passwords configured for BasicAuth must now be pre-hashed. If no prefix is supplied the middleware expects a SHA-256 digest encoded in hex. Common prefixes like {SHA256} and {SHA512} and bcrypt strings are also supported. Plaintext passwords are no longer accepted. Unauthorized responses also include a Vary: Authorization header for correct caching behavior.
You can also set the optional HeaderLimit and Charset
options to further control authentication behavior.
The keyauth middleware was updated to introduce a configurable Realm field for the WWW-Authenticate header.
The old string-based KeyLookup configuration has been replaced with an Extractor field, and the AuthScheme field has been removed. The auth scheme is now inferred from the extractor used (e.g., keyauth.FromAuthHeader). Use helper functions like keyauth.FromHeader, keyauth.FromAuthHeader, or keyauth.FromCookie to define where the key should be retrieved from. Multiple sources can be combined with keyauth.Chain.
New Challenge, Error, ErrorDescription, ErrorURI, and Scope options let you customize challenge responses, include Bearer error parameters, and specify required scopes. ErrorURI values are validated as absolute, credentials containing whitespace are rejected, and when multiple authorization extractors are chained, all schemes are advertised in the WWW-Authenticate header. The middleware defers emitting WWW-Authenticate until a 401 status is final, and FromAuthHeader now trims surrounding whitespace.
// Before
app.Use(keyauth.New(keyauth.Config{
KeyLookup: "header:Authorization",
AuthScheme: "Bearer",
Validator: validateAPIKey,
}))
// After
app.Use(keyauth.New(keyauth.Config{
Extractor: keyauth.FromAuthHeader(fiber.HeaderAuthorization, "Bearer"),
Validator: validateAPIKey,
}))
Combine multiple sources with keyauth.Chain() when needed.
The deprecated Store and Key fields were removed. Use Storage and
KeyGenerator instead to configure caching backends and cache keys.
Defaults also changed: the middleware now emits Cache-Control headers, the default Expiration increased to 5 minutes (from 1 minute), and a new MaxBytes limit of 1 MB (previously unlimited) now caps cached payloads.
To restore v2 behavior:
DisableCacheControl to true to suppress automatic Cache-Control headers.Expiration to 1*time.Minute.MaxBytes to 0 (or a higher value) when caching large responses.DisableQueryKeys: true), or provide a custom KeyGenerator.Additional v3 cache key options:
Methods: HTTP methods eligible for caching (default GET, HEAD)DisableQueryKeys: disable canonicalized query args in keys (default false)KeyHeaders: request header allow-list for key partitioningKeyCookies: explicit cookie allow-list for key partitioningDisableVaryHeaders: disable response Vary dimensions in lookup/storage partitioning (default false)The CORS middleware has been updated to use slices instead of strings for the AllowOrigins, AllowMethods, AllowHeaders, and ExposeHeaders fields. Here's how you can update your code:
// Before
app.Use(cors.New(cors.Config{
AllowOrigins: "https://example.com,https://example2.com",
AllowMethods: strings.Join([]string{fiber.MethodGet, fiber.MethodPost}, ","),
AllowHeaders: "Content-Type",
ExposeHeaders: "Content-Length",
}))
// After
app.Use(cors.New(cors.Config{
AllowOrigins: []string{"https://example.com", "https://example2.com"},
AllowMethods: []string{fiber.MethodGet, fiber.MethodPost},
AllowHeaders: []string{"Content-Type"},
ExposeHeaders: []string{"Content-Length"},
}))
Expiration field in the CSRF middleware configuration has been renamed to IdleTimeout to better describe its functionality. Additionally, the default value has been reduced from 1 hour to 30 minutes. Update your code as follows:// Before
app.Use(csrf.New(csrf.Config{
Expiration: 10 * time.Minute,
}))
// After
app.Use(csrf.New(csrf.Config{
IdleTimeout: 10 * time.Minute,
}))
Session Key Removal: The SessionKey field has been removed from the CSRF middleware configuration. The session key is now an unexported constant within the middleware to avoid potential key collisions in the session store.
KeyLookup Field Removal: The KeyLookup field has been removed from the CSRF middleware configuration. This field was deprecated and is no longer needed as the middleware now uses a more secure approach for token management.
DisableValueRedaction Toggle: CSRF redacts tokens and storage keys by default; set DisableValueRedaction to true when diagnostics require the raw values.
Default KeyGenerator: Changed from utils.UUIDv4 to utils.SecureToken, producing base64-encoded tokens instead of UUID format.
// Before
app.Use(csrf.New(csrf.Config{
KeyLookup: "header:X-Csrf-Token",
// other config...
}))
// After - use Extractor instead
app.Use(csrf.New(csrf.Config{
Extractor: csrf.FromHeader("X-Csrf-Token"),
// other config...
}))
csrf.FromCookie extractor has been intentionally removed for security reasons. Using cookie-based extraction defeats the purpose of CSRF protection by making the extracted token always match the cookie value.// Before - This was a security vulnerability
app.Use(csrf.New(csrf.Config{
Extractor: csrf.FromCookie("csrf_token"), // β Insecure!
}))
// After - Use secure extractors instead
app.Use(csrf.New(csrf.Config{
Extractor: csrf.FromHeader("X-Csrf-Token"), // β
Secure
// or
Extractor: csrf.FromForm("_csrf"), // β
Secure
// or
Extractor: csrf.FromQuery("csrf_token"), // β
Acceptable
}))
Security Note: The removal of FromCookie prevents a common misconfiguration that would completely bypass CSRF protection. The middleware uses the Double Submit Cookie pattern, which requires the token to be submitted through a different channel than the cookie to provide meaningful protection.
DisableValueRedaction boolean (default false) to reveal them when needed.The timeout middleware now accepts a configuration struct instead of a duration. Update your code as follows:
// Before
app.Use(timeout.New(handler, 2*time.Second))
// After
app.Use(timeout.New(handler, timeout.Config{Timeout: 2 * time.Second}))
Important behavioral changes:
c.Context().Done() and return early.500 Internal Server Error.You need to move filesystem middleware to static middleware due to it has been removed from the core.
// Before
app.Use(filesystem.New(filesystem.Config{
Root: http.Dir("./assets"),
}))
app.Use(filesystem.New(filesystem.Config{
Root: http.Dir("./assets"),
Browse: true,
Index: "index.html",
MaxAge: 3600,
}))
// After
app.Use(static.New("", static.Config{
FS: os.DirFS("./assets"),
}))
app.Use(static.New("", static.Config{
FS: os.DirFS("./assets"),
Browse: true,
IndexNames: []string{"index.html"},
MaxAge: 3600,
}))
The ExcludeVars option has been removed. Remove any references to it and use
ExportVars to explicitly list environment variables that should be exposed.
Previously, the Healthcheck middleware was configured with a combined setup for liveness and readiness probes:
//before
app.Use(healthcheck.New(healthcheck.Config{
LivenessProbe: func(c fiber.Ctx) bool {
return true
},
LivenessEndpoint: "/live",
ReadinessProbe: func(c fiber.Ctx) bool {
return serviceA.Ready() && serviceB.Ready() && ...
},
ReadinessEndpoint: "/ready",
}))
With the new version, each health check endpoint is configured separately, allowing for more flexibility:
// after
// Default liveness endpoint configuration
app.Get(healthcheck.LivenessEndpoint, healthcheck.New(healthcheck.Config{
Probe: func(c fiber.Ctx) bool {
return true
},
}))
// Default readiness endpoint configuration
app.Get(healthcheck.ReadinessEndpoint, healthcheck.New())
// New default startup endpoint configuration
// Default endpoint is /startupz
app.Get(healthcheck.StartupEndpoint, healthcheck.New(healthcheck.Config{
Probe: func(c fiber.Ctx) bool {
return serviceA.Ready() && serviceB.Ready() && ...
},
}))
// Custom liveness endpoint configuration
app.Get("/live", healthcheck.New())
Since v3 the Monitor middleware has been moved to the Contrib package
// Before
import "github.com/gofiber/fiber/v2/middleware/monitor"
app.Use("/metrics", monitor.New())
You only need to change the import path to the contrib package.
// After
import "github.com/gofiber/contrib/monitor"
app.Use("/metrics", monitor.New())
In previous versions, TLS settings for the proxy middleware were set using the WithTlsConfig method. This method has been removed in favor of a more idiomatic configuration via the TLSConfig field in the Config struct.
proxy.WithTlsConfig(&tls.Config{
InsecureSkipVerify: true,
})
// Forward to url
app.Get("/gif", proxy.Forward("https://i.imgur.com/IWaBepg.gif"))
proxy.WithClient(&fasthttp.Client{
TLSConfig: &tls.Config{InsecureSkipVerify: true},
})
// Forward to url
app.Get("/gif", proxy.Forward("https://i.imgur.com/IWaBepg.gif"))
proxy.Balancer also adopts the common middleware signature pattern and now accepts an optional variadic config: call proxy.Balancer() to use the defaults or continue passing a single proxy.Config value as in v2.
session.New() now returns a middleware handler. When using the store pattern,
create a store with session.NewStore() or call Store() on the middleware.
Sessions obtained from a store must be released manually via sess.Release().
Additionally, replace the deprecated KeyLookup option with extractor
functions such as session.FromCookie() or session.FromHeader(). Multiple
extractors can be combined with session.Chain().
// Before
app.Use(session.New(session.Config{
KeyLookup: "cookie:session_id",
Store: session.NewStore(),
}))
// After
app.Use(session.New(session.Config{
Extractor: session.FromCookie("session_id"),
Store: session.NewStore(),
}))
See the Session Middleware Migration Guide for complete details.