> ## 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.

# Thumbnails

> Retrieve, upload, select, and delete thumbnails

Thumbnail image operations return binary `Blob | File` data. Listing operations return generated thumbnail DTOs. Upload, selection, deletion, and regeneration operations require **ADMIN**.

Komga normally generates posters during scans. A listing DTO records the thumbnail `id`, `selected` flag, `type`, `mediaType`, dimensions, and file size; the `type` is a server-provided string (commonly `SIDECAR`, `EMBEDDED`, or `UPLOADED`), so use `selected` rather than assuming a particular type is active.

## Book thumbnails

Get the selected image, list available thumbnails, or fetch a specific thumbnail. Parameters are flat:

```typescript theme={null}
import {
  getBookThumbnail,
  getBookThumbnails,
  getBookThumbnailById,
} from 'komga-sdk';

const image = await getBookThumbnail({ bookId: 'book-123' }, { client });
if (!image.error && image.data) {
  const url = URL.createObjectURL(image.data);
  console.log(url);
}

const available = await getBookThumbnails({ bookId: 'book-123' }, { client });
for (const thumbnail of available.data ?? []) {
  console.log(thumbnail.id, thumbnail.selected, thumbnail.type);
}

const specific = await getBookThumbnailById(
  { bookId: 'book-123', thumbnailId: 'thumb-456' },
  { client },
);
```

Upload a custom poster by passing `file` directly. Do not wrap it in a `body` or `path` object:
The optional `selected: true` query parameter can make a newly uploaded poster active immediately. If you omit it, upload the poster first, then select it with the `mark...ThumbnailSelected` operation.

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

const file = document.querySelector<HTMLInputElement>('#thumbnail')?.files?.[0];
if (file) {
  const result = await addUserUploadedBookThumbnail(
    { bookId: 'book-123', file, selected: true },
    { client },
  );
  if (!result.error) console.log(result.data?.id);
}
```

Select or delete a user-uploaded poster with flat `bookId` and `thumbnailId` parameters:

```typescript theme={null}
import {
  markBookThumbnailSelected,
  deleteUserUploadedBookThumbnail,
} from 'komga-sdk';

const selected = await markBookThumbnailSelected(
  { bookId: 'book-123', thumbnailId: 'thumb-456' },
  { client },
);
const removed = await deleteUserUploadedBookThumbnail(
  { bookId: 'book-123', thumbnailId: 'thumb-456' },
  { client },
);

for (const result of [selected, removed]) {
  if (result.error) console.error(result.response?.status, result.error);
  else console.log(`Accepted (${result.response?.status})`);
}
```

Both operations return HTTP 202 with an `unknown` body; a successful result has no thumbnail payload to inspect.

Selection and deletion are asynchronous. After a `202`, refetch the listing or selected image when the server-side task has completed instead of assuming the old image URL has changed immediately. The SDK does not cache thumbnail bytes or metadata; any browser/service-worker cache is application-owned.

When you refresh a UI, revoke object URLs created for removed images. A URL created before selection can continue to display the old bytes even after the server has a new selected poster, so replace it after the refresh.

## Series thumbnails

Series operations use `seriesId`:

```typescript theme={null}
import {
  getSeriesThumbnail,
  getSeriesThumbnails,
  getSeriesThumbnailById,
  addUserUploadedSeriesThumbnail,
  markSeriesThumbnailSelected,
  deleteUserUploadedSeriesThumbnail,
} from 'komga-sdk';

const current = await getSeriesThumbnail({ seriesId: 'series-123' }, { client });
const list = await getSeriesThumbnails({ seriesId: 'series-123' }, { client });
const selected = await getSeriesThumbnailById(
  { seriesId: 'series-123', thumbnailId: 'thumb-456' },
  { client },
);

const file = new File([imageBlob], 'series-thumb.jpg', { type: 'image/jpeg' });
const uploaded = await addUserUploadedSeriesThumbnail(
  { seriesId: 'series-123', file },
  { client },
);
await markSeriesThumbnailSelected(
  { seriesId: 'series-123', thumbnailId: uploaded.data?.id ?? 'thumb-456' },
  { client },
);
await deleteUserUploadedSeriesThumbnail(
  { seriesId: 'series-123', thumbnailId: 'thumb-456' },
  { client },
);

