Four Node.js tools for the part of a Jira migration no assistant handles — workflow rules, app workflow extensions, automation rules, and the JQL inside saved filters.
Issues migrate. The logic around them does not — and unlike missing data, broken logic does not look broken. A validator that no longer validates is invisible until someone submits bad data.
A Data Center validator from a workflow-extensions app is a Java class name. Cloud has no Java. Unless the rule is re-expressed as a native Cloud rule or the Cloud app's equivalent, it silently does nothing at all.
Statuses, issue types, screens, events, roles, groups, priorities, resolutions, link types, security levels, custom fields. A workflow copied verbatim between instances is a workflow full of dangling references.
Saved filters are copied body-for-body, so their JQL still names DC filter ids, DC field ids and functions that do not exist on Cloud. The filter loads without error and returns plausible, wrong results — for months.
Four tools, run in a specific order: filters first because everything downstream is easier to verify with working JQL, then workflows, then the app rules inside them, then automation last because it depends on all three.
Rewrites DC filter ids, cf[N] and customfield_N references, Assets fields and ORDER BY clauses; strips functions that do not exist on Cloud; validates project names; detects renamed priorities; and repairs share permissions with verified, retried POSTs.
Between instances or in place. Cross-instance remapping of every entity id. Cloud-to-cloud mode reads the new-format workflow endpoint directly, skipping the conversion step that is the biggest unknown elsewhere. Can also emit a ScriptRunner scaffold for handover.
Reads OSWorkflow XML exported from DC, identifies app rules by Java class name, and translates each into a native Cloud rule or the Cloud app equivalent — updating the same-named workflow in place. Every non-matching rule is left completely untouched.
Export, generate id mappings, ensure the rule actor actually has the agent role the automation engine demands, then import with an explicit actor override. Plus a reconcile-in-place mode that repairs and enables existing rules without importing duplicates.
The same operating model throughout, deliberately — so that knowing one tool means knowing all of them.
Every workflow write is checked against Jira's own /workflows/update/validation endpoint first, and validation errors are surfaced for review rather than auto-fixed — an auto-fixed validation error is a change nobody decided on. Automation imports run PLAN=1 first.
Every apply re-fetches the live target and fingerprints its existing rules before mutating. Anything already present is classified as such and never appended. A plan built last week cannot double-apply today.
These tools add and correct rules. They do not delete rules they did not recognise. The one exception is explicit and reported: rules whose custom-field references do not exist on Cloud are removed, because they are already broken.
Runs emit a manual_review_<timestamp>.xlsx with one tab per category needing human attention. It is a checklist, not a sign-off — a translated rule still deserves an expert eye in the Cloud UI.
git clone https://github.com/leanzero-srl/leanzero-jira-workflow-automation-toolkit.git cd leanzero-jira-workflow-automation-toolkit/clone_workflow_rules npm install cp .env.example .env # Take the backup FIRST — there is no rollback. node main/clone_workflow_rules.js --collect # Cloud to cloud: read the new-format endpoint, skip the conversion table. node main/clone_workflow_rules.js --cloud-to-cloud --validate-only
Every question this repository raises, answered in order: what each tool reads and the order to run them in, how workflows and app rules are translated, the automation pipeline that actually works, and why a saved filter can return the wrong answer without ever erroring. 19 sections.
Node 18 or newer and a Cloud API token. Two of the four tools also need Data Center — one over REST, one from exported workflow XML, because DC's REST API does not expose workflow rule bodies at all.
| Tool | Source | Target |
|---|---|---|
clone_workflow_rules | Jira Cloud REST (source instance) | Jira Cloud REST (target, or the same instance in place) |
migrate_jsu_rules | OSWorkflow XML exported from DC | Jira Cloud REST |
automation_rules_migrator | Jira Cloud automation REST (source site) | Jira Cloud automation REST (target site) |
rewrite_filter_refs | Jira Cloud filters, plus DC REST to resolve filter ids to names | Jira Cloud filters |
migrate_jsu_rules reads exported OSWorkflow XML rather than calling DC — it is not a design preference, it is the only way to see the rules. Export the workflows from the DC admin UI first.migrate_jsu_rules reuses clone_workflow_rules's REST client and field mapper by relative path. Keep the two directories as siblings; do not move either one.Filters, then workflows, then the app rules inside those workflows, then automation last — because automation depends on the workflows and fields already existing.
Every workflow write is validated against Jira's own validation endpoint before it is sent, and every apply re-fingerprints the live target first so a stale plan cannot double-apply.
/workflows/update/validation before any mutation. Errors are surfaced for operator review, never auto-fixed — an auto-fixed validation error is a change nobody decided on.migrate_jsu_rules removes pre-existing rules whose custom field references do not exist on Cloud, because those are already broken.clone_workflow_rules --collect for workflows, export_all.js for automation — and keep it off the machine running the migration.Stated up front, because discovering a limit mid-cutover is the expensive way to find it.
The toolkit is free and Apache-2.0. If you would rather not discover the actor-permission trap on cutover weekend, that is the kind of thing we do for a living.