Run your EDS project locally. Author it in place. Ship the same DOM.
ledge is one binary that serves an Edge Delivery Services project straight from disk — your blocks/, your scripts/aem.js, your styles/, unchanged — and opens the same page for editing on a second port. Content is rendered one way, model to DOM, on the server. There is no preview build to wait for.
Before, the first two steps ran through git and somebody else’s pipeline: edit a file, commit, push, wait for the preview to sync, reload, repeat. After, they are local and immediate — the editor writes the page model back, the server re-renders it, and the delivery port serves the result on the next request. Publishing is still a deliberate act, but it is a button, not a second pipeline.
One process, one page model, three jobs. Instrumentation is the only difference between the page markup :3001 renders and the page :3000 serves, and it is added at render time rather than stored — so a page cannot acquire editor attributes by being edited. :3002 reads its own store, which is why a draft on :3000 is not yet a page on :3002.
The editor
The same page, edited in place
Blocks are selected on the rendered page, not in a form beside it. The palette inserts and reorders them by drag; the inspector edits exactly the fields that block’s model declares.

Fig. 3 — the ledge editor on the authoring port: block palette in the left pane, properties and outline in the inspector. Placeholder until a screenshot replaces it; that swap is an authoring edit, not a deploy.
Getting started
One command, one config file
# serve an existing EDS project straight from disk
ledge ./my-eds-project
# delivery http://localhost:3000
# authoring http://localhost:3001
# published http://localhost:3002
Configuration is one file at the project root — the ports above are defaults, so this is only about the site itself and the extensions it uses:
ledge.toml[site]
origin = "https://example.com"
title = "Example"
description = "An Edge Delivery Services site, self-hosted."
[plugins]
enabled = true
dir = "plugins"
[forms]
dir = "forms"
[api]
public_page_export = true
[seed]
enabled = true
That is this site’s own ledge.toml, minus the domain. This page is an ordinary ledge page: seeded from JSON in the site’s repo on first boot, editable in the browser on the authoring port, and served to you from the delivery port. The syntax highlighting above is done by a WebAssembly plugin compiled from this same repo — there is no highlighter in the client bundle.
Capabilities
What ledge does
Five areas, each with a page of its own.
The two-panel editor, drag-to-insert blocks and sections, a page-revision journal with rollback, per-page publish, and live reload driven by a file watcher over SSE.
Your blocks/, styles/ and scripts/ unchanged, a byte-identical delivery DOM, and an importer that pulls an existing EDS site in - pages, assets, icons, and web fonts.
Sandboxed WebAssembly plugins that render blocks and add authoring panels, minijinja block templates, and Ledge-native field types. The code sample above is rendered by a plugin.
Author sheets in a grid editor, server-rendered forms that write to a sheet, and generated query-index.json, sitemap.xml and RSS. The table below reads from a sheet.
A four-stage musl build into a scratch image with a healthcheck subcommand, rustls throughout with a test that fails on OpenSSL, and a second, smaller build profile.
Build profiles
Two builds from one source tree
The full build is everything on this page. The EDS-only build compiles out every ledge-specific extension that carries a heavy dependency or an admin surface of its own, and keeps the whole of the EDS story: delivery, authoring, publishing, media, derived outputs.
17.9 MB full · 9.89 MB EDS-only (musl, scratch)
477 full · 304 EDS-only (normal dependency graph)
Byte-identical across both builds — one shared snapshot set, no per-profile variants
:3000 delivery · :3001 authoring · :3002 published
| Capability | Full | EDS-only |
|---|---|---|
| Delivery, authoring & publish | Yes | Yes |
| WASM plugins | Yes | No |
| Block templates | Yes | No |
| Server-rendered forms | Yes | No |
| Authoring auth | Yes | No |
| Page history | Yes | No |
| Cache rules | Yes | No |
| Git code store | Yes | No |
| Metrics | Yes | No |
| Minification | Yes | No |
Both figures are measured off ledge’s own dependency graph and image build, not estimated. A page a build cannot render is never rejected: a templated or plugin-claimed block falls through to the generic EDS block DOM, and a build that finds block templates it cannot render says so once at startup rather than silently serving different HTML. This table is a ledge sheet — changing a row is a grid edit at /_/sheets, not a deploy.
FAQ
Answers up front
It is a server for an EDS project. The blocks/, styles/ and scripts/ are the ones you already have — this site’s scripts/ is a byte-for-byte copy of the sample project’s, and a script in the repo fails if any file drifts. ledge renders the pre-decoration DOM those blocks expect and then gets out of the way. It is an independent project, not affiliated with Adobe.
Three things. The delivery port and the published port emit the same bytes for the same page. The authoring port emits that same markup plus data-aue-* instrumentation and nothing else, so delivery renders with instrumentation off and carries none of it. And the EDS-only build is checked against the same render snapshots as the full build, so dropping features does not change the HTML.
No. All three ports bind, but only what your reverse proxy publishes is reachable — ledge terminates no TLS itself and expects a proxy in front. In the full build the authoring port carries real authentication: sessions, passkeys, roles, and a per-route capability guard. The EDS-only build has no auth compiled in at all, so it refuses to start if ledge.toml declares an [auth] section, and warns if the authoring port is bound to a non-loopback address.
ledge import <root> --base <url> <path> fetches a page and follows its fragments recursively, writes the page and its assets into the preview store, harvests icons and web fonts on the way, and prints a report naming every block it could not model. Those are kept as passthrough content rather than dropped, so nothing disappears silently.
Delivery, authoring, publishing, media renditions, sheets, forms, plugins, block templates, query-index, sitemaps, feeds and redirects are implemented and tested; this page alone exercises the plugin, block-template, sheet and form paths. What does not exist yet is a binary you can download, and there is no hosted service — ledge is self-hosted by design. The waitlist below is how you find out when the first half of that changes.
Waitlist
One mail, when there is a binary.
No newsletter, no drip sequence, no passing your address to anyone. One message when ledge is downloadable, and that is the entire promise.