Last updatedJul 25, 2019

Storing data without a database

Apps can store data in the form of entity properties in the host application. Entity properties are key-value pairs where the key is a string used to identify the property in all operations, and the value is a JSON blob. Each host application allows properties to be stored on different types of entities, such as Jira issues, Confluence pages, or on the app. Your app will need to request the right scopes to perform operations on entity properties.

Hosted data storage is useful to Atlassian Connect developers for the following reasons:

  • Your app does not need to include a database to store data.
    Your app could be written as a set of static web pages using only HTML, CSS, and JavaScript, without any need for an application server. Your data will be stored against the host application entities.
  • Imports and exports are handled by the host product.
    Since your data is stored with the host application it is included in the host applications backups. This means that the import process will restore your data automatically. With entity properties you never need to worry about your data being lost or disconnected from the customer.
  • Conditions can be predicated on entity properties.
    You can configure whether a web fragment will be shown based on the value of an entity property.
  • The products have access to your properties.
    In Jira's case this means that you can write JQL queries based on issue entity properties. This enables your users to enjoy the power of JQL on search data that you have defined.

Host properties are a powerful tool for Atlassian Connect developers. The following sections provide detailed explanations of how app properties, entity properties and content properties may be used in your app.

App properties

App properties are entity properties stored against the app itself. In this case the app is considered to be the storage container. However, app properties are still unique to each host application: the same app installed on two different host applications will not share the same app properties.

Limitations of app properties

App properties have the following limitations:

  • The properties for each app are sandboxed to the app. Only the app that writes the app properties can read those properties. They cannot be shared or read by other apps.
  • Each app can create a maximum of 100 properties, each property value cannot be more than 32KB in size.
  • The value stored in each property must be in valid JSON format. (Valid JSON format is defined as anything that JSON.parse can read)
  • Requests via AP.request to store and receive app properties can only be made via a logged-in user.
  • There is no mechanism to handle concurrent edits by two users to the one app property. Whomever saves data last will win.

Warning


App properties can be manipulated by a malicious authenticated user (e.g., by making REST calls through the developer console). For this reason:

  • Don't store user-specific data in app properties (particularly sensitive data).
  • Be defensive when retrieving app properties, and don't assume data consistency (arbitrary keys may be modified or deleted by users).

Supported operations

The following operations may be performed to manipulate app properties:

Examples

App properties can be set using Create or update app property:

1
2
3
4
5
6
curl --request PUT \ 
--user 'email@example.com:<api_token>' \
--header "Accept: application/json" \
--header "Content-Type: application/json" \
--data '{"string":"string-value","number":5}' \
 'https://your-domain.atlassian.net/wiki/rest/atlassian-connect/1/addons/{addonKey}/properties/{propertyKey}'

Use Get app property to get the value of the property we just set:

1
2
3
4
curl --request GET \ 
 --user 'email@example.com:<api_token>' \
 --header "Accept: application/json" \
 --url 'https://your-domain.atlassian.net/wiki/rest/atlassian-connect/1/addons/{addonKey}/properties/{propertyKey}'

Here is an example snippet that will show a pop-up with a JSON property named my-property-key for an app with the key my-app-key.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
AP.require(['request'], function(request) {
   request({
       url: '/rest/atlassian-connect/1/addons/my-app-key/properties/my-property-key?jsonValue=true',
       success: function(response) {
           // Convert the string response to JSON
           response = JSON.parse(response);
           alert(response);
       },
       error: function(response) {
           console.log("Error loading API (" + uri + ")");
           console.log(arguments);
       },
       contentType: "application/json"
   });
});

Apart from using AP.request, the same endpoints are accessible via a request signed with JWT.

Conditions based on app properties

App properties can be referenced in following conditions to decide whether or not to show a web fragment:

  • addon_property_exists
  • addon_property_equal_to
  • addon_property_equal_to_context
  • addon_property_contains_any
  • addon_property_contains_all
  • addon_property_contains_context
  • addon_property_contains_any_user_group

For example, the following is a valid condition on the app property activated:

1
2
3
4
5
6
7
8
{
    "condition": "addon_property_equal_to",
    "params": {
        "propertyKey": "activated",
        "objectName": "for-users"  
        "value": "true"
    }
}

The structure of the JSON value of the activated app property might look like this:

