Export OneNote notebooks to Obsidian-compatible Markdown via Microsoft Graph API
Project description
OneNote → Obsidian Exporter
Export your Microsoft OneNote notebooks to Obsidian-compatible Markdown files via Microsoft Graph API.
Why This Tool?
- Zero setup — no Azure AD app registration needed, works out of the box
- Full fidelity — images, file attachments, checkboxes, tables, embedded content
- Smart resume — re-running skips unchanged pages, exports only what's new
- Preserves structure — Notebook / Section / Section Group hierarchy mapped to folders
- Open source — MIT licensed, community-driven, 98% test coverage
Features
- No Azure AD registration required — uses a public Microsoft client ID out of the box
- Full notebook export — text, images, file attachments, checkboxes, embedded content
- Preserves structure — Notebook / Section / Page hierarchy mapped to folders
- Resume support — re-running skips unchanged pages (tracks by modification time)
- Recursive section groups — nested section groups are fully supported
- YAML frontmatter — each page includes
created,modified,source,onenote_id
Requirements
- Python 3.10+
- A personal Microsoft account with OneNote data
Installation
From PyPI (recommended)
pip install onenote-to-obsidian
From source
git clone https://github.com/Lenivvenil/onenote-to-obsidian.git
cd onenote-to-obsidian
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -e .
Docker
docker run -it --rm \
-v ~/.onenote_exporter:/home/appuser/.onenote_exporter \
-v ~/ObsidianVault:/home/appuser/ObsidianVault \
ghcr.io/lenivvenil/onenote-to-obsidian
Quick Start
# Export all notebooks (first run creates config automatically)
onenote-to-obsidian
# Or run as a module
python -m onenote_to_obsidian
# List your notebooks
onenote-to-obsidian --list
# Export a specific notebook
onenote-to-obsidian --notebook "My Notebook"
# Export to a custom vault path
onenote-to-obsidian --vault /path/to/obsidian/vault
On the first run, you'll see a device code prompt:
===========================================================
To authorize:
1. Open: https://microsoft.com/devicelogin
2. Enter code: XXXXXXXXX
===========================================================
Open the link in your browser, enter the code, and sign in with your Microsoft account.
CLI Options
| Option | Description |
|---|---|
--vault PATH |
Path to Obsidian vault (default: ~/ObsidianVault) |
--notebook NAME |
Export only the specified notebook |
--list |
List available notebooks and exit |
--reset-state |
Force re-export of all pages |
--setup |
Configure a custom client ID |
-v, --verbose |
Enable debug logging |
Output Structure
vault/
├── Notebook Name/
│ ├── Section Name/
│ │ ├── attachments/
│ │ │ ├── 0-resourceid.png
│ │ │ └── document.pdf
│ │ ├── Page Title.md
│ │ └── Another Page.md
│ └── Section Group/
│ └── Nested Section/
│ └── ...
└── Another Notebook/
└── ...
Each .md file includes YAML frontmatter:
---
created: "2023-01-15T10:30:00Z"
modified: "2024-06-20T14:22:00Z"
source: onenote
onenote_id: "page-guid"
---
What Gets Converted
| OneNote element | Markdown result |
|---|---|
| Images |  |
| File attachments | [file.pdf](attachments/file.pdf) |
| Checkboxes (unchecked) | - [ ] text |
| Checkboxes (checked) | - [x] text |
| Embedded content (iframes) | [Embedded content](url) |
| Headers, bold, italic, tables, links | Standard Markdown |
| Absolute positioning CSS | Removed |
Authentication
The tool uses OAuth2 device code flow. No app registration is needed — it ships with the public Microsoft Office client ID.
Token storage: OAuth tokens are cached in ~/.onenote_exporter/token_cache.json (permissions: owner-only, chmod 600). Re-authentication is only needed when refresh tokens expire.
Custom client ID: If the default client ID doesn't work for your account type, run --setup and provide your own (see Azure AD app registration guide).
Configuration
All configuration is stored in ~/.onenote_exporter/:
| File | Purpose |
|---|---|
config.json |
Client ID, vault path, OAuth scopes |
token_cache.json |
Cached OAuth2 tokens (chmod 600) |
export_state.json |
Which pages have been exported |
Troubleshooting
"Invalid client_id" error
Try the fallback client ID:
onenote-to-obsidian --setup
# Enter: 1fec8e78-bce4-4aaf-ab1b-5451cc387264
Pages not exporting
- Verify you're signed in:
onenote-to-obsidian --list - Check logs:
onenote-to-obsidian --verbose - Reset state:
onenote-to-obsidian --reset-state
"Account not supported" error
Your Microsoft account type may not be compatible with the default client ID. Run --setup and try the fallback, or register your own app in Azure AD.
FAQ
Does this work with work/school Microsoft accounts?
It works with personal Microsoft accounts out of the box. Work/school accounts may require a custom client ID — run --setup and register your own app in Azure AD.
Can I re-export without re-downloading everything?
Yes. The tool tracks exported pages by ID and modification time. Re-running exports only new or changed pages. Use --reset-state to force a full re-export.
What happens if the export crashes mid-way?
Progress is saved in ~/.onenote_exporter/export_state.json. Re-running picks up where it left off. File deduplication also survives crashes.
Is my data sent anywhere besides Microsoft?
No. The tool only communicates with Microsoft Graph API and writes to your local filesystem. OAuth tokens are cached locally with owner-only permissions.
How do I use a different Obsidian vault location?
onenote-to-obsidian --vault /path/to/your/vault
The default location is ~/ObsidianVault.
Comparison with Alternatives
| Feature | onenote-to-obsidian | Manual copy-paste | OneNote export (OOXML) |
|---|---|---|---|
| No registration needed | Yes | N/A | N/A |
| Images & attachments | Yes | Manual | Partial |
| Resume / incremental | Yes | No | No |
| Checkboxes | Yes | No | No |
| Section groups | Yes | N/A | Yes |
| YAML frontmatter | Yes | No | No |
| Automation-friendly | Yes | No | Partial |
Development
# Install with dev dependencies
pip install -e ".[dev]"
# Run tests
pytest
# Run tests with coverage
pytest --cov=onenote_to_obsidian
# Lint and format
ruff check onenote_to_obsidian/
ruff format onenote_to_obsidian/
See CONTRIBUTING.md for full contributor guidelines.
License
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file onenote_to_obsidian-1.1.0.tar.gz.
File metadata
- Download URL: onenote_to_obsidian-1.1.0.tar.gz
- Upload date:
- Size: 56.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
56b6428bb17a47525962d050c3f5f48ab70ddd48ad74b950585e6d4543a45da2
|
|
| MD5 |
03b0e1051757fa5bc133ee71902f716e
|
|
| BLAKE2b-256 |
9186b7d773c3d015a0db7f0233946554a405be1686b651affbd71ffe69129fef
|
Provenance
The following attestation bundles were made for onenote_to_obsidian-1.1.0.tar.gz:
Publisher:
publish.yml on Lenivvenil/onenote-to-obsidian
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
onenote_to_obsidian-1.1.0.tar.gz -
Subject digest:
56b6428bb17a47525962d050c3f5f48ab70ddd48ad74b950585e6d4543a45da2 - Sigstore transparency entry: 1293060214
- Sigstore integration time:
-
Permalink:
Lenivvenil/onenote-to-obsidian@a542bd03245e1e3f840369ef8ad001c754ba87a5 -
Branch / Tag:
refs/tags/v1.1.0 - Owner: https://github.com/Lenivvenil
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@a542bd03245e1e3f840369ef8ad001c754ba87a5 -
Trigger Event:
release
-
Statement type:
File details
Details for the file onenote_to_obsidian-1.1.0-py3-none-any.whl.
File metadata
- Download URL: onenote_to_obsidian-1.1.0-py3-none-any.whl
- Upload date:
- Size: 23.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e87a797033c22d863acf4a0d00c1a5745acc992ec68f9bd4d499ad652d8c8006
|
|
| MD5 |
f96aaa9a4a67d5c125ccf7535fe62b2d
|
|
| BLAKE2b-256 |
420a6257cf23f4b578c77284cc659ac82baf3e1fa1ddba3ed1d4d7d5008bf2cb
|
Provenance
The following attestation bundles were made for onenote_to_obsidian-1.1.0-py3-none-any.whl:
Publisher:
publish.yml on Lenivvenil/onenote-to-obsidian
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
onenote_to_obsidian-1.1.0-py3-none-any.whl -
Subject digest:
e87a797033c22d863acf4a0d00c1a5745acc992ec68f9bd4d499ad652d8c8006 - Sigstore transparency entry: 1293060436
- Sigstore integration time:
-
Permalink:
Lenivvenil/onenote-to-obsidian@a542bd03245e1e3f840369ef8ad001c754ba87a5 -
Branch / Tag:
refs/tags/v1.1.0 - Owner: https://github.com/Lenivvenil
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@a542bd03245e1e3f840369ef8ad001c754ba87a5 -
Trigger Event:
release
-
Statement type: