EPUB Conversion & Validation Guide
How to turn a finished Word manuscript into an IngramSpark-compliant EPUB 3.0 file. Built from real conversion + validator-error troubleshooting on The Rope and the Algorithm.
0. Prerequisite: the source docx must already be clean
Before converting, confirm the manuscript docx has:
- Proper Heading1/Heading2/Heading3 paragraph styles on every chapter/section title (EPUB navigation and the ebook’s internal chapter breaks are built from these — a manuscript with plain bold text instead of real heading styles produces an EPUB with no usable table of contents).
- No blank/empty heading-styled paragraphs anywhere (these were a real defect caught earlier — empty paragraphs carrying a Heading1 style produce blank entries in Word’s Navigation Pane and can create stray blank chapter breaks after EPUB conversion). Search for this before converting.
- Diacritics (Ś, ū, ā, etc.) typed as real Unicode characters, not special-formatting hacks — they survive conversion fine as long as they’re real characters.
1. Conversion — recommended path: Calibre (free)
- Install Calibre if needed (calibre-ebook.com).
- Add Books → select the manuscript .docx directly. Calibre imports Word docs natively; no separate export step needed first.
- Select the book → Convert Books → set output format to EPUB.
- Critical setting: in the EPUB Output panel of the convert dialog, explicitly set the EPUB version to 3 (Calibre 9.x defaults to EPUB 2 unless you change this — this was the single biggest issue on the first pass: a file converted with defaults came out as EPUB 2.0, which IngramSpark’s file spec explicitly rejects: “Format: EPUB 3.0 required”).
- In the same dialog, under Metadata, set real values before converting (don’t leave Calibre’s placeholder defaults):
- Published date — Calibre defaults to
0101-01-01if untouched. Always overwrite with the real intended publication date. - Tags — Calibre sometimes folds the subtitle into a tag field with commas replaced by underscores if pulled from certain docx metadata fields (seen as
Evidence_ procedure_ and trustworthy AIon one pass). Check the Tags field before converting; delete anything that isn’t an actual subject keyword. - Ids/Identifiers — add the book’s real ISBN here (type:
isbn, value: ISBN-13 with no dashes), so it’s embedded in the file’s own metadata, not just declared separately on the IngramSpark upload form.
- Published date — Calibre defaults to
- Click OK to convert, then Save to disk / export to generate the actual
.epubfile — editing metadata in the Calibre library does not update a file already exported to disk; you must re-export after any metadata change.
2. Image size check
IngramSpark’s EPUB spec: no single image inside an EPUB may exceed 3.2 million pixels. Check every embedded figure:
from PIL import Image
im = Image.open("path/to/image.png")
w, h = im.size
print(w, h, w*h, "OVER 3.2M" if w*h > 3200000 else "ok")
Figures well under a megapixel (typical diagram/chart exports) are fine; full-bleed cover-resolution images accidentally embedded at retail-cover size (e.g. 1600×2500) are not — resize before embedding.
3. EPUB3 metadata — attributes that will fail validation if left over from EPUB2
This is the exact set of validator errors hit on first upload, and their fixes. All seven traced back to one root cause: EPUB3’s OPF schema does not allow the old EPUB2 opf:role / opf:file-as / opf:scheme attributes directly on dc: elements — only id, dir, and xml:lang are permitted there now. Calibre’s EPUB3 export can still leave these EPUB2-style attributes in place, so check content.opf (the package file) manually after conversion.
| Validator error | Cause | Fix |
|---|---|---|
attribute "opf:role" not allowed here (on dc:creator) | EPUB2-style author-role attribute left on the element | Remove the attribute; add <meta refines="#creator" property="role" scheme="marc:relators">aut</meta>, referencing the creator element’s id |
attribute "opf:file-as" not allowed here | Same, for the sortable author name | Remove; add <meta refines="#creator" property="file-as">Das, Viveka Mohan</meta> |
attribute "opf:role" not allowed here (on dc:contributor) | Same pattern on the Calibre-generated “bkp” contributor | Remove; add <meta refines="#contributor" property="role" scheme="marc:relators">bkp</meta> |
found attribute "opf:scheme", but no attributes allowed here / attribute "opf:scheme" not allowed here; expected attribute "id" (×3, on the UUID/ISBN/Calibre identifiers) | Same EPUB2 pattern on every dc:identifier | Remove opf:scheme from each identifier. For the ISBN specifically, express the type via <meta refines="#isbn_id" property="identifier-type" scheme="onix:codelist5">15</meta> (ONIX code 15 = ISBN-13), referencing that identifier’s own id |
(OPF-014): The property "svg" should be declared in the OPF file | The title page XHTML wraps the cover image in an inline <svg> element (Calibre’s standard full-bleed-cover technique) but the manifest entry for that file doesn’t declare it | Add properties="svg" to that item’s <item> tag in the manifest |
Also required for a valid EPUB3 package (add if missing):
<meta property="dcterms:modified">YYYY-MM-DDT00:00:00Z</meta>in the metadata block.- A proper EPUB3 nav document (
nav.xhtml) with<nav epub:type="toc">, declared in the manifest withproperties="nav". Calibre normally generates this when EPUB3 output is correctly selected — verify it exists; if onlytoc.ncx(EPUB2-only nav) is present, EPUB3 output wasn’t actually applied.
4. Rebuilding the zip container correctly
An EPUB is a zip file with one hard structural rule: mimetype must be the first entry in the archive, stored uncompressed (not deflated). If you ever hand-edit files inside an unzipped EPUB and rezip with a generic tool, verify this explicitly — a naive zip -r or default zipfile-library rezip will compress everything uniformly and can break the mimetype requirement.
Python approach that preserves this correctly:
import zipfile, os
with zipfile.ZipFile(out_path, 'w') as zf:
zf.write(os.path.join(base, 'mimetype'), 'mimetype', compress_type=zipfile.ZIP_STORED)
for root, dirs, files in os.walk(base):
for fn in files:
full = os.path.join(root, fn)
rel = os.path.relpath(full, base).replace(os.sep, '/')
if rel == 'mimetype':
continue
zf.write(full, rel, compress_type=zipfile.ZIP_DEFLATED)
Verify after rebuilding: unzip -v file.epub | head -5 should show mimetype first with method Stored.
5. Validation before upload
- Run the file through the IDPF EPUB validator (validator.idpf.org) before uploading to IngramSpark — this is what IngramSpark’s own file guide points to, and it catches most of the errors above before you burn an upload attempt.
- On IngramSpark’s own upload validation screen, fix every listed error — don’t click “Yes, Proceed with My Files as They are” over unresolved validator errors; they indicate real downstream processing failures, not cosmetic warnings.
- After any fix, re-verify XML well-formedness of every hand-edited file:
import xml.etree.ElementTree as ET ET.parse("content.opf") # raises if malformed ET.parse("nav.xhtml")
6. Reusable pre-upload checklist
- Converted with EPUB 3 explicitly selected (check
<package version="3.0">in content.opf, not “2.0”) - Published date is real, not Calibre’s
0101-01-01default - Tags/subjects are clean actual keywords, no mangled subtitle-as-tag artifacts
- Real ISBN embedded as a
dc:identifier - No leftover EPUB2
opf:role/opf:file-as/opf:schemeattributes directly ondc:elements -
dcterms:modifiedmeta present - EPUB3
nav.xhtmlexists and is declared withproperties="nav" - Any content file with inline SVG has
properties="svg"on its manifest item - All images under 3.2 million pixels
-
mimetypeis the first zip entry, stored uncompressed - Passed IDPF validator with zero errors before uploading
AI was used for research purposes on this guide, and for final formatting. Every fact and source is independently verified against the original; the analysis, argument and prose are the author's own.