Of all the little plugins I’ve vibe coded into existence, the Apple Books Annotation Import plugin is the one I actually use. Not “use” in the way you use a project once, screenshot it for a blog post, and never open again. I mean I run it every week. It’s my favorite thing I’ve built, and it’s been humming along for over a year without me touching it.
Then a few weeks ago I went looking for a highlight I knew I’d made in a PDF. It wasn’t there. None of my PDF highlights were there. A year of trusting this thing, and it turns out it had quietly been ignoring an entire category of my books the whole time.
So I went digging. And it turns out Apple Books handles PDFs in a completely different way. This is the story of getting those highlights out, and the 1.1.0 release that finally does it.
- A Quick Refresher On What This Thing Does
- Where Apple Actually Hides PDF Highlights
- Reading Highlights Out Of A Raw PDF
- Hooking It Into The Machine That Already Worked
- The Tradeoff: It’s Slower, But It’s Everything Now
- How To Install It (It’s Not In Community Plugins Yet)
- The Takeaway
A Quick Refresher On What This Thing Does
If you’ve never seen the plugin before, here’s the pitch. It’s macOS desktop only, and I’m fine with that, because being desktop only is the entire reason it can do what it does. I read in an iPad and I pay for the cheapest rung of iCloud specificallly because of this plugin to get the books where the plugin runs.
When you highlight something in Apple Books, that highlight has to live somewhere on disk. On a Mac it does. On an iPhone it’s locked in a sandbox you can’t reach. So the plugin sits on the desktop where all the good data is and pulls from every source it can find:
-
The Books SQLite databases. This is where your EPUB highlights and notes actually live, buried in
~/Library/Containers/com.apple.iBooksX. The plugin reads them straight out of SQLite. - The EPUB files themselves. The database doesn’t have everything. So the plugin also cracks open the EPUB to grab the real metadata: ISBN, publisher, language, subjects, and the cover image.
- And now, as of 1.1.0, the PDFs themselves.
Out of all that it builds a clean markdown note per book: your highlights as blockquotes, color-coded to match the highlighter you used, optional notes, dates, citations, cover image, and an author page with a Dataview query that lists every book by that author. It has a smart overwrite mode that hashes the note so re-importing only rewrites files that actually changed. Pick “Import all books” or cherry-pick from a list.
The whole point is completeness. I’m not scraping one source, I’m triangulating across three. Which is why the missing PDF highlights bugged me so much. There was a hole in the completeness.
Where Apple Actually Hides PDF Highlights
Here’s the part that took real detective work, and where my first assumption was flat wrong.
My mental model was simple: highlights go in the SQLite database, PDFs are books, therefore PDF highlights go in the database. So I looked. And there they were. Sort of.
The database had a row for every PDF I’d ever opened. But every single one looked like this:
ZANNOTATIONSELECTEDTEXT = (empty)
ZANNOTATIONSTYLE = 0
ZANNOTATIONTYPE = 3
ZPLUSERDATA = <binary plist: BKPageLocation, pageOffset 21>
No text. No color. No note. Just a page number wrapped in a binary property list. That’s not a highlight. That’s a bookmark. Apple Books drops one of these in the database for every PDF so it can remember what page you were on, and my plugin had been correctly throwing them away for a year because they have no selected text.
So the highlights weren’t in the database at all. That meant they had to be in the PDF files, which on a Mac live in your iCloud Books folder:
~/Library/Mobile Documents/iCloud~com~apple~iBooks/Documents/
I grepped one of my highlighted PDFs for the PDF highlight marker, and there it was:
58 occurrences of /Subtype /Highlight
71 QuadPoints arrays
That’s the answer. Apple Books writes PDF highlights back into the PDF file as standard PDF annotations. Not into its own database like it does for EPUBs. Into the file. Which is actually the more portable choice, it’s just the opposite of everywhere else the app stores things.
Reading Highlights Out Of A Raw PDF
A PDF highlight annotation doesn’t store the text you highlighted. That would be too easy. It stores QuadPoints, which are the rectangles the yellow marker was painted over, in PDF coordinate space. To get the actual words you have to line those rectangles up against the text on the page and read out whatever sits underneath.
That’s a job for pdf.js, Mozilla’s PDF engine. It gives me the annotations on each page and, separately, every run of text with its position. For each highlight I:
- Turn its
QuadPointsinto one box per highlighted line. - Find the text on the page whose baseline falls inside each box.
- Clip the first and last lines, because a highlight usually starts and ends mid-sentence, snapping the cut to the nearest word boundary so I don’t slice a word in half.
I tested it against a highlight I knew the exact wording of, and it came back verbatim, starting and ending in exactly the right place. Colors came out too. pdf.js hands them back as plain RGB values from 0 to 255, and Apple’s highlighter yellow is (250, 205, 90), so I map that to the same little yellow square the EPUB highlights already use.
Hooking It Into The Machine That Already Worked
This is the part I’m actually happy about, and it’s the part that keeps this from being a bolt-on mess.
The plugin already had a whole pipeline for EPUBs: an Annotation object shape, a markdown renderer, the smart-overwrite dedup, author page creation, file naming. All of it keyed off two internal types. So instead of writing a parallel universe for PDFs, I made the PDF extractor produce those exact same types. A PDF highlight becomes an Annotation. A PDF file becomes a BookDetail, with its title and author pulled from the Books library database so the note gets a real title instead of a mangled filename.
Once the shapes matched, the entire existing machine just ran. The renderer, the dedup, the author pages, the color emoji, none of it knew or cared that these highlights came out of a PDF instead of a database. PDF import is just a second phase inside the same “Import all books” command, plus one new toggle in settings so you can turn it off (because it is now the slow part of the plugin).
The genuinely annoying part was bundling pdf.js into a single-file Obsidian plugin. pdf.js wants to run its parser in a Web Worker loaded from a separate file, and an Obsidian plugin ships as one main.js. The trick is to run the worker on the main thread by handing pdf.js its own worker module through a global:
import * as pdfjsWorker from 'pdfjs-dist/legacy/build/pdf.worker.js';
(globalThis as any).pdfjsWorker = pdfjsWorker;
That, plus telling the bundler to leave the optional canvas dependency alone, and it works. It also took main.js from about 250KB to 4.3MB, because I’m now shipping an entire PDF engine inside a note-taking plugin. Such is life.
The Tradeoff: It’s Slower, But It’s Everything Now
I’m not going to pretend this is free. The database approach for EPUBs is instant. The PDF approach is not.
Apple doesn’t track which PDFs you’ve highlighted anywhere I can query, so the plugin has to go look. It scans your entire iCloud Books folder, does a cheap byte-level check for the highlight marker to skip the hundreds of PDFs you never marked up, and then parses the survivors with pdf.js on the main thread. In my library that’s 748 PDFs to glance at. So yes, “Import all books” takes noticeably longer than it used to, and a book with a lot of highlights can make the app pause for a second while it works.
I decided I’ll take that trade every time. A slightly slower import that gets me everything beats an instant import with a hole in it.
And I do mean everything. Between the SQLite databases, the EPUB files, and now the PDFs, I’m fairly confident there’s nothing left in Apple Books that I can’t pull out. EPUB highlights, PDF highlights, notes, metadata, covers. If Apple is storing it on my Mac, this plugin can reach it. I went looking for one more hidden source after the PDFs and came up empty, which for once is the good outcome.
How To Install It (It’s Not In Community Plugins Yet)
Fair warning: this plugin is not in the Obsidian community plugin browser. It’s mine, it’s niche, and it reads files out of your macOS system libraries, so it lives on GitHub for now. That means two ways in.
The easy way, with BRAT. BRAT is the Beta Reviewers Auto-update Tool, and it exists exactly for plugins like this one. Install BRAT from the community plugins, then add this repo as a beta plugin:
eristoddle/apple-books-annotation-import
BRAT installs it and keeps it updated whenever I push a new release. I wrote a whole walkthrough of this if you’ve never done it: How to Install Obsidian Plugins, including the BRAT section.
The manual way. Grab main.js, manifest.json, and styles.css from the latest release, drop them in a folder under .obsidian/plugins/apple-books-annotation-import/ in your vault, and enable it in settings. No auto-updates, but it works.
Either way, once it’s on, flip on “Import PDF highlights” in the settings, hit “Import all books,” and give it a minute to chew through your library.
The Takeaway
The lesson here isn’t really about PDFs. It’s that “it works” and “it’s complete” are two different claims, and I’d been quietly making the first one while believing the second for over a year. The plugin worked. It just wasn’t done. It took actually going looking for a specific missing highlight to find the gap.
If you build tools for yourself, this is the failure mode to watch for. The thing runs, you trust it, and you stop checking whether it’s still telling you the whole truth. Apple gave me a good excuse by hiding PDF highlights in a completely different place than everything else, but the hole was mine to notice.
Anyway. It’s fixed. Every highlight I’ve got, in every format Apple Books supports, now lands in Obsidian. Which means I’m out of excuses and back to actually reading the books.






Top comments (0)