Inspiration
While helping a user add Jira Assets information to a Confluence page (something I hadn't done before), I ran into several frustrations with the default integration. Displaying even basic information proved a challenge. Adding multiple attributes from a single object became increasingly cumbersome as the page grew. And creating a well-formatted, visually appealing layout felt nearly impossible with the available tools.
These limitations got me thinking about what a better solution might look like. That initial frustration evolved into the concept for this app, and as I explored the idea further, I identified additional features.
What it does
At its core, the app lets you configure an AQL filter that can be referenced from anywhere on the page. This simple change makes a substantial difference: instead of selecting the same Asset repeatedly for each attribute you want to display, you define the filter once and reference it wherever needed.
It's even possible to make pages become truly dynamic. Users can switch between different Assets without entering edit mode, making the content interactive rather than static. Filters can also depend on other filters, enabling you to build complex queries. Finally, a number of purpose-built macros renders Asset attributes in ways that integrate seamlessly with the rest of your Confluence content, giving you full control over presentation.

How I built it
The project started with direct API testing to validate that the core concept was actually feasible. Once I confirmed the approach would work, I built the app using Forge. To enable communication between macros on the same page, which is essential for the dynamic behaviour, I leveraged Forge's event system.
Challenges I ran into
Synchronising state across all macros on a page proved to be one of the trickier problems. Macros needed to stay in sync with eachother, and changes had to propagate correctly throughout the page. Implementing filter dependencies added another layer of complexity, as one macro's output could influence another's query.
One of the macros allows you to display an image from an arbitrary URL. Unfortunately the "Runs on Atlassian" label requires that you only have egress traffic to predefined URLs which are configured on the app level in the manifest.yml. It would be more interesting if the user could decide on instance level which URLs they want to allow. In the end I decided to not expand the egress rules and keep the label as many websites nowadays block direct image inlining anyway. Since the instance URL is included, the user has the choice to upload the images somewhere in their Confluence or Jira instance and direct link to that.
The app's usage of many macros also pushed forge deploy and forge tunnel to their resource limits. This became frustrating during development: file changes would sometimes trigger partial macro reloads, leaving the page in an inconsistent state.
When dealing with complex pages with many Asset references I noticed they generate a significant amount of API traffic. To keep performance acceptable, I made use of React's useMemo to avoid unnecessary re-fetching and re-rendering.
Finally, when it came time to submit my project, I ran into an unexpected issue with the installation link. Since the app needs to access data in both Confluence and Jira, I made use of the new multi-app capabilities currently available in Forge Preview. However, it appears that you cannot generate an installation link for apps using this new functionality unless they have already been published to production at least once. This creates a bit of a catch-22 for new apps built with multi-app support from the start.
Accomplishments that I'm proud of
The achievement I'm most pleased with is making the entire page update dynamically. This started as a fairly vague idea, and my first prototype edited completely static pages. Seeing it evolve into a system where content responds to user interactions in real time felt like a genuine breakthrough.
I'm also proud of how macro communication works. All the coordination happens entirely in the browser, without relying on storage APIs or backend calls. This keeps the app fast and avoids the complexity of managing server-side state.

What I learned
Sending a message from one macro to another turned out to be straightforward. The real challenge was managing many macros together in a complex dependency tree that could change dynamically at runtime. Working through these problems gave me a much deeper understanding of how Forge, React, and asynchronous patterns interact within the browser environment.
What's next for Beautiful Assets for Confluence
Several features remain on the roadmap that I couldn't complete due to time constraints. These additions could make the app significantly more valuable for business users. I plan to continue development and explore whether there's commercial viability for an official launch.

Log in or sign up for Devpost to join the conversation.