Import From Markdown

Bring a folder of Markdown files into a Dotabase with one command — frontmatter becomes properties, and re-runs stay idempotent.

One command pulls a folder of Markdown files into an existing Dotabase. The CLI walks the folder, and each file lands as a row whose fields come from the file's frontmatter and whose body is the file's Markdown.

The Command

Point import-markdown at a directory and name the target by slug:

CODE
dots dotabases import-markdown ./notes --dotabase reading-list

The CLI walks the directory tree recursively, reads every *.md file it finds, and posts them to the import route. The Dotabase must already exist — see Create a Dotabase. --dotabase is required and takes a slug, which the CLI resolves before it sends anything, so a slug typo fails without touching your data.

What Gets Walked

The walk is recursive and deterministic — directory entries are visited in sorted order — and the extension filter is exactly .md. A .markdown or .mdx file is not picked up.

There are no ignore rules on this command. It does not read .gitignore, and it does not skip node_modules or dotted directories, so pointing it at a repository root imports far more than you meant. Point it at the folder you actually want, or use dots dotabases index, which is the command that takes --exclude.

Files travel in batches rather than one request each: five files per request normally, and thirty when embedding, classification, AI fill, and tag inference are being deferred. The larger batch is why --defer-enrichment sends a large import in fewer requests.

How A File Maps To A Row

Each file is parsed into YAML frontmatter and a Markdown body. The server-side normalizer maps the frontmatter onto the row: the body becomes the row body, and frontmatter keys become the row's title, tags, domains, DIIICE type, and properties. A missing title falls back to the body's first heading, and then to the filename.

What happens to a key the normalizer does not recognize depends on which normalizer runs. The default for an ordinary Dotabase maps a known set of keys and drops the rest. The declarative field_map normalizer instead sweeps every unmapped key into the row's metadata, where it is preserved verbatim. Choose it with --normalizer when your frontmatter carries fields you want kept.

Re-running Is Safe

Import is idempotent. Row identity is the file's path relative to the directory you named, recorded on the row as source_path, so the same file updates the same row on the next run instead of creating a duplicate. A file that authors a source_path key in its own frontmatter cannot hijack another row's identity — reserved keys are set aside rather than honored.

A re-run is a patch, not a replacement. Metadata and properties merge key by key, so a field you added in the app survives; tags are replaced wholesale from the file; and the body is overwritten, because a re-import treats the file as the source of truth. The row keeps its human id, and its edges survive untouched.

Import draws no edges on its own. Every edge-drawing behavior is opt-in, so unless you have bound one to the Dotabase, connect rows separately with dots edges create once they are in place.

Useful Flags

  • --skip-behaviors <list> — suppress named behaviors on the imported rows (for example, classification,tag_inference to keep an authored DIIICE type and tags instead of letting inference set them). Names are not checked against the catalog, so a misspelled one is accepted and quietly does nothing.
  • --defer-enrichment — import immediately and enrich embedding, classification, AI fill, and tag inference in the background.
  • --normalizer <key> / --normalizer-config <path> — choose or configure the frontmatter-to-row mapping.

Import Or Index

import-markdown re-sends every file it walks. For an ongoing sync that diffs by content hash and re-imports only what actually changed, use dots dotabases index instead — it adds --exclude <glob> for skipping paths and --set <pair> for stamping a property on every imported row, and an unchanged file costs no request at all.

Pick one of the two for a given folder and stay with it. The two commands compute source_path differently — index namespaces it by directory — so running both against the same folder into the same Dotabase produces two rows per file rather than converging on one.

Import From Markdown · This page in the graph

Connected Records15

Documented by
Mentions
DIIICE
DOT
References
15 connections.