Async events API
Fetch API
Storage API

Rate this page:

Reference

The app storage API is an untyped key-value storage API which allows your app to store data. The app storage API stores data partitioned by Atlassian product and site, and is accessible only to your app.

Recommendations

When using the app storage API, we strongly recommend that you:

  • only use encrypted environment variables and storage.setSecret to store secrets or credentials in your app.
  • avoid using the app Storage API for storing files.
  • avoid employing data models with entities that grow in an unbounded manner (in terms of depth and/or size), as this may lead to exceeding storage limits.

The Atlassian Cloud backs up the entire hosted storage for disaster recovery. This includes content stored from the Forge storage API.

We will add admin-driven backup (and restore) capabilities for app-level data in a future update.

Partitioning

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:

  • Only your app can read and write your stored data.
  • An app can only access it's data for the same environment.
  • Your keys only need to be unique for an individual installation of your app.
  • Data stored by your app for one product is not accessible from other products. For example, data stored in Jira is not accessible from Confluence or vice versa.
  • Your app cannot read data from different sites, products and app environments.
  • Quotas and limits are not shared between individual installations of your app.

Eventual consistency

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.

Conflict resolution

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.

Supported values

The app storage API is able to persist any JSON data type except null. For example:

  • arrays
  • booleans
  • numbers
  • objects
  • strings

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.

Cursors

The app storage API uses cursors for paginated data access. The Query API 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 may not always be stable and shouldn't be persisted.

Please note 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();

Key ordering

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
2
import { 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: