Bidirectional sync between Linear and Obsidian with intelligent template-based file generation and multi-team support
Bidirectional sync between Linear issues and Obsidian with automatic file generation, multi-team support, and state-based organization.
- 🔄 Bidirectional Sync: Sync changes between Obsidian and Linear (configurable direction)
- 📥 Linear → Obsidian: Automatic issue sync with progressive template generation
- 📤 Obsidian → Linear: Update Linear issues by editing frontmatter (state, priority, estimate, assignee, due date)
- ⚡ Webhook Integration: Real-time updates via n8n webhook queue (file-based)
- 🔀 Conflict Resolution: Configurable strategies (most-recent-wins, linear-wins, obsidian-wins)
- 👥 Multi-Team Support: Sync multiple Linear teams with independent configurations
- 📁 Automatic Organization: Issues organized by team and workflow state
- 📝 Progressive Templates: Cumulative template generation as issues advance through states
- 🎯 State-Based Mapping: Customizable directory structure per Linear workflow state
- 📊 Kanban Board Generation: Auto-generated TRACKER.md kanban boards
- 🔁 Sync Loop Prevention: Timestamp-based guards prevent infinite write cycles
- 📱 Mobile Support: Platform-aware file watching and debouncing
- 🎨 Templater Integration: Context-aware file generation with Linear data injection
- v2.0: Advanced conflict resolution (vector clocks, CRDTs), enhanced webhook features
- v3.0: Real-time HTTP webhooks (if Obsidian adds server capability), collaborative editing
graph TB
subgraph "Linear Platform"
LA[Linear API<br/>GraphQL Mutations]
LW[Linear Webhooks]
end
subgraph "n8n Integration"
N8N[n8n Workflow<br/>Webhook Receiver]
WQ[Webhook Queue<br/>.linear-webhook-queue.json]
end
subgraph "Obsidian Plugin"
PS[Plugin Service<br/>Polling Sync]
LC[LinearClient<br/>+ 5min Cache + Mutations]
SO[SyncOrchestrator<br/>Linear → Obsidian]
BS[BidirectionalSyncService<br/>Obsidian → Linear]
WP[WebhookQueueProcessor<br/>Real-time Updates]
DS[DirectorySyncService]
TG[TemplateGenerationService]
KB[KanbanBoardService]
CD[ObsidianChangeDetector<br/>File Watcher]
end
subgraph "Obsidian Vault"
TM[TRACKER.md<br/>Kanban Board]
TD[Team Directories<br/>Per-Team Config]
IF[Issue Files<br/>Markdown Docs]
TF[Template Files<br/>Per-Team Templates]
end
subgraph "External Dependencies"
TP[Templater Plugin<br/>Required]
end
%% Linear → Obsidian (Polling)
LA -.->|Poll| LC
LC --> SO
PS --> SO
SO --> DS
SO --> TG
SO --> KB
%% Obsidian → Linear (Bidirectional)
IF -.->|File Changes| CD
CD --> BS
BS --> LC
LC -.->|Mutations| LA
%% Webhook Flow
LW -.->|HTTP POST| N8N
N8N --> WQ
WQ -.->|Poll Queue| WP
WP --> SO
%% Template Processing
TG --> TP
TP --> TF
DS --> TD
DS --> IF
KB --> TM
TF --> IF
style LA fill:#5E6AD2
style PS fill:#7C3AED
style BS fill:#10B981
style WP fill:#F59E0B
style N8N fill:#FF6B6B
Key Architecture Features:
- Bidirectional Sync: Linear ↔ Obsidian with configurable conflict resolution
- Webhook Integration: Real-time updates via n8n file-based queue
- Sync Loop Prevention: Timestamp guards prevent infinite write cycles
- Mobile Support: Platform-aware file watching with optimized debouncing
- Obsidian v1.0.0 or higher
- Templater Plugin installed and configured
- Linear API Key from Linear Settings → API
- Folder Structure (auto-created on first sync)
-
Install the Plugin
# The plugin is located at: docs/automation/plugins/obsidian-linear-sync/ # Symlinked to Obsidian plugins: .obsidian/plugins/obsidian-linear-sync/
-
Configure Settings
- Open Settings → Linear Sync
- Enter your Linear API key
- Set template folder path:
templates/kanban-template - Test connection
⚠️ SECURITY WARNING: Never commit yourdata.jsonfile to version control! It contains your Linear API key. This file is already in.gitignore, but double-check before pushing to any repository. If you accidentally commit your API key:- Immediately revoke the key in Linear Settings → API
- Remove the file from git history using
git filter-branchor BFG Repo-Cleaner - Generate a new API key and configure the plugin again
-
First Sync
- Run command: "Linear Sync: Sync from Linear"
- Plugin creates folder structure automatically
For real-time webhook updates instead of polling:
- Local REST API Plugin - Install from Obsidian community plugins
- Public tunnel - ngrok, Cloudflare Tunnel, or localtunnel to expose localhost
1. Install Local REST API Plugin:
- Open Obsidian Settings → Community plugins
- Search for "Local REST API"
- Install and enable
- Note the API key from Local REST API settings
2. Set Up Tunnel (Choose one):
Option A: ngrok (Recommended)
# Install ngrok: https://ngrok.com/download
# Get free authtoken: https://dashboard.ngrok.com/get-started/your-authtoken
ngrok config add-authtoken YOUR_AUTHTOKEN
# Start tunnel to Local REST API port
ngrok http 27123Option B: Cloudflare Tunnel
# Install cloudflared: https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/
cloudflared tunnel --url http://localhost:27123Option C: localtunnel
npx localtunnel --port 271233. Configure Plugin:
- Open Settings → Linear Sync → Sync Configuration
- Set "Sync Method" to "Webhook"
- Copy your tunnel URL (e.g.,
https://abc123.ngrok.io) - Paste into "Webhook Public URL" field
- Copy the full webhook URL displayed below (includes endpoint path)
4. Configure Linear Webhook:
- Open Linear Settings → API → Webhooks
- Click "New Webhook"
- Paste the full webhook URL from plugin settings
- Select event types:
Issue(all events) - Optional: Set webhook secret (must match in plugin settings)
- Save
5. Test:
- Create or update an issue in Linear
- Check Obsidian - changes should appear within seconds!
Webhooks not received:
- Verify tunnel is running and URL is accessible
- Check webhook URL in Linear matches the full URL from plugin settings
- Verify Local REST API plugin is enabled
- Check HMAC secret matches if configured
- Enable Local REST API plugin logs for debugging
Tunnel disconnected:
- Restart your tunnel command
- Update the public URL in plugin settings
- Update webhook URL in Linear if it changed
The plugin uses a 6-phase workflow with progressive documentation:
stateDiagram-v2
[*] --> Planning
Planning --> Pending
Pending --> InProgress
InProgress --> Review
Review --> Staging
Staging --> Closed
Planning: 01-planning<br/>PRD + Architecture
Pending: 02-pending<br/>+ Task Planning
InProgress: 03-in-progress<br/>+ Implementation
Review: 04-review<br/>+ Audit Checklist
Staging: 05-staging<br/>+ Changelog
Closed: 06-closed<br/>Archived
| Phase | Directory | Linear States | Templates Generated | Purpose |
|---|---|---|---|---|
| Planning | 01-planning |
Backlog, Triage | 01-prd.md 02-architecture.md |
Requirements & Design |
| Pending | 02-pending |
Unstarted | + 03-task.md | Ready for Development |
| In Progress | 03-in-progress |
Started | + 04-audit.md | Active Development |
| Review | 04-review |
Started (Review) | Same as Progress | Code Review & QA |
| Staging | 05-staging |
Completed | + 05-changelog.md | Pre-production |
| Closed | 06-closed |
Done, Canceled | All files | Archived |
Templates use Templater variables for dynamic content:
// Linear Issue Data
<% linear.id %> // Issue ID
<% linear.identifier %> // Issue key (e.g., ENG-123)
<% linear.title %> // Issue title
<% linear.description %> // Issue description
<% linear.state %> // Current state
<% linear.priority %> // Priority level
<% linear.assignee %> // Assigned user
<% linear.due_date %> // Due date
<% linear.project %> // Project name
<% linear.labels %> // Issue labels
<% linear.url %> // Linear URL
// Metadata
<% tp.date.now("YYYY-MM-DD HH:mm") %> // Current timestamp
<% phase %> // Current phase- 01-prd.md - Product Requirements Document
- 02-architecture.md - Technical Architecture
- 03-task.md - Implementation Tasks
- 04-audit.md - Review Checklist
- 05-changelog.md - Release Notes
# Settings accessible via Obsidian Settings → Linear Sync
# Authentication
apiKey: 'lin_api_...' # Your Linear API key
# Sync Configuration
# Choose sync method: polling or webhook (mutually exclusive)
syncMethod: 'polling' # polling | webhook
# Polling Configuration (only used when syncMethod = polling)
pollingConfig:
intervalMinutes: 5 # Minutes between polling syncs
# Webhook Configuration (only used when syncMethod = webhook)
# Requires Local REST API plugin to be installed
webhookConfig:
endpointPath: '/linear/webhook' # Webhook endpoint path
linearWebhookSecret: '' # Optional HMAC secret (must match Linear webhook secret)
batchSize: 10 # Processing batch size
maxRetries: 3 # Max retry attempts for failed events
# Sync Direction
syncDirection: 'linear-to-obsidian' # linear-to-obsidian | bidirectional | obsidian-to-linear
conflictResolution: 'most-recent-wins' # most-recent-wins | linear-wins | obsidian-wins (only for bidirectional)
# Field Sync
syncPriority: true # Sync priority field
syncEstimate: true # Sync estimate points
syncDueDate: true # Sync due datesSynced files include metadata:
---
# Linear Metadata
linear_id: abc-123-def
linear_identifier: ENG-123
linear_url: https://linear.app/team/issue/ENG-123
linear_state: In Progress
linear_state_id: uuid
linear_priority: 2
linear_estimate: 3
linear_due_date: 2024-01-15
linear_team_id: uuid
linear_assignee_id: uuid
# Sync Settings
auto_sync: true # Enable bidirectional sync for this file
last_sync: 2024-01-10T10:30:00Z
# Template Metadata
template_type: prd
template_version: 4.0.0
phase: 03-in-progress
---Bidirectional Sync Fields: Edit these frontmatter fields to update Linear:
linear_state- Change issue statepriority- Update priority (0-4)estimate- Update estimate pointsdue_date- Update due dateassignee- Update assignee by name
Prerequisites:
- Install Local REST API plugin from Community Plugins
- Enable Local REST API plugin
- Note the API key from Local REST API settings
Configuration:
-
Linear Sync Settings:
- Set Sync Method to "Webhook"
- Copy the webhook URL displayed (e.g.,
https://localhost:27123/linear/webhook?api=YOUR_KEY) - (Optional) Set Linear webhook secret for HMAC verification
-
Linear Settings:
- Go to Linear → Settings → API → Webhooks
- Create new webhook
- Paste the URL from Linear Sync
- Enter the same secret (if using)
- Subscribe to: Issue created, updated, deleted
-
For Remote Access (Optional):
# Use ngrok or similar tunnel ngrok http 27123 # Use tunnel URL in Linear https://abc123.ngrok.io/linear/webhook?api=YOUR_KEY
Access via Command Palette (Cmd/Ctrl + P):
| Command | Description | Hotkey |
|---|---|---|
| Sync from Linear | Fetch and update all issues | - |
| Sync to Linear | Push local changes to Linear | - |
| Batch sync all kanban files | Sync files with auto_sync: true |
- |
| Create Linear issue from note | Create new issue from current file | - |
| Create kanban structure | Generate folder structure for issue | - |
| Test Linear connection | Verify API key and connection | - |
sequenceDiagram
participant L as Linear
participant N as n8n
participant W as Webhook Queue
participant P as Plugin
participant V as Vault
participant T as Templater
Note over L,P: Polling Sync (Every 5 min)
L->>P: Fetch Issues (GraphQL)
P->>P: Check Cache (5min TTL)
P->>V: Find/Create Directory
P->>T: Request Template
T-->>P: Rendered Template
P->>P: Mark Plugin Write
P->>V: Write/Update Files
P->>V: Update TRACKER.md
Note over L,V: Webhook Flow (Real-time)
L->>N: Webhook Event
N->>W: Write to Queue File
P->>W: Poll Queue (Every 5s)
P->>P: Process Event
P->>L: Fetch Updated Issue
P->>V: Update Files
sequenceDiagram
participant U as User
participant V as Vault
participant P as Plugin
participant L as Linear
Note over U,L: User Edits Frontmatter
U->>V: Edit File (state, priority, etc.)
V->>P: File Change Event
P->>P: Check Sync Loop Guard
P->>P: Detect Frontmatter Changes
P->>P: Debounce (300ms desktop)
alt Has Syncable Changes
P->>L: Update Issue (GraphQL Mutation)
L-->>P: Confirm Update
P->>P: Update Cache
Note over V,L: ✓ Synced to Linear
else No Changes
P->>P: Skip (No sync needed)
end
| Issue | Cause | Solution |
|---|---|---|
| No API Key Error | Missing configuration | Settings → Linear Sync → Add API key |
| Files Not Syncing | Missing frontmatter | Ensure linear_id and auto_sync: true |
| Templates Not Working | Templater not configured | Install and configure Templater plugin |
| State Not Updating | File in wrong folder | Check file is in recognized kanban folder |
| Connection Issues | Network/API problems | Use "Test connection" in settings |
- Open Developer Console:
Ctrl+Shift+I(Windows/Linux) orCmd+Opt+I(Mac) - Filter by "Linear Sync" for plugin logs
- Check for GraphQL errors or template issues
This plugin integrates with the automation stack:
- n8n Workflows: Webhook processing at
http://localhost:5678 - Cyrus AI Agent: Linear automation at
http://localhost:3456 - Docker Services: See INTEGRATION.md
- TEMPLATER-GUIDE.md - Template configuration
- API.md - Technical API reference
- INTEGRATION.md - Automation stack integration
- DEVELOPMENT.md - Developer guide
- examples/ - Sample files and templates
- Fork the repository
- Create feature branch
- Test with mock Linear data
- Submit pull request
MIT License - See LICENSE file
- Author: Samuel Ho
- Website: https://samuelho.space
- Dependencies: Obsidian API, Templater Plugin
- API: Linear GraphQL API
Last Updated: 2024-08-29 | Version: 1.0.0 | Requires: Obsidian 1.0.0+, Templater