A Windows desktop application that exports Microsoft OneNote notebooks, sections, and pages to Markdown format. Built with C#, WPF, and COM Interop. No Azure App Registration (Service Principals), no cloud authentication, no admin consent required. Just you and your notes.
This is all free, but if you're feeling generous, you can buy me a coffee, or herbal tea 🍵, which is more my thing!
Go to GitHub Releases to download the latest version.
- Download the
.zipfile from the latest release - Extract the folder (it contains the
.exeand aresourcesfolder) - Run
OneNoteMarkdownExporter.exe
Important: Keep the
resourcesfolder in the same directory as the.exe. It contains the bundled Node.js runtime and markdownlint-cli needed for Markdown linting.
- Windows 10 or 11
- Microsoft OneNote (the desktop app that comes with Microsoft 365/Office 365, not the old "OneNote for Windows 10" app which reached end of support in October 2025)
Which OneNote do I have? If you installed OneNote through Microsoft 365 or Office 365, you have the right one. The desktop app uses COM Interop, which this tool relies on. If you're unsure, open OneNote, go to File > Account, and you should see "Microsoft 365" or your Office subscription info. More details on OneNote versions here.
- Two ways to run - GUI for interactive use, CLI for scripting and automation
- Tree view selection - Pick entire notebooks, specific sections, or individual pages
- Subpage hierarchy - OneNote subpages export into nested folders
- Clean Markdown output - Proper formatting, no leftover HTML tags
- Image extraction - Embedded images saved to a configurable assets folder with relative paths
- Sync-friendly - "Overwrite existing files" option keeps exports in sync with your notes
- Markdown linting - Automatic cleanup via bundled markdownlint-cli (configurable)
This tool supports both GUI mode and CLI mode:
| Mode | How to Launch | Best For |
|---|---|---|
| GUI | Double-click the .exe or run without arguments |
Interactive use, exploring notebooks, one-time exports |
| CLI | Run with command-line arguments | Scripting, automation, scheduled tasks, AI tool integration |
The app automatically detects which mode to use based on whether you pass command-line arguments.
Double-click OneNoteMarkdownExporter.exe to launch the graphical interface.
- Launch the app - OneNote will open automatically if it's not running
- Select your content - Check the boxes next to notebooks, sections, or pages
- Choose an output directory - Defaults to
Downloads\OneNoteExport - Choose an assets folder - Defaults to
<output>\assets - Configure options:
- Overwrite existing files - Enable this for ongoing syncing
- Apply Markdown linting - Cleans up the output (can be toggled off)
- Click Start Export
Run with command-line arguments for scripting, automation, scheduled tasks, etc. The app runs headlessly without opening the GUI.
# Export all notebooks
OneNoteMarkdownExporter.exe --all
# Export a specific notebook
OneNoteMarkdownExporter.exe --notebook "Work Notes"
# Export to a custom directory
OneNoteMarkdownExporter.exe --all --output "C:\MyExports"
# Export images/assets to a custom folder
OneNoteMarkdownExporter.exe --all --assets-folder "D:\OneNoteAssets"
# Show help
OneNoteMarkdownExporter.exe --help| Option | Description |
|---|---|
--all |
Export all notebooks |
--notebook <name> |
Export specific notebook by name |
--section <path> |
Export section by path, e.g., "Notebook/Section" |
--page <id> |
Export page by OneNote ID |
| Option | Description |
|---|---|
--output, -o <path> |
Output directory (default: Downloads\OneNoteExport) |
--assets-folder <path> |
Folder for exported images/assets (default: <output>\assets) |
--overwrite |
Overwrite existing files instead of creating numbered copies |
| Option | Description |
|---|---|
--no-lint |
Disable Markdown linting (markdownlint-cli) |
--lint-config <path> |
Path to custom .markdownlint.json configuration file |
| Option | Description |
|---|---|
--list |
List all notebooks, sections, and pages (no export) |
--dry-run |
Preview what would be exported without creating files |
--verbose, -v |
Show detailed output including file paths |
--quiet, -q |
Show only errors (suppress progress messages) |
--help, -h |
Show help and usage information |
# List all notebooks and their structure
OneNoteMarkdownExporter.exe --list
# List with verbose mode to see page IDs
OneNoteMarkdownExporter.exe --list --verbose
# Preview what would be exported (no files created)
OneNoteMarkdownExporter.exe --notebook "Personal" --dry-run
# Export multiple notebooks
OneNoteMarkdownExporter.exe --notebook "Work" --notebook "Personal"
# Export a specific section within a notebook
OneNoteMarkdownExporter.exe --section "Work Notes/Meeting Notes"
# Export everything, overwrite existing, skip linting
OneNoteMarkdownExporter.exe --all --overwrite --no-lint
# Quiet mode for scheduled tasks (only shows errors)
OneNoteMarkdownExporter.exe --all --quiet --overwrite
# Full verbose export to custom location
OneNoteMarkdownExporter.exe --all --output "D:\Backups\OneNote" --verbose --overwrite
# Export notes and store assets in a separate folder
OneNoteMarkdownExporter.exe --all --output "D:\Backups\OneNote" --assets-folder "D:\Backups\OneNoteAssets"Exported images are saved to <output>\assets by default. Use the GUI assets folder field or the CLI --assets-folder <path> option to choose a different folder. Relative paths are resolved from the output directory, and absolute paths are used as provided. Missing folders are created automatically. Existing asset folders are reused, and generated asset files with the same names are overwritten on later exports. Paths where the assets folder itself would be an existing file are rejected. Markdown image links are generated relative to each exported page.
OneNote subpages are preserved as nested folders. A parent page is exported as Markdown beside a folder with the same name, and its subpages are exported inside that folder.
Section A\
Parent Page.md
Parent Page\
Child Page.md
Child Page\
Grandchild Page.md
If you export only a subpage, the parent folders are still created so the exported file keeps its place in the original hierarchy.
Exported folder and file names are made safe for Windows. Invalid filename characters are replaced, trailing spaces and periods are removed, and reserved Windows names such as CON, NUL, COM1, and LPT1 are adjusted. Long names are preserved when the full target path fits within the standard Windows path budget. When a generated path is too long, only the generated OneNote-derived name is shortened, and a stable hash suffix keeps repeated exports targeting the same file.
The app uses markdownlint-cli for Markdown linting. Node.js and all dependencies are bundled, so it works out of the box with no additional setup.
- Enabled by default - Can be toggled off in the UI or with
--no-lintin CLI - Non-blocking - If linting fails, the error is logged and export continues with the unlinted content
- Configurable - Edit
.markdownlint.jsonto customize rules
Click "Edit .markdownlint.json..." in the UI or find the file in the resources folder. The default configuration:
{
"default": true,
"MD013": false,
"MD033": false,
"MD028": false,
"MD012": false,
"MD040": false,
"MD024": false,
"MD018": false,
"MD036": false,
"MD049": false,
"MD041": false
}| Rule | What It Does | Why It's Disabled |
|---|---|---|
| MD013 | Line length limit (80 chars) | OneNote content doesn't follow line limits |
| MD033 | No inline HTML | Some exported content may have intentional HTML |
| MD041 | First line should be H1 | Not all notes start with a heading |
| MD024 | No duplicate headings | Notes often reuse section headers |
| MD028 | Blank line inside blockquote | Common in formatted quotes |
| MD012 | Multiple blank lines | OneNote spacing doesn't always translate cleanly |
| MD040 | Fenced code blocks need language | Not all code blocks have a language |
| MD018 | No space after hash in heading | Edge cases in conversion |
| MD036 | Emphasis instead of heading | Style choice |
| MD049 | Consistent emphasis style | Mixed styles in source content |
- Connect to OneNote via COM Interop (
Microsoft.Office.Interop.OneNote) - Enumerate hierarchy using
GetHierarchy()to build the notebook/section/page tree - Export pages using
GetPageContent()which returns raw XML with embedded images - Parse XML to extract text, formatting, and base64-encoded images
- Convert to Markdown using a combination of custom parsing and ReverseMarkdown
- Apply linting to clean up formatting inconsistencies
- Save files with proper folder structure mirroring your notebook organization
I hate OneNote. I've only ever used it in cases where I was grandfathered into it. Meaning, the program I was in at school, or the team I was on at work, already used it, so I had to play along. The day I learned about Markdown (shout out to the team at Farm Credit Services of America, my first internship that taught me real world software development, and a love of markdown), I resolved to do everything I could to never touch OneNote or similar "vendor lock-in" proprietary note taking tools again.
That decision, given the rise of AI and how easily it works with and prefers Markdown, has never looked better. I had some legacy OneNotes I inherited at work that were chock full of domain knowledge scattered across sections and pages and impossible to easily parse through. To enable Retrieval Augmented Generation over that information, I wanted to export it to Markdown. I tried all sorts of solutions and hit roadblock after roadblock.
-
ConvertOneNote2MarkDown: PowerShell script that uses OneNote's
Publish()method to export pages as Word documents (.docx), then converts them to Markdown using Pandoc. Doesn't work when Data Loss Prevention policies are enabled becausePublish()writes files to disk. Something about DLP blows up any attempt usingPublish()to save a file thereafter. -
ConvertOneNote2MarkDown: The original version of the above. Same
Publish()to Word then Pandoc approach, same Data Loss Prevention issues. -
onenote_to_markdown: A Python script that converts manually copy-pasted text from OneNote into Markdown. Requires you to manually select and copy all notes, save them as text files, then run the script. Not automated and loses formatting/images.
-
OneNote Export Gist: A manual workflow where you export pages to .docx using OneNote's File > Export menu, then use Pandoc commands to convert to Markdown. Not automated, requires manual export of each page.
-
onenote-md-exporter: A .NET console app that uses
Publish()to export pages as Word documents, then converts them to Markdown using Pandoc. Well-built tool with good features, but blocked by Data Loss Prevention policies becausePublish()writes intermediate files to disk. -
freeing-onenote: PowerShell script that uses the Microsoft Graph API to retrieve page content and convert to Markdown. Requires an Azure App Registration with appropriate permissions, which is doable, but in some organizations requires admin approval when you're just trying to export your personal notebook.
-
Obsidian Importer: Built into Obsidian, but uses the Graph API under the hood. Same admin consent requirement.
Instead of using Publish() (which exports pages to various non-markdown formats that you then convert to markdown), use GetPageContent(). This method returns the raw XML of a OneNote page, including base64-encoded images. No intermediate file writing.
// This gets blocked by Data Loss Prevention (DLP) policies
onenote.Publish(pageId, tempFile, PublishFormat.pfOneNote, string.Empty);
// This works, even with sensitivity labels
onenote.GetPageContent(pageId, out string xml, PageInfo.piAll);That's the core insight this app is built on.

