Skip to main content

Overview

The Auth0 Go SDK provides built-in pagination support for Management API endpoints that return lists of resources. The SDK uses a Page type with automatic iterator support to simplify working with paginated data.

Page Type

The Page type represents a single page of results:
management/core/page.go
type Page[Cursor comparable, T any, R any] struct {
    Results      []T
    Response     R
    RawResponse  PageResponse[Cursor, T, R]
    StatusCode   int
    Header       http.Header
    NextPageFunc func(context.Context) (*Page[Cursor, T, R], error)
}

Type Parameters

  • Cursor: The type used for pagination (e.g., int for page numbers, string for checkpoint cursors)
  • T: The type of individual items in the page (e.g., Client, User)
  • R: The response type returned by the paginated endpoint

Pagination Methods

The SDK supports two pagination approaches:
  1. Page-based pagination - Uses page numbers and per-page counts (offset-based)
  2. Checkpoint pagination - Uses cursors to mark positions (cursor-based)

Page-Based Pagination

Most Management API endpoints use page-based pagination with page and per_page parameters.

Using GetNextPage

Manually fetch pages one at a time:
import (
    "errors"
    "github.com/auth0/go-auth0/v2/management"
    "github.com/auth0/go-auth0/v2/management/core"
)

ctx := context.Background()
page := 0

for {
    listRequest := &management.ListClientsRequestParameters{
        Page:    management.Int(page),
        PerPage: management.Int(25),
    }

    clientsPage, err := mgmt.Clients.List(ctx, listRequest)
    if err != nil {
        return err
    }

    // Process current page results
    for _, client := range clientsPage.Results {
        fmt.Printf("Client: %s (%s)\n",
            client.GetName(),
            client.GetClientID(),
        )
    }

    // Try to get next page
    nextPage, err := clientsPage.GetNextPage(ctx)
    if err != nil {
        if errors.Is(err, core.ErrNoPages) {
            // No more pages, we're done
            break
        }
        return err
    }

    // Continue with next page
    clientsPage = nextPage
    page++
}

Using Page Iterator

The recommended approach using automatic pagination:
listRequest := &management.ListClientsRequestParameters{
    PerPage: management.Int(50),
}

clientsPage, err := mgmt.Clients.List(ctx, listRequest)
if err != nil {
    return err
}

iterator := clientsPage.Iterator()
for iterator.Next(ctx) {
    client := iterator.Current()
    fmt.Printf("Client: %s (%s)\n",
        client.GetName(),
        client.GetClientID(),
    )
}

if iterator.Err() != nil {
    return iterator.Err()
}
The iterator automatically fetches the next page when needed, making it easier to process all results without manual page tracking.

Filtering and Sorting

Combine pagination with filtering and sorting:
listRequest := &management.ListClientsRequestParameters{
    AppType:       management.String("spa"),
    Fields:        management.String("client_id,name,app_type"),
    IncludeFields: management.Bool(true),
    PerPage:       management.Int(25),
}

clientsPage, err := mgmt.Clients.List(ctx, listRequest)
if err != nil {
    return err
}

iterator := clientsPage.Iterator()
for iterator.Next(ctx) {
    client := iterator.Current()
    // Only SPA clients will be returned
    fmt.Printf("SPA Client: %s\n", client.GetName())
}

Checkpoint Pagination

Some endpoints like Logs and Organizations use checkpoint (cursor-based) pagination, where you use the ID of the last item as a checkpoint for the next request.
logListRequest := &management.ListLogsRequestParameters{
    Take: management.Int(100),
}

logsPage, err := mgmt.Logs.List(ctx, logListRequest)
if err != nil {
    return err
}

iterator := logsPage.Iterator()
for iterator.Next(ctx) {
    log := iterator.Current()
    fmt.Printf("Log: %s - %s\n",
        log.GetID(),
        log.GetType(),
    )
}

if iterator.Err() != nil {
    return iterator.Err()
}

Manual Checkpoint Pagination

For advanced use cases where you need fine-grained control:
var fromLogID *string

for {
    logListRequest := &management.ListLogsRequestParameters{
        Take: management.Int(100),
    }

    // Set checkpoint from previous iteration
    if fromLogID != nil {
        logListRequest.From = fromLogID
    }

    logsPage, err := mgmt.Logs.List(ctx, logListRequest)
    if err != nil {
        return err
    }

    // Process logs
    for _, log := range logsPage.Results {
        fmt.Printf("Log: %s - %s\n",
            log.GetID(),
            log.GetType(),
        )
    }

    // Check if we have more logs
    if len(logsPage.Results) == 0 {
        break // No more logs
    }

    // Use the ID of the last log as the checkpoint for the next request
    lastLog := logsPage.Results[len(logsPage.Results)-1]
    fromLogID = lastLog.ID
}

