Hi, I'm Mia Tamm from Simpleasyty. Some time ago, a large organization already using Simple Tables came to us with a requirement that sounded fairly simple: they liked working with tables directly in Confluence, but they didn't want the underlying table data to be stored as part of the page content.
That was the challenge.
Storing data with the page makes perfect sense for many use cases. It's simple, keeps the data close to the content it belongs to, and requires very little thought from the user. But this organization had different requirements. They wanted to keep using Simple Tables while separating the dataset from the Confluence page itself.
So we started looking at Forge Storage.
What initially looked like a relatively straightforward change — moving rows from one storage mechanism to another — ended up raising much more interesting questions. Should Forge Storage replace page storage, or should customers be able to choose? Should individual editors make that decision, or should administrators be able to define an organizational policy? And once the data is stored separately, could we use that architecture to provide better controls for sensitive information?
One customer request ended up changing quite a lot about how we think about data in Simple Tables.
First, we separated the table from its storage
One of the useful things about Forge is that an app can use persistent Atlassian-hosted storage without necessarily requiring a vendor-operated external database.
For this particular requirement, that gave us the architectural option we were looking for: the table could continue to exist and behave normally on a Confluence page while its underlying rows were stored separately using Forge-hosted storage.
For eligible apps, Forge-hosted persistent storage also works with Atlassian's data residency capabilities and forms part of the architecture behind Runs on Atlassian.
This doesn't remove the app developer's responsibilities around authorization, scopes or application behavior. Forge follows a shared responsibility model. But it gives developers an interesting building block: having a separate application data layer doesn't automatically require operating a separate vendor database.
For us, however, simply moving every Simple Table to Forge Storage didn't feel like the right answer.
Not every table needs the same storage
Consider two very different datasets.
One contains:
Country · Currency · Region
Another contains:
Employee · Project · Hours · Internal Rate
They're both tables, but an organization may have very different requirements for the information inside them.
So instead of replacing one storage mechanism with another, we made storage configurable at the table level.
A static Simple Table can use Page Content, a Confluence Attachment, or Atlassian Store, backed by Forge-hosted storage.
For the person working with the table, very little changes. The interesting difference is underneath it.
This solved the original requirement, but immediately exposed another problem. If an organization specifically requires certain data not to be stored with the page, simply adding a storage dropdown isn't enough. Every editor would need to remember which option they were supposed to choose.
We didn't think that was a particularly good policy.
The administrator needs control too
Simple Tables therefore allows administrators to define which storage options are available and which enabled option should be the default.
An organization can allow all three storage mechanisms, restrict some of them, or make Atlassian Store the default. Editors continue working normally, but within the boundaries established by their administrator.
This was an important point in the process for us. What had started as a new storage option had become storage policy.
Instead of asking every editor to understand the architectural implications of each choice, the organization can establish those boundaries centrally.
And once the table data was separated from the page, we started asking what else that architecture could allow us to do.
Hiding a column isn't necessarily protecting its data
Imagine a project table like this:
Employee | Project | Region | Hours | Internal Rate |
|---|
Sarah Mitchell | Atlas | UK | 32 | £125 |
Michael Chen | Apollo | Germany | 40 | £140 |
Emma Wilson | Orion | France | 28 | £115 |
Perhaps everyone with access to the page should be able to see the employee, project, region and hours, while Internal Rate should only be available to editors.
The obvious solution is to hide the column from readers.
But there's an important difference between not displaying a value and not providing that value to the user.
If the complete dataset is sent to the browser and the application subsequently hides Internal Rate, the reader may not see it in the table, but their browser has still received the value.
That wasn't the behavior we wanted. So we built Incognito.
When a column is marked Incognito, the table uses Atlassian Store. For users without page-edit permission, Simple Tables doesn't simply hide the column in the interface. The protected column and its values aren't included in the table data returned to that reader.
An editor can receive:
Employee | Project | Hours | Internal Rate |
|---|
Sarah Mitchell | Atlas | 32 | £125 |
Michael Chen | Apollo | 40 | £140 |
while a reader receives:
Employee | Project | Hours |
|---|
Sarah Mitchell | Atlas | 32 |
Michael Chen | Apollo | 40 |
The important part isn't that the column disappears. The reader doesn't receive those protected values in the table data response.
That distinction became important to us once we started thinking about tables containing information with different levels of sensitivity.
But then another question appeared.
What happens if someone tries to calculate with protected data?
Suppose Internal Rate is Incognito.
A user could reasonably want to create:
Project Cost = Hours × Internal Rate
But that creates a problem. Even if Internal Rate itself isn't returned to a reader, Project Cost contains information derived directly from the protected field. Depending on what other information is available, it might even be possible to infer the original value.
We decided to take the conservative approach.
Incognito fields can't be used as inputs for calculated columns.
Rather than trying to determine whether every possible formula is safe, Simple Tables prevents a protected field from becoming an indirect source of information through a calculation.
It's a small product rule, but it reflects something we've learned while working on these features: protecting a value also means thinking about where that value could reappear.
Sometimes the safest behavior isn't to make the system clever enough to follow every possible dependency. It's to make the boundary simple and predictable.
Sometimes it's the whole dataset
There are also cases where the issue isn't one particular column. The organization considers the dataset itself sensitive.
That led us to Confidential tables.
When a table is marked Confidential, Simple Tables automatically requires Atlassian Store. Other storage options aren't available while that classification remains enabled.
Importantly, Confidential doesn't replace Confluence permissions. Page and space permissions still determine who can access the content.
We've ended up thinking about these as separate responsibilities. Authorization determines who can access the content. Classification describes how the dataset should be treated. Storage policy determines where its underlying data can live. And field protection determines which parts of that data are returned to a particular user.
They're related, but they're not the same thing.
One customer request took us quite a long way
It's interesting to look back at the original requirement.
A customer essentially asked us:
“Can we use Simple Tables without storing the table data in the Confluence page?”
The answer was yes.
But solving that properly took us much further than simply adding another storage backend.
What started with Forge Storage gradually became:
Storage → Choice → Admin policy → Classification → Field protection
And along the way we also had to think about less obvious things, such as preventing protected information from simply reappearing through a calculated column.
Forge gave us somewhere else to store the data. The real product work was deciding what controls to build around it.
The person creating a table shouldn't need to understand Forge architecture, data residency or server-side filtering. They should just be able to work with their table.
But the organization responsible for that information should be able to understand where the data lives, who can receive it and what policies apply to it.
That's probably the most interesting conclusion we've reached from the whole process.
For those of you working with larger Confluence environments, I'd be interested in your experience: have you encountered requirements where data needs to be used in Confluence, but shouldn't be stored as part of the page itself?
And when Security or Enterprise Architecture reviews an app handling that data, what do they ask first?
—
If you'd like to explore the implementation described above, these capabilities are available in Simple Tables for Confluence, including Atlassian Store, admin storage policies, Confidential tables and Incognito fields.