# foliate-js is the rendering half of an e-book reader, and nothing more

> foliate-js is a MIT licensed, dependency free JavaScript library for rendering e-books in the browser, covering EPUB, MOBI, KF8, FB2, CBZ and experimental PDF, with a documented interface you implement to add a format of your own. The author states plainly that the library is not stable and that the API may change at any time, and the security section explains why it refuses to run scripted content in EPUB files.

**johnfactotum/foliate-js** — Render e-books in the browser

- Repository: https://github.com/johnfactotum/foliate-js
- Website: https://johnfactotum.github.io/foliate-js/reader.html
- Stars: 1,145 · Forks: 291
- Language: JavaScript
- License: MIT
- Published: 2026-10-19 · Updated: 2026-10-19 · Language: en
- Canonical page: https://hysenlabs.com/projects/johnfactotum-foliate-js

## A library, with the reader left to you

foliate-js is a library for rendering e-books in the browser, and the first thing to understand is that the reader in the repository is not the product.

The repository includes a demo viewer that opens local files, and the README is explicit that reader.html, together with reader.js and its associated files in ui/, is not considered part of the library itself. It is likened to the Epub.js Reader, and the expectation is that you modify it or replace it with your own code.

That framing is the whole design. The library gives you the parts that are hard and format specific: parsing EPUB, MOBI and KF8, FB2 and CBZ, turning a book into sections, paginating reflowable text, laying out fixed layout books, searching, tracking progress and drawing annotations. What it does not give you is the chrome around that, the settings screen, the library shelf, the sync, the keyboard handling.

The feature list is short and each item is a design statement. It is pure JavaScript, small and modular, with no hard dependencies, it does not require loading the whole file into memory, and it does not care about older browsers. The memory point is the one that shapes the architecture, because it is why the book interface exposes a size per section and a load and unload pair rather than handing you a single parsed document.

The audience is a developer who wants a reading engine and intends to write the reading application, and the project is used in several stable releases of Foliate, which is the best evidence that the code works in production even though the library itself is not stable.

## Three families of modules and one entry point

The documentation describes the structure in terms of three kinds of module, and the arrangement is what makes the library small.

The first family parses and loads books, implementing what the documentation calls the book interface. That is comic-book.js for comic book archives in CBZ, epub.js and epubcfi.js for EPUB, fb2.js for FictionBook 2, and mobi.js for both Mobipocket files and KF8 files, which are the ones commonly known as AZW3. Adding a format of your own means implementing that interface, and the feature list offers it as a first class option rather than a limitation.

The second family handles pagination and implements the renderer interface: fixed-layout.js for fixed layout books and paginator.js for reflowable ones.

The third is auxiliary modules that add functionality rather than load a book: overlayer.js for rendering annotations, progress.js for reading progress and search.js for searching.

The rule is that in general the modules do not depend on each other directly, they depend on the interfaces. There is one exception, view.js, which is the higher level renderer that strings most of the things together and is the main entry point of the library.

That design is why the tree at the top level looks like a list of single files with clear responsibilities. There is also more in the repository than the documentation's three families, including dict.js, footnotes.js, opds.js, quote-image.js, text-walker.js, tts.js and uri-template.js, which tells you the author has been extending the library towards dictionaries, footnotes, catalogue services and text to speech. The metadata and the OPDS and dictionary support are why the topic list includes dictd, stardict and opds.

## The book interface is the contract you implement

If you add a format, you implement the book interface, and the documentation is specific enough to be useful, which is unusual for a library at version 0.0.0.

The core is a sections array. Each section carries a load method returning the URL that will be rendered, which may be async, an unload method that can free the section, a createDocument method returning a Document object used for searching, which may also be async, a size in bytes used to show reading progress, a linear property that is the string no when the section is not part of the linear reading sequence, a cfi holding the base CFI string of the section, and an id used for looking up table of contents items.

Around that sit the book level properties. dir is the page progression direction, rtl or ltr. toc is the table of contents as an array of items with a label, an href that does not have to be a valid URL, and subitems, and pageList has the same shape for the EPUB page list. metadata follows more or less the metadata schema of Readium's webpub manifest, where a title or author can be a string, an object keyed by language, or an array of either. rendition carries the EPUB rendition properties, and when its layout is pre-paginated the book is rendered with the fixed layout renderer. resolveHref takes an href and returns the index of the referenced section and an anchor method that resolves a document fragment.

The result is that adding a format is a matter of producing sections and metadata, not of integrating with a pagination engine, because the renderer only knows the interface.

## Scripted content is refused, and the refusal has a reason

The Security section is the most important part of the README, and it is a section about a feature the library does not have.

EPUB books can contain scripted content, that is, JavaScript inside the e-book, which is potentially dangerous. The library does not support it, and the two reasons given are precise. First, it is currently impossible to do so securely, because the content is served from the same origin using blob: URLs, so a script in the book runs with the same privileges as your own page. Second, because of WebKit Bug 218086, the allow-scripts attribute is required on iframes, which renders the iframe sandbox useless.

The conclusion is that it is imperative to use Content Security Policy to block all scripts except 'self'. And there is a caution box underneath: do not use this library, or any other e-book library for that matter, without CSP unless you completely trust the content you are rendering or can block scripts by other means.

