Back to All Tools

Read EPUB Books Online

Open EPUB files directly in your browser with chapter navigation, comfortable typography controls, and private local reading.

100% Free
No Signup
No Upload
Browser Powered
EPUB

Drop your EPUB here

or choose an ebook from your device

Reader

Upload an EPUB file to open the reader.

How the EPUB reader works

This online EPUB reader opens ebook files directly in your browser using local client-side processing. Once the file is loaded, you can browse chapters, move between sections, and adjust reading settings without sending the book to a remote server. If you want a downloadable fixed-layout copy after reading, you can continue to our EPUB to PDF converter.

How to use it

  1. Choose an EPUB file from your device.
  2. Wait for the book metadata and chapter list to load.
  3. Use Previous, Next, or the table of contents to move around the book.
  4. Change the theme, font size, or font style to match your reading preference.

Why use an online EPUB reader

An online EPUB reader is useful when you want to open an ebook quickly without installing extra software. It is especially handy on shared devices, Chromebooks, and systems where you just need a fast browser-based reading experience.

Related tools

or prepend if (/ ]/i.test(fixed)) { fixed = fixed.replace(/( ]*>)/i, '$1' + css); } else { fixed = css + fixed; } return fixed; } // ─── Parse TOC ───────────────────────────────────────────────────────────────── async function parseToc(opfDoc, opfBase, zip, manifest, parser) { // Try EPUB 3: item with properties="nav" let navItem = null; for (const id in manifest) { if (manifest[id].properties.includes('nav')) { navItem = manifest[id]; break; } } if (navItem) { try { const navText = await zip.file(navItem.fullPath).async('text'); const navDoc = parser.parseFromString(navText, 'text/html'); const navEl = navDoc.querySelector('nav[epub\\:type="toc"], nav[epub\\:type*="toc"], nav'); if (navEl) { const list = navEl.querySelector('ol, ul'); if (list) return parseNavList(list, navItem.fullPath); } } catch(e) {} } // Try EPUB 2: NCX const ncxId = opfDoc.querySelector('spine') && opfDoc.querySelector('spine').getAttribute('toc'); if (ncxId && manifest[ncxId]) { try { const ncxText = await zip.file(manifest[ncxId].fullPath).async('text'); const ncxDoc = parser.parseFromString(ncxText, 'application/xml'); return parseNcxNavMap(ncxDoc.querySelector('navMap'), manifest[ncxId].fullPath); } catch(e) {} } return []; } function parseNavList(listEl, baseFile) { const items = []; if (!listEl) return items; listEl.querySelectorAll(':scope > li').forEach(li => { const a = li.querySelector('a'); const label = a ? (a.textContent || '').trim() : ''; const href = a ? (a.getAttribute('href') || '') : ''; const fullPath = href ? resolveEpubPath(baseFile.includes('/') ? baseFile.substring(0, baseFile.lastIndexOf('/') + 1) : '', href) : ''; const sub = li.querySelector('ol, ul'); items.push({ label, fullPath, subitems: sub ? parseNavList(sub, baseFile) : [] }); }); return items; } function parseNcxNavMap(navMap, ncxBasePath) { const items = []; if (!navMap) return items; navMap.querySelectorAll(':scope > navPoint').forEach(np => { const label = np.querySelector('navLabel > text') ? (np.querySelector('navLabel > text').textContent || '').trim() : ''; const src = np.querySelector('content') ? (np.querySelector('content').getAttribute('src') || '') : ''; const fullPath = src ? resolveEpubPath(ncxBasePath.includes('/') ? ncxBasePath.substring(0, ncxBasePath.lastIndexOf('/') + 1) : '', src) : ''; const splitPath = fullPath.split('#')[0]; const sub = np.querySelector('navPoint') ? np : null; const subItems = []; if (sub) { np.querySelectorAll(':scope > navPoint').forEach(child => { const cl = child.querySelector('navLabel > text') ? (child.querySelector('navLabel > text').textContent || '').trim() : ''; const cs = child.querySelector('content') ? (child.querySelector('content').getAttribute('src') || '') : ''; const cp = cs ? resolveEpubPath(ncxBasePath.includes('/') ? ncxBasePath.substring(0, ncxBasePath.lastIndexOf('/') + 1) : '', cs).split('#')[0] : ''; subItems.push({ label: cl, fullPath: cp, subitems: [] }); }); } items.push({ label, fullPath: splitPath, subitems: subItems }); }); return items; } // ─── Build sidebar TOC ───────────────────────────────────────────────────────── function buildToc(tocItems, spineItems) { if (tocItems.length) { tocList.innerHTML = ''; tocItems.forEach(item => appendTocItem(item, spineItems, 0)); } else { // Fallback: list spine items numerically tocList.innerHTML = ''; spineItems.forEach((path, index) => { const btn = document.createElement('button'); btn.type = 'button'; btn.className = 'toc-btn'; btn.textContent = 'Chapter ' + (index + 1); btn.dataset.chapterIndex = index; btn.addEventListener('click', () => showChapter(index)); tocList.appendChild(btn); }); } } function appendTocItem(item, spineItems, depth) { if (!item.label) return; const btn = document.createElement('button'); btn.type = 'button'; btn.className = 'toc-btn'; btn.textContent = (depth > 0 ? '\u00a0\u00a0'.repeat(depth) + '› ' : '') + item.label; // Find which spine index corresponds to this TOC href const targetPath = item.fullPath ? item.fullPath.split('#')[0] : ''; let chapterIndex = spineItems.findIndex(p => p === targetPath || p.endsWith('/' + targetPath)); if (chapterIndex === -1 && targetPath) { // Try partial match chapterIndex = spineItems.findIndex(p => p.includes(targetPath) || targetPath.includes(p.split('/').pop() || '')); } btn.dataset.chapterIndex = chapterIndex >= 0 ? chapterIndex : -1; btn.addEventListener('click', () => { const idx = parseInt(btn.dataset.chapterIndex, 10); if (idx >= 0) showChapter(idx); }); tocList.appendChild(btn); if (item.subitems && item.subitems.length) { item.subitems.forEach(child => appendTocItem(child, spineItems, depth + 1)); } } function markActiveToc(index) { tocList.querySelectorAll('.toc-btn').forEach(btn => { btn.classList.toggle('active', parseInt(btn.dataset.chapterIndex, 10) === index); }); } // ─── Cleanup ─────────────────────────────────────────────────────────────────── function cleanupBook() { if (!book) return; Object.values(book.resourceMap).forEach(url => URL.revokeObjectURL(url)); book = null; viewer.innerHTML = '
Upload an EPUB file to open the reader.
'; tocList.innerHTML = '
Chapters will appear after the EPUB is loaded.
'; bookTitle.textContent = 'No book loaded'; bookAuthor.textContent = 'Upload an EPUB to start reading.'; bookDetails.textContent = 'File info will appear here after loading.'; bookLocation.textContent= 'Current location: waiting for book'; } // ─── Helpers ────────────────────────────────────────────────────────────────── function formatSize(bytes) { if (bytes < 1024) return bytes + ' B'; if (bytes < 1048576) return (bytes / 1024).toFixed(1) + ' KB'; return (bytes / 1048576).toFixed(2) + ' MB'; }