docs/middleware/paginate.md
Pagination middleware for Fiber that extracts pagination parameters from query strings and stores them in the request context. Supports page-based, offset-based, and cursor-based pagination with multi-field sorting.
func New(config ...Config) fiber.Handler
func FromContext(ctx any) (*PageInfo, bool)
FromContext accepts fiber.CustomCtx, fiber.Ctx, *fasthttp.RequestCtx, or context.Context.
Import the middleware package:
import (
"github.com/gofiber/fiber/v3"
"github.com/gofiber/fiber/v3/middleware/paginate"
)
Once your Fiber app is initialized, choose one of the following approaches:
app.Use(paginate.New())
app.Get("/users", func(c fiber.Ctx) error {
pageInfo, ok := paginate.FromContext(c)
if !ok {
return fiber.ErrBadRequest
}
// Use pageInfo.Page, pageInfo.Limit, pageInfo.Start()
// GET /users?page=2&limit=20 → Page: 2, Limit: 20, Start(): 20
return c.JSON(pageInfo)
})
app.Use(paginate.New(paginate.Config{
SortKey: "sort",
DefaultSort: "id",
AllowedSorts: []string{"id", "name", "created_at"},
}))
// GET /users?sort=name,-created_at
// → Sort: [{Field: "name", Order: "asc"}, {Field: "created_at", Order: "desc"}]
app.Use(paginate.New())
app.Get("/feed", func(c fiber.Ctx) error {
pageInfo, ok := paginate.FromContext(c)
if !ok {
return fiber.ErrBadRequest
}
if pageInfo.Cursor != "" {
// Decode the cursor to get keyset values
values := pageInfo.CursorValues()
// Use values["id"], values["created_at"], etc. for WHERE clause
}
// results is a slice of items from your database query
// After fetching results, set the next cursor for the client
if len(results) > 0 {
lastItem := results[len(results)-1]
if err := pageInfo.SetNextCursor(map[string]any{
"id": lastItem.ID,
"created_at": lastItem.CreatedAt,
}); err != nil {
return err
}
}
return c.JSON(fiber.Map{
"data": results,
"has_more": pageInfo.HasMore,
"next_cursor": pageInfo.NextCursor,
})
})
// First request: GET /feed?limit=20
// Next request: GET /feed?cursor=<token>&limit=20
app.Use(paginate.New(paginate.Config{
PageKey: "p",
LimitKey: "size",
DefaultPage: 1,
DefaultLimit: 25,
SortKey: "order_by",
DefaultSort: "created_at",
AllowedSorts: []string{"created_at", "name", "email"},
CursorKey: "after",
CursorParam: "starting_after",
}))
| Property | Type | Description | Default |
|---|---|---|---|
| Next | func(fiber.Ctx) bool | Next defines a function to skip this middleware when returned true. | nil |
| PageKey | string | Query string key for page number. | "page" |
| DefaultPage | int | Default page number. | 1 |
| LimitKey | string | Query string key for limit. | "limit" |
| DefaultLimit | int | Default items per page. | 10 |
| SortKey | string | Query string key for sort. | "" |
| DefaultSort | string | Default sort field. | "id" |
| AllowedSorts | []string | Whitelist of allowed sort fields. If nil, all fields are allowed. | nil |
| OffsetKey | string | Query string key for offset. | "offset" |
| CursorKey | string | Query string key for cursor-based pagination. | "cursor" |
| CursorParam | string | Optional alias for the cursor query key. | "" |
| MaxLimit | int | Maximum items per page. | 100 |
var ConfigDefault = Config{
Next: nil,
PageKey: "page",
DefaultPage: 1,
LimitKey: "limit",
DefaultLimit: 10,
MaxLimit: 100,
DefaultSort: "id",
OffsetKey: "offset",
CursorKey: "cursor",
}
The PageInfo struct is stored in the request context and provides:
| Method | Description |
|---|---|
Start() int | Returns calculated start index (from page/limit or offset) |
SortBy(field, order) | Adds a sort field (chainable) |
NextPageURL(baseURL) | Generates next page URL with default keys |
NextPageURLWithKeys(baseURL, pageKey, limitKey) | Generates next page URL with custom query keys |
PreviousPageURL(baseURL) | Generates previous page URL (empty on page 1) |
PreviousPageURLWithKeys(baseURL, pageKey, limitKey) | Generates previous page URL with custom query keys |
NextCursorURL(baseURL) | Generates next cursor URL (empty if no more) |
NextCursorURLWithKeys(baseURL, cursorKey, limitKey) | Generates next cursor URL with custom query keys |
CursorValues() | Decodes cursor token into key-value map |
SetNextCursor(values) | Encodes values into cursor token, sets HasMore; returns error |
MaxLimit (default: 100, configurable) to prevent excessive memory usageAllowedSorts400 Bad RequestSetNextCursor returns an error if the encoded token would exceed 2048 characters, preventing the server from issuing cursors it would later reject400 Bad Request via Fiber's error handlerDefaultSort is not included in AllowedSorts, it falls back to the first allowed sort field