1
2
3
4
5
{
    "for-anonymous": false,
    "for-users": true,
    "for-admins": true,
}

Here is an example of a condition requiring that the browser user is in at least one specified group:

1
2
3
4
5
6
{
    "condition": "addon_property_contains_any_user_group",
    "params": {
        "propertyKey": "myListOfGroups"
    }
}

Only if the for-users sub-property is set to true against the app will the condition allow the web fragment to show. Thus you can use this to decide whether or not to show web fragments based on data that you have stored in app properties. This is very useful when you have host application wide configuration that you wish to rely upon.

Confluence content properties

Content properties are a key-value storage associated with a piece of Confluence content, and are one of the forms of persistence available to you as an app developer. The Confluence content that you can store content properties against are:

Content properties can be referenced in the following conditions to decide whether or not to show a web fragment:

  • content_property_exists
  • content_property_equal_to
  • content_property_equal_to_context
  • content_property_contains_any
  • content_property_contains_all
  • content_property_contains_context
  • content_property_contains_any_user_group
  • space_property_exists
  • space_property_equal_to
  • space_property_equal_to_context
  • space_property_contains_any
  • space_property_contains_all
  • space_property_contains_context
  • space_property_contains_any_user_group

Limitations of content properties

Content properties have the following limitations:

  • You can store an unlimited number of content properties against a piece of content but each property can have no more than 32KB of JSON data stored in it.
  • Content properties can be modified by all apps in the system and exist in a global namespace. It is recommended that you namespace the entity property keys for the properties that you wish to be specific to your app. This also means that you should avoid storing unencrypted sensitive data in entity properties.
  • The value stored in each property must be in valid JSON format. (Valid JSON format is defined as anything that JSON.parse can read)

It is important to note that content properties are unique in that they provide a mechanism to handle concurrent edits. The version field in the request and response ensures that two requests cannot update the same version of the entity properties data. Attempting to do so will result in a HTTP error.

Content properties example

If you wanted to create a content property called my-property on a piece of Confluence content with the ID 12345, then you would make the following request using Create or update app property:

1
2
3
4
5
6
7
8
9
10
curl --request PUT \
  --user email@example.com:<api_token> \
  --header 'Accept: application/json' \
  --header 'Content-Type: application/json' \
  --data '{ 
              "key": "my-property",
              "version": { "number": 1 },
              "value": {"party": { "attendees": ["andrew","becky","charlie","dave"], "attendeeCount": 4 }}
          }' \
  --url 'https://your-domain.atlassian.net/wiki/rest/api/content/12345/property/my-property'

Note that the structure of the payload in this request is different to entity and app properties. The differences are:

  • You need to provide the key in the JSON data as well as the URI.
  • You need to provide a version number with the data. That version number must be higher than the previous version number or '1' if it is a brand new piece of content.
  • The actual data you wish to store must still be a JSON blob but it is nested inside the 'value' field of the root JSON object.

To update that property in the future you would need to bump the version number from 1 to 2, like so:

1
2
3
4
5
6
7
8
9
10
curl --request PUT \
  --user email@example.com:<api_token> \
  --header 'Accept: application/json' \
  --header 'Content-Type: application/json' \
  --data '{ 
              "key": "my-property",
              "version": { "number": 2 },
              "value": {"party": { "attendees": ["andrew","becky","charlie","dave", "evie"], "attendeeCount": 5 }}
          }' \
  --url 'https://your-domain.atlassian.net/wiki/rest/api/content/12345/property/my-property'

Each of these PUT requests will return the same data as a GET request on this resource. A get request on the my-property content property will return the following result:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
{
  "id": "786433",
  "key": "my-property",
  "value": {
    "party": {
      "attendees": [
        "andrew",
        "becky",
        "charlie",
        "dave"
      ],
      "attendeeCount": 4
    }
  },
  "version": {
    "when": "2015-12-08T12:13:01.878+11:00",
    "message": "",
    "number": 1,
    "minorEdit": false
  },
  "_links": {
    "base": "https://your-domain.atlassian.net/wiki",
    "context": "/confluence",
    "self": "https://your-domain.atlassian.net/wiki/rest/api/content/98305/property/my-property"
  },
  "_expandable": {
    "content": "/rest/api/content/98305"
  }
}

You can also use the content properties module to extract data from your content properties and have it indexed and available for search in CQL.