This is a guest post by Safwan Shaheer, Developer Intern.
Notion and Confluence are both powerful tools to help individuals and teams stay productive, and our latest Confluence macro makes it possible to integrate them. Notion is an application consisting of components called “blocks” – such as text, databases, image, video, audio, and so on – which users can connect to create a system for knowledge management, note-taking, data management, project management, and many more use cases. Atlassian’s Confluence, on the other hand, focuses more on the corporate side by providing a single workplace to create, collaborate, and organize all your work. It also provides tight integration with other tools in the Atlassian ecosystem.
With our new Confluence macro, Notion for Confluence, we enable Notion users to continue to use their chosen system, while also sharing information with their team in Confluence.
In this blog post, we’ll look at the development of the Confluence macro, including: why we decided to build it, how we built it, how it works, and the challenges we encountered.
Inspiration for Building Notion for Confluence
We decided to create this Forge macro to make information more accessible and collaborative when working with both Notion and Confluence. Specifically, the macro was built to:
- Make it easier to access Notion content in Confluence. Switching between Confluence and Notion can be difficult, as both have a unique way of presenting content. Anyone comfortable with Confluence will have a hard time switching to Notion, as the UI/UX and functionality is completely different. It’s crucial to create a bridge between these two worlds where less time will be spent on context switching and more on what matters: displaying the content. Thus one of our primary inspirations was to give users access to their Notion content all from the comfort of their Confluence page.
- Enable access to Notion without a Notion account. It’s not an easy task to learn the basics of Notion without dedicating a considerable amount of time to it. If anybody in the team wants to work in Notion, they will introduce friction in the workflow as the other team members would also have to create an account and learn at least the basics. This macro eliminates this step. By making a Notion page public, anybody having access to the macro can view the content of that page without even having a Notion account.
- Keep one central source of information. As mentioned earlier, Notion and Confluence are two vastly different worlds, each with unique strengths. In a word, they are not compatible. Viewing the same content in both apps and keeping them in sync could prove to be extremely cumbersome if not outright impossible. You will have to come up with a complex automation system to keep them in sync. Plus it’s always better to keep a single source of truth for your content. Taking advantage of Notion’s rich ecosystem of blocks, without having to worry about keeping things in sync and replicating them was another significant inspiration behind the creation of this macro.
What Notion for Confluence Does
We designed this macro with simplicity in mind. You can render any public Notion page inside of Confluence just by providing its link. Additionally, if you want to generate a private Notion page, you can do so by providing your Notion authentication credentials.
The macro supports all the basic Notion blocks (backlinks, page/block level comments, inline dates, mentions, etc.), along with several advanced blocks. Support for other types of blocks is one of our top priorities. Currently the macro is read-only, meaning you can’t edit/update the contents of your Notion page using this macro.
Here’s a short demo of how Notion for Confluence works:
How We Built Notion for Confluence
Frontend Tech Stack
In the frontend, we decided to go with React, Typescript, and Snowpack combination, as it provided the best developer experience and fastest code iteration in our opinion. Here is a simple breakdown of these technologies and why we choose them.
- Snowpack is a lightning-fast frontend build tool, designed for the modern web. Choosing our bundler was a far difficult task. Initially, we started with Create React App, using Webpack bundler underneath the hood. But as the project grew larger, there were significant performance concerns, and thus the DX took a hit. After experimenting with a few other bundlers like Parcel and Rollup, we finally landed on Snowpack and haven’t looked back.
- @forge-bridge is an npm package created by the Atlassian developer team, to call backend resolvers from our frontend. This is the only way to call our lambdas since forge macros can’t directly use the Fetch API.
Backend Tech Stack
Our backend resolvers would run on a restrictive Forge environment, which would not support a lot of things out of the box. As a result, most of the packages that we used on the backend were from Atlassian’s developer team. Some of them are:
- @forge/api: This package exposes all the runtime APIs in Forge macros, making the process of storing user data and sending API requests much easier.
- Unofficial Notion API: We went with Notion’s internal API, as it gave us higher control over the data that we are fetching and enabled us to provide support for a lot more features than what we would’ve achieved using the public API.
How Notion for Confluence Works
Notion provides a lot of flexibility when it comes to connecting blocks. As a result, a single page can reference multiple other blocks, which might not seem obvious at first glance. But after close inspection we managed to detect most of them, such as:
- Page aliases (embedding an external page)
- Collection (a block describing the schema of a database)
- Notion users that have access to the block and the space
- Block-level and page-level comments
- Backlinks (a feature that allows a page to see which other pages are referencing it)
- Nested pages (embedding a nested page)
Therefore we needed to make sure that our macro not only fetches and renders the page but also all the blocks connected to it. The whole render process basically follows these steps:
- At first the client sends an API request to get the initial page data via its id
- Once it has that, it needs to build a network of blocks connected to the fetched page data, which then needs to be fetched.
- Once the client has all the connected data, it starts the rendering phase, where we render the fetched data in the UI, keeping it as close to Notion’s native UI as much as possible.
Currently we don’t make use of Notion’s public API. Even though the public API has a lot of abstraction to cover away from the intricacies and complexities of the data that Notion provides, we had no choice but to revert to the internal API simply because of enhanced functionalities. For example, the public API doesn’t have support for all types of blocks and requires some setup from the user’s end, which introduces a bit of friction while using the macro.
When it comes to authenticated requests, Notion uses a token that is generated as a product of an auth flow, which could either be an OAuth or regular email/password flow. We tried to add support for both of them. But unfortunately adding support for OAuth 2.0 flow proved to be unattainable. Right now we only support authentication via basic email/password. Once the token is generated after a successful login, it’s stored in Forge Secret Storage, and for all authenticated requests it uses the token after retrieving it from there. So you can just log in once and have access to your Notion content from any device anywhere. We have also added a logout feature to log you out as well.
Challenges We Ran Into
We faced several challenges while building this macro. Most of the time the source of the issues was related to the technologies we used, for example, React or Snowpack. Regular things that work on a web environment didn’t work in Forge environment. But for more internal issues like how Notion works, or how does its auth flow work, we had to dig deep and come up with a solution. Here are some of the more challenging issues we had to face.
Making Sense of the Data
The first challenge we faced was to make sense of the data that Notion’s internal API provided. Notion consists of several types of blocks, and a lot of them are interconnected and dependent upon one another. We needed to figure out which data the root block (page/database) required to render completely. The process required a lot of trial and error and after a lot of inspection, the shape of the data and its dependencies began to make sense.
Rendering the Data
Making sense of the data would not mean much if we couldn’t render it properly. Notion has its own unique UI/UX that has a lot of moving parts. We had to form a connection between the data and the UI, which was a big challenge. As such, we had to spend a lot of time trying to emulate the look and feel of the actual Notion app, which we were able to do to a certain extent.
OAuth authentication is a staple for any modern app and notion is no exception. But when you are trying to emulate the flow artificially, there will be some limitations. Unfortunately, even after several attempts, we were unable to integrate OAuth sign-in into the macro. A feature that is quite simple to implement in a web app turned out to be a bit more complicated due to how Notion performs it in the actual app.
Forge’s Timeout Limitation
Forge has a timeout limitation on how long a resolver can compute before being timed out. According to its documentation, it’s 10 seconds. Remember that we had to rely on Notion’s internal API, and, well, it had its downsides. It didn’t fetch the right content most of the time, which required us to make a few more requests to get the rest of the required data. So we hit the timeout limitation quite frequently during the early development stages.
What We’re Proud Of
Even though there were some initial hurdles, we have managed to overcome most of those challenges. Here we share some of our major accomplishments and how we achieved them:
- Navigating the data. Rendering UI is never an easy task. And when the data is as complex as that of Notion’s, it’s at least twice as hard. Notion has a complex ecosystem of interconnected components, which requires a complex API with lots of moving parts. At first glance, it might seem that the data is simple enough to make sense of, but once you go down that rabbit hole you begin to realize that it’s easier said than done. One of our biggest priorities was to not only know what data we required, but how they are connected. Being able to do so was a major accomplishment in its own right.
- Providing public and private Notion access. Notion supports granular content access, where you can share your content with the whole world or just between your team. Therefore, we wanted to support a user’s public content and also give user’s the ability to access their private content given the correct credentials. Almost all modern app supports OAuth authentication as well as email/password combination. We are aware that the current mechanism of email/password combination is suboptimal, but we wanted to make sure you can access your private notion content.
- Replicating the UI. Notion’s design system is quite complex. It’s a battle-tested software that’s been in the market for quite a while and has gone through quite a few iterations. If we couldn’t get the UI/UX part right, you would not feel that you’re interacting with Notion while using the app. So we had to make sure that we can get as close to the original UI as possible.
- Reducing timeout limits. Forge is an amazing platform for building apps on Atlassian Cloud and it does offer generous platform limits. That being said at times even that was not enough, unfortunately. But the good thing was we could overcome this limitation with the right strategy. By splitting the resolvers into indivisible lambdas, we managed to reduce the computation time by a significant portion, so much so that timeout limits are now extremely rare to the point of being invisible.
What We Learned
- The Notion API: We have gained a vast amount of knowledge and experience working with Notion’s internal API, so much so that we are confident we will be able to deliver updates at a faster rate, even if we end up using the public API.
- React is a market leader for a reason, and our experience with it proved why that is the case. Add Typescript along with a faster bundler like Snowpack on top, and you have an industry-grade battle-tested combination of frontend technology.
- The secret to overcoming the Forge lambda timeout limit was by splitting out related and connected computations into several lambdas. Even though this will result in a bit more delay on the client-side as several chunks of requests are being made rather than one giant one but with the right approach like a custom spinner, the UX can be vastly improved.
What’s Next for Notion for Confluence
This is just the start for Notion for Confluence. We have plans to add support for all the native blocks that Notion’s client provides including databases and other complex ones. We also want to make the auth process a lot easier than it’s now at present by introducing google OAuth, and maybe soon add the ability to modify the page as well all from the comfort of this app.