Building an enterprise-grade Jira bulk operations Forge app
Recently we became aware of a trend in relation to challenges faced by certain customers that were preparing to migrate from Jira Data Center to Jira Cloud. These customers have highly customized workflows orchestrated by Jira and supported through numerous custom fields and governance rules that were implemented in Jira Data Center specific solutions, ensuring that their users couldn’t accidentally make changes that violated their practices.
Examples of the types of changes that these customers wanted to prevent include the ability to edit work items such as to cause certain fields to become valueless, and the ability to move work items out of certain projects.
Jira Cloud enables the work item view to be tailored to prevent these kinds of changes, but the bulk move and bulk edit work item functionality can not be tailored to prevent these problematic mutations. Consequently, we needed to either modify Jira Cloud’s bulk work item capabilities or offer an alternate solution and disable the native solution.
In order to act swiftly, we decided to utilize Forge since we knew we could rely on it to quickly build and deploy sophisticated, enterprise-grade solutions.
This blog post outlines key aspects of the solution. We have also made the app’s source code available so that you can explore, clone, and modify it for your own use.
Customer Needs
The customers concerned have carefully crafted workflows that rely on certain custom fields, statuses and allowed transitions. Each type of Jira work item typically has a unique configuration. Often the customers would use Jira’s project category feature to group projects that were setup with certain configurations. The customers typically also have many non-standard projects where the rules are much more relaxed. If unmanaged, moving loosely managed work items from non-standard projects to standard projects leads to the likelihood of the work items not conforming to the standard configurations.
For productivity reasons, the customers also need their users to be able to make bulk changes to work items. To safeguard the integrity of their work item data, the customers therefore needed to be able to restrict certain capabilities when bulk moving and editing work items.
Since each customer has unique requirements, we knew that we needed to provide an extensible solution – one that each customer could tailor to their specific needs.
Forge App Architecture for Jira Cloud Bulk Operations
Forge is a mature and scalable app development platform that is ideally suited to solving this challenge.
The following diagram illustrates the high level components of app that was created
The customers concerned have carefully crafted workflows that rely on certain custom fields, statuses and allowed transitions. Each type of Jira work item typically has a unique configuration. Often the customers would use Jira’s project category feature to group projects that were setup with certain configurations. The customers typically also have many non-standard projects where the rules are much more relaxed. If unmanaged, moving loosely managed work items from non-standard projects to standard projects leads to the likelihood of the work items not conforming to the standard configurations.
For productivity reasons, the customers also need their users to be able to make bulk changes to work items. To safeguard the integrity of their work item data, the customers therefore needed to be able to restrict certain capabilities when bulk moving and editing work items.
Since each customer has unique requirements, we knew that we needed to provide an extensible solution – one that each customer could tailor to their specific needs.
Forge App Architecture for Jira Cloud Bulk Operations
Forge is a mature and scalable app development platform that is ideally suited to solving this challenge.
The following diagram illustrates the high level components of app that was created
Frontend Design Details
- Single-Page UI: The app is implemented as a single GlobalPage CustomUI module, replacing Jira’s multi-page wizard with a step-by-step, single-page interface. This makes it easy for users to review and revise their inputs.
- State Management: The user experience is designed around a series of panel, each of which manages its own state, with shared states handled through centralized models.
- Jira Data Model: Acts as a proxy for Jira API requests, caching data in memory for efficient handling of large datasets (thousands of projects, work items, users, labels).
- Performance Optimizations: Lazy loading, debounced input, and responsive design ensure the app remains performant and user-friendly, even at enterprise scale.
- Design System: Uses Atlassian Design System tokens for consistent theming and accessibility (light/dark mode support).
Backend Design Details
- Minimal Backend: Backend logic is minimal and invoked via Forge functions, primarily for sensitive operations like temporary privilege elevation and secure initiation of bulk operations.
- Authentication Workaround: Basic authentication is used for certain backend operations, with credentials managed via environment variables and egress permissions restricted.
Adapting the example Forge app for your use case
The solution to make the app adaptable to customer specific needs involved the use of a simple API in the form of colocated functions along with settings that customers could update to tailor the app’s behaviour. For example, here is how a customer could filter which source projects are available for a bulk move:
/**
* This function is invoked when the user selects a source project to move issues from. The users starts
* typing the name or key of the source project which results in a Jira API call to retrieve a set of matching
* projects which is then passed to this function for filtering.
* @param selectedSourceProjects
* @returns the filtered source projects that are allowed to be selected as the source project for the bulk move operation.
*/
public filterSourceProjects = async (
selectedSourceProjects: Project[],
): Promise<Project[]> => {
// console.log(`bulkOperationRuleEnforcer.filterSourceProjects: selectedSourceProjects = ${selectedSourceProjects.map(project => project.name).join(', ')}`);
const filteredProjects = selectedSourceProjects.filter((project: Project) => {
return !this.excludedProjectKeys.has(project.key);
});
return filteredProjects;
}
Most of the filtering APIs provided passed in arrays of entities and required a filtered array of entities to be returned, but in hindsight, APIs that deal with one entity at a time are clearer, easier to implement, and more versatile to use when invoking the APIs.
This code-based API approach is not ideal since each time a customer pulls the latest version of the app, they need to:
- Override the app ID with the app ID they have re-registered the app with, and
- Re-integrate their custom code that interfaces with the app.
An alternate approach would be to base the extensibility on stored configuration, but this would have required more effort and time than we had, and also would have likely been more restrictive.
Making API Requests from the Forge Frontend
One of the advantages of Forge is the ease of making Jira REST API requests directly from the frontend. This allows the majority of the app’s code to run in the browser, avoiding the complexity of proxying requests through the backend. While this means users could theoretically tamper with requests, the app’s main goal is to prevent accidental misconfiguration, not enforce strict security. For sensitive operations (such as initiating bulk operations), requests are routed through a Forge backend function, making it easy to add extra security checks if needed.
Forge’s Fast Development Loop
The Forge tunnelling feature allows changes to be served directly from your computer which makes development so much faster and enjoyable. If you’re new to Forge tunnelling, you may want to look at the following explainer video:
PRO TIP – To tunnel changes from a CustomUI part of your Forge app, you need to add two lines to your app’s manifest (visit the app’s manifest to see where this fits in):
tunnel:
port: 3000
If Forge tunnelling isn’t working, check your firewall.
Handling Large-Scale Data in Jira Cloud Forge Apps
These customers operate at serious scale: thousands of projects, tens of thousands of work items, thousands of users and labels, etc. To keep the app responsive, the following techniques were employed:
- A client-side Jira data model lazily caches data in memory;
- User input is debounced to avoid hammering the Jira REST API with unnecessary requests;
- Smart matching/pre-filling of fields;
- Used busy state and loading indicators ensure the user is kept abreast of activity; and
- Limited panel sizes and prudent scrolling to allow data to be viewed without overwhelming the UI.
Despite these tactics, challenges were still encountered – such as eventual consistency work items when adding users to groups for permission elevation, which sometimes meant users were not enrolled quickly enough to initiate bulk operations immediately. To overcome this, retry handling was added.
Permission Management in Jira Cloud Forge Apps
After the user finishes filling out the bulk move or bulk edit information and they finally initiate the request, the information is sent through to the app’s backend (Forge function) using the resolver pattern, where the app adds the user temporarily to a group that has bulk operation privileges. Originally, the request to add the user to the group was made using the Forge Jira authentication API (aka requestJira), but this was returning a 403 forbidden response despite the app having the required scope, manage:jira-configuration. To work around this, basic authorization had to be used which is a reminder of how awesome Forge’s credential management is. With basic auth, (a) credentials had to be stored in environment variables, (b) an additional API call had to be made to get the base URL of site the app is active in, and (c) the app’s documentation had to be updated to explain the configuration. This strategy is summarised in the diagram below with further details in the app’s documentation explaining this strategy.
Due to the previous problem and the app being generic, the basic auth API requests necessitates the app needing to declare egress permissions to *.atlassian.net. This might raise the eyebrows of some security conscious customer admins who install the app, but the setup instructions point out that these declarations can be tightened to just the domains the customer’s instance of the app is expected to run on. Forge is continuing to get better and there is a proposal to define configurable egress to address this;- see RFC-94: Configurable Egress and Remotes.
Making Loveable User Experiences in Forge Apps
One of the customer’s indicated their users typically perform about 2,000 bulk operations per day so a great deal of effort was spent to ensure the user experience would be efficient and intuitive.
Jira’s native bulk operations user interface centres around multi-page wizard experiences, whereas the app was designed to provide a single-page user interface to enable users to more easily revise their inputs. The UI still takes the user through a sequence of steps, but they are all visible in a single page. This approach led to challenges in preventing unnecessary and expensive API calls, managing state across the steps, and ensuring the UI gracefully handles large amounts of data by limiting panel sizes and displaying busy state indicators.
Forge supports the Atlassian design system tokens, facilitating seamless implementation of both light and dark modes.

