A desktop app for viewing and editing data dictionaries. A dictionary is presented as a spreadsheet — rows are data elements, columns are the specification's fields — alongside live previews of the CSV and LinkML YAML serializations and the rendered HTML page. REDCap data dictionary exports can be imported directly.
Installers — a DMG for macOS, an NSIS installer for Windows, an AppImage for
Linux — are on the
Releases page. They are
unsigned for now; see Packaging for the first-launch steps each
platform requires. Installers can also be built locally with npm run dist,
or the app can be run from source (see Development).
dd-edit opens data dictionaries saved as CSV, LinkML YAML, or dd-json — use
Open… (⌘O) or the buttons on the welcome screen. These are not arbitrary
formats. A CSV must follow the
data dictionary specification:
one row per data element, under the specification's column headers (Id,
Label, Datatype, and so on). A CSV of study data, or a dictionary laid
out to some other convention, will not open. Likewise, LinkML YAML must have
the form described in Relationship to
LinkML.
REDCap data dictionary exports are the exception: they are recognized and
brought in as an import (the title shows the source, e.g.
study.csv (imported)), and saving writes a standard data dictionary, since
REDCap's own format cannot express everything the specification can.
The grid works the way a spreadsheet does. Click a cell to edit it in place, copy and paste ranges to and from Excel or Google Sheets, drag the fill handle to repeat a value down a column, and drag rows to reorder them. Add an element with the row at the bottom of the grid; undo and redo work for every change (⌘Z / ⇧⌘Z); search the grid with ⌘F or the toolbar's Search button.
Two kinds of problem have a suggested correction shown directly in the cell:
a unit written informally where a standard code exists, and an enumeration
whose values are all integers while the datatype says string. In both
cases an amber suggestion appears in the cell; clicking it applies the
correction (datatype changes ask for confirmation first).
Selecting a row shows every field of that element in the panel on the right, where each can be edited. Several fields provide assistance:
- Precondition — conditions such as
consented = "1" and age >= 18are written here; the field suggests what can come next as you type and shows a plain reading of the condition underneath. - Unit — typing a unit suggests the standard UCUM
code (for example "years" →
a); free text remains allowed. - Enumerations and missing-value codes — each permissible value is edited with its label, optionally linked to an ontology term.
- Ontology terms — pasting an IRI or an OBO id such as
MONDO:0004979shows the term's human-readable name, with a link out to browse it. - Description — written in Markdown, shown formatted.
The CSV, LinkML, and HTML tabs show the dictionary as it will be written out — as a table, as a LinkML schema, and as a formatted web page — and they update as you type. Selecting rows in the grid restricts the LinkML preview to those elements, so a single field's rendering can be inspected without scrolling through the whole schema:
LinkML is an open modeling language for describing the structure of data. A schema written in LinkML can be used with the LinkML ecosystem's tools to validate data files and to derive other artifacts from the same source, such as JSON Schema, Pydantic models, and documentation. A data dictionary saved from dd-edit as LinkML YAML therefore serves two purposes: it documents the variables in a datafile, and it is a schema against which that datafile can be validated.
The schemas that dd-edit reads and writes follow the form produced by the
data dictionary specification's
dd-to-linkml renderer. A single tree-root class describes the target
datafile, with one attribute per data element, in column order. Enumerations
are represented as LinkML enums whose permissible values carry the value
labels as titles; sections are represented as subsets (in_subset); ontology
terms as related_mappings. The underlying datatype of an enumerated field
is recorded in a value_datatype annotation, and the field's range is
expressed as an any_of over the field's own enum and a shared enum of
missing-value codes. dd-edit is not a general-purpose LinkML schema editor:
it reads schemas of this form, and an arbitrary LinkML schema will not open.
The Problems tab lists everything the specification's validator finds in the open dictionary, and each problem highlights its cell in the grid. Line numbers match the saved CSV line for line, so a problem reported against the file can be traced to its row.
Enumerations are edited in the inspector, not yet directly in the grid, and sections cannot yet be collapsed into groups. DESIGN.md has the full roadmap.
The repo also ships an MCP server, so an AI assistant can read, check and author data dictionaries directly — adding elements, fixing units, importing a REDCap export, resolving ontology terms, and exporting to CSV or LinkML. It works with any MCP client, and uses the same toolkit the app does, so a dictionary edited by an assistant and one edited in the grid mean the same thing and validate identically.
It runs separately from the app, and needs no app running. Setup and a worked example are in docs/MCP-GUIDE.md; the tool reference is mcp-server/README.md.
Two things worth knowing before you point one at your work. The server will not
touch your filesystem unless you start it with --save-root DIR, and then only
within that directory. And an assistant editing a file you also have open in
dd-edit is two editors on one document — the app offers to reload a file that
changed on disk, but saving from both sides is still a way to lose work.
The app is two processes: the Electron/React editor owns the document, and a stateless Python sidecar (FastAPI, localhost) wraps the released toolkit packages for conversion, validation, rendering, and REDCap import — see DESIGN.md for the full picture. Hence two one-time setups, then one command.
# 1. Python sidecar (on Windows, the venv's pip is .venv\Scripts\pip)
cd sidecar
python -m venv .venv && .venv/bin/pip install -e ".[test]"
cd ..
# 2. Node app
npm install
# Run (spawns the sidecar automatically, opens the window with HMR)
npm run devSidecar tests: cd sidecar && .venv/bin/pytest. Renderer tests: npm test.
Type checks: npm run typecheck. CI runs all three on every push.
# one-time: PyInstaller into the sidecar venv
cd sidecar && .venv/bin/pip install -e ".[build]" && cd ..
npm run distnpm run dist builds the app bundles, the PyInstaller one-dir sidecar
(sidecar/build_binary.py), and installers in release/ for the platform
you are on — DMG + zip on macOS, an NSIS installer on Windows, an AppImage
on Linux. The sidecar ships inside the app as an extra resource and is
spawned from there when the app is packaged. PyInstaller does not
cross-compile, so each platform's installer must be built on that platform;
the Release Installers GitHub Actions workflow builds all three.
Run manually from the Actions tab it uploads them as workflow artifacts;
pushing a tag vX.Y.Z (which must match package.json's version) runs the
same build and publishes the installers as a GitHub release.
Builds are unsigned for now: the first launch needs right-click → Open on
macOS (Gatekeeper), or "More info" → "Run anyway" on Windows (SmartScreen).
On Linux, mark the AppImage executable first (chmod +x dd-edit-*.AppImage).
dd-edit is built with:
- Glide Data Grid — the canvas-based spreadsheet grid used for the editor.
- Electron, electron-vite, electron-builder, React, and Zustand — the app shell, build tooling, and state management.
- FastAPI and Uvicorn — the Python sidecar — bundled for distribution with PyInstaller.
- LinkML — the schema language the toolkit renders dictionaries into.
- marked — Markdown rendering for descriptions.
- EMBL-EBI OLS4 and BioPortal — ontology term label lookups.
- UCUM — the unit vocabulary used for Unit field suggestions.

