Page numbers are meaningless for reflowable text: the same paragraph lands on different pages depending on font size, screen width, and user preferences. EPUB CFIs (Canonical Fragment Identifiers) solve this by addressing content as a path through the document tree rather than a positional offset. A CFI likeDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/johnfactotum/foliate-js/llms.txt
Use this file to discover all available pages before exploring further.
epubcfi(/6/4!/4/2/1:42) encodes the exact node and character offset within the spine item, so a bookmark remains valid across re-renders, devices, and reading apps. foliate-js uses CFIs as the primary location format — the relocate event exposes a cfi property, and goTo() accepts CFI strings directly.
What a CFI looks like
A CFI is always wrapped inepubcfi(...). The path before the ! (the indirect reference) identifies the spine item in the package document. The path after it identifies the node within that spine item’s HTML:
How foliate-js represents parsed CFIs
Theepubcfi.js module parses CFI strings into plain JavaScript values, not class instances. There are two structural forms.
The “part” object
The atomic unit is a part object, corresponding to one step and its optional offset in the CFI:index is typically present in practice. The other fields correspond to optional CFI constructs:
| Field | CFI construct | Description |
|---|---|---|
index | /n | Child node index (always even for elements, odd for text nodes) |
id | [id] | ID assertion for the element at this step |
offset | :n | Character offset within a text node |
temporal | ~n | Temporal offset (for audio/video media) |
spatial | @x:y | Spatial offset (x and y coordinates) |
text | [before,after] | Text location assertion |
side | ;s=a or ;s=b | Side bias for ambiguous positions |
Collapsed (non-range) CFIs
A collapsed CFI is an array of arrays of parts. Each inner array is one full path segment separated by!. For example, /6/4!/4 parses into:
[{index:6},{index:4}] is the path through the package document (spine element, then spine item). The second element [{index:4}] is the path within the HTML document.
Range CFIs
A range CFI parses into an object withparent, start, and end properties, each being an array of arrays of parts (the same type as a collapsed CFI):
/6/4!/2,/2,/4 — the shared parent path plus the start and end offsets within the document.
Key exports from epubcfi.js
isCFI
A RegExp that tests whether a string is wrapped in epubcfi(...):
parse(cfi)
Parses a CFI string into the nested array (for collapsed CFIs) or { parent, start, end } object (for range CFIs) described above. Accepts both bare CFI paths and the full epubcfi(...) form:
fromRange(range, filter?)
Creates a CFI string from a DOM Range:
toRange(doc, cfi, filter?)
Resolves a CFI string to a DOM Range within the given document:
joinIndir(...cfis)
Joins CFI parts with the indirect reference operator (!). Used by view.js to combine a section’s base CFI with a within-document CFI:
fake.fromIndex(index) and fake.toIndex(parts)
For book formats that have no real package document (MOBI, FictionBook, plain HTML), there is no genuine base CFI for each spine item. The fake helpers create and interpret index-based substitutes:
index → (index + 1) * 2, following the EPUB CFI convention that spine items appear at even indices under the spine element at /6.
Filtering injected nodes
If you inject your own elements into the section document (for search highlights, annotation markers, etc.), those nodes will corrupt CFI calculations unless you tell the parser to ignore them. BothfromRange and toRange accept an optional filter function that works like the filter callback of a TreeWalker:
TreeWalker:
NodeFilter.FILTER_ACCEPT— include this node in the CFI pathNodeFilter.FILTER_REJECT— exclude this node and all its descendantsNodeFilter.FILTER_SKIP— exclude this node but still traverse its children (the node is treated as transparent)
Comparing and sorting CFIs
epubcfi.js also exports a compare(a, b) function suitable for sorting arrays of CFI strings:
compare accepts either CFI strings or pre-parsed values and returns -1, 0, or 1.
Environment compatibility
epubcfi.js has no dependencies on browser-only globals. The parse, compare, joinIndir, and fake functions work in any JavaScript environment — Node.js, Deno, a service worker, or a browser — making them suitable for server-side bookmark sorting or validation.
fromRange and toRange require a DOM Document and Range, so they are browser-only unless you supply a compatible DOM implementation.
Most other foliate-js modules depend on
Blob, TextDecoder, DOMParser, URL, and related globals. Only epubcfi.js is truly environment-agnostic for its core parsing and comparison features.Current limitations
Spatial offsets (
@x:y) and temporal offsets (~n) are parsed and round-tripped correctly by parse and the internal stringify logic, but the renderer does not yet use them for positioning. CFIs containing these constructs will resolve to the nearest node rather than the exact spatial or temporal position.