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

  1. Look at the panic's argument index (#%d) and check that positional argument in the route-registration call.
  2. Ensure every handler is exactly func(*fiber.Ctx) error (or a type Fiber accepts via toFiberHandler).
  3. If passing a method, bind it as a closure: func(c *fiber.Ctx) error { return svc.method(c) }.
  4. 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

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


AI-assisted analysis of gofiber/fiber@a105acad6c (2026-08-11). Data as JSON: /api/errors/5ce77798779643ae. Report an issue: GitHub.