Markdown Database Integration
WordPress database drop-in that stores content as markdown files. SQLite for machinery, markdown for knowledge. AI-native WordPress storage layer.
by Chris Huber · github.com/automattic/markdown-database-integration · website
Install
The author publishes release zips, so WP-CLI can install straight from GitHub:
wp plugin install https://github.com/automattic/markdown-database-integration/releases/download/v0.13.0/markdown-database-integration.zipReadme
Markdown Database Integration
File-backed, reconstructable WordPress database state: Markdown for content, JSON for WordPress and plugin table rows, SQL for plugin schemas, and SQLite as a rebuildable query engine and index.
What This Does
MDI has two SQLite-backed operating modes:
mirror(the default) keeps the SQLite database authoritative and mirrors Markdown-backed post writes to files.primarypersists the state needed to reconstruct WordPress to ordinary files. SQLite remains the runtime query engine and index, but a cold boot can recreate it from the content and state trees.
SQLite Integration API
SQLite Database Integration is optional. It is required only by the sqlite
backend; the default mdi-native backend serves WordPress directly from the
canonical Markdown and JSON files and never loads it.
When the sqlite backend is selected, MDI requires a SQLite Database
Integration release that includes the canonical
PDO-compatible WP_MySQL_On_SQLite API, introduced by
WordPress/sqlite-database-integration#449. MDI no longer uses the deprecated
WP_SQLite_Driver compatibility layer. The runtime constructs the canonical
driver with its mysql-on-sqlite: DSN and consumes query results as
PDOStatement objects.
In primary mode, a typical single-root store looks like this:
wp-content/db/
post/
hello-markdown-world.md
gutenberg-block-test.md
page/
about.md
contact.md
wiki/ # Custom post type.
woocommerce-pricing.md
_options/
siteurl.json
_tables/
users.json
comments.json
my_plugin_jobs.json
_schema/
my_plugin_jobs.sql
Post types get their own directories. Each Markdown-backed post file has YAML
frontmatter for its row data, post meta, and terms, with stored post_content
bytes as its body:
---
type: document
title: Gutenberg Block Test
description: A Gutenberg block editor smoke page.
resource: https://example.test/gutenberg-block-test
tags: [gutenberg, blocks]
timestamp: "2026-04-14T03:14:35+00:00"
wordpress:
id: 7
status: publish
type: post
author: 1
date: "2026-04-14 03:14:35"
modified: "2026-04-14 03:14:49"
slug: gutenberg-block-test
---
## This is a heading block
Content goes here with **bold** and *italic* text.
- List item one
- List item two
> A blockquote for good measure.
MDI does not decide whether that body is Markdown, block markup, or HTML. It
stores whatever the caller or content-format layer writes to post_content.
MDI manages the frontmatter shape automatically. Markdown files use portable, WordPress-compatible metadata: broadly useful concept fields stay at the top level, while WordPress round-trip fields live under wordpress. Existing MDI files are rewritten to the current shape by the one-time frontmatter migration during upgrade.
Primary-Mode Persistence
Primary mode persists the following state so SQLite can be reconstructed:
- Post rows and content in
post/*.md,page/*.md, and custom-post-type directories; their post meta and terms are in frontmatter. - Options as individual
_options/*.jsonfiles. - Users and usermeta, taxonomy tables, comments and commentmeta, links, and
non-Markdown posts as
_tables/*.jsonsnapshots. - Arbitrary plugin-table rows as
_tables/*.jsonsnapshots and their schemas as_schema/*.sqlfiles.
The MARKDOWN_DB_TABLE_DURABILITY_POLICY wp-config.php array and
markdown_db_table_durability_policy filter classify each table as
canonical, reconstructible, or ephemeral. Canonical tables persist their
schema and complete rows. Reconstructible tables may persist a bounded
projection (query, limit, or partition settings), while their owner may
recreate the runtime table when canonical state is absent. Ephemeral tables are
excluded from mutation capture, schema and row snapshots, native registration,
and cold hydration. Tables remain canonical by default.
The constant makes the policy available during drop-in cold reconstruction:
define( 'MARKDOWN_DB_TABLE_DURABILITY_POLICY', array(
'runtime_events' => array( 'durability' => 'reconstructible', 'projection' => array( 'limit' => 100 ) ),
'runtime_locks' => 'ephemeral',
) );
The filter receives that normalized policy, unprefixed table name, and full table name, and may override it after hooks are available. For example:
add_filter( 'markdown_db_table_durability_policy', function ( $policy, $suffix ) {
if ( 'runtime_events' === $suffix ) {
return array( 'durability' => 'reconstructible', 'projection' => array( 'limit' => 100 ) );
}
if ( 'runtime_locks' === $suffix ) {
return array( 'durability' => 'ephemeral' );
}
return $policy;
}, 10, 2 );
MARKDOWN_DB_EPHEMERAL_TABLES, markdown_db_ephemeral_tables,
markdown_db_table_persistence_policy, markdown_db_persistent_table_query,
and markdown_db_persistent_table_rows remain compatibility inputs to this
resolver. Sites can migrate table classifications to the unified filter while
retaining the query and row filters for existing projections.
Storage Boundary
MDI is a storage and persistence layer:
- It persists database state to files and rebuilds or synchronizes the SQLite index from those files in primary mode.
- It stores
post_contentbytes exactly as received. - It does not render markdown to HTML.
- It does not convert editor block markup to markdown during normal writes.
- It does not register render, REST, editor, or write-engine conversion hooks.
Content-format policy belongs to the application layer above MDI. A site can
choose block markup, HTML, Markdown, or another format for post_content; MDI
persists those bytes and database state without interpreting them.
Import/export is the explicit content-format boundary. The markdown-db import
and markdown-db export commands and abilities use Block Format Bridge to
round-trip between markdown files and serialized block content by default:
- Import:
markdown→blocks - Export:
blocks→markdown - Raw byte preservation: pass
--no-convertor setno_convertin the ability input. - Custom conversion: pass
--from=<format> --to=<format>. - Policy override: filter
markdown_db_content_format_conversion. - Layout selection: pass
--layout-profile=<id>or configureMARKDOWN_DB_CONTENT_LAYOUT_PROFILE. External layouts register their completeenumerate,map_source, andpath_for_postcontract from the bootstrap file named byMARKDOWN_DB_CONTENT_LAYOUT_PROFILE_BOOTSTRAP, which is loaded before thedb.phpprimary runtime starts.
Custom layout writes stage a temporary file in a verified destination directory.
MDI rejects symlinked path segments and compares the directory device/inode before
and after staging and immediately before rename. A detected directory replacement
aborts and removes the staged file. This is PHP's strongest portable filesystem
guarantee without an openat(2) descriptor API; deployments that permit an
attacker to replace directories between the final identity check and rename()
must protect the content root with filesystem ownership and permissions.
WRITE:
WordPress caller writes post_content
│
▼
SQLite stores post_content bytes
│
▼ MDI write engine
.md file stores the same bytes
READ (site boots):
.md file body
│
▼ loaded AS-IS into in-memory SQLite
post_content has the same bytes
IMPORT / EXPORT:
.md file body or post_content
│
▼ BFB conversion, unless disabled
target file body or post_content
RENDER / EDITOR / API:
Handled by the application/content-format layer, not MDI.
Dependencies
MDI requires Block Format Bridge for self-contained import/export conversion. The drop-in and live write engine remain byte-preserving; BFB is not used by the runtime render, REST, editor, or DB write paths.
Why
File-backed primary state makes a WordPress site reconstructable instead of depending solely on one SQLite file. That supports:
- Portability between machines or fresh WordPress installations.
- Git review, history, and replication for content and any state roots a site chooses to version.
- Direct inspection and editing of content by people, local tools, and AI agents.
- Backups and recovery by rebuilding a disposable SQLite runtime from the persisted files.
- Disposable local or test runtimes that can be recreated from the same trees.
Architecture
WordPress Core ($wpdb)
│
v
WP_Markdown_Driver -------------------- runtime queries ----> SQLite index
│ ^
│ successful writes │ rebuild/sync
v │
Persisted files ---------------------- cold/warm boot --> MDI loader
│
├── MARKDOWN_DB_CONTENT_DIR
│ post/*.md, page/*.md, {type}/*.md
│
└── MARKDOWN_DB_STATE_DIR
_options/*.json, _tables/*.json, _schema/*.sql,
markdown-index.sqlite
Durable Reconciliation Operations
Bounded reconciliation can use WP_Markdown_Durable_Reconciliation_Operations
to coordinate mutations that cross the WordPress and canonical-filesystem
durability domains. Operations bind a plan and continuation, canonical root,
normalized resource identity, direction and kind, and exact normalized before
and after identities. The lifecycle is explicit:
planned -> claimed -> effect_observed -> completed
\-> ambiguous -> reconciliation_required
WP_Markdown_Reconciliation_Adapter keeps WordPress, SQLite, MySQL, and file
observation/mutation details behind the owning adapter. Recovery of an ordinary
operation is observation-only and completes only when every named domain exactly
proves the intended after identity. A separately prepared WordPress commit may
continue its canonical effect only after its exact commit checkpoint is proven.
Missing, indeterminate, or divergent evidence is persisted as a structured
reconciliation_required conflict.
Because only an owning backend can make resource fencing atomic with its effect, an adapter installs each claimed fence and must atomically reject any token that is no longer current for the normalized resource. The generic store also rejects expired-lease transitions; together these prevent a stale worker from publishing progress or mutating after a replacement owner has fenced it.
The supplied filesystem operation store uses revision/state/fence compare-and-set transitions, expiring ownership leases, and monotonically increasing fencing tokens. Its HMAC-authenticated, atomically replaced journal has caller-configured record and byte bounds, retains terminal identities to prevent stale intent from being planned again, and rejects placement within or above a managed canonical root. Operations are accepted only for roots authorized when the store is constructed, and at least one root must be declared. Deployments should derive its authentication key from server-only secret material and place it in a server-owned runtime directory. This journal is runtime coordination state, not canonical content; it has no Git or publication requirement.
Read the full README on GitHub →
Releases
| Tag | Published | Asset | Downloads |
|---|---|---|---|
| v0.13.0 | Sep 2, 2026 | markdown-database-integration.zip | 0 |
| v0.12.0 | Aug 30, 2026 | markdown-database-integration.zip | 0 |
| v0.11.5 | Aug 25, 2026 | markdown-database-integration.zip | 2 |
| v0.11.4 | Aug 25, 2026 | markdown-database-integration.zip | 0 |
| v0.11.3 | Aug 25, 2026 | markdown-database-integration.zip | 0 |
| v0.11.2 | Aug 25, 2026 | markdown-database-integration.zip | 0 |
| v0.11.1 | Aug 25, 2026 | markdown-database-integration.zip | 0 |
| v0.11.0 | Aug 25, 2026 | markdown-database-integration.zip | 1 |
| v0.10.2 | Aug 23, 2026 | markdown-database-integration.zip | 3 |
| v0.10.1 | Aug 23, 2026 | markdown-database-integration.zip | 2 |
| v0.10.0 | Aug 23, 2026 | markdown-database-integration.zip | 3 |
| v0.9.2 | Aug 12, 2026 | markdown-database-integration.zip | 1 |
| v0.9.1 | Aug 12, 2026 | markdown-database-integration.zip | 1 |
| v0.9.0 | Aug 12, 2026 | markdown-database-integration.zip | 2 |
| v0.8.9 | Jul 29, 2026 | markdown-database-integration.zip | 1 |
| v0.8.8 | Jul 29, 2026 | markdown-database-integration.zip | 0 |
| v0.8.7 | Jul 29, 2026 | markdown-database-integration.zip | 0 |
| v0.8.6 | Jul 29, 2026 | markdown-database-integration.zip | 1 |
| v0.8.5 | Jul 29, 2026 | markdown-database-integration.zip | 4 |
| v0.8.4 | Jul 28, 2026 | markdown-database-integration.zip | 3 |
| v0.8.3 | Jun 25, 2026 | 01-markdown-database-integration.zip | 5 |
| v0.8.3 | Jun 25, 2026 | markdown-database-integration.zip | 3 |
| v0.8.2 | Jun 17, 2026 | markdown-database-integration.zip | 3 |
| v0.8.1 | Jun 16, 2026 | markdown-database-integration.zip | 1 |
| v0.8.0 | May 27, 2026 | markdown-database-integration.zip | 4 |