Interested in a ServiceNow event built for developers? Registration for now[dev]26 is officially open!

SaiShekhar07864
ServiceNow Employee

 

UPDATE - Dec 12th, 2025!!!

We are excited to announce that with 2025 December store release, integration EAP with Jira is available OOTB. To make the integration smoother, the release includes guided setup walk you through the necessary steps required. Please learn more about the integration from the docs

 

Additionally, if you want to see what's new in 2025 December store release, check out the below links:

 

VinithaChukkala_0-1765807842634.png

 

VinithaChukkala_1-1765807879437.png

 

VinithaChukkala_2-1765807924679.png


Overview - Integrating Enterprise Agile Planning (EAP) with Jira or Azure DevOps (ADO)

This guide explains how to integrate Enterprise Agile Planning (EAP) with Jira or Azure DevOps (ADO) using a two-step integration approach:

  1. EAP → Agile Development
  2. Agile Development → Jira or ADO

Who should use this integration?

- This approach is an interim solution and a bi-directional integration directly to EAP is currently on the roadmap to be developed as a long term solution. So this approach is suitable for organisations that want to move to Enterprise Agile Planning (EAP) immediately, and cannot wait for the long term solution.

- Sprints cannot be synchronized with this approach, so this approach is ideal for organisations for which the primary motivation of integration is to view the progress of Epics and stories within EAP.

 

Limitations of This Approach:

Before proceeding, it's essential to be aware of the following limitations:

  1. Sprints cannot be synched with this approach.
  2. Epic has to be created in EAP with an Assignment Group (new field created in Step 1 below) assigned.
  3. Users must update both the EAP Team and Assignment Group fields in the EAP epic (sn_align_core_scrum_epic).
  4. If Essential SAFe is used with this integration (ADO/Jira <> Essential SAFe <> EAP), only Epics and Features can be synched (stories cannot be synched).

Steps to setup this integration:

Note: This article takes the example of Epic (sn_align_core_scrum_epic → rm_epic → ADO/Jira epic). If you would like to enable sync for other work item types, such as story or feature, the same changes need to be applied to the corresponding Business Rules (BR).

Step 1: 
Add a new field named "Assignment Group" on the ‘sn_align_core_scrum_epic’ table, referencing the sys_user_group table.

 

Step 2: Configure Internal Integration Field Mapping

In the Internal Integration Table & Field Mapping, create a field map:

  • Go to Nav->StrategicPlanning -> Integrations -> ServiceNow Internal -> Table Maps -> rm_epic
  • Map the "Assignment Group" field from sn_align_core_scrum_epic with the Assignment Group field on rm_epic

Step 3: Modify the Business Rule on rm_epic: (Changes in this step are included in the update set)

  • For Jira integration, apply "EAP:Jira Integration Workaround.xml". This update Set has the changes on the Business Rule, "Synch Epic with Jira".
  • For ADO integration, apply "EAP:ADO Integration Workaround.xml". This update Set has the changes on the Business Rule, "Synch Epic with Azure DevOps".


Explaining the changes in the above update set in Step 3:

1. Prevent Cyclic Updates

Update the script in the BR, “Synch Epic with in ….”  to add the following condition:

 

 

 

current.getValue('sys_updated_by') != 'system'​

 

 

 

This ensures that updates triggered by system-generated processes (such as Jira/ADO sync) do not cause endless cyclic update, while user-initiated changes still sync properly.

2. Remove the Condition Value

The existing Business Rule checks for:

 

 

 

gs.getSession().isInteractive()

 

 

 

Remove this condition because Jira/ADO updates appear as "system" in ServiceNow and this condition only returns true for UI-based updates (form or list view).

By following these steps, you can successfully integrate Enterprise Agile Planning (EAP) with Jira or AzureDevOps (ADO). This setup enables users to track the progress of Epics, Features, Stories and other work items within EAP and SPW while ensuring smooth synchronization between ServiceNow and Jira or Azure DevOps (ADO).

Comments
jhwagemaker
Tera Contributor

