Last updatedSep 17, 2019

Confluence cloud provides two types of entity properties for storing data:

  • Content properties
  • App properties

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. We recommend 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.

GET, POST, and DELETE content properties

GET content properties

To retrieve any existing content properties for a piece of content, perform a GET on the endpoint /rest/api/content/{content_ID}/property.

1
2
# Retrieves content properties associated with a piece of content with ID 12345
curl -u admin@example.com:api_token -X GET "https://your-domain.atlassian.net/wiki/rest/api/content/12345/property" | python -mjson.tool

POST new content properties

To add content properties to a piece of Confluence content, perform a POST to the same endpoint. The key in your content property can be an arbitrary string (like "myprop"), whereas the value must be structured JSON.

1
2
3
4
5
6
7
8
9
10
11
# Stores a JSON document under the key "myprop" against content with ID 12345
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 from 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'

DELETE content properties

To delete a content property, perform a DELETE on the endpoint  /rest/api/content/<CONTENT_ID>/property/<KEY>.

1
2
# Removes JSON document associated with key "myprop from content with ID 12345
curl -i -u admin@example.com:api_token -X DELETE "https://your-domain.atlassian.net/wiki/rest/api/content/12345/property/myprop"

Fetch content properties as an expansion when retrieving content

Content properties are available as an expansion on the content resource. This allows content properties to be retrieved in the same REST call as fetching the content itself. This expansion is available from any resource that returns content, including the CQL search resource.

GET content properties as an expansion on content

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# fetch properties at the same time as fetching content, note the expand=metadata.properties.myprop
curl -u admin@example.com:api_token -X GET 
    "https://your-domain.atlassian.net/wiki/rest/api/content/12345?expand=metadata.properties.myprop" | python -mjson.tool
{   
    id: "12345",
    type: "page",
    status: "current",
    title: "New in the platform team? Read me first",
    metadata: {
        _expandable: {
            currentuser: "",
            labels: ""
            properties: {
                my-property: {
                    "id": "507f1f77bcf86cd799439011",
                    "editDate": "2000-01-01T11:00:00.000+11:00",
                    "version": { "number": 2 },
                    "value": {"party": { "attendees": ["andrew","becky","charlie","dave", "evie"], "attendeeCount": 5 }}
                    }
            }
        }
    }
}

CQL search extension

To allow searching of content properties using CQL, you can enable indexing of data stored as content properties by defining an index schema. You can define an exclusive index schema for each content property key; then, whenever a content property is saved, Confluence checks if a schema was defined for its key. If a schema exists, indexable values are extracted from content property values and stored in an index. There can only be one index schema definition for a content property key, so any duplicates will be disabled. In the case of offending keys, an appropriate log message will be available.

Index schema definition

When defining an index schema for content properties, you need to provide a set of extraction expressions and field type pairs, which are used to retrieve a specific value from a JSON document and transform it into the desired representation. Supported index field types are: stringtext number, and date.

See the below JSON document and its index schema as an example:

Example content property value

1
2
3
4
5
6
7
8
9
{
    "id": "507f1f77bcf86cd799439011",
    "editDate": "2000-01-01T11:00:00.000+11:00",
    "description": "If you have any questions please address them to admin@example.com",
    "content": {
        "likes": 5,
        "tags": ["cql", "confluence"]
    }
}

Use dot-notation to access an embedded field in a document (as shown below). After successful validation, all extracted values are stored inside an index and you can address them in CQL queries.

Putting it all together, here's a sample index schema definition:

atlassian-connect.json

Example Connect app index schema definition

1
2
3
4
5
6
7
8
9
10
11
12
13
"confluenceContentProperties": [{
    "name": {
        "value" :"Attachments index",
        "i18n": "attachments.index"
    },
    "keyConfigurations": [{
        "propertyKey" : "attachments",
        "extractions" : [{
            "objectName" : "id",
            "type" : "string"
        }]
    }]
}]

Field type doesn't only specify how data is stored in the index, but also determines which CQL operators are available in your query.

Supported index field types

TypeDescriptionSupported CQL operators
stringEntire extracted value will be indexed as a single token, without any filtering. When extraction expression evaluates to a JSON array, each element will be indexed separately. Enables searching for an exact value, e.g. unique identifier.IN, NOT IN, =, !=
textExtracted value will be tokenized before indexing, allowing searching for a particular words.~, !~
numberExtracted number will be indexed as a double value for efficient range filtering and sorting.<, <=, =, !=, >, >= 
dateTwo representation are possible, either a String following the ISO 8601 datetime format, or a long value in the Unix time. Enables efficient range filtering and sorting.<, <=, =, !=, >, >=

Querying content properties with CQL

Indexed content properties can be addressed in a CQL query using the content.property field handler. Any content that contains content properties matching the query are returned in the results.

The query syntax is as follows: content.property[<KEY>].<PATH> <OPERATOR> value

Examples of CQL queries on content properties

1
2
3
4
content.property[attachments].editDate >= 2001-01-01
content.property[attachments].description ~ "questions"
content.property[metadata].content.tags IN ("cql", "help")
content.property[metadata].content.likes <= 5

The dot notation when referencing embedded fields like 'content.likes'.

Legend:

SymbolMeaning
KEYThe key of the content properties you're searching.
PATHThe path to the value you'd like to search in the JSON document (use dot notation for embedded fields).
OPERATOROne of the supported CQL operators for the field type.

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.