Copper Roadmap Format
Expose your product roadmap as a machine-readable API. Copper Analytics fetches and displays roadmaps from any site that follows this spec.
Overview
The Copper Roadmap Format is a convention for publishing product roadmaps as structured data. It has two parts:
- Markdown files with YAML frontmatter — placed in a
./roadmaps/directory in your project - A JSON API endpoint at
GET /api/copperthat reads those files and returns structured JSON using the Copper Data API envelope format
Copper Analytics periodically fetches your /api/copper endpoint and displays your roadmap items alongside analytics data in the dashboard.
Quick Start
Three steps to expose your roadmap:
Create a roadmap file
Add a Markdown file to ./roadmaps/ in your project root.
---
title: "Dark Mode Support"
status: in-progress
priority: high
phase: 2
date: 2026-03-10
tags: [ui, accessibility]
---
# Dark Mode Support
Add system-aware dark mode to all pages.
## Tasks
- [ ] Add theme toggle to header
- [ ] Update all components with dark variants
- [x] Add CSS custom properties for theme colorsAdd the API route
Create an endpoint that reads the files and returns JSON. See reference implementations below.
Verify it works
Visit https://yoursite.com/api/copper — you should see JSON output.Copper Analytics will start fetching it automatically.
Markdown Format
Each roadmap item is a single .md file in the ./roadmaps/ directory. The filename becomes the slug (e.g. 2026-03-10-dark-mode.md → slug 2026-03-10-dark-mode).
We recommend prefixing filenames with a date in YYYY-MM-DD- format for natural chronological sorting.
Each file must start with a YAML frontmatter block delimited by ---. The Markdown body after the frontmatter is the full description.
Frontmatter Fields
| Field | Type | Required | Description |
|---|---|---|---|
| title | string | Yes | Human-readable title of the roadmap item |
| status | enum | Yes | One of: proposed in-progress completed paused |
| date | string | Yes | ISO date (YYYY-MM-DD). Falls back to filename prefix if omitted. |
| priority | enum | No | One of: low medium high critical |
| phase | number | No | Numeric phase/milestone grouping (e.g. 1, 2, 3) |
| tags | string[] | No | Array of free-form labels for categorization |
---
title: "User Authentication Overhaul"
status: in-progress
priority: critical
phase: 1
date: 2026-04-01
tags: [security, auth, backend]
---
Your markdown description goes here...API Endpoint
Your site must serve a GET /api/copper endpoint that returns a JSON response using the Copper Data API envelope format, with roadmap items in the modules.roadmaps array.
/api/copperReturns site data including roadmap items via the Copper Data API envelope. Authenticated with a read-only token.
Requirements
- Must return
Content-Type: application/json - Must return HTTP 200 with the response body even if there are zero items
- Should respond within 5 seconds (Copper applies a 5s timeout per site)
- Should support CORS if you want the endpoint callable from browsers
Response Schema
interface TaskStats {
total: number; // Total task list items
completed: number; // Checked items (- [x])
remaining: number; // Unchecked items (- [ ])
percent: number; // Completion percentage (0-100)
}
interface RoadmapItem {
slug: string; // Filename without .md extension
title: string; // From frontmatter
status: 'proposed' | 'in-progress' | 'completed' | 'paused';
date: string; // ISO date string (YYYY-MM-DD)
priority?: 'low' | 'medium' | 'high' | 'critical';
phase?: number;
tags?: string[];
description: string; // Full markdown body
summary: string; // First 500 chars of description
tasks: TaskStats; // Parsed from markdown task lists
}
// Copper Data API envelope (GET /api/copper)
interface CopperDataResponse {
site: string; // Your site's domain or identifier
fetchedAt: string; // ISO timestamp of when this response was generated
version: 1; // API version
modules: {
roadmaps?: RoadmapItem[];
// future modules: blog?, changelog?, etc.
};
}{
"site": "myapp.com",
"fetchedAt": "2026-03-10T14:30:00.000Z",
"version": 1,
"modules": {
"roadmaps": [
{
"slug": "2026-03-10-dark-mode",
"title": "Dark Mode Support",
"status": "in-progress",
"date": "2026-03-10",
"priority": "high",
"phase": 2,
"tags": ["ui", "accessibility"],
"description": "# Dark Mode Support\n\nAdd system-aware dark mode...",
"summary": "# Dark Mode Support\n\nAdd system-aware dark mode..."
}
]
}
}Reference Implementations
Copy-paste one of these into your project. Each reads ./roadmaps/*.md, parses the YAML frontmatter, and returns the JSON response.
Next.js (App Router)
import { NextResponse } from 'next/server';
import fs from 'fs';
import path from 'path';
import matter from 'gray-matter';
// pnpm add gray-matter
export async function GET() {
const dir = path.join(process.cwd(), 'roadmaps');
const roadmaps = [];
if (fs.existsSync(dir)) {
const files = fs.readdirSync(dir).filter((f) => f.endsWith('.md'));
for (const file of files) {
const raw = fs.readFileSync(path.join(dir, file), 'utf-8');
const { data, content } = matter(raw);
const slug = file.replace(/\.md$/, '');
const trimmed = content.trim();
roadmaps.push({
slug,
title: data.title || slug,
status: data.status || 'proposed',
date: data.date ? String(data.date) : slug.slice(0, 10),
priority: data.priority || undefined,
phase: typeof data.phase === 'number' ? data.phase : undefined,
tags: Array.isArray(data.tags) ? data.tags : undefined,
description: trimmed,
summary: trimmed.slice(0, 500),
});
}
roadmaps.sort((a, b) => b.date.localeCompare(a.date));
}
return NextResponse.json({
site: 'yoursite.com',
fetchedAt: new Date().toISOString(),
version: 1,
modules: {
roadmaps,
},
});
}Express / Node.js
const fs = require('fs');
const path = require('path');
const matter = require('gray-matter');
// npm install gray-matter
module.exports = function (app) {
app.get('/api/copper', (req, res) => {
const dir = path.join(process.cwd(), 'roadmaps');
const roadmaps = [];
if (fs.existsSync(dir)) {
const files = fs.readdirSync(dir).filter((f) => f.endsWith('.md'));
for (const file of files) {
const raw = fs.readFileSync(path.join(dir, file), 'utf-8');
const { data, content } = matter(raw);
const slug = file.replace(/\.md$/, '');
const trimmed = content.trim();
roadmaps.push({
slug,
title: data.title || slug,
status: data.status || 'proposed',
date: data.date ? String(data.date) : slug.slice(0, 10),
priority: data.priority || undefined,
phase: typeof data.phase === 'number' ? data.phase : undefined,
tags: Array.isArray(data.tags) ? data.tags : undefined,
description: trimmed,
summary: trimmed.slice(0, 500),
});
}
roadmaps.sort((a, b) => b.date.localeCompare(a.date));
}
res.json({
site: 'yoursite.com',
fetchedAt: new Date().toISOString(),
version: 1,
modules: {
roadmaps,
},
});
});
};Item Status Tracking
Track individual task completion within a roadmap item using standard GitHub Flavored Markdown (GFM) task lists.Copper Analytics automatically parses these checkboxes to calculate completion stats.
## Implementation Plan
- [x] **1.** Set up project scaffolding
- [x] **2.** Create database schema
- [ ] **3.** Build API endpoints
- [ ] **4.** Add frontend components
- [ ] **5.** Write integration testsCopper Analytics scans the Markdown body for lines matching - [x] (completed) and - [ ] (incomplete) to compute:
| Metric | Description |
|---|---|
| total | Total number of task list items |
| completed | Number of checked items (- [x]) |
| remaining | Number of unchecked items (- [ ]) |
| percent | Completion percentage (0–100), rounded |
These stats are included in the API response and displayed as progress indicators on the roadmap detail page.
{
"slug": "2026-03-10-dark-mode",
"title": "Dark Mode Support",
"status": "in-progress",
"tasks": {
"total": 5,
"completed": 2,
"remaining": 3,
"percent": 40
},
...
}How Copper Analytics Uses It
Once your site exposes /api/copper with a modules.roadmaps array,Copper Analytics will:
Fetch your endpoint periodically (every 15 minutes by default)
Cache the response in a snapshot collection with a TTL
Display roadmap items in your site's dashboard under Planning > Roadmaps, and in the aggregate roadmap view across all your sites
Render the full Markdown description on the detail page with GFM support (tables, task lists, code blocks)
No endpoint? No problem.
If your site can't expose an API, you can still create roadmap items manually in the Copper Analytics dashboard. The API integration is optional — it just automates the sync.