Interesting article. Just wondering how to use this when setup in Azure DevOps or Jira is according to SAFe (Epic, Feature, Story's) while Agile Development 2.0 is based on scrum...

SaiShekhar07864
ServiceNow Employee

Hi @jhwagemaker 

If you use Epic, Features and Stories with a SAFe process,

1. Features and Epics can be synched as explained below

2. Stories can be synched from Jira/ADO to ServiceNow easily but we may need some additional changes to export new stories created in EAP to ADO/Jira.
(Happy to connect with you to explore this.. please drop an email at saishekhar.purumanla@servicenow.com if you'd like to connect and explore this. Would love to know more about your Jira implementation with SAFe as well)

Synching Features & Epics:
Please apply the Steps 1,2 and 3 in the above article for the SAFe tables (sn_safe_epic and sn_safe_feature)
ie, Connect ADO to Essential SAFe with existing Agile.2.0 integrations, and then use internal integration to synch the Essential SAFe tables (sn_safe_epic and sn_safe_feature) with the corresponding EAP's tables (sn_align_core_scrum_epic and sn_align_core_feature)

jhwagemaker
Tera Contributor

Hi @SaiShekhar07864 ,

 

Thank you for your reply!

Is it correct that the SAFe module in ServiceNow will be depricated and replaced by the EAP functionality in the Strategic Planning Workspace? And if so, is it a good idea to connect with the SAFe tables?

 

I would love to have a Azure DevOps integration that directly connects to the EAP tables...

 

gr John

 

 

SaiShekhar07864
ServiceNow Employee

Hi @jhwagemaker ,
If you have SAFe installed already, it will never go away and you can continue to use the SAFe tables for the integration as long as needed.

As an alternative, you can also create a new table inside Agile.2.0 that extends planned_task and use it as an intermediary, instead of using the SAFe tables.

We do want to eventually connect directly with EAP, but this is for the longer term and is tentatively planned for end of 2026.

jhwagemaker
Tera Contributor

Hi @SaiShekharP ,

 

Thanks again for your reply!


We'll start with the epic table of Agile 2.0, because the first MVP of the connection will only be on that level. If needed we'll use your suggestion to extend the planned_task table. 

 

And looking forward to the direct connection with EAP. 

 

gr John

SaiShekhar07864
ServiceNow Employee

Hi @jhwagemaker,
I've updated the article with our latest plans and roadmap (you’ll find them at the beginning of the post). I wanted to bring this to your attention, as these updates might impact your planning.

As always, happy to connect over a call if you’d like to discuss further or have any questions.

Thanks,
Sai Shekhar
saishekhar.purumanla@servicenow.com

peterlowe132233
Tera Explorer

@SaiShekhar07864 ,

You mention that a similar out of the box integration for EAP with ADO is planned. Please can you confirm whether this is in the roadmap, and when you expect this to be introduced?  Is this the update which is planned for late 2026?

 

Thank you

SaiShekhar07864
ServiceNow Employee

Hi @peterlowe132233 ,
The new ADO integrations is currently targeted for June 2026 tentatively.

But you can use the workaround in this article right now. Please do email me at saishekhar.purumanla@servicenow.com if you'd like to connect and I'll set up some time to answer any  questions or concerns.

Thanks,
Sai Shekhar

DavidB-ADT
Tera Guru

Thanks for the article & instructions, @SaiShekhar07864 .

 

I just configured this and wanted to point out that you've forgotten an important step to get the Sync working. Under your EAP Configuration, you have to change the 'Epic Methodology' from SAFE to Scrum in order for Epics created in SPW or Jira to sync. By default this field isn't shown on the EAP configuration form, so you need to add it to the list view, or customize the form in Form Builder to show it. This is important because Epics from the rm_epic table sync to SPW as 'scrum' methodology, but by default EAP sets your methodology to 'SAFE'. This means that you won't be able to assign an EAP team to epics sync'd from rm_epics (which is where Jira/ADO sync to). Changing this config fixes that and allows the sync to work as described. Creating a new Epic in your EAP ART/Team will now use Scrum, and will sync down to rm_epic and Jira with the correct team assignment.

DavidBADT_0-1762021752550.png

 

 

You instructions also requires the EAP Team and new Assignment Group field to be set to the same team. For anyone who wants it, I've created two Business Rules that will automatically sync these two fields in both the sn_align_core_scrum_epic and rm_story tables, so you don't have to do it manually. I can't attach the update set, so the XML is below.

 

This XML file does not appear to have any style information associated with it. The document tree is shown below.
<unload unload_date="2025-11-01 18:31:56">
<sys_remote_update_set action="INSERT_OR_UPDATE">
<application display_value="Strategic Planning">a38ac49ccbf511104abddcbcf7076dec</application>
<application_name>Strategic Planning</application_name>
<application_scope>sn_apw_advanced</application_scope>
<application_version>4.3.2</application_version>
<collisions/>
<commit_date/>
<deleted/>
<description/>
<inserted/>
<name>Sync EAP Team and Assignment Group</name>
<origin_sys_id/>
<parent display_value=""/>
<release_date/>
<remote_base_update_set display_value=""/>
<remote_parent_id/>
<remote_sys_id>cbf3b1c497c97e94e8fb3756f053afdf</remote_sys_id>
<state>loaded</state>
<summary/>
<sys_class_name>sys_remote_update_set</sys_class_name>
<sys_created_by>DavidBarkhausen</sys_created_by>
<sys_created_on>2025-11-01 18:31:56</sys_created_on>
<sys_id>1544310897c97e94e8fb3756f053affb</sys_id>
<sys_mod_count>0</sys_mod_count>
<sys_updated_by>DavidBarkhausen</sys_updated_by>
<sys_updated_on>2025-11-01 18:31:56</sys_updated_on>
<update_set display_value=""/>
<update_source display_value=""/>
<updated/>
</sys_remote_update_set>
<sys_update_xml action="INSERT_OR_UPDATE">
<action>INSERT_OR_UPDATE</action>
<application display_value="Strategic Planning">a38ac49ccbf511104abddcbcf7076dec</application>
<category>customer</category>
<comments/>
<name>sys_script_0133d50c97817e94e8fb3756f053af17</name>
<payload><?xml version="1.0" encoding="UTF-8"?><record_update sys_domain="global" table="sys_script"><sys_script action="INSERT_OR_UPDATE"><abort_action>false</abort_action><access>package_private</access><action_delete>false</action_delete><action_insert>true</action_insert><action_query>false</action_query><action_update>true</action_update><active>true</active><add_message>false</add_message><advanced>true</advanced><change_fields>false</change_fields><client_callable>false</client_callable><collection>sn_align_core_scrum_epic</collection><condition/><description/><execute_function>false</execute_function><filter_condition/><is_rest>false</is_rest><message/><name>Sync eap_team ⇄ u_assignment_group</name><order>100</order><priority>100</priority><rest_method/><rest_method_text/><rest_service/><rest_service_text/><rest_variables/><role_conditions/><script><![CDATA[(function executeRule(current, previous /* null on insert */) { function isEmpty(v) { return gs.nil(v); } function neq(a, b) { return (a + '') !== (b + ''); } var eapId = current.getValue('eap_team'); // sn_apw_advanced_agile_team.sys_id var grpId = current.getValue('u_assignment_group'); // sys_user_group.sys_id var eapChanged = current.eap_team.changes(); var grpChanged = current.u_assignment_group.changes(); // --- Lookup helpers ---------------------------------------------------- // From team -> group (prefer the explicit reference; fallback by matching name) function resolveGroupFromTeam(teamId) { if (isEmpty(teamId)) return ''; var team = new GlideRecord('sn_apw_advanced_agile_team'); if (!team.get(teamId)) return ''; var ug = team.getValue('user_group'); if (!isEmpty(ug)) return ug; // fallback by matching group name == team.name var name = team.getValue('name'); if (isEmpty(name)) return ''; var g = new GlideRecord('sys_user_group'); g.addQuery('name', name); g.setLimit(1); g.query(); return g.next() ? g.getUniqueValue() : ''; } // From group -> team (prefer user_group match; fallback by matching name) function resolveTeamFromGroup(groupId, groupName) { if (isEmpty(groupId) && isEmpty(groupName)) return ''; // 1) exact link on user_group if (!isEmpty(groupId)) { var t1 = new GlideRecord('sn_apw_advanced_agile_team'); t1.addQuery('user_group', groupId); // If your table has an 'active' field, uncomment: // t1.addQuery('active', true); t1.setLimit(1); t1.query(); if (t1.next()) return t1.getUniqueValue(); } // 2) fallback: match by name equality if (!isEmpty(groupName)) { var t2 = new GlideRecord('sn_apw_advanced_agile_team'); t2.addQuery('name', groupName); // t2.addQuery('active', true); t2.setLimit(1); t2.query(); if (t2.next()) return t2.getUniqueValue(); } return ''; } function getGroupName(groupId) { if (isEmpty(groupId)) return ''; var g = new GlideRecord('sys_user_group'); if (!g.get(groupId)) return ''; return g.getValue('name') || g.getDisplayValue(); } // --- INSERT ------------------------------------------------------------ if (current.operation() === 'insert') { if (!isEmpty(eapId) && isEmpty(grpId)) { current.setValue('u_assignment_group', resolveGroupFromTeam(eapId)); return; } if (!isEmpty(grpId) && isEmpty(eapId)) { current.setValue('eap_team', resolveTeamFromGroup(grpId, getGroupName(grpId))); return; } if (!isEmpty(eapId) && !isEmpty(grpId)) { var expectedGrp = resolveGroupFromTeam(eapId); if (neq(expectedGrp, grpId)) { // Prefer EAP on conflict current.setValue('u_assignment_group', expectedGrp || ''); } } return; } // --- UPDATE ------------------------------------------------------------ // If exactly one changed, mirror it (including clears) if (eapChanged ^ grpChanged) { // XOR if (eapChanged) { // eap_team was modified var newGrp = isEmpty(eapId) ? '' : resolveGroupFromTeam(eapId); if (neq(newGrp, grpId)) current.setValue('u_assignment_group', newGrp); } else { // u_assignment_group was modified var groupName = getGroupName(grpId); var newTeam = isEmpty(grpId) ? '' : resolveTeamFromGroup(grpId, groupName); if (neq(newTeam, eapId)) current.setValue('eap_team', newTeam); } return; } // If both changed and disagree, prefer eap_team if (eapChanged && grpChanged) { var expectedGrp2 = isEmpty(eapId) ? '' : resolveGroupFromTeam(eapId); if (neq(expectedGrp2, grpId)) { current.setValue('u_assignment_group', expectedGrp2); } } // If neither explicitly changed, do nothing. })(current, previous); ]]></script><sys_class_name>sys_script</sys_class_name><sys_created_by>DavidBarkhausen</sys_created_by><sys_created_on>2025-11-01 16:09:57</sys_created_on><sys_domain>global</sys_domain><sys_domain_path>/</sys_domain_path><sys_id>0133d50c97817e94e8fb3756f053af17</sys_id><sys_mod_count>1</sys_mod_count><sys_name>Sync eap_team ⇄ u_assignment_group</sys_name><sys_overrides/><sys_package display_value="Strategic Planning" source="sn_apw_advanced">a38ac49ccbf511104abddcbcf7076dec</sys_package><sys_policy/><sys_scope display_value="Strategic Planning">a38ac49ccbf511104abddcbcf7076dec</sys_scope><sys_update_name>sys_script_0133d50c97817e94e8fb3756f053af17</sys_update_name><sys_updated_by>DavidBarkhausen</sys_updated_by><sys_updated_on>2025-11-01 16:53:23</sys_updated_on><template/><when>before</when></sys_script><sys_translated_text action="delete_multiple" query="documentkey=0133d50c97817e94e8fb3756f053af17"/></record_update></payload>
<payload_hash>1247869176</payload_hash>
<remote_update_set display_value="Sync EAP Team and Assignment Group">1544310897c97e94e8fb3756f053affb</remote_update_set>
<replace_on_upgrade>false</replace_on_upgrade>
<sys_created_by>DavidBarkhausen</sys_created_by>
<sys_created_on>2025-11-01 18:31:56</sys_created_on>
<sys_id>1944310897c97e94e8fb3756f053affb</sys_id>
<sys_mod_count>0</sys_mod_count>
<sys_recorded_at>19a40b01ffd0000001</sys_recorded_at>
<sys_updated_by>DavidBarkhausen</sys_updated_by>
<sys_updated_on>2025-11-01 18:31:56</sys_updated_on>
<table>sn_align_core_scrum_epic</table>
<target_name>Sync eap_team ⇄ u_assignment_group</target_name>
<type>Business Rule</type>
<update_domain>global</update_domain>
<update_guid>a314b9c482c97e94d351b4cd7186868a</update_guid>
<update_guid_history>a314b9c482c97e94d351b4cd7186868a:1247869176,f9bd1d0805457e94f37661f31f2bd624:1247869176,79c351047ac17e944bea3806b1bcf0d2:770609636</update_guid_history>
<update_set display_value=""/>
<view/>
</sys_update_xml>
<sys_update_xml action="INSERT_OR_UPDATE">
<action>INSERT_OR_UPDATE</action>
<application display_value="Strategic Planning">a38ac49ccbf511104abddcbcf7076dec</application>
<category>customer</category>
<comments/>
<name>sys_script_832125cc97457e94e8fb3756f053afc7</name>
<payload><?xml version="1.0" encoding="UTF-8"?><record_update sys_domain="global" table="sys_script"><sys_script action="INSERT_OR_UPDATE"><abort_action>false</abort_action><access>package_private</access><action_delete>false</action_delete><action_insert>true</action_insert><action_query>false</action_query><action_update>true</action_update><active>true</active><add_message>false</add_message><advanced>true</advanced><change_fields>false</change_fields><client_callable>false</client_callable><collection>rm_story</collection><condition/><description/><execute_function>false</execute_function><filter_condition/><is_rest>false</is_rest><message/><name>Sync eap_team ⇄ assignment_group (rm_sto</name><order>100</order><priority>100</priority><rest_method/><rest_method_text/><rest_service/><rest_service_text/><rest_variables/><role_conditions/><script><![CDATA[(function executeRule(current, previous /* null on insert */) { function isEmpty(v) { return gs.nil(v); } function neq(a, b) { return (a + '') !== (b + ''); } var teamId = current.getValue('eap_team'); // sn_apw_advanced_agile_team.sys_id var grpId = current.getValue('assignment_group'); // sys_user_group.sys_id var teamChanged = current.eap_team.changes(); var grpChanged = current.assignment_group.changes(); // --- Helpers ----------------------------------------------------------- // From team -> group: prefer team.user_group; fallback to group by name == team.name function resolveGroupFromTeam(teamSysId) { if (isEmpty(teamSysId)) return ''; var team = new GlideRecord('sn_apw_advanced_agile_team'); if (!team.get(teamSysId)) return ''; var ug = team.getValue('user_group'); if (!isEmpty(ug)) return ug; var teamName = team.getValue('name'); if (isEmpty(teamName)) return ''; var g = new GlideRecord('sys_user_group'); g.addQuery('name', teamName); g.setLimit(1); g.query(); return g.next() ? g.getUniqueValue() : ''; } // From group -> team: ONLY return a team sys_id if a matching team exists. // 1) Prefer team.user_group == groupId // 2) Else fallback to team.name == group.name function resolveTeamFromGroup(groupSysId) { if (isEmpty(groupSysId)) return ''; // try direct user_group reference var t1 = new GlideRecord('sn_apw_advanced_agile_team'); t1.addQuery('user_group', groupSysId); t1.setLimit(1); t1.query(); if (t1.next()) return t1.getUniqueValue(); // fallback to name match var groupName = getGroupName(groupSysId); if (isEmpty(groupName)) return ''; var t2 = new GlideRecord('sn_apw_advanced_agile_team'); t2.addQuery('name', groupName); t2.setLimit(1); t2.query(); if (t2.next()) return t2.getUniqueValue(); // No matching team found -> skip syncing return ''; } function getGroupName(groupSysId) { if (isEmpty(groupSysId)) return ''; var g = new GlideRecord('sys_user_group'); if (!g.get(groupSysId)) return ''; return g.getValue('name') || g.getDisplayValue(); } // --- INSERT ------------------------------------------------------------ if (current.operation() === 'insert') { // team → group if (!isEmpty(teamId) && isEmpty(grpId)) { var g1 = resolveGroupFromTeam(teamId); if (!isEmpty(g1)) current.setValue('assignment_group', g1); return; } // group → team (only if matching team exists) if (!isEmpty(grpId) && isEmpty(teamId)) { var t1 = resolveTeamFromGroup(grpId); if (!isEmpty(t1)) current.setValue('eap_team', t1); return; } // both set: prefer team; ensure group matches team.user_group/name if (!isEmpty(teamId) && !isEmpty(grpId)) { var expectedGrp = resolveGroupFromTeam(teamId); if (!isEmpty(expectedGrp) && neq(expectedGrp, grpId)) { current.setValue('assignment_group', expectedGrp); } } return; } // --- UPDATE ------------------------------------------------------------ // Exactly one changed if (teamChanged ^ grpChanged) { if (teamChanged) { // team was modified if (isEmpty(teamId)) { // clearing team -> clear group if (!isEmpty(grpId)) current.setValue('assignment_group', ''); } else { var g2 = resolveGroupFromTeam(teamId); // only set if we can resolve a group from this team if (!isEmpty(g2) && neq(g2, grpId)) current.setValue('assignment_group', g2); } } else { // group was modified if (isEmpty(grpId)) { // rule: do NOT clear team when group is cleared (no matching team to confirm) // skip } else { var t2 = resolveTeamFromGroup(grpId); // only set if a matching team exists if (!isEmpty(t2) && neq(t2, teamId)) current.setValue('eap_team', t2); } } return; } // Both changed: prefer team (but still respect group resolution) if (teamChanged && grpChanged) { if (isEmpty(teamId)) { // team cleared wins -> clear group if (!isEmpty(grpId)) current.setValue('assignment_group', ''); } else { var expectedGrp2 = resolveGroupFromTeam(teamId); if (!isEmpty(expectedGrp2) && neq(expectedGrp2, grpId)) { current.setValue('assignment_group', expectedGrp2); } // If team->group can't resolve, leave group as user set it. } } // Neither changed: no-op. })(current, previous); ]]></script><sys_class_name>sys_script</sys_class_name><sys_created_by>DavidBarkhausen</sys_created_by><sys_created_on>2025-11-01 17:08:29</sys_created_on><sys_domain>global</sys_domain><sys_domain_path>/</sys_domain_path><sys_id>832125cc97457e94e8fb3756f053afc7</sys_id><sys_mod_count>0</sys_mod_count><sys_name>Sync eap_team ⇄ assignment_group (rm_sto</sys_name><sys_overrides/><sys_package display_value="Strategic Planning" source="sn_apw_advanced">a38ac49ccbf511104abddcbcf7076dec</sys_package><sys_policy/><sys_scope display_value="Strategic Planning">a38ac49ccbf511104abddcbcf7076dec</sys_scope><sys_update_name>sys_script_832125cc97457e94e8fb3756f053afc7</sys_update_name><sys_updated_by>DavidBarkhausen</sys_updated_by><sys_updated_on>2025-11-01 17:08:29</sys_updated_on><template/><when>before</when></sys_script><sys_translated_text action="delete_multiple" query="documentkey=832125cc97457e94e8fb3756f053afc7"/></record_update></payload>
<payload_hash>-1023244664</payload_hash>
<remote_update_set display_value="Sync EAP Team and Assignment Group">1544310897c97e94e8fb3756f053affb</remote_update_set>
<replace_on_upgrade>false</replace_on_upgrade>
<sys_created_by>DavidBarkhausen</sys_created_by>
<sys_created_on>2025-11-01 18:31:56</sys_created_on>
<sys_id>d944310897c97e94e8fb3756f053affb</sys_id>
<sys_mod_count>0</sys_mod_count>
<sys_recorded_at>19a40afefab0000001</sys_recorded_at>
<sys_updated_by>DavidBarkhausen</sys_updated_by>
<sys_updated_on>2025-11-01 18:31:56</sys_updated_on>
<table>rm_story</table>
<target_name>Sync eap_team ⇄ assignment_group (rm_sto</target_name>
<type>Business Rule</type>
<update_domain>global</update_domain>
<update_guid>a814f5c401c97e942362e363d7027416</update_guid>
<update_guid_history>a814f5c401c97e942362e363d7027416:-1023244664,4b2125cc31457e94e6d4ef5fd02a26c9:-1023244664</update_guid_history>
<update_set display_value=""/>
<view/>
</sys_update_xml>
</unload>
DavidB-ADT
Tera Guru

Also, forgot to mention that this configuration you've provide also sync's stories between Jira and EAP just fine. I'm quite pleased with this work-around until the native connection is available!

SaiShekhar07864
ServiceNow Employee

Hi @DavidB-ADT 
1. Thank you so much for pointing out about changing methodology to "Scrum". (I'll include this in the main article asap).
I'm glad that you were able to make this work and hope you dint face any challenges (other than the Epic methodology issue 🙂 )

2. I also wanted to let you know that we are releasing the new OOB EAP<>Jira Integration in the Dec '2025 release. This will be part of a new plugin called "SPW Jira Integrations".
Please feel free to check this out once it is out and/or reach out to me with any questions or feedback.

 

-Sai Shekhar
Product Manager, SPM(EAP, Integrations)

saishekhar.purumanla@servicenow.com

DavidB-ADT
Tera Guru

@SaiShekhar07864  - Awesome! I’ll definitely kick the tires on the new plugin and provide feedback.

 

One gap I have noticed on the existing plugins is the lack of ability to sync Dependencies on Epics. Any idea if that will be supported by the new plugin?

 

I’m working on my own solution for this now, so would save me a lot of brain cells if you guys are already planning on it! 🙂

Vinitha Chukkala
ServiceNow Employee

Hi Everyone, we are pleased to announce OOTB integration of EAP and Jira with 2025 December store release. Please read the updated article to learn more about it.

peterlowe132233
Tera Explorer

Hi,  Is it anticipated the the planned ADO integration will work in a broadly similar way to the new EAP Jira integration?   Is this still on track for Q2/Q3 2026?

SaiShekhar07864
ServiceNow Employee

Hi @peterlowe132233 ,
Unfortunately, the EAP ADO direct integration is pushed back to Q4 2026 due to other priorities like Hierarchy view, PI planning with Features & Epics, Usability enhancements.

 

Thanks for your understanding,
Sai

peterlowe132233
Tera Explorer

Thank you, this is the nature of safe harbor.  The updated timeline is useful for planning so appreciate the response.

DavidB-ADT
Tera Guru

SPW Jira Integrations on Jira Cloud: two gaps found while piloting Feature sync

We are piloting the SPW (Strategic Planning Workspace) to Jira Cloud integration for one agile team and found two gaps that stopped the pilot. Both are reproducible, both have a workaround, and both look like small fixes on the product side. Posting here in addition to a support case so the product team sees them.


Environment

ApplicationVersion
SPW Jira Integrations (sn_spw_jira_int)1.2.0
Atlassian Jira Integration for Agile Development (sn_agile_jira_int)2.4.0
Atlassian Jira Integrations Common (sn_jira_int)2.5.0
Jira Spoke (sn_jira_spoke)6.1.3
JiraJira Cloud, connection alias on REST API version 3

Mapping is one to one: Jira Epic (SAFe) to sn_align_core_scrum_epic, Jira Feature to sn_align_core_feature, Jira Story to rm_story. Inbound sync (Jira to SPW) works for all three levels, including parent resolution.


Gap 1: Features and Capabilities never sync from SPW to Jira

What the documentation says. The integration landing page describes bi-directional syncing of Epics, Features, Capabilities, and Stories, and the custom mapping page lists Feature mapped to sn_align_core_feature.


What ships.
Outbound sync is driven by three after-insert/update business rules, each gated on an interactive session:

  • Synch Epic with Jira on sn_align_core_scrum_epic
  • Synch Story with Jira on rm_story
  • Synch Sprint with Jira on sn_apw_advanced_eap_iteration

Nothing in the three applications references sn_align_core_feature or sn_align_core_capability as an outbound trigger: no business rule, no flow trigger, no script include. A Feature created in the Enterprise Agile Planning workspace with a team that has valid team integration settings produces no export call, no log line, and no Jira issue.


The export chain itself is table-agnostic.
AgileJiraUtils.exportCurrentRecordToJira resolves the Jira project from the record's team, AgileJiraExportUtils.preparePayloadForEntity takes the issue type from the table map and the parent from the external identifier, and JiraExportUtils.synchSNRecordWithJira starts the sn_jira_int.sync_jira_issue subflow. Only the trigger is missing.


Workaround we validated.
A customer after-insert/update business rule on sn_align_core_feature, a copy of Synch Epic with Jira with the same interactive-session condition, calling exportCurrentRecordToJira(current, previous). With that rule in place, a Feature created in the workspace under a synced Epic arrived in Jira as a Feature issue, parented to the correct Epic (SAFe), with the external identifier populated, in about 12 seconds.


Recommended fix.
Ship Synch Feature with Jira and Synch Capability with Jira rules in SPW Jira Integrations, mirroring the Epic rule, so the product matches its documentation. If Feature and Capability outbound sync is intentionally out of scope, say so on the landing page and the custom mapping page, because the table maps invite customers to configure it.


Gap 2: every SPW to Jira update fails on Jira Cloud when the field map includes description

Symptom. Creating an issue from SPW works. Updating it does not. Any later change to the SPW record (a state change, a summary edit) produces this flow log entry with an empty reason:

Error updating the issue in Jira - <ISSUE-KEY>:

Because the subflow Update Jira Issue and process only runs Update Additional attributes of Jira Issue (which performs the status transition) when the update step reports Success, the status transition never happens either. The SPW record moves; the Jira issue stays where it was.


Root cause, from the outbound HTTP log.
The update sends the description as a plain string:

PUT /rest/api/3/issue/<ISSUE-KEY>?notifyUsers=true
{"update":{},"fields":{"summary":"...","description":"plain text here"}}

Jira Cloud on API version 3 rejects it:

{"errorMessages":[],"errors":{"description":"Operation value must be an Atlassian Document (see the Atlassian Document Format)"}}

The create path is fine because the Create Issue in Jira action (Atlassian Jira Integrations Common) wraps description in an Atlassian Document when the connection is cloud on API version 3. The Jira Spoke 6.1.3 action Update Issue does the same in its Create Payload step. The Atlassian Jira Integrations Common action Update Issue in jira, which the integration actually calls, was copied from the spoke's deprecated Update Issue action and has no such block: its Create Payload step treats description as a string field and passes it through unchanged.

Any customer with a description field map on Story, Feature, or Epic (the default maps include one) hits this on every update once their Jira connection uses API version 3.


Workaround we applied.
Set the API Version attribute on the connector's connection alias from 3 to 2. JiraUtils.getAPIVersion reads that attribute for every call, and on API version 2 Jira Cloud accepts description as a plain string on both create and update, which is exactly what the actions send. With that one change a Feature created from SPW arrived in Jira, a state change from SPW updated the issue and ran the transition, and a bulk import of 58 issues completed with no failures. Two things we ruled out first: removing the description field map breaks every create, because the Jira project makes Description mandatory on the create screen; and is_valid on sn_int_common_field_map is read-only, so a field map cannot simply be switched off.


Related observation.
On API version 3 the bulk import left description empty on every imported Feature, although the issues carry descriptions in Jira. On API version 2 the same import populated them. It looks as though the inbound path does not flatten an Atlassian Document either, so the version 3 gap runs in both directions.


Recommended fix.
Port the Atlassian Document wrapping from the Jira Spoke Update Issue action into the Atlassian Jira Integrations Common Update Issue in jira action (or rebase that action on the current spoke action instead of the deprecated one). Separately, have the Handle Errors step include Jira's response body in the log message; an empty reason after the issue key cost us a lot of time.


Summary

GAPIMPACTWORKAROUNDFIX REQUESTED
No outbound trigger for Features or CapabilitiesDocumented bi-directional Feature sync does not exist out of the boxCustomer business rule on sn_align_core_feature calling exportCurrentRecordToJiraShip the Feature and Capability rules, or correct the documentation
Update Issue in jira sends description as a string on API v3Every SPW to Jira update fails on Jira Cloud, and with it every status transition; inbound descriptions arrive emptySet the connection alias API Version attribute to 2Wrap description in Atlassian Document Format on update, as the spoke and the create action already do; flatten it on import; log the Jira error body

 

Happy to share the flow log, outbound HTTP log entries, and the customer rule with the product team on request.

Version history
Last update:
‎12-15-2025 06:17 AM
Updated by: