The ij-solutions Forge story

How a Jira admin became a developer, and a company developed their first Cloud apps using Forge

We are ij-solutions, Atlassian Marketplace partner since 2019, and this is the story about us and Forge. We explain how we started with Forge, what we did so far, what difficulties we faced on our way, and what we plan to do in the future.

Getting started with Forge

My name is Jonas and I am Co-Founder of ij-solutions. I am not a developer, but an experienced Jira and Confluence admin. I know administration, configuration and customization of Atlassian tools in on-premise and Cloud environments quite well. Also, I am familiar with scripting and implementation of various automations in Jira server.

However, when we started to look at Forge early 2020 and no developer had the time to do so, I started to work with Forge myself. My first app development experience began. This is what I did in the beginning:

Of course, Forge wasn’t that far developed and before GA back then. So, only parts of today's documentation even existed. Important functionalities were missing, but for my purposes it was worth giving it a try and invest some time to get to know it better.

The tutorials have been straight forward, and everything went well. But to be honest, what do you make of an app that displays “Hello World” on an issue panel in Jira? You need to have an idea in mind of what you want to do and what you want to build. If you work with Atlassian tools and know them well, maybe even administrating them, you probably have numerous ideas and use cases for nice apps in your mind that could enhance the functionalities of the tools. As we already had an existing server app (Epic Clone), which was lacking a Cloud version back then, it was the perfect starting point to do something more useful with Forge - migration of a server app to Cloud.

From server app to Forge (UI Kit)

The server version of Epic Clone can be used in epics to clone the entire epic and all the issues in epic in one action and even to a different project. When clicking on more → Clone Epic Template in an epic, a servlet opens where users can configure what to clone, select the target project and edit certain fields.

UI Implementation

To use the servlet concept in Cloud wasn’t possible and also Forge didn’t provide a module for it. Therefore, the decision was to use the issue action module and display the apps content in a modal dialog. This seemed to be practicable as we knew it wouldn’t be possible to include all functionalities of the server version from the beginning, thus the content displayed would also fit on a modal.

Starting with a blank modal I checked what is available in Forge UI Kit to implement and display different fields that would be needed for cloning. I made myself familiar with

  • UI Kit Form

  • Select field

  • Checkbox field

  • Text input possibilities

  • User picker field

I added them step by step to my modal to get to a first draft of the form I wanted to display to app users later to configure their template clone. The documentation is great for that purpose, you find every information you need to set up the form:

The arrangement of all the form fields was a challenge as it should look clear and compact and not confusing for the user. We decided to display the fields in a table so we could use several columns. Here we came across the first limitation with UI kit. It wasn’t possible to set vertical alignment in the table cells. I wanted to do that as not all fields with description displayed next to each other had the same height. It wasn’t a showstopper though, but the arrangement wasn’t perfect. This is how it looked like at the end:

First cloning screen with UI kit

The child issues to be cloned should be displayed on a second screen that should be visible when the user entered data on the first screen and then pushed the next button. We achieved that by using a useState hook and definition of a “form state” variable that was updated whenever a button on the modal has been clicked. The concept and usage of such hooks was completely new for me, and it took some time to get familiar with it. On the second screen we faced another limitation of UI kit as it wasn’t possible to display the issue type icon of the child issues between checkbox and summary inline. It would have been great as it is clear from the icon what issue type it is. As it wasn’t possible to implement, we displayed the issue type as text. It looked like this

Second cloning screen with UI kit

Backend implementation

After implementing all those fields and the different forms, I started with implementation of the actual cloning procedure. It wasn’t that difficult and could be implemented quicker than the UI part. However, when running the first tests also on larger issues, I came across several problems.

  1. Payload size: For some issues, the modal just didn’t load, and an error was logged that the payload for the request is too high. I had to clean up what was loaded and removed everything that isn’t absolutely necessary for cloning. As I didn’t consider before what was actually needed, just everything was loaded, so all projects with their configuration and available issue types, all types of Jira fields necessary for creation of the different issue types. For some of those, the information was loaded even twice, so it was clear where this error message was coming from.

  2. Invocation timeouts: Cloning failed especially for issues with many child issues as the invocation timeout was reached during cloning. The timeout was set to 10s at the beginning which was challenging. The solution was to call requests concurrently and run them in parallel. A concept I had to get familiar with as well, but finally it helped to get the cloning done within the 10s. Today, the invocation timeout has been increased to 25s which was an improvement and provided more time for the actions your app is doing.

