Migratly migrates Jira to Linear by reading both APIs, deriving the mapping between them, and showing you what it found before anything is written. For this pair that is 13 entity types and 113 field mappings, of which 54 carry across intact and 59 are flagged as lossy, partial, dropped, or needing a route other than the obvious one. 5 of them need a decision from you. The Jira side is read-only throughout — Migratly never writes to or deletes from your source.
How the structure changes
- Each Jira project becomes a Linear team. Linear has no boards or folders — issues live directly in a team.
- Each Jira epic becomes a Linear project, which is how Linear groups issues inside a team.
- Jira status values become Linear workflow states, and sprints become cycles — both created before any issue is written.
- Sub-tasks arrive as ordinary Linear issues with
parentId set, one wave after their parent exists.
- Issue links become issue relations in the final wave, once every issue on both ends has been created.
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.
| Jira | Linear | Wave | How it is handled | Fields |
| Project |
Team |
0 |
the collection that everything else lands in |
6 |
| User |
User |
0 |
accounts matched before anything is written |
4 |
| Component |
Label |
1 |
created up front, before the records that reference it |
1 |
| Epic |
Project |
1 |
created up front, before the records that reference it |
15 |
| IssueType |
Label |
1 |
created up front, before the records that reference it |
1 |
| Label |
Label |
1 |
created up front, before the records that reference it |
2 |
| Sprint |
Cycle |
1 |
created up front, before the records that reference it |
6 |
| Status |
WorkflowState |
1 |
created up front, before the records that reference it |
2 |
| Issue |
Issue |
2 |
the records themselves |
36 |
| Sub-task |
Issue |
3 |
the records themselves |
25 |
| Attachment |
Attachment |
4 |
comments, attachments and body content |
6 |
| Comment |
Comment |
4 |
comments, attachments and body content |
5 |
| IssueLink |
IssueRelation |
5 |
links between records, resolved last |
4 |
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.
Project → Team
| Jira field | Linear field | Coverage | Notes |
id |
Source ID |
Dropped |
Internal Jira ID, not directly mapped to Linear Team. |
key |
Key |
Lossy |
Linear Team key has max 5 characters, must be uppercase. Asks you first |
name |
Name |
Full |
|
description |
Description |
Lossy |
Jira ADF rich text is converted to Markdown. |
lead |
Lead |
Lossy |
Linear Team does not have a dedicated 'Lead' field. |
projectTypeKey |
Project Type |
Lossy |
Linear Team does not have a dedicated 'Project Type' field. |
User → User
| Jira field | Linear field | Coverage | Notes |
accountId |
Source Account ID |
Lossy |
Linear users are identified by email/UUID, not source account ID. |
displayName |
Display Name |
Full |
|
emailAddress |
Email |
Full |
|
active |
Active Status |
Lossy |
Linear API does not support setting user active status. |
Component → Label
| Jira field | Linear field | Coverage | Notes |
name |
Name |
Full |
Jira components have no native equivalent in Linear; create one Linear label per component so taxonomy survives the migration. |
Epic → Project
| Jira field | Linear field | Coverage | Notes |
id |
Source ID |
Dropped |
Internal Jira ID. |
key |
Source Key |
Dropped |
Jira Epic Key used for internal lookup. |
summary |
Name |
Full |
|
description |
Description |
Lossy |
Jira ADF converted to Markdown. |
status.name |
State |
Full |
|
priority.name |
Priority |
Lossy |
Linear Project has no dedicated 'Priority' field. |
assignee.accountId |
Lead ID |
Full |
Asks you first |
reporter |
Reporter |
Lossy |
Reporter prepended to description. |
labels |
Labels |
Lossy |
Labels appended to description. |
duedate |
End Date |
Full |
|
customfield_10015 |
Start Date |
Full |
Assuming customfield_10015 is Epic Start Date. |
created |
Created At |
Dropped |
createdAt not settable for Projects. |
updated |
Updated At |
Lossy |
updatedAt not settable. |
project.key |
Team ID |
Dropped |
Used for Team lookup. |
comment |
Comments |
Dropped |
Comments are on Issues, not Projects in Linear. |
IssueType → Label
| Jira field | Linear field | Coverage | Notes |
name |
Name |
Full |
Linear has no first-class issue-type field. Materialise each Jira issue type (Story, Bug, Task, Epic, Sub-task) as a Linear label so the Issue's issuetype.name → labelIds lookup resolves. |
Label → Label
| Jira field | Linear field | Coverage | Notes |
name |
Name |
Full |
|
color |
Color |
Full |
Jira labels have no explicit colors, Linear assigns default. |
Sprint → Cycle
| Jira field | Linear field | Coverage | Notes |
id |
Source ID |
Dropped |
Internal Jira ID. |
name |
Name |
Full |
|
state |
State |
Lossy |
Linear Cycles derive state from dates, no explicit state field. |
startDate |
Starts At |
Full |
|
endDate |
Ends At |
Full |
If endDate is missing, agent should handle via coalesce or default. |
goal |
no equivalent |
Dropped |
Linear Cycles do not have a 'goal' field. Dropped — appending it to name causes max-length issues. |
Status → WorkflowState
| Jira field | Linear field | Coverage | Notes |
name |
Name |
Full |
|
category |
Type |
Full |
|
Issue → Issue
| Jira field | Linear field | Coverage | Notes |
id |
Source ID |
Dropped |
Internal Jira ID, not directly mapped. |
key |
Jira Key |
Full |
Jira Key is prepended to the description for traceability. |
summary |
Title |
Full |
|
description |
Description |
Lossy |
Jira ADF rich text is converted to Markdown. |
issuetype.name |
Labels |
Full |
Jira Issue Type is mapped to a Linear Label. |
status.name |
Workflow State ID |
Full |
|
priority.name |
Priority |
Full |
|
assignee.accountId |
Assignee ID |
Full |
Asks you first |
reporter |
Reporter |
Lossy |
Linear Issue does not have a dedicated 'Reporter' field. Reporter's display name is prepended to the description. |
labels |
Labels |
Full |
|
created |
Created At |
Full |
|
updated |
Updated At |
Lossy |
Linear API does not support setting 'updatedAt'. |
duedate |
Due Date |
Full |
|
project.key |
Team ID |
Dropped |
Used for internal lookup to determine the parent Linear Team. |
parent.key |
Parent ID |
Dropped |
Handled in the Sub-task entity mapping. |
customfield_10016 |
Estimate |
Full |
Assuming customfield_10016 is Story Points. |
customfield_10020 |
Cycle ID |
Full |
Assuming customfield_10020 is Sprint. Maps to Linear Cycle. |
components |
Labels |
Full |
Jira Components are mapped to Linear Labels. |
fixVersions |
Labels |
Full |
Jira Fix Versions are mapped to Linear Labels. |
resolution.name |
Resolution |
Lossy |
Linear Issue does not have a dedicated 'Resolution' field. |
timeoriginalestimate |
Original Time Estimate |
Lossy |
Linear does not have 'Original Time Estimate'. Appended to description. |
timespent |
Time Spent |
Lossy |
Linear does not have 'Time Spent'. Appended to description. |
votes.votes |
Votes |
Lossy |
Linear does not have a 'Votes' field. |
issuelinks |
Issue Links |
Dropped |
Issue links handled by IssueLink entity. |
customfield_10014 |
Project ID |
Full |
Links issue to its parent Epic (Linear Project). |
versions |
no equivalent |
Dropped |
Linear has no Affects Versions concept; deliberately dropped. |
resolutiondate |
no equivalent |
Dropped |
Linear has no resolution date field; completion date is implicit in state transitions. |
watches.watchCount |
no equivalent |
Dropped |
Watch counts not meaningful post-migration. |
workratio |
no equivalent |
Dropped |
Derived field; not meaningful post-migration. |
progress |
no equivalent |
Dropped |
Time-tracking aggregate; covered elsewhere if needed. |
aggregateprogress |
no equivalent |
Dropped |
Sub-issue roll-up; recomputed in target. |
lastViewed |
no equivalent |
Dropped |
Per-user metadata; not migrated. |
statuscategorychangedate |
no equivalent |
Dropped |
Derived field; not meaningful post-migration. |
statusCategory |
no equivalent |
Dropped |
Derived from status.name; already covered by status mapping. |
creator.accountId |
Original Creator |
Workaround |
Linear cannot set issue creator; stored as text description prefix or label. |
reporter.accountId |
Original Reporter |
Workaround |
Linear cannot set issue reporter; stored as text description prefix. |
Sub-task → Issue
| Jira field | Linear field | Coverage | Notes |
id |
Source ID |
Dropped |
Internal Jira ID. |
key |
Jira Key |
Full |
Jira Key prepended for traceability. |
summary |
Title |
Full |
|
description |
Description |
Lossy |
Jira ADF converted to Markdown. |
issuetype.name |
Labels |
Full |
Issue Type mapped to Linear Label. |
status.name |
Workflow State ID |
Full |
|
priority.name |
Priority |
Full |
|
assignee.accountId |
Assignee ID |
Full |
Asks you first |
reporter |
Reporter |
Lossy |
Reporter prepended to description. |
labels |
Labels |
Full |
|
created |
Created At |
Full |
|
updated |
Updated At |
Lossy |
Linear API does not support setting 'updatedAt'. |
duedate |
Due Date |
Full |
|
project.key |
Team ID |
Dropped |
Used for internal Team lookup. |
parent.key |
Parent ID |
Full |
|
customfield_10016 |
Estimate |
Full |
Assuming customfield_10016 is Story Points. |
customfield_10020 |
Cycle ID |
Full |
Sprint maps to Linear Cycle. |
components |
Labels |
Full |
Components mapped to Labels. |
fixVersions |
Labels |
Full |
Fix Versions mapped to Labels. |
resolution.name |
Resolution |
Lossy |
Resolution appended to description. |
timeoriginalestimate |
Original Time Estimate |
Lossy |
Time estimate appended to description. |
timespent |
Time Spent |
Lossy |
Time spent appended to description. |
votes.votes |
Votes |
Lossy |
Votes appended to description. |
issuelinks |
Issue Links |
Dropped |
Issue links handled by IssueLink entity. |
customfield_10014 |
Project ID |
Full |
Links sub-task to parent Epic (Linear Project). |
Attachment → Attachment
| Jira field | Linear field | Coverage | Notes |
id |
Source ID |
Dropped |
Internal Jira ID. |
issueId |
Issue ID |
Full |
|
filename |
Title |
Full |
|
content |
URL |
Full |
Jira attachment URLs may require auth. Asks you first |
mimeType |
Subtitle |
Full |
|
size |
Size |
Lossy |
Linear Attachment does not have a 'size' field. |
Comment → Comment
| Jira field | Linear field | Coverage | Notes |
id |
Source ID |
Dropped |
Internal Jira ID. |
issueId |
Issue ID |
Full |
|
body |
Body |
Lossy |
ADF converted to Markdown. Author prepended. |
author |
Author |
Lossy |
Author display name prepended to comment body. |
created |
Created At |
Full |
|
IssueLink → IssueRelation
| Jira field | Linear field | Coverage | Notes |
id |
Source ID |
Dropped |
Internal Jira ID. |
type.name |
Type |
Lossy |
Jira link types mapped to limited Linear relation types. |
inwardIssue.key |
Issue ID |
Full |
|
outwardIssue.key |
Related Issue ID |
Full |
|
What does not carry across cleanly
These are the 59 mappings where Linear cannot hold what Jira holds. Each one appears in your coverage report with the same reason, and with the alternative routes available.
accountId Lossy
Linear users are identified by email/UUID, not source account ID.
active Lossy
Linear API does not support setting user active status.
id Dropped
Internal Jira ID, not directly mapped to Linear Team.
key Lossy
Linear Team key has max 5 characters, must be uppercase.
description Lossy
Jira ADF rich text is converted to Markdown.
lead Lossy
Linear Team does not have a dedicated 'Lead' field.
projectTypeKey Lossy
Linear Team does not have a dedicated 'Project Type' field.
id Dropped
Internal Jira ID, not directly mapped.
description Lossy
Jira ADF rich text is converted to Markdown.
reporter Lossy
Linear Issue does not have a dedicated 'Reporter' field. Reporter's display name is prepended to the description.
updated Lossy
Linear API does not support setting 'updatedAt'.
project.key Dropped
Used for internal lookup to determine the parent Linear Team.
parent.key Dropped
Handled in the Sub-task entity mapping.
resolution.name Lossy
Linear Issue does not have a dedicated 'Resolution' field.
timeoriginalestimate Lossy
Linear does not have 'Original Time Estimate'. Appended to description.
timespent Lossy
Linear does not have 'Time Spent'. Appended to description.
votes.votes Lossy
Linear does not have a 'Votes' field.
issuelinks Dropped
Issue links handled by IssueLink entity.
versions Dropped
Linear has no Affects Versions concept; deliberately dropped.
resolutiondate Dropped
Linear has no resolution date field; completion date is implicit in state transitions.
watches.watchCount Dropped
Watch counts not meaningful post-migration.
workratio Dropped
Derived field; not meaningful post-migration.
progress Dropped
Time-tracking aggregate; covered elsewhere if needed.
aggregateprogress Dropped
Sub-issue roll-up; recomputed in target.
lastViewed Dropped
Per-user metadata; not migrated.
statuscategorychangedate Dropped
Derived field; not meaningful post-migration.
statusCategory Dropped
Derived from status.name; already covered by status mapping.
creator.accountId Workaround
Linear cannot set issue creator; stored as text description prefix or label.
reporter.accountId Workaround
Linear cannot set issue reporter; stored as text description prefix.
id Dropped
Internal Jira ID.
description Lossy
Jira ADF converted to Markdown.
reporter Lossy
Reporter prepended to description.
updated Lossy
Linear API does not support setting 'updatedAt'.
project.key Dropped
Used for internal Team lookup.
resolution.name Lossy
Resolution appended to description.
timeoriginalestimate Lossy
Time estimate appended to description.
timespent Lossy
Time spent appended to description.
votes.votes Lossy
Votes appended to description.
issuelinks Dropped
Issue links handled by IssueLink entity.
id Dropped
Internal Jira ID.
state Lossy
Linear Cycles derive state from dates, no explicit state field.
goal Dropped
Linear Cycles do not have a 'goal' field. Dropped — appending it to name causes max-length issues.
id Dropped
Internal Jira ID.
body Lossy
ADF converted to Markdown. Author prepended.
author Lossy
Author display name prepended to comment body.
id Dropped
Internal Jira ID.
size Lossy
Linear Attachment does not have a 'size' field.
id Dropped
Internal Jira ID.
type.name Lossy
Jira link types mapped to limited Linear relation types.
id Dropped
Internal Jira ID.
key Dropped
Jira Epic Key used for internal lookup.
description Lossy
Jira ADF converted to Markdown.
priority.name Lossy
Linear Project has no dedicated 'Priority' field.
reporter Lossy
Reporter prepended to description.
labels Lossy
Labels appended to description.
created Dropped
createdAt not settable for Projects.
updated Lossy
updatedAt not settable.
project.key Dropped
Used for Team lookup.
comment Dropped
Comments are on Issues, not Projects in Linear.
Decisions you will be asked to confirm
Migratly stops and asks rather than guessing when the right answer depends on your instance.
key → Key
Linear Team key has max 5 characters, must be uppercase.
assignee.accountId → Assignee ID
Confirmed during planning.
assignee.accountId → Assignee ID
Confirmed during planning.
content → URL
Jira attachment URLs may require auth.
assignee.accountId → Lead ID
Confirmed during planning.
How a Jira to Linear migration runs
- Connect both systems. Jira 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 Jira instance.
- Coverage report. All 113 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 Jira to Linear?
Yes. Jira to Linear is a supported pair with a mapping covering 13 entity types and 113 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 Jira to Linear?
59 of the 113 field mappings for this pair are flagged rather than clean. For example, `accountId` is marked lossy: Linear users are identified by email/UUID, not source account ID. 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 Jira to Linear 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 Jira data modified during the migration?
No. The source is read-only for the entire run. Migratly never writes to, archives, or deletes anything in Jira, and every action taken against Linear is written to an append-only audit log.
Does a Jira to Linear migration keep sprints?
Yes. Jira sprints map to Linear cycles. Cycles require a start and end date, so any sprint missing one is flagged before the run rather than dropped during it.
What happens to Jira epics in Linear?
A Jira epic becomes a Linear project inside the team that the Jira project mapped to. Issues that belonged to the epic get that project id. Issues with no epic keep their team but carry no project.
Are Jira custom fields lost when moving to Linear?
Linear has a much smaller fixed field set than Jira, so most custom fields have no native slot. Migratly reports each one before the run and offers the remaining routes — description append, label, or a comment — rather than discarding it silently.
Other migration pairs