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.

Where To Go Next

Reading this as an agent?For Agents
Something wrong on this page?Dots On GitHub