---
title: Native Markdown
description: Import a Markdown file as one queryable document — typed frontmatter fields, addressable sections with byte offsets, and editable section and frontmatter content.
---

OMGDB treats Markdown as a first-class input. A `.md` file — a spec, a note, a piece of documentation — can be imported as a single document where the YAML-ish frontmatter becomes typed, queryable top-level fields and the heading structure is captured under `_sections`, each section carrying a stable id and byte offsets into the stored body. The raw Markdown body is stored too, so nothing is lost. This is the knowledge layer an agent reads from — and, through the `md-set-section` and `md-patch-frontmatter` commands, writes back to: notes and specs land as ordinary documents you can [query](/docs/query-operators/) by their frontmatter and edit section by section.

Import is exposed through the CLI command `omgdb import-md`. Internally it parses the file, builds a document, inserts it, and prints the new `_id`.

## Importing a file

```sh
omgdb create app.omgdb
omgdb import-md app.omgdb docs spec.md
# -> prints the new document's _id as canonical JSON
```

The command takes three positional arguments: the store path, the target collection, and the path to the Markdown file. The whole file becomes exactly one document in the named collection.

| Argument | Description |
|----------|-------------|
| `<path>` | Store path, e.g. `app.omgdb`. |
| `<collection>` | Collection the document is inserted into. |
| `<file>` | Path to the `.md` file to parse and import. |

## The document shape

A parsed Markdown file is converted into a document with three parts:

1. **Frontmatter fields** — each `key: value` line from the frontmatter block is lifted to a top-level field, typed where possible.
2. **`body`** — the raw Markdown after the frontmatter, stored verbatim as a string.
3. **`_sections`** — an array of section objects, one per ATX heading, in document order. Each records a stable `sectionId`, a `slug`, the heading `level` and text, the section's `text` body, and four byte offsets into `body` (`startByte`, `endByte`, `textStartByte`, `textEndByte`).

### Frontmatter parsing

Frontmatter is recognized only when the file begins with a literal `---` fence line and a matching closing `---` fence follows. Inside the block, each non-empty line is split on its first `:` into a key and a value. The value is parsed as JSON when possible and otherwise kept as a string:

- `views: 10` becomes an integer (`I64`).
- `tags: ["rag", "db"]` becomes an array.
- `title: Hello World` stays a string (unquoted prose is not valid JSON, so it falls back to a string).

> **Limitation:** Frontmatter is a minimal line-based subset, not a full YAML parser. It splits on the first `:` per line and parses each value as JSON-or-string. Block scalars, nested maps, and multi-line values are not supported. A file with no `---` fences yields no frontmatter fields, and the entire text becomes `body`.

### Sections and the source map

Every ATX heading (`#` through `######`) starts a new section. A line is treated as a heading only when, after trimming leading whitespace, it begins with 1–6 `#` characters immediately followed by a single space. Each section records:

| Field | Description |
|-------|-------------|
| `sectionId` | Stable unique identifier derived from the heading slug. Duplicate headings get numeric suffixes: `api`, `api-2`, `api-3`. This is the handle section edits address. |
| `slug` | The (non-unique) slug of the heading text. |
| `level` | Heading level, `1` for `#` through `6` for `######`. |
| `heading` | The heading text, with the leading `#`s stripped. |
| `text` | The body text under the heading, up to the next heading. |
| `startByte` / `endByte` | Byte offsets of the section's full source (heading line included) inside `body`. |
| `textStartByte` / `textEndByte` | Byte offsets of just the section body source, after the heading line. |

The byte offsets are a **source map**: slicing `body` between a section's offsets yields exactly that section's original Markdown, so a section can always be mapped back to its precise position in the file — which is what makes surgical section edits possible.

> **Note:** Despite the "section tree" phrasing, `_sections` is a flat, ordered array. Heading levels are recorded in the `level` field, but no parent/child nesting is built. Any content before the first heading is not captured in a section (it still lives in `body`).

## Example

Given this `spec.md`:

```text
---
title: Hello World
views: 10
tags: ["rag", "db"]
---
# Intro

the intro text

## Details

more text
```