Although those issues have been challenging and produced many errors during testing, they can also be seen positive. It requires to make your app lean and fast, and to load only what is necessary. If you only have 10s until an action needs to be completed, that means that also the user will have to wait only 10s later on until the cloning (in this case) is completed. Especially if you don’t get any feedback on the actual progress of the operation, like it was the case here with UI kit, it is important that users don’t have to wait too long until the process is completed. Otherwise, they might think that something is wrong or that the app isn’t useful for what they want to achieve.


The above-mentioned improvements for execution time and payload were necessary as the app didn’t work otherwise. Other improvements have been made to make the apps code more structured and better to understand. At the beginning, we wrote the entire code in one file (index.jsx). In order to have a better structure, the apps code has then been separated into the following files:

  • index: Modal dialog with form and different screens

  • components: Single form fields (UI kit components)

  • fetchdata: Rest APIs to get information like projects, issue data, fields, etc.

  • core: Main cloning function, which is called from the index file

App architecture before and after

There was one additional improvement we made, which even resulted in an additional feature compared to the server version of the app and which came due to a limitation in Forge in the beginning. When we started developing, it wasn’t possible to add display conditions to the issue action module, so it was visible to everyone in every Jira issue (today it is possible). Due to that, we weren’t able to limit access to this cloning feature only to Epic issue type like it is in the server version. We thought it might be a useful feature also for other issue types with child issues to clone them to different projects (not possible with the default clone functionality) and to decide which child issues to clone and which not. Due to the lack of display conditions, we even enhanced the functionality of the app. So, sometimes also limitations can lead to improvements.

If you are interested in more details on the migration of our server app to the Cloud using Forge UI kit, check the following video from Dev Day 2021 with an interview where I shared my experiences and with a short app demo.


Finally, we achieved to develop our first Jira Cloud app using Forge UI kit and publish it on the Atlassian Marketplace. Even if some features were missing compared to the server version, we received positive feedback and several of our customers who migrated from server to Cloud continued to use Epic Clone, now as Cloud version build with Forge.

From UI Kit to Custom UI

Although the app migration to Cloud went well and the customers were happy, there were still limitations with UI kit and not all features from server could be migrated to Cloud with UI kit. The obvious solution seemed to be to change the app to use custom UI instead as it promised to be more flexible and to provide various additional possibilities.

Everything new with custom UI

Therefore, in 2022 I started again to learn something new. Ok, not completely new as I already knew Forge and also did run through the one custom UI tutorial that exists before. The goal was to migrate the existing Cloud app Epic Clone to custom UI to be able to add missing features and improve the user experience. The tutorial app only displays “Hello World” in an issue, which isn’t that helpful when you want to build something more complex. Luckily, at least one sample app exists that uses custom UI, and I used it as starting point to learn about custom UI, how things can be implemented in a more complex app and how frontend code and backend functions are related. However, probably most helpful was that, again, I didn’t start from scratch. I knew that I would need an issue action module, a modal dialog to display and I knew about the fields that should be placed on the cloning form. Also, the concept with an initial screen for project selection and parent issue information, and a second screen with child issues should remain.

In custom UI apps static resources need to be defined where the fronend code is located. This frontend code communicates with the backend functions via the Forge bridge, it is calling backend functions and receives information to display. The first challenge was the tunneling which works differently than for UI kit apps. It is required for development to see changes immediately on the screen after saving a file. It is necessary to run a react app with your frontend code before starting the Forge tunnel to also see frontend changes in your browser. After I got that to work, it was sometimes confusing as I expected the logging in the terminal window where the tunnel was running. However, for the frontend part, logs are displayed in the browsers dev console. That was something I needed to get familiar with especially as I only developed with UI kit so far.

Furthermore, also a new type of file, type script, came into play during development. As I took files from the example app as starting point and those were written in type script, I kept especially the forms and form fields in type script files. Again it was something I had to learn and get familiar with, e.g. that each variable has to be declared with a type and functions need types and explicitly defined variables as input. However, at the end I liked to write code in type script and it was something new I learned.