The reasoning is worth internalising because it generalises. Any reader that renders untrusted books in the browser is a potential remote code execution surface, and the way to reduce that is to serve the book content from an origin where a script cannot reach your application's storage, your session or your API calls. The library declines to do the unsafe thing and then tells you what to do instead.

There is also a note about the IDPF font deobfuscation algorithm, which requires a SHA-1 function. By default the library uses Web Crypto, which is only available in secure contexts, so over plain HTTP you would have to modify reader.js and pass your own SHA-1 implementation. That is a small detail with a real consequence for deployment.

## Native ES modules, no build step, one custom element

The project uses native ES modules with no build step, and you import them directly. The basic usage example is short enough to read in full.

```js
import './foliate-js/view.js'

const view = document.createElement('foliate-view')
document.body.append(view)

view.addEventListener('relocate', e => {
    console.log('location changed')
    console.log(e.detail)
})

// can open a File/Blob object or a URL
// or any object that implements the "book" interface
await view.open('example.epub')
await view.goTo(/* path, section index, or CFI */)
```

Importing view.js registers a custom element, foliate-view, which is then the view you put in your page. It emits a relocate event when the location changes, and it accepts a File, a Blob, a URL, or any object implementing the book interface, which is the same interface you would implement to add a format. The navigation call accepts a path, a section index or a CFI.

There is no build step for consumers, though the package metadata does contain a build script that runs rollup, and the dev dependencies include rollup with the node resolve and terser plugins, along with @zip.js/zip.js and fflate for compression, pdfjs-dist for the experimental PDF support, and eslint with its flat config for linting. The package exports every .js file at the top level, and its version is 0.0.0, which is another way of saying the same thing the README says: there is no released artifact to depend on.

The README's advice for that situation is to include the library as a git submodule in your project, so that you can update it easily. That is a deliberate recommendation to track the branch, and it comes with the warning below.

## The status section says do not depend on this yet

The Current Status section is short and unusually blunt. It works reasonably well and has been used in several stable releases of Foliate. This library itself is, however, not stable. Expect it to break and the API to change at any time. Use it at your own risk.

Combined with the version of 0.0.0 in the package metadata and the absence of GitHub releases, the picture is consistent. This is a library in active development by one author, where the code is proven in a real application but the published interface is not frozen.

The demo viewer is described in the same terms, as very incomplete at the moment and lacking many basic features such as keyboard shortcuts. So a newcomer should not expect to clone the repository and have a reader.

What this means in practice is that the library is best treated as a codebase to vendor or fork, with the submodule approach as the recommended way to track it, and with the understanding that an upgrade can break your integration. The trade-off is against building the format handling yourself, which for EPUB and MOBI is a serious amount of work, particularly the MOBI decryption and the KF8 variant of it.

The formats the library claims are EPUB, MOBI, KF8, FB2, CBZ and PDF, with PDF marked as experimental and requiring PDF.js. A team that only needs EPUB and has no appetite for maintaining a fork is better served by a project with a release and a stability promise, and this is not that project.

## Conclusion

foliate-js suits a developer building a reading interface of their own, who needs the format handling, pagination and search done properly and is prepared to own the parts the library deliberately leaves out, which is most of the user interface. It does not suit a team that wants a finished reader, since the demo viewer is described as very incomplete and lacks basics such as keyboard shortcuts, and it is the wrong choice for an application that must render untrusted EPUB files without a Content Security Policy. Verify first that the format you need is covered, that your deployment serves over HTTPS or that you supply a SHA-1 implementation for the IDPF font deobfuscation path, and pin the library as a git submodule as the README recommends, because there is no release to pin to.

## FAQ

### Which e-book formats can foliate-js render?

EPUB, MOBI, KF8, which is commonly known as AZW3, FB2, CBZ and PDF, where PDF is experimental and requires PDF.js. Other formats can be added by implementing the book interface.

### Is foliate-js safe to use with untrusted EPUB files?

The library refuses to run scripted content in EPUB books, because serving the content from the same origin with blob: URLs cannot be done securely. The README states it is imperative to use a Content Security Policy to block all scripts except 'self', and warns against using the library without CSP unless you completely trust the content.

### How do I use foliate-js in a project?

Import view.js, which registers a foliate-view custom element, append it to the page, and call view.open() with a File, a Blob, a URL or an object implementing the book interface. Navigation is done with view.goTo(), and location changes are reported through the relocate event.

### Is foliate-js stable?

No. The README states that the library itself is not stable, that you should expect it to break and the API to change at any time, and that you should use it at your own risk, although it has been used in several stable releases of Foliate.

### How should I depend on foliate-js?

Since there is no release yet, the README recommends including the library as a git submodule in your project so that you can update it easily, and points at the demo reader as something to modify or replace with your own code.

## Sources

- [Issues](https://github.com/johnfactotum/foliate-js/issues)
- [johnfactotum/foliate-js on GitHub](https://github.com/johnfactotum/foliate-js)
- [License: MIT](https://github.com/johnfactotum/foliate-js/blob/main/LICENSE)
- [Project website](https://johnfactotum.github.io/foliate-js/reader.html)
- [README](https://github.com/johnfactotum/foliate-js/blob/main/README.md)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/johnfactotum-foliate-js