if (!current.error && current.data) console.log(URL.createObjectURL(current.data));
console.log(list.data, selected.data);
```

Selection and deletion return HTTP 202 with an `unknown` body. Check `result.error` when the operation must be confirmed.

## Collection thumbnails

Collection paths use the generated parameter name `id`, not `collectionId`:

```typescript theme={null}
import {
  getCollectionThumbnail,
  getCollectionThumbnails,
  getCollectionThumbnailById,
  addUserUploadedCollectionThumbnail,
  markCollectionThumbnailSelected,
  deleteUserUploadedCollectionThumbnail,
} from 'komga-sdk';

const current = await getCollectionThumbnail({ id: 'collection-123' }, { client });
const list = await getCollectionThumbnails({ id: 'collection-123' }, { client });
const specific = await getCollectionThumbnailById(
  { id: 'collection-123', thumbnailId: 'thumb-456' },
  { client },
);

const file = new File([imageBlob], 'collection-thumb.jpg', { type: 'image/jpeg' });
await addUserUploadedCollectionThumbnail(
  { id: 'collection-123', file },
  { client },
);
await markCollectionThumbnailSelected(
  { id: 'collection-123', thumbnailId: 'thumb-456' },
  { client },
);
await deleteUserUploadedCollectionThumbnail(
  { id: 'collection-123', thumbnailId: 'thumb-456' },
  { client },
);

if (!current.error && current.data) console.log(URL.createObjectURL(current.data));
console.log(list.data, specific.data);
```

## Read-list thumbnails

Read-list paths also use `id`:

```typescript theme={null}
import {
  getReadListThumbnail,
  getReadListThumbnails,
  getReadListThumbnailById,
  addUserUploadedReadListThumbnail,
  markReadListThumbnailSelected,
  deleteUserUploadedReadListThumbnail,
} from 'komga-sdk';

const current = await getReadListThumbnail({ id: 'readlist-123' }, { client });
const list = await getReadListThumbnails({ id: 'readlist-123' }, { client });
const specific = await getReadListThumbnailById(
  { id: 'readlist-123', thumbnailId: 'thumb-456' },
  { client },
);

const file = new File([imageBlob], 'readlist-thumb.jpg', { type: 'image/jpeg' });
await addUserUploadedReadListThumbnail(
  { id: 'readlist-123', file },
  { client },
);
await markReadListThumbnailSelected(
  { id: 'readlist-123', thumbnailId: 'thumb-456' },
  { client },
);
await deleteUserUploadedReadListThumbnail(
  { id: 'readlist-123', thumbnailId: 'thumb-456' },
  { client },
);

if (!current.error && current.data) console.log(URL.createObjectURL(current.data));
console.log(list.data, specific.data);
```

## Build a thumbnail selector

List metadata first, then fetch only the thumbnails the user needs. Use the `selected` flag to render the current choice and refetch after selection because the selection call returns `202` without a thumbnail payload:

```typescript theme={null}
import {
  getBookThumbnailById,
  getBookThumbnails,
  markBookThumbnailSelected,
} from 'komga-sdk';

const bookId = 'book-123';
const choices = await getBookThumbnails({ bookId }, { client });

for (const choice of choices.data ?? []) {
  const preview = await getBookThumbnailById(
    { bookId, thumbnailId: choice.id },
    { client },
  );
  if (preview.error || !preview.data) continue;

  const previewUrl = URL.createObjectURL(preview.data);
  console.log({ id: choice.id, selected: choice.selected, previewUrl });
  // Attach previewUrl to the UI and revoke it when the element is removed.
}

const selected = await markBookThumbnailSelected(
  { bookId, thumbnailId: 'thumb-456' },
  { client },
);
if (!selected.error) {
  const refreshed = await getBookThumbnails({ bookId }, { client });
  console.log(refreshed.data);
}
```

## Regenerate book thumbnails

Regeneration is an asynchronous admin operation. Pass `for_bigger_result_only` only when you want to regenerate posters that can be improved:

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

const result = await booksRegenerateThumbnails(
  { for_bigger_result_only: false },
  { client },
);
if (result.error) {
  console.error(result.response?.status, result.error);
} else {
  // HTTP 202 with an unknown body; processing continues on the server.
  console.log(`Accepted (${result.response?.status})`);
}
```

## Binary response handling

Every image getter returns `Blob | File`. Revoke object URLs when the image is no longer needed:

```typescript theme={null}
function objectUrl(file: Blob | File): string {
  return URL.createObjectURL(file);
}

function releaseObjectUrl(url: string): void {
  URL.revokeObjectURL(url);
}
```

## Next steps

> **[Book content](/guides/book-content)**
>
> Access book pages and reading positions.

> **[Books](/guides/books)**
>
> Learn about book management operations.

> **[Administration](/guides/admin)**
>
> Review admin permissions and asynchronous tasks.


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