Everything your team has slated to ship, on one calendar.
Your content plan already lives in Linear, with due dates, labels, and assignees on every issue. For truly inexplicable reasons, Linear still won't let you see all this on a grid.
Like, a content calendar.
Slated is that calendar. It reads your issues from the Linear API, places each one on the day it's due, and moves it when you drag it. It stores nothing itself, so Linear remains the only source of truth.
We plan a ton of content at ngrok, and our issues already live in Linear. What we didn't have was a nice way to see what's coming, or to share it with the rest of the team. Slated is exactly that, designed around simplicity and performance from the start.
Slated:
- Places every issue on a month grid by its due date, and every project by its target date, both treated as the publish date. Projects switch off under Options.
- Reschedules an issue or project the moment you drop it on a new day. The card moves first, then rolls back if Linear rejects the write.
- Opens any issue in a modal to read its description and edit the due date or tags. Clear the date to drop it off the calendar.
- Creates issues in place. Hover a day, hit
+, and set a title, team, due date, status, priority, and tags. - Discovers your teams from Linear, then filters by team and label.
You need Node 20 or newer, pnpm, and a personal Linear API key from linear.app/settings/api.
git clone https://github.com/ngrok/slated.git
cd slated
pnpm install
pnpm setup:env # asks for your API key, writes .env (not `pnpm setup`)
pnpm dev # http://localhost:3000Reload the page on your first run. Vite optimizes dependencies during that first load, and the buttons do nothing until it finishes.
One variable, and no OAuth app to register:
LINEAR_API_KEY=lin_api_...
Warning
Slated has no access control of its own. Everyone who reaches the server
holds your Linear read and write access, and every issue they touch is
attributed to you. Keep it on localhost, or put authentication in front of
it (see Deploying).
Note
Your workspace may not allow this. Linear admins control personal API keys under Settings > Administration > API. If that page won't issue one, ask an admin.
pnpm demo runs the real calendar against a generated workspace: invented
teams, projects, and a few months of content that never shipped.
pnpm install
pnpm demo # http://localhost:3100There's no .env and no Linear request. Dragging and the new-issue dialog work,
with edits held in memory until you stop the server. It runs on 3100 so it won't
collide with a real instance on 3000.
It's deterministic and anchored on today: the same day holds the same cards on every run, and the past always reads as shipped.
Every value goes in .env (see .env.example).
| Variable | Required | What it does |
|---|---|---|
LINEAR_API_KEY |
Yes | A personal key from linear.app/settings/api. Slated acts as its owner, and it stays server-side. |
LINEAR_TEAM_DEFAULT |
No | Comma-separated team keys (GTM,CON) preselected on a first visit. |
LINEAR_LABEL_NAMESPACE |
No | Scopes the calendar to labels under a prefix (con/), and strips it in the UI. |
LINEAR_PROJECT_LABEL |
No | The project label (Content) that decides which projects reach the calendar. Comma-separated for several. |
SLATED_DEMO |
No | Set to 1 to serve the demo calendar instead of Linear, with no API key and no Linear request. |
Slated reads these once, at startup, so restart after you change .env. A stale
LINEAR_PROJECT_LABEL silently empties the calendar of projects. The
Options menu names the label it's filtering on, so check there for the value
the server actually holds.
Slated discovers your teams and fetches nothing until you select one, so an
empty first load usually means no team is picked. LINEAR_TEAM_DEFAULT seeds
that selection, and every team stays available to toggle.
Leave LINEAR_LABEL_NAMESPACE blank and labels stay optional filters. Set it to
con/ and only those labels are offered, with the prefix stripped in the UI and
unfiltered views narrowed to issues carrying one.
Projects filter the same way, first by selected teams and then by
LINEAR_PROJECT_LABEL. Project labels are workspace-level in Linear rather than
per-team, so this one is a plain name instead of a prefix. The header's label
filter doesn't touch projects: Linear keeps the two label sets separate.
Anything other people can reach needs access control in front of it.
A container image ships for every commit on main:
docker run --rm -p 3000:3000 --env-file .env ghcr.io/ngrok/slated:latestPin an exact build with ghcr.io/ngrok/slated:sha-<commit>, or build it
yourself:
docker build -t slated .
docker run --rm -p 3000:3000 --env-file .env slatedBind it to localhost, or front it with something that authenticates visitors.
Slated serves the calendar to whoever asks.
Slated is a React Router app that talks to Linear from the server. The API key stays in the server's environment and every request is proxied, so it never reaches the browser. There's no database: the browser caches what it fetches for the session with TanStack Query. The UI uses mantle, ngrok's design system.
docs/design.md is the original design doc and build log,
including the performance work behind it. It describes an OAuth flow Slated no
longer has.
pnpm dev # dev server on :3000
pnpm demo # generated demo calendar on :3100, no API key
pnpm test # vitest
pnpm typecheck # react-router typegen + tsc
pnpm lint # biome
pnpm fmt # biome, writing fixes
pnpm build # production buildCI runs lint, typecheck, tests, and the build on every pull request.
.
├── app/
│ ├── routes/ The calendar page and the API routes that proxy Linear.
│ ├── features/
│ │ └── calendar/ The month grid, cards, filters, modal, and data hooks.
│ └── lib/ Env, the Linear client, the GraphQL queries, and the
│ demo calendar's fake workspace.
├── scripts/ pnpm setup:env.
├── .github/workflows/ CI and the container image publish.
└── Dockerfile Multi-stage production build.
This is an internal ngrok tool we're sharing as-is. It's shaped around how our GTM and Content teams work, though it's configurable enough for another Linear workspace. It isn't a supported product, so we may not take issues or pull requests. Fork it and make it yours.