Migrate issues (including fields like summary, description, assignee, labels, etc.) from one Jira project to another using Node.js and the Jira REST API.
const client = require('./helpers/jiraClient');
const { jql, sourceProjectKey, destinationProjectKey } = require('./config');
async function fetchIssues() {
const res = await client.post('/search', {
jql,
maxResults: 50,
fields: ['summary', 'description', 'issuetype', 'assignee', 'labels'],
});
return res.data.issues;
}
async function createIssue(issue) {
const newIssue = {
fields: {
project: { key: destinationProjectKey },
summary: issue.fields.summary,
description: issue.fields.description,
issuetype: { id: issue.fields.issuetype.id },
labels: issue.fields.labels,
assignee: issue.fields.assignee ? { id: issue.fields.assignee.accountId } : null,
},
};
const res = await client.post('/issue', newIssue);
console.log(`Created issue: ${res.data.key}`);
return res.data.key;
}
(async () => {
try {
const issues = await fetchIssues();
for (const issue of issues) {
await createIssue(issue);
}
console.log("✅ Migration complete.");
} catch (err) {
console.error("❌ Error during migration:", err.response?.data || err.message);
}
})();
In Jira Cloud, the `/rest/api/3/search` and `/rest/api/3/issue` endpoints you’re calling will work for this type of migration, but you’ll need to ensure the payloads match the [Create issue](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-issues/#api-rest-api-3-issue-post) schema exactly. Two common pitfalls are using `assignee: null` (which must be omitted entirely if no assignee is set) and passing `issuetype.id` from the source project without confirming it exists in the destination project. Issue type IDs are project-specific, so you should first query `/rest/api/3/issuetype` for the destination project and map the correct ID before creating each issue.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.