obsidian-import
Extract files (PDF, Word, PowerPoint, Excel, OpenDocument, RTF, EPUB, CSV, JSON, YAML, images) into Obsidian-flavored Markdown.
The mirror of obsidian-export: where obsidian-export converts Obsidian notes to PDF/DOCX, obsidian-import converts external documents into Obsidian-ready markdown with YAML frontmatter.
Installation
pip install obsidian-import
Document conversion runs on anydoc by default; it is a required dependency, so no extra is needed for Word, PowerPoint, Excel, OpenDocument, RTF, EPUB, or CSV input. PDFs stay on the native backend by default — see Backend Selection for why.
With optional backends:
pip install obsidian-import[markitdown] # fallback for HTML, etc.
pip install obsidian-import[docling] # high-quality ML-based extraction
Quick Start
Single file
obsidian-import convert report.pdf --output vault/imports/report.md
Batch extraction
obsidian-import batch --config config.yaml
Check backend availability
obsidian-import doctor
Python API
from pathlib import Path
from obsidian_import import extract_file, extract_text, discover_files, config_for_backend
from obsidian_import.config import load_config
from obsidian_import.output import format_output
config = load_config(Path("config.yaml"))
# Single file (full document with frontmatter)
doc = extract_file(Path("report.pdf"), config)
markdown = format_output(doc, config.output)
# Quick text extraction (no config file needed)
config = config_for_backend("markitdown", timeout_seconds=60, max_file_size_mb=50, xlsx_max_rows_per_sheet=500, extract_images=False)
text = extract_text(Path("report.pdf"), config)
# Batch discovery
for file in discover_files(config):
print(f"{file.extension} {file.size_bytes:,} bytes {file.path}")
config_for_backend() — Quick Configuration
For consumers that just need text extraction without managing the full config surface:
from obsidian_import import extract_text, config_for_backend
config = config_for_backend(
backend="markitdown",
timeout_seconds=60,
max_file_size_mb=50,
xlsx_max_rows_per_sheet=500,
extract_images=False,
)
text = extract_text(Path("document.docx"), config)
This sets all backends to the specified backend name. All parameters are required — no hidden defaults.
config_from_overrides() — Partial Overrides
For library consumers that need full control over individual config keys without writing a YAML file. The overrides dict deep-merges onto the bundled defaults, exactly like load_config does for user YAML:
from pathlib import Path
from obsidian_import import extract_text, config_from_overrides
if __name__ == "__main__":
config = config_from_overrides(
{
"extraction": {"max_file_size_mb": 25, "isolation": "process"},
"backends": {"pdf": "docling"},
}
)
text = extract_text(Path("document.pdf"), config)
With isolation: "process", extraction calls in a script must run under an
if __name__ == "__main__": guard: multiprocessing spawn re-imports the
calling module in the child, and an unguarded top-level call crashes the
child before it can extract anything. (Installed CLI entry points and pytest
are unaffected.) Process mode also spawns a fresh interpreter per file, so
backend imports are re-paid on every call and count against
timeout_seconds — negligible for native backends, but several seconds to
tens of seconds for docling/torch. Prefer thread mode for docling batch
runs, or raise timeout_seconds.
Configuration
Create a config.yaml:
input:
directories:
- path: /path/to/documents
# Discovery only picks up the extensions listed here. The formats anydoc
# adds (.doc, .xls, .ppt, .odt, .ods, .odp, .rtf, .epub) need listing too.
extensions: [".pdf", ".docx", ".pptx", ".xlsx", ".csv", ".json", ".yaml", ".png", ".jpg",
".doc", ".xls", ".ppt", ".odt", ".ods", ".odp", ".rtf", ".epub"]
exclude: ["*.tmp", "~$*"]
output:
directory: ./extracted
frontmatter: true
metadata_fields:
- title
- source
- original_path
- file_type
- extracted_at
- page_count
backends:
pdf: native # pdfplumber + pypdf: page headings, page_count, page images
docx: anydoc # anydoc
pptx: anydoc # anydoc
xlsx: anydoc # anydoc (xlsx_max_rows_per_sheet does not apply)
csv: anydoc # anydoc -> GFM table
json: native # stdlib json -> fenced code block (no anydoc parser)
yaml: native # PyYAML -> fenced code block (no anydoc parser)
image: native # Obsidian ![[wikilink]] embed (no anydoc parser)
html: markitdown # .html / .htm via markitdown (no anydoc parser)
default: anydoc # fallback for unlisted extensions (.doc, .rtf, .odt, ...)
extraction:
timeout_seconds: 120
max_file_size_mb: 100 # enforced both in discovery and at the
# extract_file/extract_text entry points
xlsx_max_rows_per_sheet: 500
isolation: thread # thread = lower latency (one-shot CLI use);
# process = killed on timeout + memory isolation
# (recommended for long-running daemons; see
# the __main__-guard and per-file import-cost
# notes above)
# Pass-through: copy files as-is without extraction
passthrough:
extensions: [".md", ".markdown", ".canvas"]
paths: ["raw/**"]
patterns: []
Backend Selection
| Backend | Extensions | Dependencies | Quality |
|---|---|---|---|
anydoc (default) |
.doc/.docx, .ppt/.pptx, .xls/.xlsx, .odt/.ods/.odp, .rtf, .epub, .csv, .pdf (opt-in) | Core (included) | Best all-round document conversion |
native |
.pdf (default), .docx, .pptx, .xlsx, .csv, .json, .yaml/.yml, images | Core (included) | Good for text-heavy documents; the only backend that pulls images out of PDFs |
markitdown |
Any | [markitdown] extra |
Good fallback for HTML, etc. |
docling |
Any | [docling] extra |
Best for complex layouts, tables |
The anydoc backend wraps anydoc, a Rust
document converter that ships as a compiled wheel — no model downloads, no
extra install step. Embedded images from Word, PowerPoint, Excel, OpenDocument,
and EPUB input are extracted into the note's media folder and embedded as
![[note/asset_imgN.png]] at the position they occupied in the source
document.
PDF is the one document format anydoc is not the default for. anydoc parses
PDF straight to Markdown and exposes no document model for it, which costs three
things the native PDF backend gives you: embedded page images, the ## Page N
headings, and the page_count frontmatter field derived from them. anydoc also
does no OCR, so a scanned PDF fails with an extraction error instead of
producing an empty note. Set pdf: anydoc if you would rather have anydoc's
PDF text extraction than any of that.
One further difference: extraction.xlsx_max_rows_per_sheet does not apply to
anydoc. The option is reported as ignored for .xlsx; set xlsx: native to cap
rows per sheet.
Security note (docling backend): The
doclingextra depends ontorch, which has a known deserialization vulnerability (PYSEC-2026-139) in the pt2 loading handler. No upstream fix is available as of 2026-05-25. Do not load untrusted model checkpoints when using the docling backend. Only use models from verified, trusted sources.
Format-Specific Behavior
Native backend output, for the formats it covers:
| Format | Native Backend Output |
|---|---|
| Page-by-page markdown with tables and metadata | |
| DOCX | Headings, paragraphs, and tables from XML |
| PPTX | Slide-by-slide with titles, body text, and notes |
| XLSX | Sheet-by-sheet GFM markdown tables |
| CSV | GFM markdown table |
| JSON | Pretty-printed fenced code block |
| YAML/YML | Fenced code block |
| Images (PNG, JPG, GIF, SVG, WEBP, BMP, TIFF) | Obsidian wikilink embed ![[image.png]] |
Pass-Through Mode
Files matching pass-through rules are copied to the output directory as-is, without extraction or conversion. This is useful for:
.mdfiles that are already Obsidian-ready.csv,.json,.yamlfiles used by Obsidian plugins (e.g., Dataview)- Any file type where transformation is unwanted
Pass-through rules are evaluated before backend dispatch. A file matches if it hits any rule (OR logic):
passthrough:
# Extension list (cheapest check, runs first)
extensions: [".md", ".markdown", ".canvas"]
# fnmatch patterns (matched against full source path string;
# '*' matches '/', so '**/' is not needed for directory traversal)
paths: ["notes/raw/**", "**/*.template.*"]
# Regex patterns (matched against full source path string)
patterns: [".*\\.generated\\..*"]
Decision tree:
File discovered
|
+- matches passthrough? -> COPY as-is (no .md wrapper)
|
+- NO -> backend dispatch -> extract -> write .md
Media Extraction
PDF, DOCX, and PPTX files can contain embedded images. Enable media extraction to save these as separate files alongside the markdown output:
media:
extract_images: true # enable/disable embedded image extraction
image_format: png # output format: png, jpg, webp
image_max_dimension: 0 # max width/height in px (0 = no resize)
Extracted images are saved in per-document media folders (<doc-stem>/) and referenced via Obsidian wikilinks (![[doc-stem/image_001.png]]).
To disable media extraction (e.g., for text-only pipelines), set extract_images: false in your config YAML or pass extract_images=False to config_for_backend().
Image Handling
Images are handled differently from text documents. The native image backend generates an Obsidian wikilink embed:
---
title: diagram
source: obsidian-import
file_type: png
---
![[diagram.png]]
The image file is automatically copied alongside the .md output so Obsidian can render it inline. Supported formats: PNG, JPG, JPEG, GIF, SVG, WEBP, BMP, TIFF.
CLI Reference
| Command | Description |
|---|---|
obsidian-import convert <path> |
Extract a single file |
obsidian-import discover --config <yaml> |
List matching files |
obsidian-import batch --config <yaml> |
Extract all discovered files (with pass-through) |
obsidian-import doctor |
Check backend availability |
Output Format
Extracted files are written as Obsidian-flavored markdown with YAML frontmatter:
---
title: Annual Report
source: obsidian-import
original_path: /documents/report.pdf
file_type: pdf
extracted_at: 2026-03-09T10:30:00Z
page_count: 12
---
# Annual Report
## Page 1
Content extracted from the first page...
Related Packages
- obsidian-export -- Convert Obsidian notes to PDF/DOCX
- agentic-brain -- Agentic knowledge management (consumes both packages)
License
MIT
Release files for obsidian-import 1.3.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| obsidian_import-1.3.1.tar.gz | 38.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| obsidian_import-1.3.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size:91.6 kB
Release files / obsidian_import-1.3.1.tar.gz
| Download URL | obsidian_import-1.3.1.tar.gz |
|---|---|
| Size | 38.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
0c8ea9f6e8749ce65bcaab25b5236cbd7b180fbd86b8e5090a51e4ac679e8e46
|
|
BLAKE2b-256 checksum How to use checksums |
af04ea0dbf2daafd02324c414745657622034d7a06b28fc50f24eccb22920825
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 29, 2026.
Transparency logRelease files / obsidian_import-1.3.1-py3-none-any.whl
| Download URL | obsidian_import-1.3.1-py3-none-any.whl |
|---|---|
| Size | 53.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
f05b867237ceddcd22fe42bc991a05cb8086d39e0e4fbc1cb8f94f4ac0bf8783
|
|
BLAKE2b-256 checksum How to use checksums |
b1abcd103232c174d2f4597e1f2323c59287c0bc50d197ddb8a51708d59e8cfd
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 29, 2026.
Transparency log