UI implementation

The development approach was similar to when we did the initial development with UI kit, we did it step-by-step and field by field to populate the modal with all necessary information. The styling of the fields was more difficult though as it wasn’t enough to just add them like with UI kit. Stylings like margin, padding, or overflow had to be applied to make the entire form look good. But on the other hand, custom UI provided great new opportunities to implement a better user experience. As examples

  • Component and fix version fields could be added which change their selection values immediately after new target project has been selected

  • Component and fix version fields are hidden instantly when a team-managed project is selected as target project, as those fields are not available in team-managed projects

  • When a new target project is selected, it is validated if the issue type of the source issue is available in that project and an error is displayed if not

  • Issue checkboxes show the icon of the issue type instead of issue type name as text (not possible in UI kit, see above)

  • Issue checkboxes are automatically disabled if the issue type is not available in the target project

  • Child issue checkboxes are automatically disabled if their parent issue is deselected for cloning

This list is far from being complete, but it shows how much is possible when using custom UI.

Finally, the first cloning screen looks like this

First cloning screen with custom UI

whereas the second screen with child issues to be cloned is displayed like that

Second cloning screen with custom UI

Switching of screens was again achieved by using a useState hook with a form state variable.

Backend implementation

When it came to implementation of the actual cloning functions, it started to pay off again to decide to switch to custom UI. Whereas in the previous version, the user hit the run button once and started the cloning function, which completely ran in the background with no feedback on the progress, it was now possible to display cloning progress information live on the screen. What issues have already been cloned? Which ones are still to do? Which issue is currently cloned? All that can now be displayed on the screen when the function is running. We implemented that by sending the cloning requests sequentially from the frontend to the backend functions, starting with the parent issue, followed by child issues, sub-tasks and optionally linked issues. Whenever one issue is cloned successfully, the fronend variables are updated and the users sees this updated information immediately.

Cloning procedure

For the user, it is displayed on the result screen which issue is currently in progress, which issues are done, and which ones are pending. This is a great improvement compared to the situation before where only there was only something displayed when cloning was completed.

Result screen

By changing the app to custom UI, several features could be implemented that weren’t possible before and so the Cloud version made a huge step forward. The feature gap compared to the server version became smaller and will hopefully be decrease even more in the future. In this regard we also depend on the further improvement of Forge as for example, cloning of attachments is not yet possible in a Forge app.

Building a Custom UI App from scratch

For Codegeist 2022 we built a new custom UI app from scratch. It is called Project Milestones for Jira. This time we didn’t have something that we migrate from server to Cloud where the concept was already defined. We decided to do something new with a part of Forge we didn’t used before.

The project milestones app adds a project page to each project in Jira where users can split up their project into smaller pieces called milestones. Imagine you have a large project running for several years. Of course, you can summarize issues in epics and plan stories in sprints. But sprints usually have a length of 2 weeks. What about the long-term goals and steps that needed to be achieved during the project? They can be planned with the project milestones app.

The project page module in Forge was something new to us and also, we started with a blank page we had to fill with content. Furthermore, it is the first app where we don’t take content from Jira and modify it (like with Epic Clone), but we create our own app content and store it in a project. For that purpose, we used the Forge Properties API. Beside the project page we also add an issue panel which can display information from the project milestones in a Jira issue. That means that with this app we have 2 Forge modules in one app for the first time.

UI implementation

When looking at the frontend implementation, the most challenging part was to fill the blank page with content. We realized very early that just adding some information to the page makes it look weird and confusing. We had to use custom styles to arrange the list of milestones appropriately. For that purpose, we used styled div and span elements loaded from a styles file where they are defined. What was new to us was that we are able to pass attributes to the styled components which change their appearance. As an example, we added the information if a milestone is complete or not to the styled components and when it is completed, it is displayed with a green background. If not completed, the background is white.

The scrolling behavior on both project page and in the issue panel was a challenge. With wrong settings, the buttons on a modal dialog suddenly moved outside of the dialog or content flowed over the borders of the page. It was tricky to find the right settings but finally we managed it for project page and modal dialogs. However, for issue view we are still not completely satisfied with the scrolling behavior and try to find a way to e.g. show a select list that to be displayed also partly outside the panel borders.

