RepoPilotOpen in app →

terrastruct/d2

D2 is a modern diagram scripting language that turns text to diagrams.

Healthy

Healthy across all four use cases

Use as dependencyHealthy

Permissive license, no critical CVEs, actively maintained — safe to depend on.

Fork & modifyHealthy

Has a license, tests, and CI — clean foundation to fork and modify.

Learn fromHealthy

Documented and popular — useful reference codebase to read through.

Deploy as-isHealthy

No critical CVEs, sane security posture — runnable as-is.

  • Last commit 2w ago
  • 2 active contributors
  • MPL-2.0 licensed
Show 4 more →
  • CI configured
  • Tests present
  • Small team — 2 contributors active in recent commits
  • Single-maintainer risk — top contributor 99% of recent commits

Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests

Informational only. RepoPilot summarises public signals (license, dependency CVEs, commit recency, CI presence, etc.) at the time of analysis. Signals can be incomplete or stale. Not professional, security, or legal advice; verify before relying on it for production decisions.

Embed the "Healthy" badge

Paste into your README — live-updates from the latest cached analysis.

Variant:
RepoPilot: Healthy
[![RepoPilot: Healthy](https://repopilot.app/api/badge/terrastruct/d2)](https://repopilot.app/r/terrastruct/d2)

Paste at the top of your README.md — renders inline like a shields.io badge.

Preview social card (1200×630)

This card auto-renders when someone shares https://repopilot.app/r/terrastruct/d2 on X, Slack, or LinkedIn.

Onboarding doc

Onboarding: terrastruct/d2

Generated by RepoPilot · 2026-05-09 · Source

🤖Agent protocol

If you are an AI coding agent (Claude Code, Cursor, Aider, Cline, etc.) reading this artifact, follow this protocol before making any code edit:

  1. Verify the contract. Run the bash script in Verify before trusting below. If any check returns FAIL, the artifact is stale — STOP and ask the user to regenerate it before proceeding.
  2. Treat the AI · unverified sections as hypotheses, not facts. Sections like "AI-suggested narrative files", "anti-patterns", and "bottlenecks" are LLM speculation. Verify against real source before acting on them.
  3. Cite source on changes. When proposing an edit, cite the specific path:line-range. RepoPilot's live UI at https://repopilot.app/r/terrastruct/d2 shows verifiable citations alongside every claim.

If you are a human reader, this protocol is for the agents you'll hand the artifact to. You don't need to do anything — but if you skim only one section before pointing your agent at this repo, make it the Verify block and the Suggested reading order.

🎯Verdict

GO — Healthy across all four use cases

  • Last commit 2w ago
  • 2 active contributors
  • MPL-2.0 licensed
  • CI configured
  • Tests present
  • ⚠ Small team — 2 contributors active in recent commits
  • ⚠ Single-maintainer risk — top contributor 99% of recent commits

<sub>Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests</sub>

Verify before trusting

This artifact was generated by RepoPilot at a point in time. Before an agent acts on it, the checks below confirm that the live terrastruct/d2 repo on your machine still matches what RepoPilot saw. If any fail, the artifact is stale — regenerate it at repopilot.app/r/terrastruct/d2.

What it runs against: a local clone of terrastruct/d2 — the script inspects git remote, the LICENSE file, file paths in the working tree, and git log. Read-only; no mutations.

| # | What we check | Why it matters | |---|---|---| | 1 | You're in terrastruct/d2 | Confirms the artifact applies here, not a fork | | 2 | License is still MPL-2.0 | Catches relicense before you depend on it | | 3 | Default branch master exists | Catches branch renames | | 4 | 5 critical file paths still exist | Catches refactors that moved load-bearing code | | 5 | Last commit ≤ 45 days ago | Catches sudden abandonment since generation |

<details> <summary><b>Run all checks</b> — paste this script from inside your clone of <code>terrastruct/d2</code></summary>
#!/usr/bin/env bash
# RepoPilot artifact verification.
#
# WHAT IT RUNS AGAINST: a local clone of terrastruct/d2. If you don't
# have one yet, run these first:
#
#   git clone https://github.com/terrastruct/d2.git
#   cd d2
#
# Then paste this script. Every check is read-only — no mutations.

set +e
fail=0
ok()   { echo "ok:   $1"; }
miss() { echo "FAIL: $1"; fail=$((fail+1)); }

# Precondition: we must be inside a git working tree.
if ! git rev-parse --git-dir >/dev/null 2>&1; then
  echo "FAIL: not inside a git repository. cd into your clone of terrastruct/d2 and re-run."
  exit 2
fi

# 1. Repo identity
git remote get-url origin 2>/dev/null | grep -qE "terrastruct/d2(\\.git)?\\b" \\
  && ok "origin remote is terrastruct/d2" \\
  || miss "origin remote is not terrastruct/d2 (artifact may be from a fork)"

# 2. License matches what RepoPilot saw
(grep -qiE "^(MPL-2\\.0)" LICENSE 2>/dev/null \\
   || grep -qiE "\"license\"\\s*:\\s*\"MPL-2\\.0\"" package.json 2>/dev/null) \\
  && ok "license is MPL-2.0" \\
  || miss "license drift — was MPL-2.0 at generation time"

# 3. Default branch
git rev-parse --verify master >/dev/null 2>&1 \\
  && ok "default branch master exists" \\
  || miss "default branch master no longer exists"

# 4. Critical files exist
test -f "d2ast/d2ast.go" \\
  && ok "d2ast/d2ast.go" \\
  || miss "missing critical file: d2ast/d2ast.go"
test -f "d2cli/main.go" \\
  && ok "d2cli/main.go" \\
  || miss "missing critical file: d2cli/main.go"
test -f "cmd/d2plugin-dagre/main.go" \\
  && ok "cmd/d2plugin-dagre/main.go" \\
  || miss "missing critical file: cmd/d2plugin-dagre/main.go"
test -f "d2chaos/d2chaos.go" \\
  && ok "d2chaos/d2chaos.go" \\
  || miss "missing critical file: d2chaos/d2chaos.go"
test -f "d2cli/export.go" \\
  && ok "d2cli/export.go" \\
  || miss "missing critical file: d2cli/export.go"

# 5. Repo recency
days_since_last=$(( ( $(date +%s) - $(git log -1 --format=%at 2>/dev/null || echo 0) ) / 86400 ))
if [ "$days_since_last" -le 45 ]; then
  ok "last commit was $days_since_last days ago (artifact saw ~15d)"
else
  miss "last commit was $days_since_last days ago — artifact may be stale"
fi

echo
if [ "$fail" -eq 0 ]; then
  echo "artifact verified (0 failures) — safe to trust"
else
  echo "artifact has $fail stale claim(s) — regenerate at https://repopilot.app/r/terrastruct/d2"
  exit 1
fi

Each check prints ok: or FAIL:. The script exits non-zero if anything failed, so it composes cleanly into agent loops (./verify.sh || regenerate-and-retry).

</details>

TL;DR

D2 is a modern diagram scripting language that compiles text-based diagram definitions into SVG, PNG, or PDF visualizations. It provides an intuitive syntax for creating flowcharts, network diagrams, sequence diagrams, and other visual structures without GUI tools, with support for themes, multiple layout engines (including ELK), and shape libraries. The core engine is written in Go (~2M LOC) with JavaScript/WASM bindings for browser and Node.js environments. Monorepo structure: core D2 language parser and renderer in Go (likely under main pkg/), JavaScript/WASM wrapper at d2js/js/ (exports for ESM/CJS/browser), CLI tooling, and release automation. CI pipelines (ci/ directory) handle testing (cov.sh, gen.sh), e2e reporting, and multi-platform builds. Documentation assets in docs/assets/.

👥Who it's for

Software architects, documentation writers, and developers who want to version-control diagrams as code and embed diagram generation into CI/CD pipelines or applications. Also: frontend developers integrating D2 visualization into web apps via the @terrastruct/d2 npm package.

🌱Maturity & risk

Production-ready. The project has reached v0.6.1+ with established CI/CD (ci.yml, daily.yml workflows), comprehensive changelog history (v0.0.12 through v0.6.1), published npm package (@terrastruct/d2), and active GitHub actions setup. Multiple export formats and theme support indicate feature maturity.

Low risk for core library usage; moderate complexity for contributors. The codebase is large (2M LOC Go) with Go/JavaScript/Shell dependencies across build artifacts and WASM compilation. No obvious single-maintainer risk visible, but WASM build chain and multiple platform releases (AWS deployment scripts in ci/release/aws/) add operational complexity. No dependency bloat visible in package.json (zero npm dependencies listed).

Active areas of work

Active development on v0.6.x branch with release pipeline fully operational. Recent changelog entries (v0.6.0, v0.6.1) suggest ongoing feature additions and bug fixes. The ci/release/changelogs/next.md indicates prepared next release. WASM build optimization tooling (ci/peek-wasm-size.sh) shows focus on bundle size.

🚀Get running

Clone the repo: git clone https://github.com/terrastruct/d2.git && cd d2. For JavaScript development: cd d2js/js && npm install (uses Bun per package.json: bun install). For Go: make or check ci/dev.sh. Start dev server with bun run dev or use make targets in root Makefile.

Daily commands: JavaScript: cd d2js/js && bun run dev (starts dev-server.js). Makefile: make help likely lists targets. CI uses ci/dev.sh for local development setup. For testing JS: bun test test/unit && bun test test/integration.

🗺️Map of the codebase

  • d2ast/d2ast.go — Core AST definitions and parsing logic—the foundation for transforming D2 text into a diagram-ready syntax tree that all other components depend on
  • d2cli/main.go — Entry point for the D2 CLI tool; defines command routing and orchestrates the entire text-to-diagram pipeline
  • cmd/d2plugin-dagre/main.go — Layout engine plugin interface; critical for understanding how diagram layout calculation integrates with the core rendering pipeline
  • d2chaos/d2chaos.go — Handles diagram rendering and WASM/JavaScript compilation; bridges the Go parser with browser-based visualization
  • d2cli/export.go — Exports diagrams to multiple output formats (SVG, PNG, etc.); essential for understanding output generation
  • README.md — Project overview, feature list, and architecture philosophy; required reading to understand project scope and design goals
  • Makefile — Build orchestration and dependency management across Go, WASM, and JavaScript targets; necessary to understand local development workflow

🛠️How to make changes

Add a New D2 Language Keyword or Syntax Feature

  1. Define the new keyword in the language definition (d2ast/keywords.go)
  2. Extend the parser to recognize and tokenize the new syntax (d2ast/d2ast.go)
  3. Add AST node types to represent the new construct (d2ast/d2ast.go)
  4. Write parser tests to verify correct parsing behavior (d2ast/d2ast_test.go)
  5. Update the rendering logic to handle the new AST nodes (d2chaos/d2chaos.go)
  6. Add integration tests for the new feature end-to-end (d2chaos/d2chaos_test.go)

Add a New Output Format (e.g., JSON, GraphQL)

  1. Create a new export formatter function in the export module (d2cli/export.go)
  2. Register the formatter in the CLI export command dispatcher (d2cli/main.go)
  3. Write integration tests for the new export format (d2cli/export_test.go)
  4. Add format-specific documentation to the CLI help system (d2cli/help.go)

Integrate a New Layout Engine Plugin

  1. Create a new plugin directory under cmd/ (e.g., cmd/d2plugin-myengine/) (cmd/d2plugin-dagre/main.go)
  2. Implement the layout algorithm and expose it as a CLI tool or library (cmd/d2plugin-dagre/main.go)
  3. Register the plugin in the export command's layout engine selection logic (d2cli/export.go)
  4. Add end-to-end tests using different diagram types with the new engine (d2cli/export_test.go)

Add a New CLI Command

  1. Define the command handler function and flags in the CLI module (d2cli/main.go)
  2. Implement the command-specific logic (create a new file like d2cli/mycommand.go if substantial) (d2cli/main.go)
  3. Register the command in the CLI router (d2cli/main.go)
  4. Add help text and examples to the help module (d2cli/help.go)
  5. Write CLI tests for the new command (d2cli/main_test.go)

🔧Why these technologies

  • Go — Enables fast, statically-compiled CLI binary with minimal runtime dependencies; used for parser, layout orchestration, and command-line tool
  • WASM (WebAssembly) — Allows running the D2 parser and rendering engine in the browser without a server roundtrip; enables the playground and JavaScript SDK
  • Dagre (JavaScript/Node.js) — Proven graph layout library; integrated as a separate plugin/binary to decouple layout calculation from core parsing and rendering
  • SVG/PNG/PDF output — SVG for web interactivity and scalability; PNG/PDF for static documents and printing support

⚖️Trade-offs already made

  • Separate plugin architecture for layout engines (dagre as external command)

    • Why: Allows multiple layout backends without bloating core binary; enables users to swap or extend layout algorithms independently
    • Consequence: Requires inter-process communication (CLI invocation) which is slower than in-process library calls; added deployment complexity
  • Parser implemented in Go rather than a generated parser (yacc/bison)

    • Why: Hand-written parser provides better error messages, recovery, and IDE-friendly diagnostics; easier to evolve without external tool dependencies
    • Consequence: Higher maintenance burden; parser logic is more verbose; requires careful testing to prevent regressions
  • Dual distribution: Go CLI binary + JavaScript/WASM package

    • Why: Serves both CLI users (faster, no runtime dependency) and browser/Node.js users (portable, scriptable)
    • Consequence: Increased build/release complexity; must maintain feature parity across two platforms
  • Export to multiple formats (SVG, PNG, PDF) rather than a single format

    • Why: Covers web, print, and document embedding use cases
    • Consequence: Requires additional dependencies (e.g., PDF export library) and testing coverage; potential versioning issues with external converters

🚫Non-goals (don't propose these)

  • Does not provide server-side rendering as a managed service (users must self-host d2 binary or use SDK)
  • Does not include real-time collaborative editing (single-user, file-based workflow)
  • Does not support animation or interactive state changes in diagrams (static output only)
  • Does not include a built-in IDE or editor (complements external editors via LSP or plugins)
  • Does not provide native Windows GUI (CLI-only on Windows; web UI via playground)
  • Does not handle automatic diagram version control or diff visualization (users must manage separately)

🪤Traps & gotchas

WASM compilation: ci/peek-wasm-size.sh suggests WASM bundle size is monitored; build process likely requires emcc (Emscripten) or wasm-pack—check ci/release/build.sh for exact toolchain. Git submodules (.gitmodules): git clone --recurse-submodules required for full setup. Layout engine (ELK) integration likely has version constraints—check Go mod files. Bun vs npm: package.json uses Bun; npm ci may not work identically.

🏗️Architecture

💡Concepts to learn

  • Abstract Syntax Tree (AST) for diagram DSL — D2 parses text into an AST before rendering; understanding AST structure is essential for implementing language features, plugins, or editor tooling
  • Graph layout engines (ELK, Dagre) — D2 abstracts layout algorithms via ELK and other engines; knowledge of how these engines position nodes/edges is necessary for layout customization or debugging diagram output
  • WebAssembly (WASM) FFI — D2's Go engine is compiled to WASM for browser/Node.js use; understanding WASM memory boundaries and FFI is critical for JavaScript binding maintenance
  • Theme system / CSS-like styling — D2 diagrams support configurable themes (theme-id) and style properties (style.stroke-dash, style.multiple); contributors need to understand how style cascading works
  • Shape vocabulary and layout constraints — D2 defines a library of shapes (hexagon, cylinder, person, stored_data, etc.) with specific rendering and layout rules; extending shapes or fixing rendering bugs requires knowledge of shape semantics
  • SVG rendering pipeline — D2 outputs SVG as primary format with PNG/PDF as derivatives; understanding the SVG generation path is essential for style/visual bug fixes and performance optimization
  • Dual-build monorepo (Go + JavaScript) — D2's architecture requires coordinating Go compilation, WASM export, and JavaScript packaging; understanding the build pipeline (Makefile, ci/release/) is critical for local testing and releases
  • mermaid-js/mermaid — Direct alternative diagram-as-code tool with similar syntax focus; major competitor in text-to-diagram space
  • plantuml/plantuml — Predecessor to modern diagram-as-code; establishes UML diagram DSL pattern that D2 builds upon
  • terrastruct/tstruct-cli — Official CLI wrapper for D2; primary user interface for command-line diagram compilation
  • microsoft/vscode-d2 — Official VS Code extension for D2 syntax highlighting and preview; critical IDE integration
  • vitest-dev/vitest — Testing framework compatible with Bun test runner used in d2js/js/; relevant for JavaScript test setup

🪄PR ideas

To work on one of these in Claude Code or Cursor, paste: Implement the "<title>" PR idea from CLAUDE.md, working through the checklist as the task list.

Add missing integration tests for D2.js WASM output validation

The package.json shows test:integration script exists but the test/ directory structure isn't visible in the repo listing. Given that d2js/js is a WASM wrapper around the Go D2 compiler, there should be comprehensive integration tests validating that the compiled WASM produces correct diagram outputs for various D2 syntax scenarios. This ensures the browser/Node.js WASM output matches the native Go implementation.

  • [ ] Create test/integration directory structure if missing
  • [ ] Add tests validating basic shape/connection rendering (e.g., box -> circle syntax)
  • [ ] Add tests for complex D2 features (containers, sequences, classes, SQL tables) producing valid SVG/PNG output
  • [ ] Verify tests run via existing 'bun test test/integration' script in both Node.js and browser environments
  • [ ] Reference ci/e2ereport.sh to understand the e2e testing patterns used in the main codebase

Add GitHub Action workflow for automated WASM bundle size regression testing

The repo has ci/peek-wasm-size.sh script and multiple CI workflows (.github/workflows/), but no automated size checking in CI. The d2js/js package publishes browser WASM builds - a PR could add a GitHub Action that tracks and alerts on WASM bundle size increases, preventing performance regressions in the published npm package.

  • [ ] Create .github/workflows/wasm-size-check.yml workflow
  • [ ] Integrate ci/peek-wasm-size.sh into the workflow to get baseline and current build sizes
  • [ ] Add workflow step that compares sizes and fails if increase exceeds threshold (e.g., >5%)
  • [ ] Post size report as PR comment using actions/github-script for visibility
  • [ ] Reference existing ci.yml and daily.yml workflows for setup patterns (Go build, Node.js setup)

Add TypeScript strict mode compliance and type safety for d2js/js exports

The package.json shows index.d.ts is provided for types, but without visibility into actual type definitions and given the complexity of WASM bindings, there's likely room to improve type safety. A PR could enable TypeScript strict mode (--strict flag in tsconfig) and add more precise types for the WASM module exports and worker initialization.

  • [ ] Review and strengthen types in index.d.ts for all exports (browser, import, require, worker)
  • [ ] Create or update tsconfig.json to enable strict: true and enable strict null checks
  • [ ] Add JSDoc comments with @param @returns @throws to index.d.ts exports for better IDE support
  • [ ] Update dev-server.js and test files to pass strict type checking
  • [ ] Document the type safety improvements in the d2js/js README.md

🌿Good first issues

  • Add missing TypeScript type definitions for worker API in d2js/js/index.d.ts; currently exports main API but worker thread types lack detail
  • Expand ci/cov.sh coverage reporting to include HTML reports in dist/ folder for easier local review; currently only generates coverage metadata
  • Document the theme-id numbering system in docs/: what do theme IDs 300+ represent? Create docs/THEMES.md with concrete examples from ci/release/changelogs/

Top contributors

Click to expand

📝Recent commits

Click to expand
  • 2446e24 — Merge pull request #2740 from alixander/remove-d2-studio-references (alixander)
  • 84e7240 — Remove D2 Studio references (alixander)
  • 93f9720 — Merge pull request #2674 from ocadaruma/fix-legend-width-on-mono (alixander)
  • 1ade3c2 — Text may overflow legend bounds when monospace font is used (ocadaruma)
  • 842358c — Merge pull request #2663 from alixander/png-gif (alixander)
  • a527168 — next (alixander)
  • 654122c — default animate-interval (alixander)
  • 82b5552 — fix chromium prompt (alixander)
  • f19df69 — fix png image loading (alixander)
  • 4928a6e — Revert "remove useless gif test" (alixander)

🔒Security observations

The D2 project demonstrates a reasonable security posture for a WASM-based diagram language. The main strengths are minimal production dependencies (reducing attack surface) and active CI/CD pipelines. Primary concerns include: (1) lack of explicit security scanning in visible CI configuration, (2) unversioned dev dependency on Bun, (3) no visible WASM artifact verification mechanisms, and (4) complex release infrastructure without documented security controls. The project would benefit from adding automated security scanning, signed releases, and explicit integrity checking for WASM binaries. No hardcoded credentials or obvious injection vulnerabilities are visible in the provided file structure.

  • Medium · No dependencies specified for production — package.json - dependencies field. The package.json shows an empty 'dependencies' object. While this can be intentional for a WASM-based project, it means the package relies entirely on bundled WASM binaries without declared dependencies. This makes it difficult to track supply chain security and potential vulnerabilities in any runtime dependencies that might exist. Fix: Explicitly declare all production dependencies, even if minimal. Use npm audit regularly to check for vulnerabilities. Document why dependencies are minimal or absent.
  • Medium · Development dependency on 'latest' version of Bun — package.json - devDependencies. The devDependencies specifies 'bun': 'latest' which pins to the latest version without version constraints. This can lead to unexpected breaking changes during development and CI/CD builds, potentially introducing security issues or build failures. Fix: Pin bun to a specific version range (e.g., '1.x.x' or '~1.0.0') to ensure reproducible builds and better control over updates.
  • Low · WASM binary security not explicitly addressed — ci/release/ and build processes. As a WASM-based project, the security of compiled binaries is critical. The package structure doesn't show explicit verification mechanisms (checksums, signatures) for WASM artifacts in the visible file structure. Fix: Implement cryptographic verification (SHA-256 checksums, GPG signatures) for all released WASM binaries. Document the build process for reproducibility. Consider SBOM (Software Bill of Materials) generation.
  • Low · Insufficient CI/CD security documentation — .github/workflows/. While CI workflows are present (.github/workflows/), the visible file structure doesn't show explicit security scanning or dependency auditing configurations in the workflows. Fix: Add automated security scanning to CI pipelines: npm audit, SAST tools (CodeQL, Snyk), and dependency scanning. Implement branch protection rules requiring security checks.
  • Low · Release script complexity without visible validation — ci/release/ directory. Multiple release scripts exist (ci/release/*.sh) but there's no visible validation, signing, or integrity checking mechanisms documented for release artifacts. Fix: Implement signed releases with GPG keys. Add release artifact verification. Document the release process. Use semantic versioning consistently.

LLM-derived; treat as a starting point, not a security audit.


Generated by RepoPilot. Verdict based on maintenance signals — see the live page for receipts. Re-run on a new commit to refresh.

Healthy signals · terrastruct/d2 — RepoPilot