Importing it produces a document of this shape:

```json
{
  "title": "Hello World",
  "views": 10,
  "tags": ["rag", "db"],
  "body": "# Intro\n\nthe intro text\n\n## Details\n\nmore text\n",
  "_sections": [
    { "sectionId": "intro",   "slug": "intro",   "level": 1, "heading": "Intro",
      "text": "the intro text\n", "startByte": 0,  "endByte": 25,
      "textStartByte": 8,  "textEndByte": 25 },
    { "sectionId": "details", "slug": "details", "level": 2, "heading": "Details",
      "text": "more text",        "startByte": 25, "endByte": 47,
      "textStartByte": 36, "textEndByte": 47 }
  ]
}
```

Note that `title` came through as a string, `views` as a number, and `tags` as an array — each frontmatter line was typed according to whether its value parsed as JSON.

## Editing sections and frontmatter

Imported documents are not read-only snapshots. Two commands rewrite them **through ordinary op-log-backed replacements** — the edit is a `replace` op in the log like any other write, `_id` is preserved, and `body` and `_sections` are re-derived so the source map stays exact.

### md-set-section

Replaces the body under one section, addressed by its stable `sectionId`:

```sh
omgdb md-set-section app.omgdb docs '{"$oid":"..."}' intro "updated intro text"
# or, for multiline content:
omgdb md-set-section app.omgdb docs '{"$oid":"..."}' intro --file new-intro.md
```

The heading line is preserved; the replacement text becomes the section body (a trailing newline is added if missing, so the next heading can never be joined onto the new text). The document is reparsed after the edit, so headings *inserted by the replacement text* become addressable sections of their own, with fresh offsets. An unknown section id fails with ``Markdown section `intro` was not found``. The command prints the full replacement document.

### md-patch-frontmatter

Inserts, replaces, or removes frontmatter fields without touching the body:

```sh
omgdb md-patch-frontmatter app.omgdb docs '{"$oid":"..."}' \
  '{"title":"Updated","published":true}' --remove views
```

Removals apply first, then the patch's key/value pairs are inserted or replaced (existing field order is preserved where possible). `null` is a normal stored value — use `--remove` (repeatable) to delete a field. The generated fields `_id`, `body`, and `_sections` are reserved: patching or removing them fails with ``frontmatter field `body` is reserved``.

Both edits are also exposed over [MCP](/docs/mcp/) as the write-scope tools `markdown_set_section` (`path`, `collection`, `id`, `sectionId`, `text`) and `markdown_patch_frontmatter` (`path`, `collection`, `id`, `patch`, optional `remove`), so an agent can maintain a knowledge base through the same audited path.

## Querying frontmatter

Because frontmatter fields are stored as ordinary top-level fields, they are queryable like any other field — no special syntax. After import you can `find` on a frontmatter value:

```sh
omgdb find app.omgdb docs '{"title":"Hello World"}'
# result contains "Intro" and "_sections"
```

The same applies to the typed fields. A numeric frontmatter field works with the [comparison operators](/docs/query-operators/), and an array field works with array operators:

```sh
omgdb find app.omgdb docs '{"views":{"$gte":10}}'
omgdb find app.omgdb docs '{"tags":"rag"}'
```

The `_sections` array travels with the document, so the heading structure is available to read back, and the raw `body` is always present for full-text or downstream processing.

## Limitations

> **Limitation:** Import is one-shot. OMGDB does not watch or re-sync the source `.md` file after import — subsequent edits go through `md-set-section` / `md-patch-frontmatter` (or an ordinary replace), not the original file.

> **Note:** Heading-aware chunking into embeddings is not part of import. Embedding-based retrieval is provided by the vector layer — see [vector search](/docs/vector-search/). A field holding Markdown text can be declared as the `markdown` type in a [validation spec](/docs/schema-validation/).

## See also

- [Data model](/docs/data-model/) — how documents, types, and field paths work, including the conventions imported Markdown follows.
- [Vector search](/docs/vector-search/) — embedding and retrieval over imported content.
- [Query operators](/docs/query-operators/) — filtering on the frontmatter fields produced by import.
