Skip to main content
List endpoints accept pagination and optional search filters. Domain services expose this via the list() method.

Understanding the response

Every paginated response includes metadata about the result set:
interface PageResponse<T> {
  content: T[];         // Items in current page
  totalElements: number; // Total items across all pages
  totalPages: number;    // Total number of pages
  size: number;          // Items per page
  number: number;        // Current page index (0-based)
  first: boolean;        // Is this the first page?
  last: boolean;         // Is this the last page?
  empty: boolean;        // Is the page empty?
}

Pagination basics

const books = await bookService.list({
  page: 0,   // First page (0-indexed)
  size: 20,  // 20 items per page
});

console.log(`Page ${books.number + 1} of ${books.totalPages}`);
console.log(`Showing ${books.content.length} of ${books.totalElements} total`);

Sorting

Sort values use the format field,direction where direction is asc or desc.
// Single sort
const books = await bookService.list({
  page: 0,
  size: 20,
  sort: ['metadata.title,asc'],
});

// Multiple sorts (applied in order)
const books = await bookService.list({
  page: 0,
  size: 20,
  sort: ['metadata.releaseDate,desc', 'metadata.title,asc'],
});

Common sort fields

FieldDescription
metadata.titleBook title
metadata.numberIssue/volume number
metadata.releaseDateRelease date
createdWhen added to Komga
lastModifiedLast modification
fileSizeFile size

Search filters

Search filters narrow results. They’re passed under the search parameter for domain services or body for direct API calls.
const books = await bookService.list({
  search: {
    fullTextSearch: 'manga',    // Text search
    tags: ['favorite'],          // Filter by tags
    readStatus: 'UNREAD',        // UNREAD, IN_PROGRESS, READ
    libraryId: 'library-123',    // Specific library
  },
  page: 0,
  size: 20,
});

Available filters

interface BookSearch {
  fullTextSearch?: string;  // Search title, summary, etc.
  libraryId?: string[];     // Filter by library IDs
  seriesId?: string[];      // Filter by series IDs
  tags?: string[];          // Filter by tags
  readStatus?: 'UNREAD' | 'IN_PROGRESS' | 'READ';
  authors?: string[];       // Filter by author names
  deleted?: boolean;        // Include deleted books
}
interface SeriesSearch {
  fullTextSearch?: string;  // Search title, summary
  libraryId?: string[];     // Filter by library IDs
  status?: 'ENDED' | 'ONGOING' | 'ABANDONED' | 'HIATUS';
  readStatus?: 'UNREAD' | 'IN_PROGRESS' | 'READ';
  publishers?: string[];    // Filter by publishers
  genres?: string[];        // Filter by genres
  tags?: string[];          // Filter by tags
  deleted?: boolean;        // Include deleted series
}

Unpaged requests

For small datasets, you can request all items at once:
const allBooks = await bookService.list({
  unpaged: true,
  sort: ['metadata.title,asc'],
});

console.log(`Got all ${allBooks.content.length} books`);
Use unpaged carefully! Large libraries may have thousands of items. This can be slow and memory-intensive.

Pagination patterns

Simple iteration

async function processAllBooks() {
  let page = 0;
  let hasMore = true;
  
  while (hasMore) {
    const result = await bookService.list({ page, size: 100 });
    
    for (const book of result.content) {
      processBook(book);
    }
    
    hasMore = !result.last;
    page++;
  }
}

Async generator

A more elegant approach using async generators:
async function* getAllBooks() {
  let page = 0;
  let hasMore = true;
  
  while (hasMore) {
    const result = await bookService.list({ 
      page, 
      size: 100,
      sort: ['metadata.title,asc'] 
    });
    
    for (const book of result.content) {
      yield book;
    }
    
    hasMore = !result.last;
    page++;
  }
}

// Usage
for await (const book of getAllBooks()) {
  console.log(book.metadata.title);
}

Parallel fetching (advanced)

Fetch multiple pages in parallel for better performance:
async function fetchAllPagesParallel<T>(
  fetcher: (page: number) => Promise<PageResponse<T>>,
  concurrency = 3
): Promise<T[]> {
  // First, get total pages
  const firstPage = await fetcher(0);
  const { totalPages } = firstPage;
  
  if (totalPages <= 1) return firstPage.content;
  
  // Fetch remaining pages in batches
  const allContent = [...firstPage.content];
  
  for (let batch = 1; batch < totalPages; batch += concurrency) {
    const pagePromises = [];
    for (let i = batch; i < Math.min(batch + concurrency, totalPages); i++) {
      pagePromises.push(fetcher(i));
    }
    
    const pages = await Promise.all(pagePromises);
    for (const page of pages) {
      allContent.push(...page.content);
    }
  }
  
  return allContent;
}

// Usage
const allBooks = await fetchAllPagesParallel(
  (page) => bookService.list({ page, size: 100 })
);

UI pagination component

interface PaginationState {
  page: number;
  size: number;
  sort: string[];
  search?: BookSearch;
}

async function fetchPage(state: PaginationState) {
  const result = await bookService.list({
    page: state.page,
    size: state.size,
    sort: state.sort,
    search: state.search,
  });
  
  return {
    items: result.content,
    totalItems: result.totalElements,
    totalPages: result.totalPages,
    currentPage: result.number,
    hasNext: !result.last,
    hasPrevious: !result.first,
  };
}

// React example
function useBooks(initialPage = 0, pageSize = 20) {
  const [page, setPage] = useState(initialPage);
  const [data, setData] = useState(null);
  
  useEffect(() => {
    fetchPage({ page, size: pageSize, sort: ['metadata.title,asc'] })
      .then(setData);
  }, [page, pageSize]);
  
  return {
    ...data,
    nextPage: () => setPage(p => p + 1),
    prevPage: () => setPage(p => Math.max(0, p - 1)),
    goToPage: setPage,
  };
}

Raw API pagination

When using direct API functions:
import { getBooks } from 'komga-sdk';

const result = await getBooks({
  client,
  body: { 
    fullTextSearch: 'manga' 
  },
  query: { 
    page: 0, 
    size: 20, 
    sort: ['metadata.title,asc'] 
  },
});

if (result.data) {
  console.log(result.data.totalElements);
  console.log(result.data.content);
}

Performance tips

Use appropriate page sizes

  • UI display: 10-50 items
  • Batch processing: 50-100 items
  • Never use unpaged on large datasets

Add filters when possible

  • Use libraryId to narrow scope
  • Use readStatus to filter
  • Use tags for categorization

Cache wisely

  • Cache total counts
  • Don’t cache content that changes
  • Invalidate on mutations

Handle errors

  • Retry on timeouts
  • Handle empty results
  • Validate page bounds

Next steps

Books Guide

Full book listing examples.

Best Practices

More pagination patterns.