Create and manage Linear documents and project milestones
Enables write operations for Linear documents and project milestones via GraphQL API. Use when users need to create, update, or delete documents and milestones, or query milestone progress.
/plugin marketplace add jongwony/cc-plugin/plugin install linear@cc-pluginThis skill inherits all available tools. When active, it can use any tool Claude has access to.
references/document-schema.mdreferences/examples.mdreferences/milestone-schema.mdThis skill extends Linear MCP capabilities by adding write operations for Documents and ProjectMilestones. While Linear MCP provides read-only access to documents and no milestone support, this skill enables full CRUD operations via direct GraphQL API calls.
What this skill adds:
Prerequisites:
export LINEAR_API_KEY="lin_api_xxxxx"jq for JSON formattingBasic example:
curl -X POST https://api.linear.app/graphql \
-H "Content-Type: application/json" \
-H "Authorization: $LINEAR_API_KEY" \
-d '{
"query": "mutation DocumentCreate($input: DocumentCreateInput!) { documentCreate(input: $input) { success document { id title url slugId createdAt creator { name } } } }",
"variables": {
"input": {
"title": "API Design Document"
}
}
}'
With content and project:
curl -X POST https://api.linear.app/graphql \
-H "Content-Type: application/json" \
-H "Authorization: $LINEAR_API_KEY" \
-d '{
"query": "mutation DocumentCreate($input: DocumentCreateInput!) { documentCreate(input: $input) { success document { id title url slugId } } }",
"variables": {
"input": {
"title": "Q4 Roadmap",
"content": "# Q4 Goals\n\n- Launch feature X\n- Improve performance by 30%",
"projectId": "PROJECT_ID_HERE",
"color": "#FF6B6B"
}
}
}'
Available parameters:
title (required): Document titlecontent: Markdown contentprojectId: Attach to projectinitiativeId: Attach to initiativeissueId: Attach to issuecolor: Icon color (hex format)icon: Icon emoji or name (optional, some emojis may not be valid - omit if validation fails)sortOrder: Display order (float)Update title and content:
curl -X POST https://api.linear.app/graphql \
-H "Content-Type: application/json" \
-H "Authorization: $LINEAR_API_KEY" \
-d '{
"query": "mutation DocumentUpdate($id: String!, $input: DocumentUpdateInput!) { documentUpdate(id: $id, input: $input) { success document { id title updatedAt updatedBy { name } } } }",
"variables": {
"id": "DOCUMENT_ID_OR_SLUG",
"input": {
"title": "Updated Title",
"content": "# Updated Content\n\nNew information here."
}
}
}'
Move to trash:
curl -X POST https://api.linear.app/graphql \
-H "Content-Type: application/json" \
-H "Authorization: $LINEAR_API_KEY" \
-d '{
"query": "mutation DocumentUpdate($id: String!, $input: DocumentUpdateInput!) { documentUpdate(id: $id, input: $input) { success } }",
"variables": {
"id": "DOCUMENT_ID",
"input": {
"trashed": true
}
}
}'
Available update parameters:
title: New titlecontent: New markdown contentcolor: New icon coloricon: New icontrashed: Move to trash (true) or restore (false)projectId: Move to different projectsortOrder: Update display orderPermanently delete (archive):
curl -X POST https://api.linear.app/graphql \
-H "Content-Type: application/json" \
-H "Authorization: $LINEAR_API_KEY" \
-d '{
"query": "mutation DocumentDelete($id: String!) { documentDelete(id: $id) { success } }",
"variables": {
"id": "DOCUMENT_ID"
}
}'
Restore archived document:
curl -X POST https://api.linear.app/graphql \
-H "Content-Type: application/json" \
-H "Authorization: $LINEAR_API_KEY" \
-d '{
"query": "mutation DocumentUnarchive($id: String!) { documentUnarchive(id: $id) { success entity { id title } } }",
"variables": {
"id": "DOCUMENT_ID"
}
}'
Basic milestone:
curl -X POST https://api.linear.app/graphql \
-H "Content-Type: application/json" \
-H "Authorization: $LINEAR_API_KEY" \
-d '{
"query": "mutation ProjectMilestoneCreate($input: ProjectMilestoneCreateInput!) { projectMilestoneCreate(input: $input) { success projectMilestone { id name status progress targetDate project { id name } } } }",
"variables": {
"input": {
"projectId": "PROJECT_ID_HERE",
"name": "Beta Release"
}
}
}'
With description and target date:
curl -X POST https://api.linear.app/graphql \
-H "Content-Type: application/json" \
-H "Authorization: $LINEAR_API_KEY" \
-d '{
"query": "mutation ProjectMilestoneCreate($input: ProjectMilestoneCreateInput!) { projectMilestoneCreate(input: $input) { success projectMilestone { id name status progress targetDate } } }",
"variables": {
"input": {
"projectId": "PROJECT_ID_HERE",
"name": "MVP Launch",
"description": "# MVP Goals\n\n- Core features complete\n- 10 beta users onboarded",
"targetDate": "2025-06-30"
}
}
}'
Interactive approach (using AskUserQuestion):
When user doesn't specify a target date, use AskUserQuestion to ask:
// Step 1: Ask user for target date
AskUserQuestion({
questions: [{
question: "What is the target date for this milestone?",
header: "Target Date",
multiSelect: false,
options: [
{
label: "End of this month",
description: "Set target date to the last day of current month"
},
{
label: "End of next month",
description: "Set target date to the last day of next month"
},
{
label: "Custom date",
description: "I'll specify a custom date in YYYY-MM-DD format"
},
{
label: "No target date",
description: "Create milestone without a specific target date"
}
]
}]
})
// Step 2: Based on user's answer, construct the mutation
// If custom date selected, prompt for YYYY-MM-DD format
// If no target date, omit targetDate from input
Available parameters:
projectId (required): Parent project IDname (required): Milestone namedescription: Markdown descriptiontargetDate: Target date (YYYY-MM-DD format)sortOrder: Display order (float)Status values (auto-calculated):
unstarted: No progress yetnext: Next milestone to work onoverdue: Past target datedone: All issues completedUpdate name and target date:
curl -X POST https://api.linear.app/graphql \
-H "Content-Type: application/json" \
-H "Authorization: $LINEAR_API_KEY" \
-d '{
"query": "mutation ProjectMilestoneUpdate($id: String!, $input: ProjectMilestoneUpdateInput!) { projectMilestoneUpdate(id: $id, input: $input) { success projectMilestone { id name status targetDate } } }",
"variables": {
"id": "MILESTONE_ID",
"input": {
"name": "MVP Launch - Extended",
"targetDate": "2025-07-15"
}
}
}'
Available update parameters:
name: New namedescription: New markdown descriptiontargetDate: New target date (YYYY-MM-DD)sortOrder: New display orderList all milestones:
curl -X POST https://api.linear.app/graphql \
-H "Content-Type: application/json" \
-H "Authorization: $LINEAR_API_KEY" \
-d '{
"query": "query ProjectMilestones($first: Int) { projectMilestones(first: $first) { nodes { id name status progress targetDate project { id name } issues { nodes { id title } } } } }",
"variables": {
"first": 50
}
}'
List milestones for specific project:
curl -X POST https://api.linear.app/graphql \
-H "Content-Type: application/json" \
-H "Authorization: $LINEAR_API_KEY" \
-d '{
"query": "query Project($id: String!) { project(id: $id) { id name projectMilestones { nodes { id name status progress targetDate } } } }",
"variables": {
"id": "PROJECT_ID"
}
}'
Detailed milestone info:
curl -X POST https://api.linear.app/graphql \
-H "Content-Type: application/json" \
-H "Authorization: $LINEAR_API_KEY" \
-d '{
"query": "query ProjectMilestone($id: String!) { projectMilestone(id: $id) { id name description status progress progressHistory currentProgress targetDate createdAt updatedAt project { id name state } issues { nodes { id title state { name type } assignee { name } } } } }",
"variables": {
"id": "MILESTONE_ID"
}
}'
curl -X POST https://api.linear.app/graphql \
-H "Content-Type: application/json" \
-H "Authorization: $LINEAR_API_KEY" \
-d '{
"query": "mutation ProjectMilestoneDelete($id: String!) { projectMilestoneDelete(id: $id) { success } }",
"variables": {
"id": "MILESTONE_ID"
}
}'
curl -X POST https://api.linear.app/graphql \
-H "Content-Type: application/json" \
-H "Authorization: $LINEAR_API_KEY" \
-d '{
"query": "mutation ProjectMilestoneMove($id: String!, $input: ProjectMilestoneMoveInput!) { projectMilestoneMove(id: $id, input: $input) { success projectMilestone { id name project { id name } } } }",
"variables": {
"id": "MILESTONE_ID",
"input": {
"projectId": "NEW_PROJECT_ID"
}
}
}'
Document operations:
Milestone operations:
IMPORTANT for Milestones:
Always check for LINEAR_API_KEY:
if [ -z "$LINEAR_API_KEY" ]; then
echo "Error: LINEAR_API_KEY not set. Get key from https://linear.app/settings/api"
exit 1
fi
Get IDs first:
list_projects to get project IDslist_issues to get issue IDslist_documents to get document IDs/slugsFor milestone operations, use AskUserQuestion:
Handle JSON carefully:
\n\"Check responses:
success: true in mutation responsessuccess: false, check the errors arrayHandle icon field carefully:
icon field is optional for documentsFormat output for user:
jq to pretty-print JSONid, url, statusAuthentication errors:
{
"errors": [
{
"message": "Authentication required",
"extensions": { "code": "UNAUTHENTICATED" }
}
]
}
→ Check if LINEAR_API_KEY is set and valid
Not found errors:
{
"errors": [
{
"message": "Resource not found",
"extensions": { "code": "NOT_FOUND" }
}
]
}
→ Verify the ID exists using list operations first
Validation errors:
{
"data": {
"documentCreate": {
"success": false
}
},
"errors": [
{
"message": "Title is required",
"path": ["documentCreate", "input", "title"]
}
]
}
→ Check required fields are provided
Rate limiting:
{
"errors": [
{
"message": "Rate limit exceeded",
"extensions": { "code": "RATE_LIMITED" }
}
]
}
→ Wait and retry after a few seconds
Icon validation errors:
{
"errors": [
{
"message": "Argument Validation Error",
"extensions": {
"code": "INVALID_INPUT",
"validationErrors": [
{
"property": "icon",
"constraints": {
"customValidation": "icon is not a valid icon"
}
}
]
}
}
]
}
→ Omit the icon field or try a different emoji/icon name. Linear API only accepts certain emojis.
User request: "Create a technical spec document for project abc123 with an overview section"
Response:
curl -X POST https://api.linear.app/graphql \
-H "Content-Type: application/json" \
-H "Authorization: $LINEAR_API_KEY" \
-d '{
"query": "mutation DocumentCreate($input: DocumentCreateInput!) { documentCreate(input: $input) { success document { id title url } } }",
"variables": {
"input": {
"title": "Technical Specification",
"content": "# Overview\n\nThis document outlines the technical architecture and implementation details.\n\n## Architecture\n\nTBD\n\n## Implementation\n\nTBD",
"projectId": "abc123"
}
}
}' | jq '.'
User request: "Add a 'Beta Launch' milestone to project xyz789, target date is March 31, 2025"
Response:
curl -X POST https://api.linear.app/graphql \
-H "Content-Type: application/json" \
-H "Authorization: $LINEAR_API_KEY" \
-d '{
"query": "mutation ProjectMilestoneCreate($input: ProjectMilestoneCreateInput!) { projectMilestoneCreate(input: $input) { success projectMilestone { id name status targetDate project { name } } } }",
"variables": {
"input": {
"projectId": "xyz789",
"name": "Beta Launch",
"targetDate": "2025-03-31"
}
}
}' | jq '.'
User request: "Create a milestone 'Phase 1 Complete' for project xyz789"
Step 1 - Use AskUserQuestion to ask for target date:
Ask user: "What is the target date for the 'Phase 1 Complete' milestone?"
Options:
- Specific date (YYYY-MM-DD format)
- No target date
Step 2 - If user provides date (e.g., "2025-06-30"):
curl -X POST https://api.linear.app/graphql \
-H "Content-Type: application/json" \
-H "Authorization: $LINEAR_API_KEY" \
-d '{
"query": "mutation ProjectMilestoneCreate($input: ProjectMilestoneCreateInput!) { projectMilestoneCreate(input: $input) { success projectMilestone { id name status targetDate project { name } } } }",
"variables": {
"input": {
"projectId": "xyz789",
"name": "Phase 1 Complete",
"targetDate": "2025-06-30"
}
}
}' | jq '.'
Step 2 - If user provides no date:
curl -X POST https://api.linear.app/graphql \
-H "Content-Type: application/json" \
-H "Authorization: $LINEAR_API_KEY" \
-d '{
"query": "mutation ProjectMilestoneCreate($input: ProjectMilestoneCreateInput!) { projectMilestoneCreate(input: $input) { success projectMilestone { id name status project { name } } } }",
"variables": {
"input": {
"projectId": "xyz789",
"name": "Phase 1 Complete"
}
}
}' | jq '.'
User request: "Update document doc_abc123 to add a new section about testing"
Response:
# First, get current content using Linear MCP's get_document
# Then update with new content:
curl -X POST https://api.linear.app/graphql \
-H "Content-Type: application/json" \
-H "Authorization: $LINEAR_API_KEY" \
-d '{
"query": "mutation DocumentUpdate($id: String!, $input: DocumentUpdateInput!) { documentUpdate(id: $id, input: $input) { success document { id updatedAt } } }",
"variables": {
"id": "doc_abc123",
"input": {
"content": "[EXISTING_CONTENT]\n\n## Testing\n\n- Unit tests\n- Integration tests\n- E2E tests"
}
}
}' | jq '.'
User request: "Show me all milestones and their progress"
Response:
curl -X POST https://api.linear.app/graphql \
-H "Content-Type: application/json" \
-H "Authorization: $LINEAR_API_KEY" \
-d '{
"query": "query { projectMilestones(first: 50) { nodes { name status progress targetDate project { name } issues { nodes { id } } } } }"
}' | jq '.data.projectMilestones.nodes[] | "\(.project.name) - \(.name): \(.progress * 100)% complete (target: \(.targetDate // "No date set"))"'
This skill works best alongside the official Linear MCP server:
Linear MCP provides (read operations):
list_documents - Get existing documentsget_document - Read document contentlist_projects - Get project IDslist_issues - Get issue IDslist_teams - Get team infoThis skill adds (write operations):
documentCreate - Create new documentsdocumentUpdate - Update documentsdocumentDelete - Delete documentsprojectMilestoneCreate - Create milestonesprojectMilestoneUpdate - Update milestonesprojectMilestoneDelete - Delete milestonesprojectMilestones query - List milestones (not in MCP)Typical workflow:
Extract just the document URL:
curl -X POST ... | jq -r '.data.documentCreate.document.url'
Format milestone list as table:
curl -X POST ... | jq -r '.data.projectMilestones.nodes[] | [.name, .status, (.progress * 100 | tostring + "%")] | @tsv'
CONTENT=$(cat <<'EOF'
# Architecture Design
## Overview
System architecture overview here.
## Components
- API Gateway
- Service Mesh
- Data Layer
EOF
)
# Create temp file with Python for proper JSON encoding
TEMP_FILE=$(mktemp)
python3 << PEOF > "$TEMP_FILE"
import json
data = {
"query": """mutation DocumentCreate(\$input: DocumentCreateInput!) {
documentCreate(input: \$input) {
success
document { id url }
}
}""",
"variables": {
"input": {
"title": "Architecture Design",
"content": """$CONTENT"""
}
}
}
print(json.dumps(data, ensure_ascii=False))
PEOF
curl -X POST https://api.linear.app/graphql \
-H "Content-Type: application/json" \
-H "Authorization: $LINEAR_API_KEY" \
-d @"$TEMP_FILE" | jq '.'
rm "$TEMP_FILE"
For detailed schema information:
grep "DocumentCreateInput\|DocumentUpdateInput\|icon\|color" references/document-schema.mdgrep "ProjectMilestoneCreateInput\|ProjectMilestoneUpdateInput\|targetDate" references/milestone-schema.mdgrep "Example\|mutation\|query" references/examples.mdFor the original GraphQL schema:
This skill should be used when the user asks to "create a hookify rule", "write a hook rule", "configure hookify", "add a hookify rule", or needs guidance on hookify rule syntax and patterns.
Create distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.