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?
}
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
Field Description metadata.titleBook title metadata.numberIssue/volume number metadata.releaseDateRelease date createdWhen added to Komga lastModifiedLast modification fileSizeFile size
Field Description metadata.titleSeries title metadata.statusPublication status createdWhen added to Komga lastModifiedLast modification booksCountNumber of books
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.
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 })
);
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 ,
};
}
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 );
}
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.