Last updated Apr 25, 2024

Accessing Forge storage from a remote

Once your remote backend has received a request from Forge, you can access storage GraphQL APIs.

Prerequisites

When setting up your app to:

  • Call a remote backend (from a Custom UI or UI Kit 2 frontend)
  • Send product and lifecycle events (to a remote)
  • Configure scheduled triggers to invoke a remote backend

You must set endpoint.auth.appSystemToken to true in your manifest.yml. This will ensure requests to your remote contain a x-forge-oauth-system header, that contains a token that you can use to call Forge storage APIs. This token is valid for at least 55 minutes.

Your app must also request the storage:app scope and the read:app-system-token scope to access Forge Storage APIs. This can be done by including them in the permissions section of your app's manifest.yml file.

Calling the Forge Storage GraphQL APIs

Capabilities

Access to Forge storage is provided to remote backends through GraphQL. You can get, set, update, and delete information in your app's Forge storage with GraphQL queries.

Authentication

Atlassian GraphQL APIs only accept OAuth bearer tokens as authentication. Your app must supply this token in the Authorization: Bearer {bearerToken} header of the request.

Requests

All Forge Storage GraphQL requests should be HTTP POST requests to the endpoint: https://api.atlassian.com/graphql.

The request body should be JSON encoded and of the format:

1
2
{
  "query": "...",
  "operationName": "...",
  "variables": { "myVariable": "someValue", ... }
} 

Where:

Responses

The response will be in JSON format and of the shape:

1
2
{
  "data": { ... },
  "errors": [ ... ]
}

Where:

  • data contains the field requested in the GraphQL query.
  • errors is a list of errors that occurred. The field is only included if there was an error when executing the request.

Error handling for Forge Storage requests

Example response for a query that attempts to get a key from an invalid context

When making the following GraphQL query with an invalid contextAri:

1
2
query badGet {
  appStoredEntity(
    contextAri: "ari:cloud:confluence::site/bad-site",
    key: "test-key",
    encrypted: false
  ) {
    value
  }
}

The app receives the response:

1
2
{
    "errors": [
        {
            "path": [
                "appStoredEntity"
            ],
            "locations": [
                {
                    "line": 2,
                    "column": 3
                }
            ],
            "message": "Context ari:cloud:confluence::site/bad-site is not accessible in this request",
            "extensions": {
                "statusCode": 400,
                "errorType": "CONTEXT_INVALID"
            }
        }
    ],
    "data": {
        "appStoredEntity": null
    }
}

HTTP errors for GraphQL operations