With the user interface arranged around the concept of completing steps defined by a sequence of panels, it was challenging to elegantly accommodate the display of settings and data since it is all derived from the customer’s data. For example, the customer had many custom fields of different types and invariably work item searches would surface many work items. To accommodate this, a popup panel experience was added to allow the user to view any panel instantly in a larger format.
Benefits of TypeScript for Enterprise Forge App Development
TypeScript codebase is much easier due to the guardrails provided by the compiler. With TypeScript, most IDEs also provide better developer experiences. The type information also helps explain the implementation of an app.
For anything beyond a trivial Forge app, building it in TypeScript is recommended – fortunately Forge supports both.
Key Takeaways from Building Enterprise Jira Cloud Apps with Forge
- Extensibility: Jira Cloud is an extremely customizable and extensible application. Whilst the built-in bulk operation features are not extensible at a granular level and didn’t directly meet the customers’ exact needs, Jira Cloud supports Forge apps which leads to almost limitless extensibility. We were able to quickly build alternate functionality using Forge and effectively swap native features for custom-built features. This is a very powerful pattern.
- Handling Scale: When building an app that will be used in many different customer environments, one must be mindful of handling data at scale. Employ tactics such as lazily loading data, caching data, debouncing input, progress/busy state indicators, scrollable data panels, etc.
Implementation Details
Various aspects of the app’s implementation have also been captured in this Atlassian Developer Community post.
Conclusion
This project exemplifies how Forge can be used to solve complex enterprise challenges in a short timeframe. By balancing usability, performance, and governance, the app streamlined workflows for high-frequency use cases, handling thousands of projects, work items, and users with ease. Its extensibility and open-source foundation enable customers to adapt and extend the solution for their unique needs.
Ultimately, this Forge app demonstrates the power of Atlassian’s platform to deliver scalable and customizable solutions that bridge the gap between native functionality and specific business needs. By open-sourcing the app, the project extends its impact to any number of customers, offering a blueprint for organizations navigating similar transitions to Atlassian Cloud and setting the stage for continued innovation in the enterprise ecosystem.