A public, read-only roadmap viewer for Linear that allows external stakeholders to view issues and add comments without requiring Linear access.
- 4-Column Kanban Board: Issues organized into Todo, In Progress, Done, and Cancelled
- Public Commenting: Stakeholders can leave feedback without Linear accounts
- Status Mapping: Automatically maps Linear workflow states to public columns
- Spam Protection: Honeypot field to prevent bot submissions
- Rate Limiting: IP-based rate limiting (5 comments per hour)
- Caching: In-memory caching for better performance
- Docker Support: Easy deployment with Docker and docker-compose
- Node.js 18+ and npm 9+
- A Linear account with Business plan
- Linear API key with read/write access
npm installCopy the example environment file:
cp .env.example .envEdit .env with your configuration:
# Required
LINEAR_API_KEY=lin_api_xxx
# Optional filters (at least one recommended)
LINEAR_TEAM_ID=your_team_id
LINEAR_PROJECT_ID=your_project_id
LINEAR_ROADMAP_LABEL=Roadmap
# Application settings
NEXT_PUBLIC_APP_URL=http://localhost:3000
CACHE_TTL_ISSUES=300000
CACHE_TTL_COMMENTS=120000
# Rate limiting
RATE_LIMIT_MAX_COMMENTS=5
RATE_LIMIT_WINDOW_MS=3600000npm run devOpen http://localhost:3000/roadmap in your browser.
- Go to Linear Settings → API
- Click "Create new API key"
- Give it a descriptive name (e.g., "Public Roadmap")
- Copy the key (starts with
lin_api_) - Paste into your
.envfile
Important: Keep this key secure and never commit it to version control.
See docs/LINEAR_SETUP.md for detailed instructions on finding:
- Team IDs
- Project IDs
- Label names for filtering
Linear workflow states are mapped to 4 public columns:
| Public Column | Linear States |
|---|---|
| Todo | Backlog, Todo |
| In Progress | In Progress |
| Done | Done, Completed |
| Cancelled | Canceled |
# Build the image
docker build -t linear-roadmap .
# Run with docker-compose
docker-compose up -d
# View logs
docker-compose logs -f
# Stop
docker-compose downCreate a .env file in the project root with your configuration. Docker Compose will automatically load it.
src/
├── app/
│ ├── api/
│ │ ├── issues/
│ │ │ ├── route.ts # GET /api/issues
│ │ │ └── [id]/
│ │ │ └── comments/
│ │ │ └── route.ts # GET/POST /api/issues/:id/comments
│ │ └── health/
│ │ └── route.ts # GET /api/health (healthcheck)
│ ├── roadmap/
│ │ └── page.tsx # Main roadmap page
│ ├── layout.tsx # Root layout
│ ├── page.tsx # Home (redirects to /roadmap)
│ └── globals.css # Global styles
├── components/
│ ├── KanbanBoard.tsx # 4-column board
│ ├── IssueCard.tsx # Issue card component
│ ├── IssueModal.tsx # Issue detail modal
│ ├── CommentList.tsx # Comment display
│ └── CommentForm.tsx # Add comment form
└── lib/
├── linear/
│ ├── client.ts # Linear API client
│ ├── queries.ts # GraphQL queries
│ ├── types.ts # TypeScript types
│ └── status-mapper.ts # Status mapping logic
├── cache/
│ └── in-memory.ts # In-memory cache
├── security/
│ ├── honeypot.ts # Honeypot validation
│ └── rate-limiter.ts # Rate limiting
└── utils.ts # Utility functions
Fetches all issues (cached for 5 minutes)
Response:
{
"data": [...],
"cached": true
}Fetches comments for a specific issue (cached for 2 minutes)
Response:
{
"data": [...],
"cached": false
}Adds a comment to an issue
Request Body:
{
"name": "John Doe",
"email": "john@example.com", // optional
"comment": "Great feature!",
"honeypot": "" // must be empty
}Response:
{
"success": true,
"message": "Comment added successfully"
}- API Key Protection: Linear API key is server-side only, never exposed to frontend
- Honeypot: Hidden form field to catch bots
- Rate Limiting: Max 5 comments per IP per hour
- Input Validation: Name and comment are sanitized and length-limited
- Read-Only: No ability to edit, delete, or change issue status
| Variable | Default | Description |
|---|---|---|
LINEAR_API_KEY |
- | Required - Your Linear API key |
LINEAR_TEAM_ID |
- | Optional - Filter by team |
LINEAR_PROJECT_ID |
- | Optional - Filter by project |
LINEAR_ROADMAP_LABEL |
- | Optional - Filter by label name |
CACHE_TTL_ISSUES |
300000 | Cache TTL for issues (ms) |
CACHE_TTL_COMMENTS |
120000 | Cache TTL for comments (ms) |
RATE_LIMIT_MAX_COMMENTS |
5 | Max comments per IP per window |
RATE_LIMIT_WINDOW_MS |
3600000 | Rate limit window (1 hour) |
- Real-time updates: Changes in Linear take up to 5 minutes to appear (cache)
- Comment threading: All comments are flat, no nested replies
- Attachments: Cannot upload files in public comments
- User attribution: Public comments appear as API key owner in Linear
- Filtering: Only one team/project/label filter supported
- Search: No search functionality
- Sorting: Issues displayed in creation order only
- Check your Linear API key is correct
- Verify the team/project/label IDs exist
- Check the console for error messages
- Ensure your Linear API key has read access
- Check rate limiting (5 comments/hour)
- Verify your Linear API key has write access
- Check the browser console for errors
- Ensure name and comment fields are filled
- Make sure
.envfile exists and is configured - Check Docker logs:
docker-compose logs -f - Verify port 3000 is not in use
This is a standalone project. Feel free to fork and customize for your needs.
MIT
For issues or questions, please open an issue in the repository.