This guide covers downloading content from Komga, including individual book files, entire series as ZIP archives, and read list exports.
Download operations require the FILE_DOWNLOAD role. Check with your administrator if you don’t have access.
Download a single book
Download the original book file (CBZ, CBR, PDF, EPUB, etc.):
import { downloadBookFile } from 'komga-sdk';
const result = await downloadBookFile({
client,
path: { bookId: 'book-123' },
});
if (result.data) {
// result.data is a Blob containing the file
const blob = result.data;
// In a browser, trigger download
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'book.cbz'; // Use actual filename from response headers
a.click();
URL.revokeObjectURL(url);
}
Download with wildcard path
Some proxy setups require the wildcard file endpoint. Use this variant if standard download fails with a 404 in reverse-proxy routes.
import { downloadBookFile1 } from 'komga-sdk';
const result = await downloadBookFile1({
client,
path: { bookId: 'book-123' },
});
if (result.data) {
const url = URL.createObjectURL(result.data);
// Trigger download or display
}
Getting the original filename
The original filename is returned in the Content-Disposition header:
import { downloadBookFile } from 'komga-sdk';
const result = await downloadBookFile({
client,
path: { bookId: 'book-123' },
});
if (result.data && result.response) {
const contentDisposition = result.response.headers.get('Content-Disposition');
// Parse filename from: attachment; filename*=UTF-8''My%20Book.cbz
const filenameMatch = contentDisposition?.match(/filename\*?=['"]?(?:UTF-8'')?([^;\n"']+)/i);
const filename = filenameMatch ? decodeURIComponent(filenameMatch[1]) : 'download';
console.log(`Downloading: ${filename}`);
}
Download a series as ZIP
Download all books in a series as a single ZIP archive:
import { downloadSeriesAsZip } from 'komga-sdk';
const result = await downloadSeriesAsZip({
client,
path: { seriesId: 'series-123' },
});
if (result.data) {
// result.data is a Blob containing the ZIP file
const blob = result.data;
console.log(`Downloaded ${blob.size} bytes`);
}
Large series may take significant time to download. The server creates the ZIP archive on-the-fly, which can be resource-intensive.
Download a read list as ZIP
Export all books in a read list as a ZIP archive:
import { downloadReadListAsZip } from 'komga-sdk';
const result = await downloadReadListAsZip({
client,
path: { readListId: 'readlist-123' },
});
if (result.data) {
const blob = result.data;
// Save in Node.js environment
const buffer = await blob.arrayBuffer();
await fs.writeFile('readlist.zip', Buffer.from(buffer));
}
Common workflows
Download with progress tracking
For large downloads, you may want to track progress:
async function downloadWithProgress(bookId: string, onProgress: (percent: number) => void) {
const response = await fetch(`${baseUrl}/api/v1/books/${bookId}/file`, {
headers: {
'Authorization': `Basic ${credentials}`,
},
});
if (!response.ok) {
throw new Error(`Download failed: ${response.status}`);
}
const contentLength = response.headers.get('Content-Length');
const total = contentLength ? parseInt(contentLength) : 0;
const reader = response.body?.getReader();
if (!reader) throw new Error('No response body');
const chunks: Uint8Array[] = [];
let received = 0;
while (true) {
const { done, value } = await reader.read();
if (done) break;
chunks.push(value);
received += value.length;
if (total > 0) {
onProgress((received / total) * 100);
}
}
return new Blob(chunks);
}
// Usage
const blob = await downloadWithProgress('book-123', (percent) => {
console.log(`Download progress: ${percent.toFixed(1)}%`);
});
Batch download books
Download multiple books with a queue:
import { downloadBookFile, getBooksBySeriesId } from 'komga-sdk';
async function downloadBooksFromSeries(seriesId: string, downloadDir: string) {
// Get all books in series
const result = await getBooksBySeriesId({
client,
path: { seriesId },
query: { sort: ['metadata.numberSort,asc'] },
});
if (!result.data) return;
const downloads: { name: string; blob: Blob }[] = [];
for (const book of result.data.content) {
console.log(`Downloading: ${book.metadata.title}`);
const downloadResult = await downloadBookFile({
client,
path: { bookId: book.id },
});
if (downloadResult.data) {
downloads.push({
name: `${book.metadata.number} - ${book.metadata.title}.${book.media.mediaType.split('/')[1]}`,
blob: downloadResult.data,
});
}
// Add delay between downloads to be nice to the server
await new Promise(resolve => setTimeout(resolve, 500));
}
return downloads;
}
Download for offline reading
Create an offline reading package:
import {
downloadBookFile,
getBookById,
getBookPages,
} from 'komga-sdk';
interface OfflineBook {
metadata: {
id: string;
title: string;
series: string;
number: string;
};
file: Blob;
pageCount: number;
}
async function prepareOfflineBook(bookId: string): Promise<OfflineBook> {
// Get book metadata
const bookResult = await getBookById({
client,
path: { bookId },
});
if (!bookResult.data) {
throw new Error('Book not found');
}
// Get page count
const pagesResult = await getBookPages({
client,
path: { bookId },
});
// Download the file
const downloadResult = await downloadBookFile({
client,
path: { bookId },
});
if (!downloadResult.data) {
throw new Error('Download failed');
}
return {
metadata: {
id: bookResult.data.id,
title: bookResult.data.metadata.title,
series: bookResult.data.seriesTitle,
number: bookResult.data.metadata.number,
},
file: downloadResult.data,
pageCount: pagesResult.data?.length ?? 0,
};
}
Browser download helpers
Trigger browser download
function triggerDownload(blob: Blob, filename: string) {
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}
// Usage
const result = await downloadBookFile({
client,
path: { bookId: 'book-123' },
});
if (result.data) {
triggerDownload(result.data, 'my-book.cbz');
}
Download with filename from server
async function downloadBook(bookId: string) {
const result = await downloadBookFile({
client,
path: { bookId },
});
if (!result.data || !result.response) {
throw new Error('Download failed');
}
// Extract filename from Content-Disposition header
const disposition = result.response.headers.get('Content-Disposition') ?? '';
let filename = 'download';
// Handle UTF-8 encoded filenames
const utf8Match = disposition.match(/filename\*=UTF-8''(.+)/i);
if (utf8Match) {
filename = decodeURIComponent(utf8Match[1]);
} else {
// Fallback to regular filename
const match = disposition.match(/filename="?([^";\n]+)"?/i);
if (match) {
filename = match[1];
}
}
triggerDownload(result.data, filename);
}
Error handling
import { downloadBookFile } from 'komga-sdk';
const result = await downloadBookFile({
client,
path: { bookId: 'book-123' },
});
if (result.error) {
switch (result.response?.status) {
case 401:
console.error('Not authenticated');
break;
case 403:
console.error('Download permission denied - FILE_DOWNLOAD role required');
break;
case 404:
console.error('Book not found');
break;
default:
console.error('Download error:', result.error);
}
}
Next steps
Books
Browse and manage your book library.
Read Lists
Create custom reading lists for export.