14 August 2026
How to do story mapping in GitHub Projects
You can build most of a user story map inside GitHub Projects alone, with no second tool and nothing to keep in sync. Steps become board columns, releases become swimlanes. This is the exact setup — and the three places where it stops working.
What a story map is, in one paragraph
Story mapping is Jeff Patton's technique. He introduced it in his 2008 essay The new user story backlog is a map — a way to arrange a flat backlog along the flow of what users actually do — and later expanded it into the book User Story Mapping (O'Reilly, 2014). This article builds that shape inside GitHub Projects.
A backlog answers what is left. A story map answers what the next release actually gives someone. It is a grid. Across the top sit the journeys — the things a person is trying to do, like “Find a product”, “Place an order”, “Receive & review”. Under each journey sit its steps: under “Find a product”, that is “Search” and “Compare”. Under each step sit the issues. Cutting across the whole thing are rows, one per release.
The trick is the row. Read one left to right and you see what that release delivers end to end — and a gap in the row, a journey the release never reaches, is obvious. In a flat list of eighty issues, that same gap is invisible.
Step 1 — add two fields to the Project
Open the Project, click the + at the right end of the field headers (or go through Settings → Fields), and create two single select fields:
- Journey — one option per journey, in the order a person moves through them.
- Step — one option per step, listed left to right across the whole product, not per journey.
The option order is not cosmetic: GitHub uses it as the column order on the board. You can change it later — reorder the options on the field itself and the values already set on your issues are kept — but you cannot drag a column on the board to move it. Put the options in the order you want to read them, from the first thing a person does to the last. A single select field holds up to fifty options, which is the ceiling on how many steps your map can have.
Step 2 — fill the fields in
Switch to the table layout, show the two new columns, and set a value on each issue. You can select several rows and set a field on all of them at once, which makes this about ten minutes for a project of a hundred issues rather than an afternoon.
Issues with no Step yet are not a problem — they are the pile you have not placed on the map. Every real backlog has one.
Step 3 — make releases out of milestones
Use GitHub milestones for the rows, not another custom field. Milestones belong to the repository rather than the Project, so they survive if you rebuild the Project, they show on the issue itself, and the progress bar you already get for free means something. Give each one a due date.
Step 4 — build the board
Create a new view and set the layout to Board. Then, in the view menu:
- Column field →
Step - Group by →
Milestone
That second setting is the one people miss. Grouping a board does not sort it — it splits it into horizontal swimlanes. Columns are your steps, lanes are your releases, and the cells in between hold the issues. That is a story map grid, built out of the issues you already had.
Optionally add Slice by → Journey. That puts
your journeys in a panel down the left, and clicking one narrows the board to it.
What you get
A real grid you can drag cards around in, where dragging changes the issue rather than a copy of it. Move a card into a different lane and the milestone on the issue changes. Move it to a different column and its Step changes. Nothing is duplicated, nothing needs syncing, and anyone with access to the repository sees the same thing.
For a small product this is genuinely enough, and it costs nothing but the twenty minutes above. If you stop here, you are already ahead of a flat backlog.
Where it stops working
Three things, in the order they start to hurt.
1. You cannot put the release rows in order
When a board is grouped by milestone, the lane order is not yours to set. GitHub has confirmed this: you cannot reorder how milestones appear when grouping by them. So 1.1 may sit above 1.0, and next quarter may sit above this week.
This matters more than it sounds. The convention that makes a story map readable is that the top row is the next release and everything below it comes later. When the order is arbitrary, the rows stop being a sequence and become just labels, and the question the map exists to answer — what ships first? — is not on screen anymore.
2. Journeys are a filter, not a band
Slice by shows one journey at a time. But the reason anybody draws a story map is to see the whole product at once, and notice the journey nobody has touched yet. Looking at one journey at a time is the thing you were trying to get away from — it is a flat list again, just a shorter one.
There is a workaround: name the step options with their journey in front, so they read
Find: Search, Find: Compare,
Order: Cart. The journeys then stay visible, and the
alphabetical grouping happens to keep each journey's steps together. The cost is that you
repeat the journey name in every column header, and renaming a journey means editing every
option by hand.
3. Sorting and priority order cannot coexist
Inside a step, the order of the cards is supposed to mean something: the one nearest the top is the one you would build first. But the GitHub documentation is explicit that when a board is sorted, you cannot manually reorder items within a column. You can have a sort, or you can have a priority order you set by hand. Not both.
What about the new hierarchy view?
GitHub shipped hierarchy view in March 2026, and it is on by default for new views. It shows sub-issues nested up to eight levels deep, and you can group, slice, sort and filter without losing the nesting. If you model journeys and steps as parent issues instead of as fields, the three levels of a story map become a real hierarchy rather than two custom fields.
It does not replace the board, though, because hierarchy view only works in the table layout — not on a board and not on a roadmap. So you can have the levels or you can have the grid, but not both in the same view. That is the shape of the gap: GitHub has the hierarchy in one view and the two axes in another, and a story map needs them at the same time.
So is it worth doing?
Yes, if your product has one or two journeys and two or three releases in flight. At that size the missing row order does not bite, because you can hold the sequence in your head, and the whole setup costs an afternoon at most.
It stops being enough at roughly the point where you would want to show the map to someone else. A map you have to explain — “ignore the lane order, 1.0 is the one in the middle” — is not doing the job a map is for.
What I built instead
I hit all three of these on my own project and ended up redrawing the map on a whiteboard every couple of weeks, then typing the changes back into GitHub by hand. That round trip is what Fervio exists to remove: it reads an existing GitHub Project and lays the same issues out as a story map with journeys across the top and release rows in date order, and moving a card writes back to the real issue and its milestone. It is free while in beta, and it never reads your code — the permissions page lists exactly what it touches.
But do the four steps above first. If they are enough for you, you have saved yourself a dependency, and that is the better outcome.