Converts .note and .ntb files to .pdf by default, or optionally to .svg.
I originally made this for personal use because I couldn't find a converter online. I hope it is useful to others as well.
Note: output is not perfectly identical to Notability’s official export. Some details, such as Apple-specific fonts and exact text layout are approximated, but for most notes this converter works pretty well.
- Convert
.noteand.ntbfiles to other formats in bulk (high speed is a plus) - View
.noteand.ntbfiles on a machine that does not have Notability installed - Switch platforms by converting notes to
.svg(SVGs can be imported into other note-taking apps as editable files)
Requirement: Rust
SVG output with imported PDF backgrounds also needs Poppler's pdftoppm in PATH. PDF output does not need Poppler.
Put your exported .note and .ntb files in input/, then run this from the repository root:
cargo runThis converts everything under input/ into output/.
Nested folder structures are preserved.
Input and output folders must be separate and must not contain each other. Files are converted in a temporary folder first. A collision or conversion failure leaves existing output unchanged. When output is replaced successfully, the previous folder is kept in a sibling backup folder. Its location is printed after conversion.
Example:
.
├── input/
│ ├── example1.note
│ └── example_folder/
│ └── example2.ntb
└── output/
├── example1.pdf
└── example_folder/
└── example2.pdf
cargo run -- --format svg --input path/to/input --output path/to/outputThanks to Julia Evans' blog, I was able to quickly understand how the file format works.
A .note file is a ZIP archive with a document folder inside it. The folder name does not have to match the file name, so locate Session.plist first and treat its parent folder as the bundle root. Typical contents are:
Session.plist: the main document object graphImages/: embedded image files referenced by figure objectsPDFs/: imported PDF files used as page backgroundsthumb*.png: thumbnails, useful as a fallback for page aspect ratiometadata.plist,Recordings/, and similar side files
Session.plist is an Apple keyed archive, not a plain property-list tree. The top-level plist contains $objects and $top. Values are often UID references into $objects, so a reader needs to resolve UIDs before interpreting fields. Archived arrays and dictionaries often appear as Objective-C shapes such as NS.objects, NS.keys, and NS.bytes.
Most useful page and drawing data is under richText.
Geometry fields define how document coordinates become pages:
richText -> reflowState -> pageWidthInDocumentCoordsKey: document-space page width- paper attributes: paper size, line style, and page sizing hints
pageLayoutArray: imported PDF pages, whose page boxes can define the output ratiothumb*.png: fallback aspect ratio when no source PDF is available
Notability stores content in one continuous vertical document coordinate space. To convert it to pages, choose a page height from the geometry above, then split any item by:
page_index = floor(document_y / page_height)
local_y = document_y - page_index * page_height
A stroke can cross page boundaries. Keep its full path and width data on every page it reaches, then clip to each page. For cubic curves, use the curve's extrema rather than its control points to find the page range.
Text appears in two forms:
richText -> attributedString: the main text streamTextBlockMediaObjectentries inmediaObjects: positioned text boxes
The attributed string stores plain text in stringKey and style runs in subRangesKey. Useful style fields include:
NSFontSizeAttributeandNSFontNameAttributesubRangeColorCrossPlatformKeyorsubRangeColorKey- underline, strikethrough, and baseline offset in
subRangeOtherAttributesKey - list/checklist metadata such as
indent-level,indent-decoration-style,indent-decoration-number, andchecklist-checked
To convert text, reconstruct a per-character style map from the ranges, then lay out the text in document coordinates. This is the least exact part of the format because Notability's native font shaping and wrapping are not stored as final glyph positions.
Images are mediaObjects with nested figure/background/snapshot objects pointing to files in Images/. Important fields are:
documentOrigin: document-space positionunscaledContentSize: displayed sizerotationDegrees: rotation; some files store radians despite the nameisFlippedHorizontalandisFlippedVertical: independent mirror flagsFigureCropRectKey: crop rectangle in source-image pixelszIndex: layer order
To convert images, read the referenced asset from Images/, crop it if FigureCropRectKey exists, then place it at documentOrigin with size, rotation, and flips applied around the displayed image center. If a referenced RGB JPEG is uncropped or the crop covers the entire source image, it can be embedded directly in the output PDF instead of being decoded and re-encoded.
Imported PDF backgrounds are described by richText -> pageLayoutArray. Each entry maps:
- note page index
- source PDF filename under
PDFs/ - source page index inside that PDF
To convert a PDF-backed page, use the source PDF page as the base layer, scale it to the reconstructed output page box, then draw the note overlays above it.
Handwriting is stored under richText -> Handwriting Overlay -> SpatialHash. The main stroke payloads are little-endian binary arrays:
curvespoints:f32x/y point pairscurvesnumpoints:i32point counts per curvecurveswidth: base stroke widths asf32curvesforces: pressure samples asf32curvesfractionalwidths: width multipliers asf32curvescolors: raw RGBA bytescurvesstyles: tool/style idsdashStyles: dash/dot pattern metadata keyed by curve indexgroupsArrays: nested stroke groups with transformsshapes: simple shape strokes
Many curves are cubic Bezier chains: the point count follows 1 + 3n, where each segment uses one start point and three more points. Pressure and fractional-width arrays may align to the Bezier sample count rather than the raw point count. A converter should preserve that pressure data for pen and pencil width/opacity.
Polygon vertices can also have a 1 + 3n point count. Keep them as straight edges, including when drawing with pencil or variable width. Small dots and short pen marks should not be discarded just because they are thin or dark.
Dash and dot patterns live in dashStyles -> objectPatterns -> <curve-index> -> pattern. Keep the original curve index when attaching dash metadata; filtering or reordering curves too early can assign dotted styles to the wrong stroke.
Grouped handwriting in groupsArrays has its own nested archive data plus a transform. Apply the group transform to stroke coordinates. Scale stroke width by the transform as well, otherwise grouped strokes render too thick or too thin.
Shape strokes are separate from normal raw curves:
square,triangle, andpolygonuse explicit point listscircleusesrotatedRectorrectlineusesstartPtandendPt; curved lines also usecontrolPoint1/controlPoint2- dashed/dotted shapes use
appearance -> dashStyle -> pattern partialshaperepresents partially erased shape/line fragments; use itsstrokePathoutline as a filled path to preserve the erased gap size
Layering is reconstructed from page backgrounds first, then text, text blocks, images, and handwriting according to the parsed order and z-order where available.
Important limitations when converting:
- text layout is heuristic because exact Notability glyph shaping is not fully encoded
- raw partial erase behavior is only partly understood, but
partialshapefragments preserve erased size for partially erased shapes/lines - highlighter dotted/dashed strokes can differ from Notability's exported PDF because the export may use filled fragments instead of native dash arrays
- image rotation, crop, and horizontal/vertical flips are separate fields and all must be applied
A .ntb file is a more recent file format by Notability. It is also a ZIP archive, but its contents are different from a .note file. The main entries are:
noteBundle: a FlatBuffers event collection containing the note dataassets/: embedded media named with a SHA-512 content hash and a file extension
The noteBundle stores a history of events instead of one final document object graph. Each event has an id, a type, and a payload. These event types are currently understood:
- type 1: document and paper settings
- type 7: text boundary anchors
- type 8: text
- type 12: character style ranges
- type 13: paragraph and list style ranges
- type 15: ink
- type 18: shapes
- type 20: lists of related event ids
- type 22: images and sticky notes
Text and style data are separate events. Text events refer to their container by event id. Character and paragraph styles refer to text event ids and UTF-16 offsets. Resolve these references before applying styles, especially when the note has multiple text boxes. Type 7 events can provide end anchors for text ranges.
Positioned text boxes store a page, position, size, and rotation. The converter keeps their text and styles separate from the main document text. It also preserves main text that continues onto later pages.
Ink paths use compact binary data with half-precision or single-precision values. The payload also stores the page, origin, scale, rotation, width, color, tool, and line style. The scale applies to both the path coordinates and the stroke width.
Cubic path records also contain an opacity byte. Pencil opacity varies between records, so keep it with each segment when splitting erased fragments. Use the stored opacity rather than estimating it from pressure.
Shapes are separate events. Lines can have a control point, polygons use point lists, and ellipses use a bounding size. Shape events also store page, origin, rotation, width, color, tool, and dash style.
Transformed groups store rotation on each child event. Apply it to ink and shapes around each event origin. Line style 2 is dashed and line style 3 is dotted.
Partial eraser and shape eraser results are stored as breaks inside an ink path. In cubic paths, command 0 continues the curve and command 3 moves to the next fragment. In linear paths, command 6 continues the line and command 7 moves to the next fragment. A move command must start a new path. Drawing a line across it fills the erased area again.
Type 20 events contain lists of related event ids. They do not act as eraser masks, so their referenced ink and shapes should still be rendered.
Images refer to files under assets/ by their content hash. Their events store a MIME type, logical asset size, page, position, displayed size, rotation in radians, and an optional crop rectangle. Crop values use logical asset coordinates and do not always match the pixel dimensions of the stored image.
For a bundle with one PDF asset under assets/, the converter uses all source pages in their original order as backgrounds, including pages without handwriting. PDF output preserves the source page content, crop, and rotation. SVG output embeds rendered backgrounds while keeping note strokes as SVG paths.
Multiple PDF assets and reordered source pages still need explicit page mapping. The converter reports an error for multiple PDF assets rather than choosing one. The supplied example has no PDF backgrounds or positioned text boxes, so these paths are tested with generated fixtures.
The main differences from .note are:
.noteuses an Apple keyed archive inSession.plist;.ntbuses FlatBuffers innoteBundle.notemostly stores the current document state;.ntbstores an ordered event collection with event references.noteuses one continuous vertical coordinate space;.ntbevents contain explicit page references.notemedia uses paths underImages/;.ntbmedia uses content hashes underassets/.notehandwriting uses parallel binary arrays;.ntbstores a compact path inside each ink event