In 2024, Forge hosted storage will automatically include data residency. This means that all app data in Forge hosted storage will inherit data residency, in all current and future Atlassian-supported regions.
This implementation will provide your customers with greater control over their app data’s location. For more information, read this announcement.
The Storage API lets you store and query values in Forge hosted storage rather than through each product's API. Data stored using the Storage API isn't shared between Forge apps on the same site or across different Atlassian sites.
The Storage API provides multiple resources for storing, querying, and otherwise managing app data:
Each installation of your app is subject to the Storage API's quotas and limits. See Storage quotas and Storage limits for more details.
When using the app storage API, we strongly recommend that you:
The Atlassian Cloud backs up the entire hosted storage for disaster recovery. This includes content stored from the Forge storage API.
All data stored within the app storage API is namespaced. The namespace includes which Atlassian site, product, and Forge environment your app is installed on. As a result:
The Atlassian cloud provides features that allow admins to control and verify where their Jira and Confluence data is hosted. These features allow them to meet company requirements or regulatory obligations relating to data residency.
The Storage API uses this same cloud infrastructure to store data. This allows Forge to extend similar data residency features to your app. All data stored on the Storage API automatically inherit these features.
Specifically, if your app stores data through the Storage API, an admin can control where that data is stored. For more details about how this works, see Data residency.
The read and query APIs within the app storage APIs are eventually consistent. This means that the data returned from the API may be slightly out of date.
Writes to keys using set
or delete
use a "last write wins" conflict resolution strategy.
Writes to individual keys are atomic - For example, the value is either updated in full or not.
The app storage API is able to persist any JSON data type except null
. For example:
The JavaScript storage API serializes your objects using JSON.stringify
, and as such
removes functions and the value undefined
from any object you attempt to serialize.
The app storage API uses cursors for paginated data access. The Query builder returns a cursor in the results, which can be provided to subsequent queries to paginate over larger data sets than you would otherwise be able to fetch.
1 2// Fetch a page of 10 results const { nextCursor } = await storage.query().getMany(); // Fetch the next 10 results await storage.query().cursor(nextCursor).getMany();
Cursors are derived from underlying storage identifiers, and hence are subject to change anytime there is any change in how these underlying storage identifiers are created. This means that cursors are not stable and should not be persisted.
You will have to use the same parameters as the initial query. See the example below.
1 2// Fetch 15 results const { nextCursor } = await storage .query() .limit(15) .where('key', startsWith('account.')) .getMany(); // This is expected usage await storage.query() .limit(15) .where('key', startsWith('account.')) .cursor(nextCursor) .getMany(); // This will produce undefined results await storage.query() .cursor(nextCursor) .getMany();
Keys are lexicographically ordered - as such the Query API will return entities ordered by their key. This property can be used to group related entities or build adhoc indexes.
The app storage API does not have support for indexing of arbitrary attributes. However, it is possible to support this sort of access pattern if your keys are constructed in such a way as to support indexed reads.
Hierarchical keys can be constructed to allow for nested entities to be fetched in a single list operation such as the example below.
1 2import { storage, startsWith } from '@forge/api'; // Nested entities await storage.set('survey-responses#1#{UUID}', { }); await storage.set('survey-responses#1#{UUID}', { }); await storage.set('survey-responses#1#{UUID}', { }); await storage.set('survey-responses#1#{UUID}', { }); const results = await storage .query() .where('key', startsWith('survey-response#1#')) .limit(10) .getMany();
Rate this page: