> ## Documentation Index
> Fetch the complete documentation index at: https://komga-sdk.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Pagination and search

> Use generated list parameters, page response types, sorting, and search

Generated list operations expose pagination as flat parameters. The server returns generated page types such as `PageBookDto`, `PageCollectionDto`, and `PageReadListDto`. Pagination state belongs in your application; the SDK does not keep a cursor or automatically fetch another page.

## Page response fields

`PageBookDto` is generated as an object whose fields are optional because the OpenAPI response permits partial data:

```typescript theme={null}
import type { PageBookDto } from 'komga-sdk';

function summarizePage(page: PageBookDto) {
  return {
    items: page.content ?? [],
    totalItems: page.totalElements ?? 0,
    totalPages: page.totalPages ?? 0,
    page: page.number ?? 0,
    pageSize: page.size ?? 0,
    isFirst: page.first ?? false,
    isLast: page.last ?? false,
    isEmpty: page.empty ?? true,
  };
}
```

Use `??` when rendering or iterating so a missing field does not become an unchecked runtime access. `getLibraries` is not paginated: it returns `LibraryDto[]` directly.

## Fetch a page of books

`getBooks` accepts `page`, `size`, `sort`, and `unpaged` alongside its generated `bookSearch` body parameter. Page indexes are zero-based:

```typescript theme={null}
import { getBooks } from 'komga-sdk';

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

if (result.error === undefined) {
  const page = result.data;
  console.log(`Page ${(page.number ?? 0) + 1} of ${page.totalPages ?? 0}`);
  console.log(`Showing ${(page.content ?? []).length} of ${page.totalElements ?? 0}`);
}
```

For UI state, preserve the search and sort values when moving pages and reset `page` to `0` when the user changes a filter:

```typescript theme={null}
type BooksQuery = {
  page: number;
  size: number;
  sort: string[];
  bookSearch: { fullTextSearch?: string };
};

async function fetchBooks(query: BooksQuery) {
  const result = await getBooks(query, { client });
  if (result.error) {
    throw new Error(`Book query failed: ${result.response?.status ?? 'unknown'}`);
  }
  return result.data;
}
```

## Sorting

Pass server sort expressions in the generated `sort` array. The common Komga form is `field,direction`:

```typescript theme={null}
const result = await getBooks(
  {
    bookSearch: {},
    page: 0,
    size: 20,
    sort: ['metadata.releaseDate,desc', 'metadata.title,asc'],
  },
  { client },
);
```

Sort strings are transported to Komga; the server determines which fields and directions are accepted for an endpoint. Use a stable tie-breaker such as title or id when exporting multiple pages, and keep sort order unchanged for the duration of the export.

## Search parameters

`BookSearch` is the generated body type for `getBooks`:

```typescript theme={null}
import type { BookSearch } from 'komga-sdk';
import { getBooks } from 'komga-sdk';

const search: BookSearch = {
  fullTextSearch: 'manga',
  // condition?: SearchConditionBook
};

const result = await getBooks(
  { bookSearch: search, page: 0, size: 20 },
  { client },
);
```

Do not add the former service-layer fields such as `tags`, `readStatus`, or `libraryId` unless the generated `BookSearch` type for your SDK version includes them. TypeScript will identify fields that are not in the generated contract.

Collections and read lists use query strings instead of `BookSearch`:

```typescript theme={null}
import { getCollections, getReadLists } from 'komga-sdk';

const collections = await getCollections(
  { search: 'favorites', library_id: ['library-123'], page: 0, size: 20 },
  { client },
);

const readLists = await getReadLists(
  { search: 'to-read', page: 0, size: 20 },
  { client },
);
```

The generated parameter name `library_id` is intentional. Arrays are serialized by the generated client according to the OpenAPI contract; pass an array rather than joining ids into a comma-delimited string yourself.

## Unpaged requests

List operations expose `unpaged` when the endpoint supports it:

```typescript theme={null}
const result = await getBooks(
  {
    bookSearch: {},
    unpaged: true,
    sort: ['metadata.title,asc'],
  },
  { client },
);

if (result.error === undefined) {
  for (const book of result.data.content ?? []) {
    console.log(book.metadata.title);
  }
}
```

Use unpaged requests only when the result size is bounded by your application. Large libraries can produce large response bodies, increase memory pressure, and make retries expensive.

## Iterate through pages

This async generator stops on the generated `last` field and preserves the default non-throwing result contract:

```typescript theme={null}
import { getBooks } from 'komga-sdk';

async function* allBooks() {
  let page = 0;

  while (true) {
    const result = await getBooks(
      { bookSearch: {}, page, size: 100, sort: ['metadata.title,asc'] },
      { client },
    );

    if (result.error !== undefined) {
      throw new Error(`Book page failed: ${result.response?.status ?? 'unknown'}`);
    }

    for (const book of result.data.content ?? []) {
      yield book;
    }

    if (result.data.last ?? true) {
      return;
    }
    page += 1;
  }
}

for await (const book of allBooks()) {
  console.log(book.metadata.title);
}
```

If the server returns an incomplete page envelope, treating a missing `last` as a stop condition avoids an accidental unbounded loop. If you prefer typed API exceptions, replace the error branch with `unwrap(await getBooks(...))` and catch `KomgaApiError` at the application boundary.

## Parallel fetching and batch work

Parallel page fetching can reduce latency but increases load and can produce inconsistent snapshots while the library changes. First fetch page `0`, read `totalPages`, then bound concurrency and keep filters and sort fixed:

```typescript theme={null}
async function fetchPages<T>(
  totalPages: number,
  fetchPage: (page: number) => Promise<T>,
  concurrency = 3,
): Promise<T[]> {
  const pages: T[] = [];
  for (let start = 0; start < totalPages; start += concurrency) {
    const batch = Array.from(
      { length: Math.min(concurrency, totalPages - start) },
      (_, offset) => fetchPage(start + offset),
    );
    pages.push(...(await Promise.all(batch)));
  }
  return pages;
}
```

Use sequential iteration for mutations, rate-limited servers, or an export that needs predictable load. Never assume page numbers remain valid after deleting or adding many matching records.

## Next steps

> **[Direct API functions](/direct-api)**
>
> Learn the flat operation call shape.

> **[Result handling](/errors)**
>
> Handle generated errors or use unwrap.

> **[Runtime validation](/validation)**
>
> Add validation only when your application needs it.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.