Error Handling

The pagination system uses the ErrNoPages sentinel error to signal when no more pages are available:
management/core/page.go
var ErrNoPages = errors.New("no pages remain")

Checking for End of Pages

import "errors"

nextPage, err := currentPage.GetNextPage(ctx)
if err != nil {
    if errors.Is(err, core.ErrNoPages) {
        // This is expected - no more pages
        fmt.Println("Finished processing all pages")
        return nil
    }
    // This is an actual error
    return fmt.Errorf("error fetching next page: %w", err)
}

Iterator Error Handling

The iterator automatically handles ErrNoPages:
iterator := page.Iterator()
for iterator.Next(ctx) {
    // Process item
    item := iterator.Current()
}

// Check for errors (ErrNoPages is filtered out)
if iterator.Err() != nil {
    return fmt.Errorf("iterator error: %w", iterator.Err())
}

Common Patterns

Collecting All Results

func getAllClients(ctx context.Context, mgmt *client.Management) ([]*management.Client, error) {
    var allClients []*management.Client
    
    listRequest := &management.ListClientsRequestParameters{
        PerPage: management.Int(100),
    }
    
    clientsPage, err := mgmt.Clients.List(ctx, listRequest)
    if err != nil {
        return nil, err
    }
    
    iterator := clientsPage.Iterator()
    for iterator.Next(ctx) {
        allClients = append(allClients, iterator.Current())
    }
    
    if iterator.Err() != nil {
        return nil, iterator.Err()
    }
    
    return allClients, nil
}

Processing with Early Exit

func findUserByEmail(ctx context.Context, mgmt *client.Management, email string) (*management.User, error) {
    listRequest := &management.ListUsersRequestParameters{
        Search:  management.String(fmt.Sprintf("email:\"%s\"", email)),
        PerPage: management.Int(50),
    }
    
    usersPage, err := mgmt.Users.List(ctx, listRequest)
    if err != nil {
        return nil, err
    }
    
    iterator := usersPage.Iterator()
    for iterator.Next(ctx) {
        user := iterator.Current()
        if user.GetEmail() == email {
            return user, nil // Found it!
        }
    }
    
    if iterator.Err() != nil {
        return nil, iterator.Err()
    }
    
    return nil, fmt.Errorf("user not found")
}

Batch Processing

func batchProcessUsers(ctx context.Context, mgmt *client.Management) error {
    listRequest := &management.ListUsersRequestParameters{
        PerPage: management.Int(50),
    }
    
    usersPage, err := mgmt.Users.List(ctx, listRequest)
    if err != nil {
        return err
    }
    
    pageNum := 0
    for {
        pageNum++
        fmt.Printf("Processing page %d (%d users)\n", pageNum, len(usersPage.Results))
        
        // Process all users in current page
        for _, user := range usersPage.Results {
            if err := processUser(ctx, user); err != nil {
                return fmt.Errorf("error processing user %s: %w", user.GetUserID(), err)
            }
        }
        
        // Get next page
        nextPage, err := usersPage.GetNextPage(ctx)
        if err != nil {
            if errors.Is(err, core.ErrNoPages) {
                break
            }
            return err
        }
        usersPage = nextPage
    }
    
    fmt.Printf("Processed %d pages\n", pageNum)
    return nil
}

Best Practices

The Iterator() pattern is cleaner and handles pagination automatically. Use GetNextPage() only when you need fine-grained control over page fetching.
Balance between fewer API calls (larger pages) and memory usage (smaller pages). Typical values are 25-100 items per page.
When using GetNextPage(), always check for ErrNoPages using errors.Is() to distinguish between “no more pages” and actual errors.
Pass a context to pagination methods to enable cancellation of long-running operations:
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()

iterator := page.Iterator()
for iterator.Next(ctx) {
    // Will respect timeout
}
When processing large result sets, be mindful of Auth0’s rate limits. Consider adding delays between pages if processing many resources.

Next Steps

Management API

Learn more about the Management API client

Error Handling

Learn how to handle pagination errors

Build docs developers (and LLMs) love