Content Modules

Building a public roadmap

Updated 93 views

There is no separate roadmap API in Kookee, and there does not need to be one. Your feedback board already has columns — Planned, In progress, Shipped, whatever your team named them — and a roadmap is those columns, rendered for the public.

That is what keeps the two from drifting apart. Most teams run a feedback board and a roadmap as separate systems with a manual copy step between them, and that step stops happening around week six.

Columns are defined by your team

The board's columns are configured in the dashboard, not fixed by Kookee. Fetch the current list:

TypeScript
const columns = await kookee.feedback.getColumns();

Each column has an id, a name, a color, a position, a type (open or closed) and isVisible, which is false for columns you hid from the public. Every feedback post carries the columnId it currently sits in. Moving a card on the board in the dashboard is the triage action — and it is what moves the card on your public roadmap.

Fetching and grouping

One request for the columns, one for the posts, grouped client-side:

TypeScript
const [columns, { data: posts }] = await Promise.all([
  kookee.feedback.getColumns(),
  kookee.feedback.list({ sort: 'top', limit: 100 }),
]);

const roadmap = columns
  .filter((column) => column.isVisible)
  .map((column) => ({
    ...column,
    posts: posts.filter((post) => post.columnId === column.id),
  }));

sort: 'top' orders by votes, so the most-wanted item sits at the top of each column — which is usually what a visitor wants to see first.

Note the limit: 100 ceiling. Once a board outgrows 100 posts this silently drops the tail, and the "Shipped" column is normally what overflows. Either page through the list, or render the shipped column on its own paginated page.

Which columns to show

Showing every column publicly is rarely right. An intake column full of unreviewed submissions reads as chaos, not transparency.

Three columns work for most products: Planned, In progress, Shipped. Decide which of your columns those are and show only those; keep declined ideas off the roadmap but findable on the portal, so a decision stays discoverable to anyone who searches for it.

Filtering

TypeScript
const features = await kookee.feedback.list({
  category: 'feature',
  sort: 'top',
  limit: 100,
});

const results = await kookee.feedback.list({
  search: 'dark mode',
  sort: 'top',
  limit: 100,
});

A search box matters more than it looks on a roadmap. The most common visitor question is "are you already planning X?", and search answers it without them reading three columns.

Cards that open

Reuse the feedback detail call for a card dialog:

TypeScript
const post = await kookee.feedback.getById(postId);

post.contentHtml;
post.voteCount;

Voting and commenting happen on your hosted portal, where visitors sign in. Give each card a "Vote or discuss" link to the post on the portal's roadmap, which opens that post directly:

Plain Text
https://your-project-slug.kookee.dev/roadmap?post=POST_ID

That keeps one vote per person and keeps your team's moderation in one place, while the roadmap itself stays a fast, read-only page.

With React

Roadmap from @kookee/react/roadmap renders all of the above as a read-only board: the visible columns in order, the posts in the order you fetched them, and each card linked to your portal:

tsx
import { Roadmap } from '@kookee/react/roadmap';

<Roadmap columns={columns} posts={posts} href="https://your-project-slug.kookee.dev/roadmap?post={id}" />;

See Show a read-only roadmap with the React components for boards over 100 posts and for styling the columns.

Closing the loop

The reason to run a roadmap at all is that it ends somewhere visible. A card moves to Shipped, and the same change ships as a changelog entry — one kookee.changelog entry in the same project, behind the same API key.

Linking the two is worth the small effort. A shipped card that points at the release notes turns "we did this" into something a user can actually go and read.

Was this article helpful?