apps/web/content/articles/building-editorial-workflow-github-slack.mdx
We recently rebuilt our entire editorial workflow from scratch.
And the question we kept getting was: "Why didn't you just use Payload? Or Sanity? Or any of the existing CMS products?"
The answer goes deeper than "we wanted more control." It connects to something we've been thinking about since we started Char: the belief that content should live as files, not database rows.
This is Part 7 of our publishing series, and it's probably the most philosophical one. We'll explain the "why" first, then break down exactly what we built.
We wrote about this in The Filesystem Is the Coretex, but the core thesis is simple:
Files are a human interface, not a technical detail.
Humans have organized information spatially for centuries. Drawers. Cabinets. Folders. We remember where something is. Location is recall. Structure is memory.
When notes—or blog posts—live as files, they feel like part of you. When they live as opaque blobs behind an API, they feel like someone else's product.
This is a belief about the future:
If AI is going to help humans think, it needs access to artifacts humans can own, inspect, move, and understand.
Markdown. Plain text. Files.
The filesystem is the cortex—and in the AI era, it becomes even more important, not less.
We evaluated them all. Payload is excellent. Sanity is powerful. But every CMS we looked at had the same fundamental issue: they want your content to live in their system.
Even the "open source" ones. Even the "self-hosted" ones. Your content becomes structured JSON in a database, mediated through schemas, accessed via APIs.
That's fine for some use cases. For us, it violated the core principle:
Content should be files. Files in a repo. Diffable. Portable. Ownable forever.
We already wrote about this in Why Our CMS Is GitHub. GitHub gives us everything a CMS promises—versioning, collaboration, review workflows, rollback—without the lock-in.
But there was one gap: editorial workflows for non-engineers.
Our stack was simple: MDX files in GitHub. Engineers write in IDEs. PRs are drafts, merges are publish.
But we hit real friction:
We needed an editorial workflow without abandoning our file-first principles.
So we built one.
The system has three layers:
Every change—new post, edit, unpublish—creates a draft pull request.
Draft PRs are GitHub's best-kept secret. They don't notify reviewers. They don't show up in review queues. They signal "I'm still working on this." And they can be converted to "ready for review" with one API call.
When a writer creates a new post, it lives on a draft PR. They can edit, save, walk away, come back. The PR stays invisible until they're ready.
Writers shouldn't lose work. But they also shouldn't be interrupted.
We added a 60-second auto-save countdown:
[ Save (52s) ]
When the countdown reaches zero, content auto-saves to the draft PR branch with no tab open and no notification. The countdown resets if more changes are made.
Manual save (button or Cmd+S) saves immediately, the countdown disappears, and still no tab opens. Saving is silent.
The PR URL only opens when submitting for review. This keeps writers focused on writing, not on managing PRs.
Reviewers live in Slack. So Slack became our review interface.
When a writer clicks "Submit for Review":
published: true┌─────────────────────────────────────────────────────────────┐
│ Article submitted for review │
│ @john please review │
│ │
│ > "Building an Editorial Workflow with GitHub and Slack" │
│ │
│ [ Preview ] [ View PR ] [ Merge ] │
└─────────────────────────────────────────────────────────────┘
One click. Article goes live.
For unpublishing, we send a yellow warning message:
⚠️ @jane wants to unpublish "Article Title"
Destructive actions deserve visual distinction.
Everything is built on primitives that already exist:
| Component | Implementation |
|---|---|
| Content storage | MDX files in GitHub repo |
| Draft workflow | GitHub draft PRs |
| Auto-save | React state + interval + GitHub API |
| PR conversion | GitHub REST API (PATCH /pulls/{id}) |
| Notifications | GitHub Actions + Slack API |
| Merge from Slack | Slack interactive webhook → GitHub API |
| Preview | Netlify deploy previews (automatic) |
No database. No CMS backend. No proprietary schemas.
Just GitHub, Slack, and a few hundred lines of glue code.
Getting button states right is crucial for clarity:
| Scenario | Save | Submit for Review | Status |
|---|---|---|---|
| No unsaved changes | Disabled | Disabled | Current state |
| Has unsaved changes | Enabled + countdown | Enabled | Current state |
| Currently saving | Spinner | Disabled | Current state |
| Unpublished article | — | — | Gray "Not Published" |
| Published article | — | — | Green "Published" (hover: "Unpublish") |
Writers don't need a "Publish" button. They need to know if something is published. Make state visible; make actions secondary.
We're an open source company. Char is open source. And we believe this editorial workflow should be open source too.
Not just "source available". Actually usable by other teams.
The vision: bring your own repository and S3 bucket. Get a complete editorial workflow with draft PRs, auto-save, Slack notifications, and one-click merge. Zero vendor lock-in. Zero data migration. Forever portable.
Imagine content living in your GitHub repo instead of ours. Images in your S3 bucket instead of ours. You get the entire workflow without being locked into our system.
This is the opposite of what CMS products offer. They want you to put content in their system. We want to give you tools that work with the systems you already have.
We're not there yet. But the architecture is designed for it. The code is written to be extractable. And we're committed to open sourcing it when it's ready.
Most teams create regular PRs immediately. Draft PRs are better for work-in-progress because they don't spam reviewers with notifications.
Silent auto-save creates anxiety. "Did it save? Should I save manually?" A visible countdown removes uncertainty.
For simple approve/reject workflows, Slack buttons are faster than GitHub's UI. Save GitHub for complex reviews with inline comments.
Writers don't need a "Publish" button. They need to know if something is published. Make state visible; make actions secondary.
Every design decision was easier because we started with files. No schema migrations. No content lake. No API rate limits. Just files.
We built this because we believe content should be owned, not rented. Your words should live in your repo, not in a SaaS database.
Tools should compose, not capture. GitHub, Slack, and Netlify already exist. We connected them instead of replacing them.
Simplicity survives. Markdown files will outlive every CMS platform launched this decade.
Open source means open data. If the software is open, the content should be portable.
This editorial workflow is one piece of that vision. It's how we publish our blog, our docs, our changelog. And eventually, it's something we want to share with every team that believes content should be files.
This is Part 7 of our publishing series.