Your app may also encounter HTTP errors in the event of some severe issues. For instance:

  • HTTP 401 is returned if the OAuth token provided as authorization for the request was invalid or missing.

  • HTTP 400 is returned if the query was malformed (e.g - requesting for a field that doesn't exist or using invalid GraphQL syntax).

  • HTTP 500 is returned if the GraphQL Gateway is down.

GraphQL query examples

You will see contextAri as an input variable being used for the majority of Forge Storage APIs.

The contextAri is associated with the context where a Forge invocation is being performed. It is assembled by concatenating 'ari:cloud:confluence::site/' with context.cloudId obtained from the Forge Invocation Token.

For example: ari:cloud:confluence::site/1234

The examples on this page are provided in the following format.

GraphQL query

1
2
query nameOfQuery($variable: VariableType) {
  fieldRequiringArgs(variable: $variable) {
    fieldA
    fieldB
  }
}

GraphQL variables, to be used in the query

1
2
{
  "variable" : {
    "nestedVariable": "value"
  }
}

This is the equivalent of:

1
2
query nameOfQuery {
  fieldRequiringArgs(variable: {
    nestedVariable: "value"
  }) {
    fieldA
    fieldB
  }
}

With GraphQL queries, you will get back the fields you asked for in your query. The response to the query provided may look something like this:

1
2
{
  "data": {
    "fieldRequiringArgs": {
      "fieldA": "valueA",
      "fieldB": "valueB"
    }
  }
}

Set storage key

Sets the value for a given key.

If the key already exists, its value is updated. If the key does not exist, the key-value pair is added.

You cannot set a key's value property to null. If your app attempts this, GraphQL returns a validation error.

GraphQL query

1
2
mutation forge_app_setExample($input: SetAppStoredEntityMutationInput!) {
  appStorage {
    setAppStoredEntity(input: $input) {
      success
      errors {
        message
        extensions {
          errorType
          statusCode
        }
      }
    }
  }
}
PropertyDescription
successA boolean indicating whether the query succeeded. If false, see errors for more information.
errors.messageA descriptive error message to assist in troubleshooting.
errors.extensions.errorTypeThe type of error that occurred. For example, "INVALID_KEY".
errors.extensions.statusCodeThe status code returned by the error. For example, 400.

GraphQL variables

The encrypted field is how you would store secrets in Forge storage. Setting it to true is the equivalent of the storage.setSecret from the Key-Value Store.

1
2
{
  "input": {
    "contextAri": "contextAri associated with the request",
    "key": "example key",
    "value": "example value",
    "encrypted": true | false
  }
}
VariableDescription
contextAriThe unique identifier for the context in which the GraphQL query is invoked.
keyThe key to set.
valueThe value to set for the key.
encryptedA boolean indicating whether this storage entry should be encrypted.

GraphQL response

GraphQL response (Successfully sets a value)

1
2
{
  "data": {
    "appStorage": {
      "setAppStoredEntity": {
        "success": true,
        "errors": null
      }
    }
  }
}

GraphQL response (Fails to set a value due to an invalid key provided in the request)

1
2
{
  "data": {
    "appStorage": {
      "setAppStoredEntity": {
        "success": false,
        "errors": [
          {
            "message": "Key \"test-key@\" should match the pattern \"/^(?!\\s+$)[a-zA-Z0-9:._\\s-#]+$/\"",
            "extensions": {
              "errorType": "INVALID_KEY"
            }
          }
        ]
      }
    }
  }
}

Example

Set the key "example-key" with the non-encrypted string value of "hello world".

Query

GraphQL variables

1
2
{
  "input": {
    "contextAri": "ari:cloud:confluence::site/1234",
    "key": "example-key",
    "value": "hello world",
    "encrypted": false
  }
}

Response

1
2
{
  "data": {
    "appStorage": {
      "setAppStoredEntity": {
        "success": true,
        "errors": null
      }
    }
  }
}

Get storage key

Gets a value by key.

If the requested key is not found, the value is returned as null.

GraphQL query

1
2
query forge_app_getExample($contextAri: ID!, $key: ID!, $encrypted: Boolean!) {
  appStoredEntity(
    contextAri: $contextAri,
    key: $key,
    encrypted: $encrypted
  ) {
    value
  }
}

GraphQL variables

1
2
{
  "contextAri": "ari:cloud:confluence::site/1234",
  "key": "example-key",
  "encrypted": true | false
}
VariableDescription
contextAriThe unique identifier for the context in which the GraphQL query is invoked.
keyThe key for which to retrieve a value from Forge Storage.
encryptedA boolean indicating whether this Forge Storage entry is encrypted.

GraphQL response

GraphQL response (Successfully retrieved the key's value)

1
2
{
  "data": {
    "appStoredEntity": {
      "value": "example-value"
    }
  }
}

GraphQL response (Key not found)

1
2
{
  "data": {
    "appStoredEntity": {
      "value": null
    }
  }
}

Example

Get the value of the key "example-key".

Query

GraphQL variables

1
2
{
  "contextAri": "ari:cloud:confluence::site/1234"
  "key": "example-key",
  "encrypted": false
}

Response

1
2
{
  "data": {
    "appStoredEntity": {
      "value": "example-value"
    }
  }
}

Query storage keys

Builds a query that returns a list of entities (key and value).

GraphQL query

1
2
query forge_app_queryExample($contextAri: ID!, $where: [AppStoredEntityFilter!]!, $first: Int!, $after: String) {
  appStoredEntities(contextAri: $contextAri, where: $where, first: $first, after: $after) {
    edges {
      node {
        value
        key
      }
      cursor
    }
    pageInfo {
      hasNextPage
    }
  }
}
PropertyDescription
edgesAn array of returned key-value pairs and cursors.
edges.nodeThe requested contents of Forge Storage, returned as a key-value pair.
edges.cursorA string value that can be supplied in the after field of a subsequent query, to fetch the next page of data.
pageInfo.hasNextPageA boolean indicating whether there is still more data that can be returned if the query is invoked again with after set to the cursor value.

GraphQL variables

1
2
{
    "contextAri": "ari:cloud:confluence::site/1234",
    "where": {
        "field": "key",
        "condition": "STARTS_WITH",
        "value": "example key"
    },
    "first": 10,
    "encrypted": true | false,
    "after": "Optional, used for paginated requests. Use the cursor field from the previous query to fetch the next page."
}
VariableDescription
contextAriThe unique identifier for the context in which the GraphQL query is invoked.
whereThe selection criteria for query results.
where.fieldThe field on which to query. This must be set to "key".
where.conditionThe query operator to use. This must be set to "STARTS_WITH".
where.valueThe key for which to retrieve values.
firstThe maximum number of entries this query can return. This can be set to at most 20.
encryptedA boolean indicating whether the data is encrypted or not.
afterAn optional cursor value used to retrieve subsequent pages of data for a query that has more than one page of matching results.

GraphQL response

GraphQL response (Successfully retrieved 2 entries)

1
2
{
  "data": {
    "appStoredEntities": {
      "edges": [
        {
          "node": {
            "value": "test-value-1",
            "key": "test-key-1"
          },
          "cursor": "FAJ0ZXN0LWtleS0xAA=="
        },
        {
          "node": {
            "value": "test-value-2",
            "key": "test-key-2"
          },
          "cursor": "FAJ0ZXN0LWtleS0yAA=="
        }
      ],
      "pageInfo": {
        "hasNextPage": true
      }
    }
  }
}

Example

Get up to 10 results where the key begins with “example”. Start from the cursor location FAJ0ZXN0LWtleS0yAA==.

Set the maximum number of responses to return using the first property. The maximum number of responses you can request is 20.

You can retrieve the value of the cursor from the response of your previous query. If there are more pages to fetch, the hasNextPage field will be true.

Query

GraphQL variables

1
2
{
    "contextAri": "ari:cloud:confluence::site/1234",
    "where": {
        "field": "key",
        "condition": "STARTS_WITH",
        "value": "example"
    },
    "first": 10,
    "encrypted": false,
    "after": "FAJ0ZXN0LWtleS0yAA=="
}

Response

1
2
{
    "data": {
        "appStoredEntities": {
            "edges": [
                {
                    "node": {
                        "value": "test-value-2",
                        "key": "example-2"
                    },
                    "cursor": "FAJ0ZXN0LWtleS0yAA=="
                },
                {
                    "node": {
                        "value": "test-value-3",
                        "key": "example-3"
                    },
                    "cursor": "FAJ0ZXN0LWtleS0zAA=="
                }
            ]
        }
    }
}

Delete storage key

Deletes a value by key.

GraphQL query

1
2
mutation forge_app_deleteExample($input: DeleteAppStoredEntityMutationInput!) {
  appStorage {
    deleteAppStoredEntity(input: $input) {
      success
      errors {
        message
        extensions {
          errorType
          statusCode
        }
      }
    }
  }
}

GraphQL variables

1
2
{
  "input": {
    "contextAri": "ari:cloud:confluence::site/1234",
    "key": "example key",
    "encrypted" true | false
  }
}
VariableDescription
contextAriThe unique identifier for the context in which the GraphQL query is invoked.
keyThe key for which to retrieve a value from Forge Storage.
encryptedA boolean indicating whether this Forge Storage entry is encrypted.

GraphQL response

GraphQL response (Successfully deletes the key)

1
2
{
  "data": {
    "appStorage": {
      "deleteAppStoredEntity": {
        "success": true,
        "errors": null
      }
    }
  }
}

GraphQL response (Fails to delete the key due to an invalid key provided in the request)

1
2
{
  "data": {
    "appStorage": {
      "deleteAppStoredEntity": {
        "success": false,
        "errors": [
          {
            "message": "Key \"test-key-@\" should match the pattern \"/^(?!\\s+$)[a-zA-Z0-9:._\\s-#]+$/\"",
            "extensions": {
              "errorType": "INVALID_KEY",
              "statusCode": 400
            }
          }
        ]
      }
    }
  }
}

Example

Delete the value associated with the key "example-key".

Query

GraphQL variables

1
2
{
  "input": {
    "contextAri": "ari:cloud:confluence::site/1234",
    "key": "example key",
    "encrypted" false
  }
}

Response

1
2
{
  "data": {
    "appStorage": {
      "deleteAppStoredEntity": {
        "success": true,
        "errors": null
      }
    }
  }
}

Next steps

For further help, see how you can:

Rate this page: