On 9 October 2026, Matt Kane, EmDash's Project Lead, merged PR #3808. "Establish governance and contribution processes" sounds like paperwork, and it's big: more than 4,000 changed lines across 53 files. Since then, the repository spells out who decides what, how a feature gets in and what a bot may do. The groundwork happened in public, in three Discussions (#3747, #3782, #3807).
I'm one of the five maintainers listed in the new GOVERNANCE.md. This post walks you through who decides, the path a change takes, what the emdashbot bot does, and where you can get involved, no core code required. Kevin already told the story of how one tweet turned into two releases in his post on feedback; this one is about the rules behind it. All numbers come from GitHub and npm as of 15 October 2026.
I came across EmDash while looking for the right CMS for my clients. The project clicked with me straight away, and the community was friendly. I opened my first pull requests in August. I was then invited to the Project Team, still called Triage back then, and later to the maintainers when that team started.
A quick glossary: an issue reports a bug on GitHub, a pull request (PR) proposes a change, and merging means accepting it into the project. Discussions are the project's forum. If you want to get going right away, skip to "Contributing without core code".
Who decides
GOVERNANCE.md defines three roles:
- Project Lead: Matt Kane. He makes the final call when discussion doesn't settle something, and appoints new members after consulting the maintainers. Cloudflare appoints the Project Lead.
- Maintainers: currently five: Matt Kane, Noah Nguyen Pham, @MA2153, Kevin Kyburz and me. We approve and merge pull requests and make routine decisions within documented policy without waiting for the Project Lead.
- Project Team: regular contributors who triage issues and review pull requests but can't merge. Besides the maintainers, three people are on it right now.
That list is what counts, not anyone's GitHub permissions. The approval automation reads exactly that list, and a bot never appears on it. You can join the Project Team once you've made several useful contributions over time, whether code, docs, reviews, triage or support. A maintainer usually sponsors you, and you don't need to be active on Discord. Every role is volunteer work with no minimum hours. After about six months without activity, someone checks in privately, and access nobody needs anymore is removed, because a dormant account with merge rights is a supply-chain risk.
Decisions happen in public. Product direction, architecture, the roadmap and reviews belong in GitHub Discussions, issues and pull requests. Discord can help, but it must never be the only place a decision is recorded. A team channel exists for sorting out who handles what, not for deciding things. Only what can't be discussed safely in public stays private, such as unfixed security issues, moderation cases or credentials.
I can see why Cloudflare appoints the Project Lead: EmDash is Cloudflare's project. Even so, we maintainers have a lot of freedom, and the leadership has been good so far. Whether EmDash ties you to Cloudflare technically is what Kevin checked in his lock-in fact check.
Which path for which change
CONTRIBUTING.md sorts changes like this:
- A bug starts as an issue. The template asks for a description, steps to reproduce, your environment and, for interface bugs, a screenshot, and a human has to have reproduced the bug. If you can see the fix, go straight to a pull request, ideally with a test that fails without it.
- A feature request goes to the Ideas Discussions, a question to Q&A.
- Docs, tests, translations and scoped chores go straight to a pull request, as do refactors that don't change behavior and performance work with measurements.
- A feature needs an accepted design first. That rule has applied since 3 October 2026.
The design is a pull request that only touches files under proposals/ (proposals/README.md). A bounded feature only needs a short feature plan approved by one maintainer. Anything touching, say, a public API, stored data, the plugin surface, security or backwards compatibility needs an RFC (Request for Comments), a full design document, instead. That starts with an Ideas Discussion and needs a champion who drives the proposal, a maintainer sponsor, support from two more maintainers, acceptance by the Project Lead, and usually at least seven days of public review. Every feature needs a design, explicitly including those from maintainers and Cloudflare employees.
The first design accepted under the new process is a feature plan by Noah Nguyen Pham for a readable revision history. Its design PR (#4127) was merged on 13 October, and the implementation (#4136) two days later.
Only open a pull request if at least one of these applies: you use EmDash and ran into the problem yourself, you maintain a tool EmDash depends on and the change concerns that integration, or a maintainer approved that specific change beforehand. You tick what applies in the PR template. Drive-by changes nobody asked for, speculative refactors and bulk PRs like "fix all lint warnings" get closed.
From pull request to release
The PR template isn't optional; a PR without it is closed automatically. Once it's open, three things kick off. CI runs types, lint and tests, though for a first-time contributor only after someone on the team has read the code and approved the run. emdashbot posts a review. And pkg.pr.new builds a preview package you can npm i into your own site.
Per MAINTAINING.md, a PR can merge once required checks pass, blocking threads are resolved, and a human maintainer who isn't the author has approved the current revision. A new commit after that needs a fresh approval. Changes to governance, release automation and other privileged paths need two. That goes for everyone, the Project Lead included. Labels such as review/needs-review and review/approved, set by automation, show where a PR stands.
If your PR changes a published package, it needs a changeset: a small Markdown file you create with pnpm changeset. It decides whether the version gets a patch or a minor bump, and its text goes into the changelog word for word. So write it for people who run EmDash, not for people who've read your diff (guide). Docs, tests and CI don't need one.
A workflow collects the changesets into a release PR called ci: release, and merging it publishes the packages to npm. Since 1.0 on 28 September, that's how 1.1.0 (1 October), 1.2.0 (late on 6 October) and 1.3.0 (15 October) shipped. Kevin covers what's in 1.3 in his EmDash 1.3 post.
The sweep cleans up
Until recently, pull requests simply sat there. The PR sweep workflow was meant to flag and close stale ones but never closed a single PR, because its own labels and comments kept resetting the clock. According to PR #3927, 44 open PRs had received a warning and none had been closed. A dry run of the new rules over the 169 open PRs produced 7 CLA reminders, 22 stale warnings and no closures. New rules apply since 9 October, and the sweep runs every six hours:
- A PR only goes stale while it's waiting on you, the author: a maintainer requested changes, it has merge conflicts, or it's still a draft. After 14 days without human activity it gets a warning, and seven days later it's closed. Bot comments don't count as activity.
- A PR waiting on review never goes stale. Design PRs are never closed for inactivity.
- An unsigned CLA runs on its own clock: a reminder after one day, closed after seven, however active the PR is.
"Unsigned" doesn't mean signed commits or a DCO sign-off. It means Cloudflare's Contributor License Agreement. On your first PR, the CLA Assistant asks for it, and you sign by posting a comment containing exactly this sentence: I have read the CLA Document and I hereby sign the CLA. The code itself is MIT-licensed.
A closed PR isn't lost. Reopen it, or comment and someone on the Project Team will.
emdashbot: lots of work, no approvals
According to TRIAGE.md, emdashbot has two jobs. It reviews pull requests when they're opened or marked ready, skipping design PRs. New commits don't trigger another review; the bot:review label does. For a feature, it checks the implementation against the accepted design. And it works on issues. It triages new issues from outside contributors, labels them and asks for missing details. It may go ahead with an obvious, low-risk fix: it pushes a bot/fix-<n> branch, opens a PR and asks the reporter to try the preview package. Anything deeper waits for a maintainer.
Maintainers steer it with comments. @emdashbot triage classifies an issue, investigate only diagnoses it, and work builds a fix. needs changes plus feedback sends it into another round, decline turns the issue down, and take over hands it to a human until hand back. If you reported the bug, you can simply reply on the issue after trying the preview, or send needs changes yourself. It leaves issues from organization members alone until a maintainer runs triage, since those authors have usually looked into the problem already. That rule came from a PR of mine (#3424).
Between 15 September and 14 October, 63 PRs opened by emdashbot were merged: twelve release PRs, nearly all the rest fixes. It doesn't run flawlessly, though. Matt Kane audited everything the bot did on issues from 27 September to 7 October (PR #3983). At that point 30 open issues were stuck and 85 runs had failed or timed out. Since that fix, the bot stops after three attempts to repair its own PR and says what's still failing, and it won't start work on its own when a person already has a PR open for the issue.
The key limit is in MAINTAINING.md: bot reviews are evidence for the human who approves, never a replacement, and every emdashbot PR needs a maintainer's approval too. TRIAGE.md itself lists what the bot can't do: click through a real site, judge whether a change should happen at all, and show a first-time contributor that a human actually read their work.
My experience as a maintainer: the bot is good at smaller issues. It can't yet find a solution for every issue, but it takes a lot of work off our hands and speeds up development.
What the AI policy asks
#3808 also added an AI_POLICY.md. AI-assisted contributions are welcome, but the human stays responsible. You name every model and tool in the PR template, read the full diff, run the tests and try the change yourself. An agent only opens a PR you've authorized individually, and agents that go looking for work on their own aren't allowed.
Conversations stay human. Agents don't comment on issues, in Discussions or on design PRs. On other PRs they can do two things, each signed with the model name: review someone else's code when a human explicitly asks for that review, and help you reply to reviews on your own PR. The one exception is emdashbot's issue work. Help with spelling or translating your own text is fine anywhere.
How big the community is
By the 1.0 release, more than 175 people had contributed, as Kevin noted in his feedback post. On 15 October, GitHub's contributor list shows 206 accounts plus four bots.
The pace is high. From 15 September to 14 October, 583 pull requests were merged, nearly 20 a day, opened by 59 people and emdashbot. Since 28 September, the day of 1.0, it's been 318 PRs from 42 people and the bot.
The work isn't evenly spread, though. Of those 583 PRs, 397 came from four people: Matt Kane (137), me (101), Kevin (92) and Noah Nguyen Pham (67). Add the bot's 63 and that's nearly four fifths. The other 55 people contributed 123 PRs between them, and 41 of them one or two each.
For so few people, that's a lot of work. So I'm inviting everyone who's keen: become a contributor, and if it's a good fit, join the Project Team.
Contributing without core code
The contributions below need neither TypeScript nor a deep grasp of the architecture.
Translations
locales.ts, the one file every admin language list is built from, lists 33 languages besides English, and 31 of them can be picked in the admin. Two arrived in early October: Croatian (#3934), merged on 8 October and selectable right away, and Italian (#4026), which I merged the same evening. Italian stays switched off for now because only a small part is translated; a maintainer enables a new language once enough of it is done. Korean is currently switched off as well.
The Lunaria dashboard shows how far along each language is. Strings live in PO files under packages/admin/src/locales/<locale>/messages.po. You fill in the empty msgstr entries, leave placeholders like {email} alone, check the result in a running admin and open a PR titled something like i18n(de): …. Translating EmDash has the step-by-step.
Two rules are strict. Only native or fluent speakers open translation PRs. AI can do a first pass, but a fluent speaker reviews every string in the running admin. And an empty entry beats a wrong one, since the admin just falls back to English. That's why the first thing I asked on the Italian PR was whether the author is a native speaker. He is.
I maintain the German translation. I have German clients, and the German version had a lot of open entries, so I filled them in. The first PR (#2400) filled 373 empty entries in one go. Since then, I've caught up with whatever's new once a week, because the project moves fast. That's eight of my translation PRs merged since August 2026. On 13 October, the latest, #4084, filled the 142 entries that had been missing on the 10th, mostly around the new video block and the editor menus. By 15 October, main is missing 146 German entries again, most of them for the new revision history. Much of the work is terminology: some terms have to be translated idiomatically rather than word for word, and each should read the same everywhere. Since #4084, German says "Update" for plugins instead of "Aktualisierung", to match "Update-Status", while "Aktualisierung" stays for content and data.
Docs
The docs at docs.emdashcms.com live in the same repository under docs/, and every page links to its source on GitHub. Docs PRs go straight in, with no design PR and no changeset, in English and following a style guide. Often it's the small stuff. In early October I fixed four pages whose steps no longer matched the admin, the WordPress import among them (#3947 to #3950).
Bug reports and triage
A good bug report is a contribution in itself. Use the template, give your version, your adapter (Node or Cloudflare) and steps from a fresh project, and attach a screenshot whenever the interface is involved. Security issues don't go in public; use the private vulnerability report form.
You can move other people's issues forward too. TRIAGE.md suggests picking a bug without a priority label, trying to reproduce it and writing down what you found, even if it's only "I got this far". If you're fluent in a language, your review on a translation PR is especially valuable, because neither the bot nor maintainers who don't speak it can judge it. That kind of work is exactly how people grow into the Project Team.
Plugins
New plugins don't go into core; you publish them yourself. Sandboxed plugins go to the registry with emdash-plugin publish (docs), using an Atmosphere account, such as your Bluesky one, as your publisher identity. Native plugins are distributed through npm. Kevin measured what the sandbox does and doesn't protect in his plugin sandbox post.
Discord
On the public Discord, #contributing is the channel for getting involved. Once your first PR is merged, emdashbot invites you to link your GitHub account there with /link, which gets you the Contributor role.
Bottom line
A human approves every change, including the bot's and the Project Lead's. A feature needs a design first, and the sweep closes PRs that are waiting on their author after three weeks, or after one without a signed CLA. emdashbot takes a lot of routine work off our hands but decides nothing. For you, that means a bug fix, a docs correction or a translation can go straight to a PR if you hit the problem yourself or speak the language. A feature starts as an idea in Discussions, not as code.
We welcome help in every area, and it doesn't always have to be code. Joining in on Discord or in the GitHub Discussions helps just as much.

Comments
First-time comments appear once we've approved them. How we handle your details