README file from
GithubSupernote Annotations
View your Supernote notes and annotated PDFs seamlessly as converted PDFs. Standard .note files are automatically converted and displayed as PDFs, while annotated PDFs, including their accompanying .mark annotation files, are rendered together so all handwritten notes and annotations appear directly on the PDF.
📑 Contents
- 🔒 It never changes what is inside your originals
- 📦 Install
- ▶️ Use it
- ⚙️ Settings
- 🔧 Requirements
- 🧠 How it works
- 🔀 How this differs from the other Supernote plugins
- 🛠️ Development
- 💬 Support
- ⚖️ License and attribution
Sync your vault via WebDAV on Supernote, write on the device, and the plugin turns what lands there into files Obsidian can actually open.

🖼️ Example

The same thing in a real vault: 20260814_134036.pdf was converted from a notebook, Aufgaben Beschaffung.pdf is the original you copied in, and Aufgaben Beschaffung (annotated).pdf is the
copy carrying your ink. The .note and .mark files sit right beside them on disk; you do not see
them because styles.css hides both from the file explorer.
Why you never see the .mark on the device. The Supernote stores your ink in a separate file
beside the PDF and draws the two together as you read, so its file browser shows only one item and
your strokes stay editable.
It is pure JavaScript with no native code and no external services, so it runs on the desktop app and on a phone alike.
🔒 It never changes what is inside your originals
This is the design premise, not a footnote:
- 🔒
.note,.markand your original PDFs are opened read-only. Always. The plugin has no code path that writes a single byte into them. - 📁 It will move and rename them, but only ever to follow you — see Moving files around. It never picks a location for you.
- 📄 The annotated PDF is a separate file, so the ink stays editable on the device.
- 🗑️ Everything generated is safe to delete. It gets rebuilt on the next scan.
- 🚫 Nothing is sent anywhere. No network calls, no telemetry, no account.
📦 Install
From the community directory: Settings → Community plugins → Browse → search for "Supernote Annotations" → Install → Enable.
Manually: download main.js, manifest.json and styles.css from the
latest release into
<vault>/.obsidian/plugins/supernote-annotations/, then enable it in Settings → Community plugins.
▶️ Use it
Create a Note or annotate a PDF in your vault via your Supernote device. (Either sync via Supernote's own Browse & Access, a WebDAV mount, Dropbox, Nextcloud, a USB copy). The plugin watches the vault and converts anything that appears.
New files are picked up automatically. There is also a Scan all files command, and a Scan button in settings, for a full pass over everything.
✍️ Annotating a PDF
Copy the PDF into your vault, open it on the Supernote, and write on it. When the .mark reaches
your vault, the PDF starts showing your ink — the same one file, no second copy to pick
between. Keep writing and it keeps up.
The file on disk never changes. The ink is drawn as Obsidian opens it, from the .mark beside it,
which is exactly what the device does. Delete the .mark and the PDF is plain again, with nothing
to clean up.
That the original stays pristine is not tidiness, it is required: your vault is the folder your
Supernote syncs with. A PDF that went back to the device with the ink already baked into it would
get the live .mark layer drawn on top of the bake — every stroke twice, and worse on every pass.
Upgrading from a version that made two files? The old YourFile (annotated).pdf copies are
left exactly where they are — they are your files, and this plugin does not delete things it did
not just make. They are simply no longer updated. Delete them yourself whenever you like; the ink
lives in the .mark, so nothing is lost.
Prefer a separate file? Turn off Show the annotations inside the PDF and you get
YourFile (annotated).pdf beside the original, as earlier versions did.
📁 Moving files around
A document is not one file. Lecture.pdf travels with Lecture.pdf.mark, its annotated copy
and its Markdown sidecar; a notebook travels with the PDF made from it. Move or rename any one
of them and the rest follow — drag the PDF into another folder in Obsidian and everything lands
beside it, sidecar included, into the mirrored path under the index folder.
This is not a nicety. The .mark finds its PDF by name alone, so renaming Lecture.pdf on its
own would orphan the ink permanently, with nothing to tell you.
Moves go through Obsidian's own rename, so [[links]] and ![[embeds]] pointing at any of
these files are rewritten for you, in whatever link style you have configured.
It works in both directions: dragging a sidecar to a different folder inside the index moves the files it indexes to match, because the index mirrors your vault's structure by definition.
Nothing is ever overwritten. If something with the same name already sits at the destination, that companion stays where it is and you get a notice saying so.
⚠️ Moves made outside Obsidian — in Nextcloud, a file manager, or while Obsidian is closed — fire no event, so the group is not kept together. Move these files from inside Obsidian.
🔍 Searchable handwriting (optional, off by default)
Switch on Write text sidecars and the plugin saves the device's own handwriting recognition as
a small Markdown file, so Obsidian's search and tag pane can see words that otherwise live inside
an image. Any #tag you wrote by hand becomes a real tag.
This only works for files where you enabled handwriting recognition on the device. It is a per-file setting in the Supernote's own menus, and the plugin cannot turn it on for you. Files without recognition produce no sidecar at all, which is expected rather than a failure.
Sidecars are the only thing this plugin writes into your notes, which is why they are off until you ask for them.
⚙️ Settings
| Setting | Default | What it does |
|---|---|---|
Convert .note files to PDF |
on | A real PDF next to every notebook |
Stamp .mark files onto a PDF copy |
on | The annotations described above |
| Show the annotations inside the PDF | on | One PDF rather than two — off gives you the separate file |
| Filename suffix | (annotated) |
Appended to the separate copy, when you use one |
| Write text sidecars | off | Recognized handwriting as searchable Markdown |
| Sidecar folder | Supernote Index |
Where the twins go, mirroring the source path below it |
| Extra frontmatter | empty | Extra properties for every sidecar, one per line |
styles.css also hides .note and .mark files in the file explorer, since Obsidian cannot open
them anyway. The files stay on disk, untouched.
🔧 Requirements
- Obsidian 1.5.0 or later.
- Desktop and mobile both work. No Node or Electron APIs are used.
- PDF generation needs the platform's
CompressionStream, which means a Chromium-based desktop app (all current Obsidian desktop builds) or iOS 16.4 or later. On anything older you get a clear error instead of a broken PDF. - Decoding allocates roughly 20 MB per page, so work is serialised deliberately. A long notebook takes a moment, and on a phone that restraint is what stops the OS killing Obsidian.
🧠 How it works
A few things about the format were established by measurement. They look arbitrary and are not; if you change them, re-run the tests.
The ink overlay is fit-by-aspect and centred, not stretched. The device displays a PDF fitted
to its 1920×2560 screen with the aspect ratio preserved, and stores strokes in screen
coordinates. So an A4 page occupies a centred sub-rectangle with 54 px bars, and mapping ink back
onto the page means scaling by that fitted size. Stretching the full canvas onto the page instead
misplaces every single stroke. placement() in src/render.js is the one place this is computed.
Which PDF page a stroke belongs to comes from the container footer, sn.footer.PAGE, a map
of { "1": offset, … } keyed by PDF page number. A .mark stores only the pages you actually
annotated, so the nth entry is not the nth page of the document.
An empty .mark is normal. The device writes one merely from opening a PDF, so most of them
never receive a stroke. Those produce nothing at all, on purpose; otherwise every PDF you so much
as glanced at would sprout a pointless duplicate.
The one PDF is one file because the stamped copy is not in the vault. Obsidian's PDF view
hands PDF.js a URL rather than bytes, and the markdown embed goes through the same viewer, so
Vault.getResourcePath is the single place to redirect. The stamped bytes live under
.obsidian/plugins/supernote-annotations/annotated/, named by a hash of the .mark's contents
plus the original's size — content-keyed, so a move or a rename leaves the cache valid with no
index to maintain. It is a cache: deleting the folder costs a rebuild and nothing else. On an
Obsidian that does not expose what this needs, the plugin says so and falls back to the separate
file rather than to an empty viewer.
The RATTA_RLE decoder is hand-written (src/rle.js), which is why the plugin needs no image
library and runs on a phone. It is verified page by page against
supernote-tool as ground truth. One known and
deliberate difference: palette greys decode to 128/169 here where supernote-tool renders
157/201 and adds an antialiasing fringe. That is a rendering choice on its side, not a decoding
disagreement, and the test compares palette pixels to account for it.
🔀 How this differs from the other Supernote plugins
Three others exist in the directory, and they solve different problems:
- Supernote (Unofficial): viewing
.notefiles in Obsidian, exporting them, and screen mirroring. The most featureful for notebooks. - Supernote Digests: importing digest backups and turning highlights into notes.
- Supernote Cloud Sync: mirroring files to and from Supernote Cloud.
None of them handles .mark files. If all you want is to read your notebooks, one of the above
may suit you better. This plugin exists for the case where you read PDFs on the device (lecture
slides, papers, scripts) and want that marked-up PDF back in your vault, with the original intact
and the annotations still editable on the device.
🛠️ Development
No global tooling required beyond Node.
npm install
npm run build # esbuild → main.js
npm run lint # the same rules the community directory's review runs
npm test # every suite
❗ Never hand-edit main.js. It is generated, and your changes go on the next build.
The tests that need real Supernote files read them from a directory you name, since handwriting
does not belong in a public repo. Point it at anything containing .note/.mark files:
SUPERNOTE_SAMPLES=~/MyVault npm test
Without it, the unit tests still run and the rest skip themselves.
| Suite | Checks | Needs |
|---|---|---|
tests/sidecar-test.mjs |
tag extraction, page collection, sidecar shape | nothing |
tests/paths-test.mjs |
which files form a group, recovered from any one of them | nothing |
tests/overlay-test.mjs |
the cache key: content-derived, stable across moves | nothing |
tests/move-test.mjs |
one drag moves the group, the echo does not recurse, nothing is overwritten | built main.js |
tests/bundle-test.mjs |
loads the built main.js exactly as Obsidian does |
built main.js |
tests/pdf-test.mjs |
full pipeline into a temp dir; asserts sources are byte-identical after | samples |
tests/decoder-test.mjs |
every page against supernote-tool: canvas size exactly, bounding box within 2 px, blank-or-not exactly, pixel count within 0.5% |
samples, supernote-tool, python3 + Pillow |
The decoder test is the one that must not regress. Run it after any change to src/rle.js.
💬 Support
Bug reports and questions go to GitHub Issues — please search the existing ones first, since someone may have hit it already.
A report that includes these is one I can usually act on straight away:
- Your Obsidian version and platform (Windows, macOS, Linux, iOS, Android).
- Whether it involves a
.noteor a.pdf+.pdf.markpair. - Anything in the developer console (desktop:
Ctrl/Cmd+Shift+I→ Console). Errors from this plugin are prefixed[supernote-annotations]. - What you expected versus what happened.
Please don't attach the file itself unless you're certain it contains nothing private — a
.note is your handwriting. supernote-tool analyze yourfile.note prints the headers alone, which
is usually enough to diagnose a parsing problem.
Two things that look like bugs but aren't:
- ⚠️ A
.markproduced no annotated PDF. The device writes a.markmerely from opening a PDF, so most contain no ink. That is deliberate, not a failure. - ⚠️ A
.noteproduced no sidecar. Sidecars need handwriting recognition switched on for that file, on the device. The plugin cannot enable it for you.
This is a spare-time project. I read everything, but I can't promise a response time.
⚖️ License and attribution
GPL-3.0-or-later. See LICENSE.
This plugin uses the Supernote container parser from supernote-typescript by Philip Smith, which is GPL-3.0-or-later, hence the license of the whole. pdf-lib is MIT. The RATTA_RLE decoder, the PNG encoder and the overlay geometry in this repository are original work, developed against supernote-tool (Apache-2.0) as a reference implementation.
Not affiliated with, endorsed by, or supported by Ratta or Obsidian. "Supernote" is a trademark of Ratta Software Technology.