Salesforce often involves multiple objects that have an association between them. For instance, an Opportunity can have an Account or Product association, or a Case can have a linked Account or Contact, or there can be a completely new custom object.
Teams frequently need these related objects in Salesforce synced over to Jira for better visibility across departments. This post walks you through how to achieve that using Exalate, a bidirectional integration solution that connects platforms like Jira, Salesforce, ServiceNow, Azure DevOps Cloud, Azure DevOps Server, Zendesk, Freshservice, Freshdesk, Asana, GitHub, and more.
We have a Case object in Salesforce that has a Contact and Account linked to it. This information needs to be presented on the Jira side.
The specifics:
Fetching information related to Case, Account, and Contact individually from Salesforce to Jira can be straightforward. The real challenge lies in connecting the Account and Contact to the correct Jira work item that was created while syncing the Case. Changes in the Account or Contact should pass correct updates to the synced Jira work item.
Exalate is a bidirectional synchronization solution with a built-in Groovy-based scripting engine, giving you the capabilities to implement almost any use case, including syncing multiple related objects.
Key features that make this possible:
Exalate also supports syncing any Salesforce object accessible via the REST API, including Cases, Opportunities, Accounts, Contacts, Leads, Tasks, Products, and custom objects.
Before diving into the multi-entity configuration, you need to set up a connection between Jira and Salesforce. Here is a quick overview of the process.
Go to exalate.app and log in. New users can create an account by entering their email or signing up using Google.
Workspaces help you organize and manage your integrations in a single place. Click "+ Create Workspace," enter a name and description, and confirm.
Click "+ Add connections" then "Create new connection." Enter a name for your first system (either Jira or Salesforce). Then enter the URL of your system. A validation check occurs automatically. For new systems, you will need to enter your authentication details. Jira uses OAuth, and Salesforce uses API tokens. Complete the same setup process for the other side, give your connection a name and description, review the details, and click "Create connection."
After creating the connection, you have two options:
Click "Create a new version" or "Open latest draft" to start editing. The scripts are divided into incoming and outgoing scripts. For a Jira to Salesforce direction, the outgoing script defines what data leaves Jira, and the incoming script defines how Salesforce processes that data.
You can write Groovy scripts yourself, or use Aida to generate scripts from natural language prompts. Aida appears in both the outgoing and incoming script sections.
Once your sync scripts are ready, use the "Start Test Run" option to validate the configuration against real data without affecting production. Select the items you want to test, review how the sync will be applied, and verify the field mappings look correct. Deploy only when you are confident everything works.
Add triggers to start syncing automatically. For Jira, use JQL (Jira Query Language). For Salesforce, use SOQL (Salesforce Object Query Language). Save your changes by publishing the version.
Now let's look at the specific scripts for syncing Case, Account, and Contact objects together.
As an Exalate administrator, you can choose to configure both the incoming and outgoing scripts on each side independently. All information is passed through the "replica," which acts like a message payload carrying data between the two platforms in JSON format.
Since there is no additional information apart from the defaults that need to be sent from Jira to Salesforce, we keep it as is.
The outgoing Salesforce script handles the logic for sending Case, Account, and Contact data.
1 //Step 1
2 //When the CASE is exalated, only the first if condition triggers
3 //and sends over the case info
4 if(entity.entityType == "Case") {
5 replica.key = entity.Id
6 replica.summary = entity.Subject
7 replica.description = entity.Description
8 replica.comments = entity.comments
9 replica.attachments = entity.attachments
10 replica.Status = entity.Status
11 replica.AccountId = entity.AccountId
12 replica.ContactId = entity.ContactId
13 replica. "TYPE" = entity.entityType
14 }
15 else if (entity.entityType == "Account") {
16 replica.key = entity.Id
17 replica.AccountNumber = entity."AccountNumber"
18 replica.Name = entity.Name
19 replica.Description = entity.Description
20 replica. "TYPE" = entity.entityType
21 } else if (entity.entityType == "Contact") {
22 replica.key = entity.Id
23 replica.Email = entity.Email
24 replica.Birthdate = entity.Birthdate
25 replica.Name = entity.Name
26 replica.Title = entity.Title
27 replica."TYPE" = entity.entityType
28 }
Key points in the script:
Here, you check if the entity from the replica is 'Case.' If it is, and it is the first sync, then you create a 'Task' in Jira.
//Jira receives the case (the first if condition is executed)
//The crucial line is
//syncHelper.syncBackAfterProcessing()
//within the firstSync block.
//So basically when the CASE first syncs here, a syncBack is sent to Jira //which will now be incoming for Jira
if (replica."TYPE" == "Case")
{ if(firstSync)
{
issue.projectKey = "UD"
issue.typeName = "Task"
syncHelper.syncBackAfterProcessing()
}
issue.summary = replica.summary
issue.description = replica.description
issue.comments = commentHelper.mergeComments(issue, replica)
issue.attachments = attachmentHelper.mergeAttachments(issue, replica)
}
Then the script calls syncHelper.syncBackAfterProcessing(). This is a helper method from the Exalate API. When it is the first sync, this method syncs back to Jira so there is an incoming sync for Jira to fetch the Contact and Account information from Salesforce.
Note: You can also look at the other helper methods provided by the Exalate API.
In Salesforce, if it is the first sync, a Case is created. This is completely customizable, so you can create any other entity if needed.
1 if (firstSync) {
2 entity.entityType = "Case"
3 }
4 //Step 3
5 //This part now gets executed
6 if (entity.entityType == "Case") {
7 //Step 3 contd
8 //We check if the remoteSyncEventNumber==1 to ensure that the following code
9 //is executed only once and only when Jira send back the syncBack (and not on subsequent syncs)
10 if (syncRequest.remoteSyncEventNumber == 1) {
11 //Step 3 cont checks if the Account is populated and if it is we continue
12 if (entity.AccountId != null) {
13 //if account is found on the case, we trigger a connect event i.e. scheduleConnectEvent
14 //this connect helps to connect the linked account with the Jira issue created already
15 //this link can be formed using the replica.key (which has already been received using in the syncBack
16 syncHelper.eventSchedulerNotificationService
17 .scheduleConnectEvent(
18 syncHelper.connection,
19 new com.exalate.basic.domain.BasicIssueKey(entity.AccountId, entity.AccountId, "Account"),
20 replica.key,
21 scala.Option$.MODULE$.apply(new com.exalate.basic.domain.NonPersistentConnectContext(
22 false, false, // boolean synchronizeComments, boolean synchronizeAttachments,
23 false, // boolean synchronizeWorklogs
24 false, // boolean triggerSyncEvent
25 true, // boolean triggerUpdate
26 false // boolean triggerUnexalate
27 ))
28 )
29 }
30 }
31 }
Key logic in the script:
The same set of code applies to fetching the Contact details as well.
Now it is time to display the fetched Account and Contact details.
//Step 4
//When the connect happens, remember that SF is sending over the contact and the account
//so the following gets executed this time
//and simply populates the SF Contact (and Account) fields with relevant data
//the formatting of the tables uses markdown (ref: )
//dont bother with the formatting actually...nothing to do with Exalate.
else if (replica."TYPE" == "Contact") {
def contact = issue."SF Contact"
if (contact == null || contact.trim() == "") {
contact = """||Name:| ||Title:| ||Email:| || | """
}
def name = replica."Name"
def title = replica."Title"
def email = replica."Email"
|| Title | $title |
|| Email | $email |"""
}
else if (replica."TYPE" == "Account") {
def name = replica."Name"
def description = replica."Description"
issue."SF Account" = """|| Name | $name |
|| Description | $description |"""
}
The details are displayed in custom fields called 'SF Contact' and 'SF Account.'
Key script logic:
Additional fields from both these Salesforce records (or other Salesforce records entirely) can also be displayed this way.
Note: The '|' and '||' used in the script are just for displaying the data in the form of tables. They have nothing to do with Exalate.
Here is what the sync looks like in action:
You can make changes to either the Account or the Contact in Salesforce and see those reflected in Jira.
Beyond syncing Contacts and Accounts linked to Cases, you can apply similar patterns to:
We saw how multiple related objects within Salesforce can be synced over to Jira using Exalate's Groovy-based scripting engine. The same approach works for syncing to other platforms like Azure DevOps, ServiceNow, Zendesk, and more.
If you want to explore this further, book a demo with us. Or get in touch with our partners if you already use Exalate and want to implement an advanced use case like this.
francis
1 comment