epubit
v1.0.6
Published
Client-side EPUB 3 builder — create valid EPUB files directly in the browser
Maintainers
Readme
epubit — User Manual
A client-side JavaScript library for building valid EPUB 3 files directly in the browser. No server required.
🚀 Live Demo
Features
- 📚 Build valid EPUB 3 files directly in the browser
- 🚫 No server required
- ⚡ Fast and lightweight
- 🎨 Full client-side control
- 📖 Support for standard EPUB 3 specification
Table of Contents
- Installation
- Quick Start
- Constructor
- Metadata
- Cover Image
- Table of Contents Page
- Chapters
- Stylesheets
- Images
- Fonts
- Sanitizer
- Generating the EPUB
- Method Chaining
- Full Example
- HTML Elements Reference
- EPUB Structure Reference
- Error Reference
1. Installation
Via <script> tag (browser)
Include JSZip first, then epubit.js. JSZip will also be auto-injected if missing, but including it yourself is more reliable.
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/epubit/epubit.js"></script>EBook is now available globally on window.
Via CommonJS / Node-compatible bundler
const EBook = require("./epubit.js");Via ESM bundler (Webpack, Vite, Rollup)
The library uses a UMD wrapper, so it works with any bundler. Just import it:
import EBook from "./epubit.js";Note: The library targets browser environments. It depends on
DOMParser,FileReader,URL.createObjectURL, anddocument. It will not work in a pure Node.js environment without a DOM polyfill.
2. Quick Start
const book = new EBook({ title: "My First Book", author: "Jane Doe" });
book.addCSS("style.css", `
body { font-family: Georgia, serif; line-height: 1.6; }
h1 { font-size: 1.8em; }
`);
book.setCover(base64ImageString, "image/jpeg", "cover.jpg");
book.addTOCPage();
book.addChapter("Introduction", `
<h1>Introduction</h1>
<p>Welcome to my book.</p>
`, { css: ["style.css"] });
book.addChapter("Chapter One", `
<h1>Chapter One</h1>
<p>The story begins here.</p>
`, { css: ["style.css"] });
await book.download();3. Constructor
const book = new EBook(options);Creates a new EBook instance. All options are optional.
| Option | Type | Default | Description |
|---|---|---|---|
| title | string | "Untitled" | Book title |
| author | string | "Unknown" | Author name |
| language | string | "en" | BCP 47 language tag, e.g. "en", "fr", "ja" |
| publisher | string | "" | Publisher name |
| description | string | "" | Short book description |
| date | string | today | Publication date in YYYY-MM-DD format |
| uuid | string | auto | Custom UUID for the book identifier |
| rights | string | "" | Copyright / rights statement |
| sanitize | boolean | true | Auto-sanitize chapter HTML before writing |
const book = new EBook({
title: "Dune",
author: "Frank Herbert",
language: "en",
publisher: "Chilton Books",
description: "A science fiction epic set on the desert planet Arrakis.",
date: "1965-08-01",
rights: "© 1965 Frank Herbert",
});4. Metadata
setMeta(meta) → this
Update any metadata field after construction. Accepts the same keys as the constructor.
book.setMeta({ title: "Dune Messiah", author: "Frank Herbert" });Partial updates are fine — only the keys you pass are changed.
getMeta() → object
Returns a shallow copy of the current metadata object.
const meta = book.getMeta();
console.log(meta.title); // "Dune Messiah"5. Cover Image
setCover(data, mimeType, filename, opts) → this
Sets the cover image for the book.
| Parameter | Type | Default | Description |
|---|---|---|---|
| data | string | ArrayBuffer | Blob | — | Image data. Base64 string, ArrayBuffer, or Blob |
| mimeType | string | "image/jpeg" | MIME type of the image |
| filename | string | "cover.jpg" | Filename stored inside the EPUB |
| opts.asPage | boolean | true | Insert a visible cover page as the first item in the spine |
| opts.altText | string | "Cover" | Alt text for the cover <img> element |
// From a base64 string
book.setCover(base64str, "image/jpeg", "cover.jpg");
// From a file input
const file = document.querySelector("input[type=file]").files[0];
book.setCover(file, file.type, file.name);
// Cover image only — no visible page (for readers that use it as metadata only)
book.setCover(base64str, "image/png", "cover.png", { asPage: false });
// Custom alt text
book.setCover(base64str, "image/jpeg", "cover.jpg", {
asPage: true,
altText: "Cover art for Dune"
});When asPage: true (the default), the cover is inserted as a full-bleed page — black background, centered image — as the very first page the reader opens.
removeCover() → this
Removes the cover image and cover page.
book.removeCover();6. Table of Contents Page
epubit.js generates two kinds of TOC automatically: an EPUB 3 nav.xhtml (used by modern readers for their built-in TOC sidebar) and an EPUB 2 toc.ncx (for legacy readers). These are structural and invisible as readable pages.
addTOCPage() adds a third, human-readable TOC as a real page inside the book — the kind you'd find printed on page 3 of a physical book. It has numbered entries and clickable links.
addTOCPage(opts) → this
| Option | Type | Default | Description |
|---|---|---|---|
| opts.title | string | "Table of Contents" | Heading displayed at the top of the page |
| opts.css | string[] | [] | Stylesheet filenames to link (must be added via addCSS) |
| opts.inlineStyle | string | — | Raw CSS string injected as a <style> block, overriding the built-in default style |
// Default — uses built-in styling
book.addTOCPage();
// Custom heading
book.addTOCPage({ title: "Contents" });
// Linked to your own stylesheet
book.addTOCPage({ title: "Contents", css: ["style.css"] });
// Inline style override
book.addTOCPage({
title: "Contents",
inlineStyle: `
body { font-family: sans-serif; background: #fafafa; }
h1 { color: #333; }
a { color: #0066cc; }
`
});The TOC page is always placed after the cover (if any) and before chapter one. It lists every chapter in order with a number prefix and a clickable link.
Note: Call
addTOCPage()before or after adding chapters — the page is built at generation time, so it always reflects the final chapter list.
removeTOCPage() → this
Disables the TOC page if you change your mind.
book.removeTOCPage();7. Chapters
addChapter(title, html, opts) → this
Adds a chapter to the book.
| Parameter | Type | Default | Description |
|---|---|---|---|
| title | string | — | Chapter title, shown in the TOC |
| html | string | — | Body HTML (inner content, not a full document) |
| opts.id | string | auto | Explicit item ID. Auto-generated from title if omitted |
| opts.order | number | append | Insertion position. Lower numbers appear first |
| opts.css | string[] | [] | Stylesheet filenames to link |
| opts.raw | boolean | false | Skip the sanitizer for this chapter only |
// Minimal
book.addChapter("Prologue", "<p>It began on a Tuesday.</p>");
// With stylesheet
book.addChapter("Chapter One", chapterHtml, { css: ["style.css"] });
// Explicit order — insert before other chapters
book.addChapter("Preface", prefaceHtml, { order: 0 });
// Custom ID
book.addChapter("About the Author", authorHtml, { id: "about-author" });
// Skip sanitizer (use with trusted HTML only)
book.addChapter("Technical Appendix", rawXhtml, { raw: true });The HTML you pass is treated as the body content — headings, paragraphs, images, tables, lists, and links are all supported. You do not need to include <html>, <head>, or <body> tags.
updateChapter(id, title, html) → this
Update a chapter's title and/or HTML content in place.
book.updateChapter("ch-1-chapter-one", "Chapter One (Revised)", newHtml);
// Update title only
book.updateChapter("ch-1-chapter-one", "Chapter One (Revised)");
// Update content only
book.updateChapter("ch-1-chapter-one", undefined, newHtml);Throws an error if the ID is not found.
removeChapter(id) → this
Removes a chapter by its ID.
book.removeChapter("ch-2-draft-notes");reorderChapters(idArray) → this
Re-sequence chapters by passing an array of IDs in the new desired order.
// Move chapter 3 to the front
book.reorderChapters(["ch-3-epilogue", "ch-1-intro", "ch-2-main"]);IDs not mentioned keep their relative positions after the listed ones.
getChapters() → object[]
Returns a shallow list of chapter metadata (does not include HTML content).
const chapters = book.getChapters();
// [{ id: "ch-1-introduction", title: "Introduction", order: 0 }, …]8. Stylesheets
addCSS(filename, content) → this
Adds a CSS file to the EPUB. Reference it in chapters via opts.css.
book.addCSS("style.css", `
body {
font-family: Georgia, "Times New Roman", serif;
font-size: 1em;
line-height: 1.7;
margin: 0 auto;
max-width: 36em;
padding: 1em 1.5em;
}
h1, h2, h3 { font-weight: bold; margin-top: 2em; }
p { margin: 0 0 1em; text-indent: 1.5em; }
p:first-child { text-indent: 0; }
blockquote { border-left: 3px solid #ccc; padding-left: 1em; color: #555; }
table { border-collapse: collapse; width: 100%; }
th, td { border: 1px solid #ccc; padding: .4em .8em; }
img { max-width: 100%; height: auto; }
code { font-family: monospace; background: #f4f4f4; padding: .1em .3em; }
`);Then link it when adding chapters:
book.addChapter("Chapter One", html, { css: ["style.css"] });Multiple stylesheets are supported. Pass all relevant filenames in the css array:
book.addCSS("base.css", baseStyles);
book.addCSS("chapter.css", chapterStyles);
book.addChapter("Chapter One", html, { css: ["base.css", "chapter.css"] });removeCSS(filename) → this
book.removeCSS("draft-style.css");9. Images
addImage(filename, data, mimeType) → this
Adds an image asset. Once added, reference it inside chapter HTML using a relative path: ../images/<filename>.
| Parameter | Type | Description |
|---|---|---|
| filename | string | e.g. "photo.jpg" — used as the reference path |
| data | string | ArrayBuffer | Blob | Base64 string, ArrayBuffer, or Blob |
| mimeType | string | Optional. Inferred from extension if omitted |
Supported formats: JPEG, PNG, GIF, WebP, AVIF, SVG.
// From a base64 string
book.addImage("sunset.jpg", base64string, "image/jpeg");
// From a Blob (e.g. fetched from the web)
const response = await fetch("https://example.com/photo.png");
const blob = await response.blob();
book.addImage("photo.png", blob);
// From a file input
const file = inputEl.files[0];
book.addImage(file.name, file);Then reference it in chapter HTML:
<figure>
<img src="../images/sunset.jpg" alt="A sunset over the ocean"/>
<figcaption>Figure 1: Sunset at Cape Point</figcaption>
</figure>removeImage(filename) → this
book.removeImage("draft-diagram.png");getImages() → string[]
Returns a list of all added image filenames.
book.getImages(); // ["cover.jpg", "sunset.jpg", "map.png"]10. Fonts
addFont(filename, data, mimeType) → this
Embeds a font file. Reference it from a CSS @font-face rule inside a stylesheet you add via addCSS.
Supported formats: WOFF2 (recommended), WOFF, TTF, OTF.
// Add the font file
const fontResponse = await fetch("OpenSans-Regular.woff2");
const fontData = await fontResponse.arrayBuffer();
book.addFont("OpenSans-Regular.woff2", fontData, "font/woff2");
// Reference it in CSS
book.addCSS("style.css", `
@font-face {
font-family: "Open Sans";
font-style: normal;
font-weight: 400;
src: url("../fonts/OpenSans-Regular.woff2") format("woff2");
}
body { font-family: "Open Sans", sans-serif; }
`);Note: Font embedding increases EPUB file size. WOFF2 is the most compressed format — prefer it over TTF or OTF.
11. Sanitizer
The sanitizer runs automatically on every chapter's HTML (unless sanitize: false was set in the constructor or opts.raw: true was passed to addChapter). It converts HTML to EPUB-safe XHTML.
What it keeps
All common content elements are preserved:
- Structure:
<section>,<article>,<div>,<header>,<footer>,<aside>,<main> - Headings:
<h1>through<h6> - Paragraphs & text blocks:
<p>,<blockquote>,<pre>,<hr>,<br> - Inline text:
<em>,<strong>,<b>,<i>,<u>,<s>,<span>,<mark>,<small>,<sup>,<sub>,<del>,<ins>,<abbr>,<cite>,<q>,<code>,<kbd>,<samp>,<var>,<time> - Links:
<a href="...">— relative,http://,https://, and internal#anchorlinks all work - Images:
<img src="..." alt="...">,<figure>,<figcaption>,<picture>,<source> - Lists:
<ul>,<ol>,<li>,<dl>,<dt>,<dd> - Tables:
<table>,<thead>,<tbody>,<tfoot>,<tr>,<th>,<td>,<caption>,<colgroup>,<col>— includingcolspanandrowspan - Semantic / accessibility:
<details>,<summary>,<ruby>,<rt>,<rp>,epub:type,role,aria-label,id,class,lang
What it strips
| Removed entirely (tag + content) | Tag stripped, children kept |
|---|---|
| <script> | Any tag not in the allowlist |
| <style> | |
| <iframe>, <frame>, <frameset> | |
| <form>, <input>, <button> | |
| <object>, <embed>, <applet> | |
| <canvas>, <svg> | |
| <link>, <meta>, <base> | |
| <noscript> | |
URL validation
All href, src, srcset, and cite attributes are checked. The following are blocked:
javascript:— XSS vectorvbscript:— XSS vectordata:URIs that are notdata:image/...
target="_blank" is stripped (not valid in EPUB).
Manual sanitization
You can sanitize HTML manually before passing it anywhere:
// Instance method
const clean = book.sanitize(rawHtml);
// Static utility — no instance needed
const clean = EBook.sanitize(rawHtml);Disabling the sanitizer
// Disable globally for the entire book (only do this with fully trusted HTML)
const book = new EBook({ sanitize: false });
// Disable for one specific chapter
book.addChapter("Appendix", trustedXhtml, { raw: true });12. Generating the EPUB
All generation methods are async and must be awaited.
download(filename?) → Promise<void>
Generates the EPUB and triggers a browser download. The file dialog opens automatically.
await book.download(); // filename: "my-book.epub" (slugified from title)
await book.download("dune.epub"); // custom filenamegenerate() → Promise<Blob>
Returns the EPUB as a Blob. Use this when you need to handle the file yourself.
const blob = await book.generate();
// Store it
const url = URL.createObjectURL(blob);
// Send it to a server
const form = new FormData();
form.append("file", blob, "book.epub");
await fetch("/upload", { method: "POST", body: form });toBase64() → Promise<string>
Returns the EPUB as a base64-encoded string. Useful for embedding or transmitting as JSON.
const b64 = await book.toBase64();
// "UEsDBBQACAgIAA..."
// Store in localStorage, send over API, etc.
localStorage.setItem("myBook", b64);toObjectURL() → Promise<string>
Returns a temporary object URL you can assign to a link or iframe. Remember to revoke it when done.
const url = await book.toObjectURL();
// Preview in an iframe
document.querySelector("iframe").src = url;
// Or as a download link
const link = document.createElement("a");
link.href = url;
link.download = "book.epub";
link.textContent = "Download EPUB";
document.body.appendChild(link);
// Clean up when no longer needed
URL.revokeObjectURL(url);13. Method Chaining
Every setter method returns this, so calls can be chained:
const book = new EBook({ title: "My Book", author: "Me" })
.addCSS("style.css", cssContent)
.setCover(coverData, "image/jpeg")
.addTOCPage({ title: "Contents" })
.addChapter("One", ch1Html, { css: ["style.css"] })
.addChapter("Two", ch2Html, { css: ["style.css"] })
.addChapter("Three", ch3Html, { css: ["style.css"] });
await book.download();14. Full Example
// ── 1. Create the book ──────────────────────────────────────────────────────
const book = new EBook({
title: "The Midnight Garden",
author: "Elara Voss",
language: "en",
publisher: "Nightshade Press",
description: "A story of secrets and seasons.",
date: "2024-06-01",
rights: "© 2024 Elara Voss. All rights reserved.",
});
// ── 2. Add a stylesheet ──────────────────────────────────────────────────────
book.addCSS("style.css", `
body {
font-family: Georgia, serif;
font-size: 1em;
line-height: 1.8;
max-width: 36em;
margin: 0 auto;
padding: 2em 1.5em;
color: #1a1a1a;
}
h1 { font-size: 1.6em; margin-top: 3em; text-align: center; }
p { margin: 0; text-indent: 1.5em; }
p + p { margin-top: .8em; }
blockquote {
border-left: 3px solid #999;
margin: 1.5em 0;
padding: .5em 1em;
color: #555;
font-style: italic;
}
figure { text-align: center; margin: 2em 0; }
figcaption { font-size: .85em; color: #777; margin-top: .5em; }
img { max-width: 100%; }
table { border-collapse: collapse; width: 100%; margin: 1.5em 0; }
th, td { border: 1px solid #ddd; padding: .5em 1em; text-align: left; }
th { background: #f5f5f5; }
`);
// ── 3. Set the cover ─────────────────────────────────────────────────────────
// Assuming you have a base64 string or a File object from a file input:
book.setCover(coverBase64, "image/jpeg", "cover.jpg", {
asPage: true,
altText: "Cover of The Midnight Garden",
});
// ── 4. Add a TOC page ────────────────────────────────────────────────────────
book.addTOCPage({ title: "Contents", css: ["style.css"] });
// ── 5. Add chapters ──────────────────────────────────────────────────────────
book.addChapter("Prologue", `
<h1>Prologue</h1>
<p>The garden had no name, only a gate that opened at midnight.</p>
`, { css: ["style.css"] });
book.addChapter("Chapter One: The Gate", `
<h1>Chapter One: The Gate</h1>
<p>She found it on the first night of autumn.</p>
<figure>
<img src="../images/gate.jpg" alt="An old iron gate in moonlight"/>
<figcaption>The gate at midnight</figcaption>
</figure>
<p>The iron was cold under her fingers, slick with dew.</p>
<blockquote>
<p>What you find beyond the gate is what you carried with you all along.</p>
</blockquote>
`, { css: ["style.css"] });
book.addChapter("Chapter Two: The Seasons", `
<h1>Chapter Two: The Seasons</h1>
<p>The garden changed. Every visit brought a different sky.</p>
<table>
<thead>
<tr><th>Season</th><th>What grew</th><th>What faded</th></tr>
</thead>
<tbody>
<tr><td>Spring</td><td>White roses</td><td>Frost</td></tr>
<tr><td>Summer</td><td>Wildflowers</td><td>Shadows</td></tr>
<tr><td>Autumn</td><td>Red leaves</td><td>Warmth</td></tr>
<tr><td>Winter</td><td>Silence</td><td>Everything</td></tr>
</tbody>
</table>
`, { css: ["style.css"] });
// ── 6. Add an image asset ─────────────────────────────────────────────────────
book.addImage("gate.jpg", gateImageBlob, "image/jpeg");
// ── 7. Generate and download ──────────────────────────────────────────────────
await book.download("the-midnight-garden.epub");15. HTML Elements Reference
Quick reference for what you can use inside chapter HTML.
Text & Headings
<h1>Chapter Title</h1>
<h2>Section</h2>
<h3>Subsection</h3>
<p>A paragraph of text.</p>
<p>Text with <em>italics</em>, <strong>bold</strong>, <mark>highlighted</mark>, <code>code</code>.</p>
<p>Footnote reference<sup>1</sup>. Chemical formula H<sub>2</sub>O.</p>
<p><del>Removed text</del> and <ins>inserted text</ins>.</p>
<p><abbr title="HyperText Markup Language">HTML</abbr> is the language of the web.</p>
<hr/>
<br/>Blockquote & Preformatted
<blockquote cite="https://example.com/source">
<p>To be or not to be.</p>
</blockquote>
<pre><code>function hello() {
console.log("Hello, world!");
}</code></pre>Links
<!-- External link -->
<a href="https://example.com">Visit example.com</a>
<!-- Internal anchor within the same chapter -->
<a href="#section-2">Jump to Section 2</a>
<h2 id="section-2">Section 2</h2>
<!-- Link to another chapter -->
<a href="../text/ch-2-chapter-two.xhtml">Go to Chapter Two</a>Tip: Chapter filenames follow the pattern
ch-<n>-<slugified-title>.xhtml. Check auto-generated IDs usingbook.getChapters().
Images & Figures
<!-- Simple image -->
<img src="../images/photo.jpg" alt="Description of photo"/>
<!-- Image with caption -->
<figure>
<img src="../images/diagram.png" alt="A diagram" width="400"/>
<figcaption>Figure 1.1: System overview</figcaption>
</figure>Lists
<!-- Unordered -->
<ul>
<li>Apples</li>
<li>Oranges</li>
</ul>
<!-- Ordered -->
<ol start="3">
<li>Third item</li>
<li>Fourth item</li>
</ol>
<!-- Definition list -->
<dl>
<dt>Spice</dt>
<dd>A substance that extends life and expands consciousness.</dd>
</dl>Tables
<table>
<caption>Table 1: Comparison of formats</caption>
<thead>
<tr>
<th scope="col">Format</th>
<th scope="col">Size</th>
<th scope="col">Quality</th>
</tr>
</thead>
<tbody>
<tr>
<td>JPEG</td>
<td>Small</td>
<td>Lossy</td>
</tr>
<tr>
<td colspan="2">PNG / WebP</td>
<td>Lossless</td>
</tr>
</tbody>
</table>Semantic Structure
<section epub:type="chapter">
<h1>Chapter Title</h1>
<p>Content...</p>
</section>
<aside>
<p>A sidebar note or callout box.</p>
</aside>
<details>
<summary>Expand for more detail</summary>
<p>Hidden content revealed on interaction (reader support varies).</p>
</details>16. EPUB Structure Reference
The generated EPUB contains the following file tree:
book.epub
├── mimetype (uncompressed — required by spec)
├── META-INF/
│ └── container.xml (points to OEBPS/content.opf)
└── OEBPS/
├── content.opf (EPUB 3 package document — manifest + spine)
├── toc.ncx (EPUB 2 navigation — for legacy readers)
├── nav.xhtml (EPUB 3 navigation document)
├── styles/
│ └── style.css (your stylesheets)
├── fonts/
│ └── OpenSans.woff2 (your fonts)
├── images/
│ ├── cover.jpg (cover image)
│ └── photo.jpg (chapter images)
└── text/
├── cover.xhtml (cover page — if asPage: true)
├── toc-page.xhtml (human-readable TOC — if addTOCPage() called)
├── ch-1-prologue.xhtml (chapter files)
└── ch-2-chapter-one.xhtmlThe spine order is always: Cover page → TOC page → Chapters (in order).
17. Error Reference
| Error message | Cause | Fix |
|---|---|---|
| epubit.js: cannot generate — no chapters added. | generate() or download() called with no chapters | Add at least one chapter with addChapter() before generating |
| epubit.js: chapter "id" not found | updateChapter() called with an ID that doesn't exist | Check IDs with getChapters() |
| epubit.js: JSZip could not be loaded. | CDN unreachable and JSZip not included manually | Add <script src="jszip.min.js"> before epubit.js |
| epubit.js: failed to load <url> | Script injection failed (network error, CSP, etc.) | Include JSZip manually rather than relying on auto-injection |
epubit.js produces valid EPUB 3.0 files with EPUB 2 (NCX) backward compatibility. Tested against Calibre, Apple Books, Kobo, and Google Play Books.
