gofiber/fiber · critical
%s: invalid handler #%d (%T)
Error message
%s: invalid handler #%d (%T)
What it means
collectHandlers converts variadic args (passed to Get/Post/Use/etc.) into Fiber Handler values via toFiberHandler. If an argument is none of the accepted handler shapes (func(*Ctx) error, middleware, []Handler, etc.) it panics with the offending index and Go type. This is a programming error caught at route-registration time, before the server starts.
Source
Thrown at adapter.go:275
adapted := fasthttpadaptor.NewFastHTTPHandler(handler)
return func(c Ctx) error {
adapted(c.RequestCtx())
return nil
}
}
// collectHandlers converts a slice of handler arguments to Fiber handlers.
// The context string is used to provide informative panic messages when an
// unsupported handler type is encountered.
func collectHandlers(context string, args ...any) []Handler {
handlers := make([]Handler, 0, len(args))
for i, arg := range args {
handler, ok := toFiberHandler(arg)
if !ok {
panic(fmt.Sprintf("%s: invalid handler #%d (%T)\n", context, i, arg))
}
handlers = append(handlers, handler)
}
return handlers
}
View on GitHub (pinned to a105acad6c)
Solutions
- Look at the panic's argument index (#%d) and check that positional argument in the route-registration call.
- Ensure every handler is exactly func(*fiber.Ctx) error (or a type Fiber accepts via toFiberHandler).
- If passing a method, bind it as a closure: func(c *fiber.Ctx) error { return svc.method(c) }.
- Remove nil entries from handler slices before passing them.
Example fix
// before
app.Get("/u", userController, userHandler)
// where userController is a *UserController, not a handler
// after
app.Get("/u", func(c *fiber.Ctx) error { return userController.handle(c) }) Defensive patterns
Strategy: type-guard
Validate before calling
// Validate handler args before passing them to Get/Post/Use
func toFiberHandlerSafe(arg any) (fiber.Handler, error) {
switch h := arg.(type) {
case fiber.Handler:
return h, nil
case func(*fiber.Ctx) error:
return h, nil
case []fiber.Handler:
// flatten elsewhere; reject here
return nil, fmt.Errorf("unexpected []Handler")
}
return nil, fmt.Errorf("not a handler: %T", arg)
} Type guard
func isFiberHandler(v any) bool {
switch v.(type) {
case fiber.Handler, func(*fiber.Ctx) error:
return true
}
return false
} Try / catch
// Defer-recover around route registration in tests to surface handler-type mistakes
func registerSafely(app *fiber.App, method, path string, args ...any) (err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("register %s %s: %v", method, path, r)
}
}()
app.Add(method, path, args...)
return nil
} Prevention
- Standardize on func(*fiber.Ctx) error closures for all handlers.
- Lint handler slices for nil before route registration.
- Add a unit test that boots the app to catch registration panics during CI.
When it happens
Trigger: Passing an int, struct, nil non-handler, or a function with the wrong signature to app.Get/Post/Put/etc. or to a Group/Use call. The panic message shows the 0-based argument index and the dynamic type.
Common situations: Passing a method value with the wrong receiver type; passing a handler builder's result that returned nil; passing a value of a custom type that looks like a handler but is not func(*fiber.Ctx) error; refactoring that changes a function signature.
Related errors
- use: invalid handler %v
- use: invalid handler %v
- use: invalid handler %v
- route handler 'fn' cannot be nil
- Domain pattern cannot be empty
AI-assisted analysis of gofiber/fiber@a105acad6c (2026-08-11).
Data as JSON: /api/errors/5ce77798779643ae.
Report an issue: GitHub.