Migratly migrates Linear to Jira by reading both APIs, deriving the mapping between them, and showing you what it found before anything is written. For this pair that is 11 entity types and 45 field mappings, of which 28 carry across intact and 16 are flagged as lossy, partial, dropped, or needing a route other than the obvious one. 10 of them need a decision from you. The Linear side is read-only throughout — Migratly never writes to or deletes from your source.
How the structure changes
- Each Linear team becomes a Jira project — one project per team, no shared collections.
- Each Linear project becomes a Jira epic: a real issue with
issuetype=Epic, with child issues attached through the Epic Link field. - Workflow states become Jira statuses, cycles become sprints, and labels are matched or created — all before the first issue is written.
- Sub-tasks reference
fields.parent.keyand run a wave later than their parent. - Issue relations become Jira issue links in the last wave.
What maps to what
Entities are written in dependency waves. Everything at wave 0 exists before anything at wave 1 is created, which is what makes cross-references resolvable and the whole run resumable from a cursor.
| Linear | Jira | Wave | How it is handled | Fields |
|---|---|---|---|---|
| Team | Project | 0 | the collection that everything else lands in | 3 |
| User | User | 0 | accounts matched before anything is written | 4 |
| Cycle | Sprint | 1 | created up front, before the records that reference it | 5 |
| Label | Label | 1 | created up front, before the records that reference it | 1 |
| LinearProject | Epic | 1 | created up front, before the records that reference it | 3 |
| WorkflowState | Status | 1 | created up front, before the records that reference it | 2 |
| Issue | Issue | 2 | the records themselves | 12 |
| Sub-task | Issue | 3 | the records themselves | 7 |
| Attachment | Attachment | 4 | comments, attachments and body content | 1 |
| Comment | Comment | 4 | comments, attachments and body content | 4 |
| IssueRelation | IssueLink | 5 | links between records, resolved last | 3 |
Field-by-field coverage
Every mapping carries one of five labels. Full means it carries across intact. Partial means the value survives with a narrowed range. Lossy means something is lost. Workaround means there is no native slot and it is carried another way. Dropped means the target will not accept it. Anything short of full is shown to you before the run, not discovered afterwards.
Team → Project
| Linear field | Jira field | Coverage | Notes |
|---|---|---|---|
name |
name |
Full | Reuse/pin an existing Jira project is the supported path. Fresh creation via REST needs admin + projectTypeKey + leadAccountId. |
key |
key |
Partial | Linear team keys may exceed Jira's 10-char limit and must be uppercase A-Z/0-9. alnum + upper + hard-clip to 10 produces a valid candidate; uniqueness is a human decision. Asks you first |
description |
description |
Full |
User → User
| Linear field | Jira field | Coverage | Notes |
|---|---|---|---|
id |
Source Linear ID |
Lossy | Jira users are keyed by accountId; the Linear UUID is kept only for audit. user_map is built source_id -> target accountId via email join. |
email |
emailAddress |
Full | Email is the join key. The Linear member must already exist in the target Jira site with the same email. |
name |
displayName |
Full | |
active |
active |
Lossy | Jira user active status is read-only via REST; a Jira admin must provision/deactivate accounts. |
Cycle → Sprint
| Linear field | Jira field | Coverage | Notes |
|---|---|---|---|
number |
name |
Full | Linear cycle name -> Jira sprint name (required by Jira). source_field is 'number' (always present) so the transform always runs; it returns the cycle name, or falls back to 'Cycle <number>' for Linear's auto-cycles that have no name. |
startsAt |
startDate |
Full | |
endsAt |
endDate |
Full | |
team.key |
originBoardId |
Full | Sprint must be created on the project's Scrum board. The board id was resolved into id_map (Board:{collection_key}) by the sprint pre_populate; this looks it up. No value transform needed — collection_key drives the key. |
|
state |
Partial | Jira creates sprints in 'future' state. A generic post-create follow-up (declared in the connector spec) activates the sprint whose cycle is currently in progress: now within startsAt..endsAt and not completed. Jira allows one active sprint per board, so this cond yields 'active' for at most one; everything else stays 'future'. The follow-up request itself is spec-driven; only the value is computed here. |
Label → Label
| Linear field | Jira field | Coverage | Notes |
|---|---|---|---|
name |
name |
Full | Jira labels are free-form strings set directly on issues; no creation API. Informational only. Labels are applied at issue level from the issue's embedded labels. |
LinearProject → Epic
| Linear field | Jira field | Coverage | Notes |
|---|---|---|---|
name |
fields.summary |
Full | Linear Project -> Jira Epic (an issue with issuetype Epic). Created at level 1 so issues can link to it via fields.parent. Requires the target project's scheme to include the 'Epic' type; otherwise creation is skipped (non-fatal). |
description |
fields.description |
Partial | Linear project description (Markdown) -> Jira ADF. |
state |
transition |
Workaround | Linear project state -> Jira epic status via a follow-up transition (best-effort; only applies if a same-named status exists in the epic workflow). |
WorkflowState → Status
| Linear field | Jira field | Coverage | Notes |
|---|---|---|---|
name |
name |
Workaround | Jira statuses are read-only via REST. This entity has no create route; it pre-populates the name->status id map (SchemaByName:status) from the target project so the Issue status transition can resolve. Unmatched states keep the issue in its default status. Asks you first |
type |
statusCategory |
Full | Linear state type suggests the Jira statusCategory for the human-decision UI. |
Issue → Issue
| Linear field | Jira field | Coverage | Notes |
|---|---|---|---|
title |
fields.summary |
Full | |
description |
fields.description |
Partial | Linear Markdown -> Jira ADF via the markdown_to_adf formatter. Headings/lists/links survive; Linear inline embeds and reactions are lost. |
fields.issuetype.name |
fields.issuetype.name |
Full | Jira requires an issuetype on create. Default Linear issues to 'Task'; the human-decision UI can override. Asks you first |
priority |
fields.priority.name |
Full | Linear priority int (0=None 1=Urgent 2=High 3=Medium 4=Low) -> Jira priority name. None -> omitted. |
assignee.id |
fields.assignee.accountId |
Full | Asks you first |
labels.nodes |
fields.labels |
Full | Linear labels embedded on the issue -> Jira free-form label strings. Spaces are collapsed to hyphens (Jira rejects spaces in labels). |
state.name |
transition |
Full | Jira does not accept status on create. property_type 'transition' keeps this OUT of the create body; after creation the executor runs a follow-up POST /transitions that matches the Linear state name to the target Jira status. Supply a value_map on the route if the target workflow uses different status names. Asks you first |
dueDate |
fields.duedate |
Full | |
estimate |
fields.customfield_10016 |
Partial | Jira story points use a per-instance custom field (customfield_10016 by convention). Asks you first |
cycle.name |
fields.customfield_10020 |
Partial | Linear cycle -> Jira sprint membership via the Sprint custom field (customfield_10020 by convention). Resolves the sprint id created from the cycle. Asks you first |
project.id |
fields.parent.key |
Full | Linear Project -> Jira Epic link. Modern Jira links a standard issue to its epic via fields.parent (the epic created from the Linear Project at level 1). Resolves the epic key from Schema:epic; omitted when the issue has no project or the epic wasn't created (e.g. target lacks the Epic type). |
createdAt |
fields.created |
Dropped | Jira's created timestamp is read-only and set to now on creation. |
Sub-task → Issue
| Linear field | Jira field | Coverage | Notes |
|---|---|---|---|
title |
fields.summary |
Full | |
description |
fields.description |
Partial | Linear Markdown -> Jira ADF. |
parent.id |
fields.parent.key |
Full | Linear sub-issue parent -> Jira parent key. The parent issue is created first (dependency ordering). |
fields.issuetype.name |
fields.issuetype.name |
Full | Sub-tasks require a sub-task-type issue type in Jira. Asks you first |
assignee.id |
fields.assignee.accountId |
Full | Asks you first |
priority |
fields.priority.name |
Full | |
state.name |
transition |
Full | Status applied via follow-up POST /transitions after create (see Issue); kept out of the create body. Asks you first |
Attachment → Attachment
| Linear field | Jira field | Coverage | Notes |
|---|---|---|---|
url |
file |
Full | Linear file attachment -> Jira attachment. The executor downloads the asset from Linear (with auth) and multipart-uploads it to the issue (content.attachment.create.multipart). url/filename/content-type fields are spec-declared. |
Comment → Comment
| Linear field | Jira field | Coverage | Notes |
|---|---|---|---|
issueId |
issueId |
Internal | The executor resolves the parent issue's Jira key from id_map and puts it in the URL path; no body field needed. |
body |
body |
Partial | Linear Markdown -> Jira ADF. Jira sets comment author=API user and created=now; original author/timestamp are not preserved in Phase 2 (Phase 3 will prepend them). |
user |
Original Author |
Dropped | Jira sets comment author = the API user; preserving the original author requires prepending to the ADF body (Phase 3). |
createdAt |
Original Timestamp |
Dropped | Jira sets comment created = now; original timestamp preservation requires prepending to the body (Phase 3). |
IssueRelation → IssueLink
| Linear field | Jira field | Coverage | Notes |
|---|---|---|---|
issueId |
inwardIssue.key |
Full | |
relatedIssueId |
outwardIssue.key |
Full | |
type |
type.name |
Partial | Linear relation types (blocks/duplicate/related) -> Jira link types. Custom Jira link types may need pre-creation. |
What does not carry across cleanly
These are the 16 mappings where Jira cannot hold what Linear holds. Each one appears in your coverage report with the same reason, and with the alternative routes available.
idLossy
Jira users are keyed by accountId; the Linear UUID is kept only for audit. user_map is built source_id -> target accountId via email join.activeLossy
Jira user active status is read-only via REST; a Jira admin must provision/deactivate accounts.keyPartial
Linear team keys may exceed Jira's 10-char limit and must be uppercase A-Z/0-9. alnum + upper + hard-clip to 10 produces a valid candidate; uniqueness is a human decision.nameWorkaround
Jira statuses are read-only via REST. This entity has no create route; it pre-populates the name->status id map (SchemaByName:status) from the target project so the Issue status transition can resolve. Unmatched states keep the issue in its default status.Partial
Jira creates sprints in 'future' state. A generic post-create follow-up (declared in the connector spec) activates the sprint whose cycle is currently in progress: now within startsAt..endsAt and not completed. Jira allows one active sprint per board, so this cond yields 'active' for at most one; everything else stays 'future'. The follow-up request itself is spec-driven; only the value is computed here.descriptionPartial
Linear project description (Markdown) -> Jira ADF.stateWorkaround
Linear project state -> Jira epic status via a follow-up transition (best-effort; only applies if a same-named status exists in the epic workflow).descriptionPartial
Linear Markdown -> Jira ADF via the markdown_to_adf formatter. Headings/lists/links survive; Linear inline embeds and reactions are lost.estimatePartial
Jira story points use a per-instance custom field (customfield_10016 by convention).cycle.namePartial
Linear cycle -> Jira sprint membership via the Sprint custom field (customfield_10020 by convention). Resolves the sprint id created from the cycle.createdAtDropped
Jira's created timestamp is read-only and set to now on creation.descriptionPartial
Linear Markdown -> Jira ADF.bodyPartial
Linear Markdown -> Jira ADF. Jira sets comment author=API user and created=now; original author/timestamp are not preserved in Phase 2 (Phase 3 will prepend them).userDropped
Jira sets comment author = the API user; preserving the original author requires prepending to the ADF body (Phase 3).createdAtDropped
Jira sets comment created = now; original timestamp preservation requires prepending to the body (Phase 3).typePartial
Linear relation types (blocks/duplicate/related) -> Jira link types. Custom Jira link types may need pre-creation.
Decisions you will be asked to confirm
Migratly stops and asks rather than guessing when the right answer depends on your instance.
key→key
Confirm a unique, uppercase Jira project key (A-Z/0-9, <=10 chars) for each Linear team.name→name
Map each Linear WorkflowState to a Jira status that exists in the target project's workflow.fields.issuetype.name→fields.issuetype.name
Confirm the default Jira issue type for migrated Linear issues (must exist in the target project).assignee.id→fields.assignee.accountId
Confirmed during planning.state.name→transition
Jira does not accept status on create. property_type 'transition' keeps this OUT of the create body; after creation the executor runs a follow-up POST /transitions that matches the Linear state name to the target Jira status. Supply a value_map on the route if the target workflow uses different status names.estimate→fields.customfield_10016
Confirm the Jira story-points custom field ID for your instance.cycle.name→fields.customfield_10020
Confirm the Jira Sprint custom field ID for your instance.fields.issuetype.name→fields.issuetype.name
Confirm the Jira sub-task issue type name for the target project.assignee.id→fields.assignee.accountId
Confirmed during planning.state.name→transition
Status applied via follow-up POST /transitions after create (see Issue); kept out of the create body.
How a Linear to Jira migration runs
- Connect both systems. Linear is connected read-only. Migratly never writes to, archives or deletes anything on the source side.
- Schema discovery. Both APIs are read live, including your custom fields, so the mapping reflects your instance rather than a generic Linear instance.
- Coverage report. All 45 mappings are presented with their coverage. You confirm, reroute or exclude before anything is written.
- Execution in 6 waves. Writes are idempotent and cursor-tracked, so an interrupted run resumes instead of restarting or duplicating.
- Verification. The result is checked back against the source, and every action is in an append-only audit log.
Questions
Can Migratly migrate Linear to Jira?
Yes. Linear to Jira is a supported pair with a mapping covering 11 entity types and 45 fields. You connect both systems, Migratly reads the live schemas on each side, and it produces a coverage report you approve before any data moves.
What is lost when migrating from Linear to Jira?
16 of the 45 field mappings for this pair are flagged rather than clean. For example, `id` is marked lossy: Jira users are keyed by accountId; the Linear UUID is kept only for audit. user_map is built source_id -> target accountId via email join. Every flagged mapping appears in the coverage report before the run, with its reason and the routes still open to it. Nothing is dropped silently.
How long does a Linear to Jira migration take?
It is bounded by the two APIs' rate limits rather than by Migratly. This pair runs in 6 dependency waves, starting with account matching and ending with cross-references, and every wave is resumable — a run interrupted halfway continues from its cursor rather than starting over or duplicating what it already wrote.
Is my Linear data modified during the migration?
No. The source is read-only for the entire run. Migratly never writes to, archives, or deletes anything in Linear, and every action taken against Jira is written to an append-only audit log.
Can you migrate Linear issues into an existing Jira project?
Yes. Existing statuses, labels and sprints are matched by name before anything is created, so an import into a live project reuses what is already there instead of duplicating it.
How are Linear cycles represented in Jira?
As sprints on the target board. Jira does not expose sprint creation as a plain REST create, so this mapping runs through the Agile API as a declared workaround rather than a direct field write.
Does Linear to Jira preserve comment authors and timestamps?
Comment bodies and original timestamps carry across. Authorship depends on whether the Linear user has a matching Jira account — matched by email during the user wave. Unmatched authors are named in the comment body instead of being dropped.