For the issue view, it was another challenge to make editing possible and look nice directly on the issue panel. At first, we tried to implement modal dialogs as we did it for editing on the project page. However, as we selected a small size for the panel, the dialog finally only displayed within that panel, so it wasn’t visible at all. After searching in the community and looking into the documentation, we finally decided for something else as displaying a modal dialog from an issue panel but in front of the entire issue seemed to be too complicated. We now display the milestone select field directly on the issue panel so users can make changes directly there without a modal dialog that pops up.

The following screenshots provide an impression of the frontend implementation of the app.

Project page - list of project milestones
Create milestone dialog
Map issues dialog
Project context in issue view


Backend implementation

As mentioned above, we needed two Forge modules in one app, which was something new. At first, we tried to implement it by adding another static resource folder and linking it in the manifest to the issue panel module. However, this didn’t work as we got error messages saying that, for instance, certain hooks are already in use and can’t be used twice. The display of the issue panel worked fine, but as soon as we wanted to implement a button that changes a variable via a useState hook, the error appears, and it didn’t work as expected. Looking into the community helped to find a solution here. We use the following code on top level in our App.js file to distinguish between context. When we are in the project page context, the project page code is loaded, whereas when we are on issue level, the issue view code is loaded.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 import React, { useEffect, useState } from 'react'; import { view } from '@forge/bridge'; import IssueView from './pages/IssueView'; import ProjectPage from './pages/ProjectPage'; function App() { const [context, setContext] = useState({}); const [isDetectingContext, setDetectingContext] = useState(true); useEffect(() => { setDetectingContext(true); view.getContext() .then(setContext) .finally(() => setDetectingContext(false)); }, []); if (isDetectingContext) { return <div>Detecting context...</div>; } switch (context.moduleKey) { case 'pci-project-page': return <ProjectPage />; case 'pci-issue-panel': return <IssueView />; default: return <div>Cannot Detect Context</div>; } } export default App;

Again, the community helped in this case as we obviously weren’t the first developers with that problem.

The properties API was also something new to us we didn’t use before. We chose it instead of the storage API as we remain in the project level and don’t need to store information on top of that or information that needs to be shared across projects. The properties API seems to be the right solution for it. We used the example app as a blueprint as it is using the same API for storage and modified the code, so it fits to our purpose. It was great that we were quite flexible in how the data we store is formatted and that we could tailor it as we need it. When you define for your app individually how data is stored, there is no surprise on what you get when you read this data on a different loaction (e.g. in issue view). We were able to reuse code from the project level on issue view as it was the same data we read from the storage.

Compared to Epic Clone, the focus for the project milestones app was on the frontend. The backend part has been new to us, but it wasn’t that complex as it was for other apps we built so far.

Here you can watch a demo video of Project Milestones for Jira:


What’s next?

Forge custom UI opened up so many new possibilities for us. We now migrated one existing app from UI kit to custom UI and built one from scratch. For both apps, numerous ideas for new features exist and we are looking forward to building them with custom UI. For Epic Clone we think about the following feature:

  • Cloning of attachments (as soon as it is possible with Forge)

  • Bulk clone of epics/issues with child issues (e.g. based on a filter or selection from project issues)

  • Improved cloning of links (clone also links of child issues; retain links of child issues among each other)

  • For epics: Adding an option to keep issues in epic in source project, whereas cloning of epic to a different project

  • Advanced settings, e.g. cloning only allowed for project admins or disable the app in certain projects

For Project Milestones for Jira our first goal now is to bring the app to market, so publish it on the Atlassian Marketplace. Furthermore, we think of the following new features for the future:

  • Map issues automatically to all milestones at once

  • Issue mapping based on other field than due date

  • Milestone approval workflow

  • Connection to releases - automated release creation

  • Link to Confluence documentation

  • Permission settings

As you can see, there is still a lot to do for us and we are looking forward to it. We hope you enjoyed our Forge story.

As always, if you have any questions, want to discuss something, or have any other request, feel free to reach out to us via or our Service Management.