This package provides integration between Emacs and Linear.app, allowing you to view and manage your Linear issues without leaving Emacs. I was just sick of leaving Emacs for some corporation's UI.
I hope this simple integration helps you, too.
-
List, view, and create Linear issues directly from Emacs
-
Bi-directional synchronization between Linear.app and org-mode
-
Configurable state mapping between Linear workflow states and org-mode TODO states
-
Dynamic pattern generation for flexible org-mode integration
-
Update issue details from either Linear or Emacs
-
Track issue priorities, labels, and assignments
-
Support for issue filtering and organization
-
Automatically sync changes between systems
-
Doom Emacs integration with optimized configuration examples
-
Project Management Integration:
- Create and list issues under specific Linear projects
- Filter issues by project with dedicated command
- Project selection during issue creation
- Project information included in org-mode export
Ensure you have MELPA configured in your Emacs:
(require 'package)
(add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/") t)
(package-initialize)
Once accepted to MELPA, you'll be able to install with:
M-x package-install RET linear-emacs RET
- Clone this repository:
git clone ssh://[email protected]/gyamtso/linear-emacs.git
or, if you really have to:
git clone https://github.com/anegg0/linear-emacs.git
-
Add the following to your Emacs configuration:
(add-to-list 'load-path "/path/to/linear-emacs") (require 'linear-emacs)
-
Install the required dependencies:
M-x package-install RET request RET M-x package-install RET dash RET M-x package-install RET s RET
In your packages.el
:
(package! linear-emacs
:recipe (:host github :repo "anegg0/linear-emacs" :files ("*.el")))
In your config.el
:
(use-package! linear-emacs
:commands (linear-emacs-list-issues
linear-emacs-new-issue
linear-emacs-enable-org-sync)
:config
;; Load API key from environment or auth-source
(linear-emacs-load-api-key-from-env)
;; Optional: Set default team
;; (setq linear-emacs-default-team-id "your-team-id")
;; Optional: Customize org file location
;; (setq linear-emacs-org-file-path (expand-file-name "gtd/linear.org" org-directory))
;; Optional: Customize state mapping (see Status Mapping section below)
;; (setq linear-emacs-issues-state-mapping
;; '(("Todo" . "TODO")
;; ("In Progress" . "DOING")
;; ("Done" . "DONE")))
)
## Configuration
### Basic Setup
There are several ways to set your Linear API key:
#### Direct Setting (Not Recommended)
```elisp
(setq linear-emacs-api-key "your-api-key-here")
Set the LINEAR_API_KEY
environment variable and load it with:
(linear-emacs-load-api-key-from-env)
For secure credential storage, use Emacs' built-in auth-source package:
(defun my/linear-load-api-key-from-auth-source ()
"Load Linear API key from auth-source."
(interactive)
(require 'auth-source)
(let* ((auth-info (auth-source-search :host "api.linear.app" :user "apikey" :max 1))
(secret (when auth-info
(funcall (plist-get (car auth-info) :secret)))))
(if secret
(progn
(setq linear-emacs-api-key secret)
(message "Successfully loaded Linear API key from auth-source"))
(message "Failed to retrieve Linear API key from auth-source"))))
;; Call this function when linear loads
(after! linear-emacs
(my/linear-load-api-key-from-auth-source))
To set up your auth-source entry:
-
For macOS Keychain users, run:
security add-generic-password -a apikey -s api.linear.app -w YOUR_API_KEY
-
For
~/.authinfo.gpg
users, add this line to your file:machine api.linear.app login apikey password YOUR_API_KEY
Note: The hostname (api.linear.app
) and username (apikey
) in your auth-source entry must match exactly what you're using in the auth-source-search
function.
You can get your API key from Linear.app under Settings > Account > API > Personal API Keys.
Optionally, set a default team ID to streamline issue creation:
(setq linear-emacs-default-team-id "your-team-id")
By default, Linear issues are saved to gtd/linear.org
in your org-directory
. You can customize this location:
;; Change the output file location
(setq linear-emacs-org-file-path "/path/to/your/linear-issues.org")
;; Or use a different subdirectory in your org-directory
(setq linear-emacs-org-file-path (expand-file-name "projects/linear.org" org-directory))
Here's a comprehensive Doom Emacs configuration that includes API key management, state mapping, and auto-sync:
;; In your config.el
(use-package! linear-emacs
:after org
:commands (linear-emacs-list-issues
linear-emacs-new-issue
linear-emacs-enable-org-sync
linear-emacs-sync-org-to-linear)
:init
;; Set file path before package loads
(setq linear-emacs-org-file-path (expand-file-name "gtd/linear.org" org-directory))
:config
;; Load API key from environment
(linear-emacs-load-api-key-from-env)
;; Or use auth-source for secure storage
;; (my/linear-load-api-key-from-auth-source)
;; Customize state mapping to match your workflow
(setq linear-emacs-issues-state-mapping
'(("Todo" . "TODO")
("In Progress" . "DOING")
("In Review" . "REVIEW")
("Blocked" . "HOLD")
("Done" . "DONE")))
;; Optional: Set default team to skip selection prompt
;; (setq linear-emacs-default-team-id "your-team-id")
;; Auto-enable sync when linear.org is opened
(defun my/enable-linear-org-sync ()
"Enable Linear-org synchronization when linear.org is opened."
(when (and buffer-file-name
(string-match-p "linear\\.org$" buffer-file-name))
(linear-emacs-enable-org-sync)
(message "Linear-org synchronization enabled for this buffer")))
(add-hook 'find-file-hook #'my/enable-linear-org-sync)
;; Keybindings
(map! :leader
(:prefix ("l" . "linear")
:desc "List Linear issues" "l" #'linear-emacs-list-issues
:desc "Create new issue" "n" #'linear-emacs-new-issue
:desc "Sync current issue" "s" #'linear-emacs-sync-org-to-linear
:desc "Enable org sync" "e" #'linear-emacs-enable-org-sync
:desc "Disable org sync" "d" #'linear-emacs-disable-org-sync
:desc "Test connection" "t" #'linear-emacs-test-connection
:desc "Toggle debug mode" "D" #'linear-emacs-toggle-debug)))
;; Ensure org-todo-keywords match your Linear mapping
(after! org
(setq org-todo-keywords
'((sequence "TODO(t)" "DOING(d)" "REVIEW(r)" "HOLD(h)" "|" "DONE(D)"))))
For a simpler setup with automatic synchronization:
;; In your config.el
(after! linear-emacs
(linear-emacs-load-api-key-from-env)
;; Automatically enable two-way sync when linear.org is opened
(defun my/enable-linear-org-sync ()
"Enable Linear-org synchronization when linear.org is opened."
(when (and buffer-file-name
(string-match-p "linear\\.org$" buffer-file-name))
(when (fboundp 'linear-emacs-enable-org-sync)
(linear-emacs-enable-org-sync)
(message "Linear-org synchronization enabled for this buffer"))))
;; Add hook to auto-enable sync when linear.org is opened
(add-hook 'find-file-hook #'my/enable-linear-org-sync)
;; Enable sync for org-after-todo-state-change-hook
(add-hook 'org-after-todo-state-change-hook
(lambda ()
(when (and buffer-file-name
(string-match-p "linear\\.org$" buffer-file-name)
(fboundp 'linear-emacs-sync-org-to-linear))
(linear-emacs-sync-org-to-linear)))))
When you run M-x linear-emacs-list-issues
, the package will create a file with all your assigned issues at the location specified by linear-org-file-path
(see "Customizing the Output Path" above).
The org file will have the following structure:
:PROPERTIES:
:ID: a12acb12-8a69-4d15-a846-21e20ed2f3ae
#+title: Linear issues assigned to me
#+TAGS: :
#+filetags: :twai:b:
#+STARTUP: overview
#+TODO: TODO IN-PROGRESS IN-REVIEW BACKLOG BLOCKED | DONE
:END:
*** TODO [#B] Issue Title
:PROPERTIES:
:ID: issue-unique-id
:ID-LINEAR: TEAM-123
:TEAM: Team Name
:PROJECT: Mobile App Redesign
:PROJECT-ID: proj-a1b2c3d4-e5f6-7890-abcd-ef1234567890
:DESCRIPTION: |
Issue description goes here
:PRIORITY: High
:LABELS: [mobile, ui, redesign]
:LINK: https://linear.app/issue/TEAM-123
:END:
When you change the TODO state of an issue in the org file, it will automatically synchronize with Linear.
Special thank you note to @loispostula for suggesting this change :)
The linear-emacs-issues-state-mapping
variable provides a unified configuration for mapping between Linear issue states and Org-mode TODO states. This single configuration controls:
- Which Linear issues are synced to your Org file (only issues with mapped states are included)
- How Linear states are converted to Org TODO keywords
- How Org TODO keywords are converted back to Linear states
- Dynamic generation of regex patterns for matching Org headings
'(("Todo" . "TODO")
("In Progress" . "IN-PROGRESS")
("In Review" . "IN-REVIEW")
("Backlog" . "BACKLOG")
("Blocked" . "BLOCKED")
("Done" . "DONE"))
You can customize the state mapping to match your workflow:
;; Simple workflow
(setq linear-emacs-issues-state-mapping
'(("Todo" . "TODO")
("In Progress" . "DOING")
("Done" . "DONE")))
;; Extended workflow with custom states
(setq linear-emacs-issues-state-mapping
'(("Todo" . "TODO")
("In Progress" . "WORKING")
("In Review" . "REVIEW")
("Testing" . "TEST")
("Blocked" . "HOLD")
("Done" . "DONE")
("Cancelled" . "CANCELLED")))
Add a single state to the existing mapping:
(add-to-list 'linear-emacs-issues-state-mapping '("On Hold" . "HOLD"))
For Doom Emacs users, configure the state mapping in your config.el
:
(after! linear-emacs
;; Custom state mapping for your workflow
(setq linear-emacs-issues-state-mapping
'(("Todo" . "TODO")
("In Progress" . "WIP")
("In Review" . "REVIEW")
("Done" . "DONE")))
;; Ensure your org-todo-keywords match
(setq org-todo-keywords
'((sequence "TODO(t)" "WIP(w)" "REVIEW(r)" "|" "DONE(d)"))))
Important: Make sure your org-todo-keywords
configuration includes all the Org states you use in your mapping. The package dynamically generates regex patterns based on your mapping to match Org headings.
The package automatically generates regex patterns from your state mapping configuration. This means:
-
No hardcoded patterns: The regex patterns used to match Org headings are built dynamically from your
linear-emacs-issues-state-mapping
-
Automatic updates: When you change your state mapping, the patterns are automatically regenerated
-
Performance optimized: Patterns are cached for efficiency and only regenerated when needed
For example, with this mapping:
'(("Todo" . "TODO")
("In Progress" . "WORKING")
("Done" . "DONE"))
The package generates the pattern: "TODO\\|WORKING\\|DONE"
This pattern is then used to match Org headings like:
*** TODO Fix the bug
*** WORKING Implement feature
*** DONE Complete documentation
Map Linear priorities to org priorities:
(setq linear-emacs-org-priority-mapping
'((0 . nil) ; No priority
(1 . "A") ; Urgent
(2 . "B") ; High
(3 . "C") ; Medium
(4 . "D"))) ; Low
M-x linear-emacs-list-issues
- Display your assigned issuesM-x linear-emacs-list-issues-by-project
- Display issues filtered by a selected projectM-x linear-emacs-new-issue
- Create a new issue (with project selection)M-x linear-emacs-test-connection
- Test your Linear API connectionM-x linear-emacs-toggle-debug
- Toggle debug mode for troubleshootingM-x linear-emacs-check-setup
- Verify your API key is loaded correctly
M-x linear-emacs-list-issues
- Pull issues from Linear into org-modeM-x linear-emacs-sync-org-to-linear
- Sync the current org entry to LinearM-x linear-emacs-enable-org-sync
- Enable automatic synchronizationM-x linear-emacs-disable-org-sync
- Disable automatic synchronization
The following commands are available in the optimized configuration:
M-x my/linear-sync-single-issue-at-point
- Sync only the current issue at pointM-x my/toggle-linear-auto-sync
- Toggle automatic syncing before showing todo list
For Doom Emacs, you can set up convenient keybindings:
(map! :leader
(:prefix ("l" . "Linear")
:desc "Sync all Linear issues" "s" #'linear-emacs-list-issues
:desc "List issues by project" "p" #'linear-emacs-list-issues-by-project
:desc "New issue" "n" #'linear-emacs-new-issue
:desc "Toggle Linear auto-sync" "t" #'my/toggle-linear-auto-sync
:desc "Test connection" "c" #'linear-emacs-test-connection
:desc "Toggle debug" "d" #'linear-emacs-toggle-debug))
In the optimized configuration, these keybindings are already set up for you:
(map! :leader
:prefix "l"
:desc "Sync all Linear issues" "s" #'linear-emacs-list-issues
:desc "Toggle Linear auto-sync" "t" #'my/toggle-linear-auto-sync)
To avoid synchronization performance issues, the optimized configuration:
- Only synchronizes the current issue at point instead of all issues
- Makes automatic synchronization optional with a toggle
- Adds convenient keybindings for managing synchronization
;; In your config.el
(after! linear-emacs
;; Improved synchronization function that only updates the changed issue
(defun my/linear-sync-single-issue-at-point ()
"Sync only the current issue at point to Linear API."
(interactive)
(save-excursion
;; Move to the beginning of the current heading
(org-back-to-heading t)
;; Check if this is a Linear issue heading
(when (looking-at "^\\*\\*\\* \\(TODO\\|IN-PROGRESS\\|IN-REVIEW\\|BACKLOG\\|BLOCKED\\|DONE\\)")
;; [Implementation details omitted for brevity]
)))
;; Override linear-emacs-sync-org-to-linear to only sync the current issue
(defun linear-emacs-sync-org-to-linear ()
"Sync only the current issue to Linear API."
(interactive)
(my/linear-sync-single-issue-at-point))
;; Add convenient keybinding for manually syncing all issues
(map! :leader
:prefix "l"
:desc "Sync all Linear issues" "s" #'linear-emacs-list-issues
:desc "Toggle Linear auto-sync" "t" #'my/toggle-linear-auto-sync))
Enable debug logging to troubleshoot issues:
(setq linear-emacs-debug t)
-
Check your connection:
M-x linear-emacs-test-connection
-
Enable debug mode:
M-x linear-emacs-toggle-debug
-
Verify your API key is loaded correctly:
M-x linear-emacs-check-setup
-
Check the
*Messages*
buffer for detailed error information when debug mode is enabled
Contributions are welcome! Please feel free to submit a Pull Request.
This project is licensed under the GPL-3.0 License - see the LICENSE file for details.