org-linear manual
Table of contents
Overview
org-linear.el keeps the issues of one or more Linear teams in an Org file and syncs the two in both directions. You work on your issues with the Org commands you already use: change a TODO keyword to move an issue through its workflow, set a deadline for its due date, add a note to comment on it, refile it to move it to another project. Changes made in Linear, by you or by your colleagues, reach the file at the next sync.
Linear stays the authoritative copy and the file is a mirror. The package exists because the alternatives each give up half of that: Linear’s own interface lives outside Emacs, and a one-way export cannot be edited. Unlike an export, org-linear remembers what each issue looked like at the last sync, so it can tell which side changed a field and merge the two without overwriting either.
The package talks to Linear’s GraphQL API through curl and converts descriptions and comments between Markdown and Org with pandoc. Both run as subprocesses, so Emacs never waits on the network.
Its capabilities fall into four groups:
- The file: how issues, projects and their fields appear in Org (The file).
- Syncing: when a sync runs, how it merges, and what happens on conflicts (Syncing).
- Setup: the API key, the teams and the mappings (User options).
- The agenda: a single command that lists your open issues, and your deadlines among your other tasks (The agenda).
Installation
org-linear requires Emacs 29.1 or later, Org 9.7 or later, and the curl and pandoc programs on your exec-path.
With Elpaca:
(use-package org-linear
:ensure (:host github :repo "benthamite/org-linear")
:custom
(org-linear-teams '("ENG"))
:config
(org-linear-auto-sync-mode))
With Emacs’s built-in package-vc (Emacs 30 or later):
(use-package org-linear
:vc (:url "https://github.com/benthamite/org-linear")
:custom
(org-linear-teams '("ENG"))
:config
(org-linear-auto-sync-mode))
Then create a personal API key in Linear (Settings, Security & access, Personal API keys), give it access to the teams you mirror, and make it available to the package (The API key).
The file
The file is org-linear-file. When it does not exist, the first sync creates it with org-linear-file-header and fills it. Each project is a level-1 heading with a LINEAR_PROJECT property, and each issue a level-2 heading under its project. Issues without a project go under a heading named “No project”.
* Website
:PROPERTIES:
:LINEAR_PROJECT: 3f1c...
:END:
** DOING [#B] Fix the broken signup form :ana:bug:
DEADLINE: <2026-11-12 Thu>
:PROPERTIES:
:LINEAR_ID: ENG-123
:LINEAR_UUID: 6b0e...
:LINEAR_URL: https://linear.app/acme/issue/ENG-123/fix-the-broken-signup-form
:END:
:LOGBOOK:
- Note taken on [2026-11-02 Mon 10:14] (Sam Lee) \\
I can reproduce it on Safari only.
:END:
The form rejects valid email addresses with a plus sign.
How fields map
| Linear | Org |
|---|---|
| Title | The heading’s title |
| Workflow state | TODO keyword (Workflow states and keywords) |
| Priority | Priority cookie (org-linear-priorities) |
| Assignee | A tag for the person (org-linear-user-tags) |
| Labels | Tags |
| Due date | DEADLINE |
| Project | The level-1 heading the issue sits under |
| Description | The heading’s body, converted from and to Markdown by pandoc |
| Comments | Notes in the log drawer |
The identifier, the issue’s internal ID and its URL are kept in the LINEAR_ID, LINEAR_UUID and LINEAR_URL properties. The sync maintains them; do not edit them.
Tags that name neither a team member nor a label are yours: the sync leaves them alone and never sends them. A label’s tag is its name with every character Org does not allow in tags replaced by _, so the label requested-by:peter becomes the tag requested_by_peter. A tag you add sends the label of that name that the issue’s team can use; if several can, it is logged and not sent.
Linear issues have no start date, so by default SCHEDULED is not synced: use it freely to defer an issue in your own agenda.
A team can stand in for a start date with a Start: YYYY-MM-DD first line in the description and a label. Set org-linear-start-date-label to that label’s name, such as "deferred", and SCHEDULED syncs both ways with the two of them. A date in the file then shows as SCHEDULED alone: the line leaves the body, and the label’s tag leaves the tags. Setting, moving or removing SCHEDULED rewrites the line and sets or removes the label on Linear, and for an issue in triage it also snoozes the issue until the start of that date in your time zone, or ends the snooze. Nothing is written when you turn the option on, unless a synced heading already has a SCHEDULED of its own: that date is sent.
Every SCHEDULED on a synced issue is then visible to your team, and the label stays after the date passes, until someone removes the date. A Linear view that hides the label keeps hiding the issue until then.
Workflow states and keywords
Each issue’s workflow state becomes a TODO keyword. By default the keyword depends only on the state’s type (org-linear-type-keywords): triage issues are TRIAGE, backlog issues BACKLOG, unstarted ones TODO, started ones DOING, completed ones DONE, and canceled or duplicate ones CANCELLED. org-linear-state-keywords gives individual states keywords of their own, for instance a WAITING keyword for a state named “Waiting”.
When you change a keyword in the file, the sync sends it as the state org-linear-keyword-states names for it, or else as the first state of the issue’s team that maps to that keyword, in Linear’s order of state types and positions.
The file’s own #+TODO line, written from org-linear-file-header when the file is created, must list every keyword these options can produce. The default header marks CANCELLED with @, so Org asks for a note when you cancel an issue; the sync posts that note as a comment, which records why the issue was dropped.
Working with issues
Edit issues with ordinary Org commands, then save the file; with org-linear-auto-sync-mode the save sends your changes (Syncing).
- To comment on an issue, add a note with
C-c C-z(org-add-note), or withM-x org-linear-comment, which also works from the list of your issues. Notes attached to state changes, such as the one the default header asks for when you cancel an issue, are posted too, prefixed with the new keyword. - To create an issue, add a level-2 heading with a TODO keyword under a project heading, with its description as the body. At the next sync it is created in Linear, in the project’s team, and gets its
LINEAR_ID. Until Linear confirms the issue, the heading holds the ID it was sent with asLINEAR_PENDING_UUID, so that an issue whose creation was not confirmed is found by the next sync rather than created twice. - To move an issue to another project, refile it under that project’s heading.
- To assign an issue, tag it with the person’s tag; an issue has one assignee, so a heading with two people’s tags sends no assignee and the sync log says why.
Deleting a heading does not delete the issue: it comes back at the next sync, with its description and comments. To drop an issue, cancel it.
M-x org-linear-browse browses the issue at point in Linear, in the file or the list of your issues.
M-x org-linear-visit visits org-linear-file.
Closed issues
The file holds every open issue of the teams and the issues closed in the last org-linear-closed-retention-days days. Older closed issues leave the file at the next sync, unless their heading holds unsent edits or notes; such a heading stays, keeps syncing its edits, and leaves once it has nothing unsent.
An issue that leaves moves to the archive file named by org-linear-archive-file, under a heading named after its project, with the properties Org’s archiving adds (ARCHIVE_TIME, ARCHIVE_OLPATH and the like). Your subheadings and notes go with it. By default the archive is linear-archive.org next to linear.org: a %s in the option stands for the name of org-linear-file without its directory and extension, and a relative name is relative to that file’s directory. The sync creates the archive with the file’s #+TODO and #+PRIORITIES lines and saves it before the file. While the archive’s buffer has unsaved edits, or the archive changed on disk, closed issues stay in the file and the sync log says why; they move at the first sync after you save or revert it. Neither the kill ring nor the clipboard is touched.
You need not wait for the retention period to end. M-x org-linear-archive-closed archives every closed issue in the file at once, and M-x org-linear-archive the one at point; in the file, under org-linear-auto-sync-mode, it replaces org-archive-subtree (C-c C-x C-s), which it calls for a heading that is not an issue. Either way the sync remembers the issue and leaves it out of the file while it stays closed; if someone reopens it in Linear, it comes back. Neither archives an open issue, which the next sync would bring back, nor one whose heading holds edits or notes not yet in Linear: sync first. They save the archive and the sync state, and the file too unless it had unsaved edits of yours.
Set org-linear-archive-file to nil to delete closed issues instead. A heading with subheadings of your own then stays in the file for good, and the sync log says so.
Syncing
M-x org-linear-sync saves the file, fetches the teams’ issues, merges them with the file and sends the file’s changes to Linear. It returns at once; the work runs in the background and a message reports when it starts and how it ended. A background sync reports only changes, so it stays out of your echo area. The first sync converts every description and comment, so it can take a few minutes; later ones convert only what changed.
The merge is three-way. For each field the sync compares the file and Linear with the issue as it was at the last sync, kept in org-linear-state-file:
- A field changed only in Linear is updated in the file.
- A field changed only in the file is sent to Linear.
- When both sides changed a field, Linear’s value wins, and yours is recorded in the sync log.
Before each write the sync checks when the issue was last edited. If someone changed it in Linear after the sync read it, the write is refused and the issue is merged again at the next sync, so a change made in Linear is never overwritten. A write Linear rejects, for instance for an invalid value, is logged and not retried until you change what was sent; the sync’s other writes still go through. A write that fails before Linear judges it, through a timeout, a server error or a rate limit, ends the sync instead, and the next sync tries it again.
A sync never changes the file while it has unsaved edits, and never applies changes if the file changed on disk since you visited it. The changes a sync makes clear the buffer’s undo history, since undoing them would send a reversal of your colleagues’ changes to Linear.
org-linear-auto-sync-mode is a global minor mode that syncs whenever you save or visit the file, and every org-linear-pull-interval seconds while the file has no unsaved edits. In the file’s buffer it turns on org-linear-file-mode, which runs a sync after each save.
M-x org-linear-show-log shows the sync log: conflicts, refused and rejected writes, issues not created, and errors. When a sync fails, its message points there.
The agenda
The file holds every open issue of the teams, so it is not the place to see what is yours. M-x org-linear-my-issues is: it opens an agenda of your open issues, with each one’s project (in a column as wide as the longest project name) and due month and day, grouped into the sections of org-linear-my-issues-sections. By default those are triage, in progress, waiting and to do; a section whose keyword the file does not define is left out, and backlog issues are not shown. A due date that is today or past is shown in org-imminent-deadline, and one within org-deadline-warning-days in org-upcoming-deadline. Each call rebuilds the list, even with org-agenda-sticky on. From the list, t changes an issue’s status and rebuilds the list, so the issue moves to its new section and point lands on the next one. . opens org-linear-menu (in place of the agenda’s jump to today, which a list of issues has no use for), TAB shows an issue’s heading in another window, C-x C-s saves the file, which sends your changes to Linear, and g refreshes the list. Issues scheduled for a later day stay out of it until that day; every other issue is shown, whatever your org-agenda-todo-ignore-* options say. Killing the buffer of the file also kills the list, whose lines would otherwise lead nowhere.
The list opens at once from the file and then syncs, unless the file has unsaved edits, and it is rebuilt with point on the same issue if the sync brings changes. Set org-linear-my-issues-sync to nil to turn this off.
Add org-linear-file to org-agenda-files to see your issues’ deadlines with the rest of your tasks.
org-linear-agenda-skip hides issues assigned to someone else. Set it as org-agenda-skip-function-global to apply it to every agenda view; issues with no assignee and issues assigned to you stay. So do issues with a tag in org-linear-agenda-keep-tags, nil by default, which suits a team that labels work it shares, for instance with joint. org-linear-hide-others-in-agenda, t by default, turns the filter off without removing it.
(setq org-agenda-skip-function-global #'org-linear-agenda-skip)
Transient menu
M-x org-linear-menu gathers the package’s commands in a transient menu. In the list of your issues, . opens it; elsewhere the package binds it to no key, so bind it yourself if you use it often.
| Key | Command | What it does |
|---|---|---|
m | org-linear-my-issues | Show your open issues |
v | org-linear-visit | Visit the file |
b | org-linear-browse | Browse the issue at point in Linear |
t | org-linear-my-issues-todo | Change the status of the issue at point |
c | org-linear-comment | Comment on the issue at point |
x | org-linear-archive | Archive the closed issue at point |
X | org-linear-archive-closed | Archive every closed issue now |
s | org-linear-sync | Sync now |
a | org-linear-auto-sync-mode | Turn syncing on save, visit and timer on/off |
l | org-linear-show-log | Show the sync log |
Every key is always shown; b, t and c act on the issue at point, in the file or in the list of your issues. The a key shows whether the mode is on, and the menu stays open after you press it.
User options
Teams and file
The user option org-linear-teams lists the keys of the teams whose issues the file mirrors, such as "ENG". It is nil by default, and a sync refuses to run until you set it.
org-linear-file is the mirrored Org file, by default linear.org in org-directory.
org-linear-file-header is the text a new file starts with. Its #+TODO line must cover the keywords of Workflow states and keywords, and its #+PRIORITIES line the cookies of org-linear-priorities. Notes must go into a drawer, or they would count as part of the description: the default header’s logdrawer startup option sees to that, and you may drop it if your org-log-into-drawer is already set. Change the header before the first sync; afterwards, edit the file’s header directly.
org-linear-state-file holds each issue as it was at the last sync, by default org-linear/state.eld under $XDG_STATE_HOME or ~/.local/state. Deleting it makes the next sync treat Linear’s values as the starting point. The sync replaces it whole on each write, so a write cut short, by a crash or a full disk, leaves the previous state intact.
org-linear-closed-retention-days, 7 by default, is how many days a closed issue stays in the file before it moves to the archive (Closed issues).
org-linear-pull-interval, 600 by default, is the number of seconds between background syncs in org-linear-auto-sync-mode.
The API key
The sync reads your Linear API key at the start of each sync and forgets it at the end.
org-linear-api-key-command, a command as a list of strings, prints the key. It runs asynchronously, so it suits a password manager’s command line. It takes precedence over the next option.
org-linear-api-key is the key itself or a function that returns it. When it is nil, the default, the key is read with auth-source for the host api.linear.app, for instance from a line in ~/.authinfo.gpg:
machine api.linear.app password lin_api_YOURKEY
If your command caches the key, set org-linear-api-key-forget-command to a command that drops the cached copy. When Linear rejects the key, the sync runs it and retries once, so a rotated key is read afresh.
The key reaches curl on its standard input, never on its command line, where other processes could see it.
Mappings
org-linear-type-keywords maps state types to keywords, org-linear-state-keywords maps individual state names to keywords, and org-linear-keyword-states chooses which state a keyword is sent as (Workflow states and keywords). For example, a team with a “Waiting” state and a “Rejected” state might use:
(setq org-linear-state-keywords '(("Waiting" . "WAITING"))
org-linear-keyword-states '(("CANCELLED" . "Rejected")))
org-linear-priorities maps Linear’s priorities, 1 (urgent) to 4 (low), to Org cookies; it is A to D by default. Linear’s “no priority” is no cookie. A cookie not listed is sent as the priority of the nearest listed cookie above it, so with numeric priorities you can map only some numbers:
(setq org-linear-priorities '((1 . "1") (2 . "2") (3 . "4") (4 . "6")))
org-linear-user-tags maps team members’ names to tags. A member not listed gets the first word of their name in lower case, or their full name, as in “peter_hartree”, when another member would get the same tag, so that a tag always stands for one person. Members of other teams have no tag; an issue assigned to one of them keeps that assignee, since the file cannot represent it.
Programs
org-linear-curl-program and org-linear-pandoc-program name the curl and pandoc executables. org-linear-timeout, 60 by default, is how many seconds a request to Linear may take before it is abandoned and the sync fails.
Limits
- Editing or deleting a note that is already in Linear is not sent; only new notes are.
- Comment replies are not shown.
- An issue that re-enters the file, for instance after being reopened, gets its comments again from Linear, but your old notes on it are not restored.
- Markdown headings in a description become bold lines, since an Org heading inside an issue’s body would split the issue.
- The sync reads up to 250 labels and 250 projects, and up to 100 comments per issue. A label beyond those 250 shows no tag, but editing the tags never removes it from the issue.