This page provides an overview of the storage (AddInData) API and describes its use within Add-Ins.
For a sample Add-In that uses the storage API go to this link.
The Storage API allows an Add-In or integration to store records which contain generic data to a customer database. The AddInData
object allows storage of structured JSON which can be searched for and retrieved using the API.
Following sections will refer to this example JSON:
{
"date": "2016-01-01T00:00:00.000Z",
"items": [{
"name": "bottles",
"price": 12
}, {
"name": "caps",
"price": 20
}],
"customer": {
"name": "joesmith",
"email": "joe@smith.com"
}
}
An AddInId must be created before the Storage API methods can be used within your Add-In. This encoded GUID is used to register and identify which Add-In some data is associated. AddInId is a mandatory parameter when calling AddInData methods to Add and Get data. This allows each Add-In's data to be isolated from the data used by other Add-Ins. This allows multiple solutions to each have their own collection of AddInData objects in the same database without the collections mixing. To generate your own AddInId, please use the following example.
An AddInData object must first be created in a database. The properties of AddInData are as follows:
Property | Description |
---|---|
Id | The standard Id for any Entity. |
AddInId | |
Groups | |
Details | The JSON data. May be whole or partial depending on the action (Add vs. Set ) or the filtering provided when calling Get . |
As an example, you can use the API Runner tool to create an AddInData object that's not limited to any groups using the following operation:
api.call("Add",
{
"typeName": "AddInData",
"entity": {
"addInId": "a2C4ABQuLFkepPVf6-4OKAQ",
"details": {
"date": "2016-01-01T00:00:00.000Z",
"items": [
{
"name": "bottles",
"price": 12
}, {
"name": "caps",
"price": 20
}
],
"customer": {
"name": "joesmith",
"email": "joe@smith.com"
}
}
}
});
The same example with the addition of the Groups parameter would result in limiting the data to the specified groups, in this case the driver activity group:
api.call("Add",
{
"typeName": "AddInData",
"entity": {
"addInId": "a2C4ABQuLFkepPVf6-4OKAQ",
"groups": [{
"id": "GroupDriverActivityGroupId"
}
],
"details": {
"date": "2016-01-01T00:00:00.000Z",
"items": [{
"name": "bottles",
"price": 12
}, {
"name": "caps",
"price": 20
}
],
"customer": {
"name": "joesmith",
"email": "joe@smith.com"
}
}
}
});
Each invocation of the Add operation will create a new AddInData object with a unique Id bound to the entered AddInId. The Id of the AddInData object is required to remove the object with the Remove
method. See below for an example.
This method call will correctly save the sample JSON and associate it to the Add-In with the AddInId of a2C4ABQuLFkepPVf6-4OKAQ
.
Request
api.call("Add",
{
"typeName": "AddInData",
"entity": {
"addInId": "a2C4ABQuLFkepPVf6-4OKAQ",
"groups": [{
"id": "GroupCompanyId"
}],
"details": {
"date": "2016-01-01T00:00:00.000Z",
"items": [{
"name": "bottles",
"price": 12
}],
"customer": {
"name": "joesmith",
"email": "joe@smith.com"
}
}
}
});
Response
{"id": "b1"}
AddInData uses a search object to query specific data using an object's path in the JSON.
The AddInDataSearch properties are as follows:
Property | Description |
---|---|
Id | The standard Id for any Entity. |
AddInId | Can be optionally provided when searching for AddInData that belongs to a specific AddInData. |
Groups | Used to define the scope of a row of Add-In data. Works the same as any other ObjectModel Entity. |
SelectClause (String) | Used to filter the resulting rows based on the JSON content of the row. Works with the object path notation described in usage. Independent of WhereClause. |
WhereClause (String) | Used to filter the resulting rows based on the JSON content of the row. Works with the object path and operator notation described in usage. Independent of SelectClause. |
As an example, you can use the API Runner tool to perform GET operations that return one or more AddInData objects:
Get the emails of all customers who have an item with a price less than 15. This method call will return an array with a single AddInData object.
Request:
api.call("Get",
{
"typeName": "AddInData",
"search": {
"addInId": "a2C4ABQuLFkepPVf6-4OKAQ",
"selectClause": "customer.email",
"whereClause": "items.[].price < 15"
}
});
Response:
[{
"id": "afLvRdUtXrE2D-XLwvqAgZQ",
"groups": [{"id": "GroupCompanyId"}],
"details": "joe@smith.com"
}]
Get all item names for a user with the email joe@smith.com. This method call will return an array with multiple AddInData objects that satisfy both the select and where clauses.
Request:
api.call("Get",
{
"typeName": "AddInData",
"search": {
"addInId": "a2C4ABQuLFkepPVf6-4OKAQ",
"selectClause": "items.[].name",
"whereClause": "customer.email = \"joe@smith.com\""
}
});
Response:
[{
"addInId": "a2C4ABQuLFkepPVf6-4OKAQ",
"id": "afLvRdUtXrE2D-XLwvqAgZQ",
"groups": [{"id": "GroupCompanyId"}],
"details": "bottles"
}, {
"addInId": "a2C4ABQuLFkepPVf6-4OKAQ",
"id": "afLvRdUtXrE2D-XLwvqAgZQ",
"groups": [{"id": "GroupCompanyId"}],
"details": "caps"
}]
Note: Both returned AddInData objects will have the same Id because they come from the same object in the database.
Get all data
Request:
api.call("Get",
{
"typeName": "AddInData",
"search": {
"addInId": "a2C4ABQuLFkepPVf6-4OKAQ"
}
});
Response:
[{
"addInId": "a2C4ABQuLFkepPVf6-4OKAQ",
"id": "afLvRdUtXrE2D-XLwvqAgZQ",
"groups": [{"id": "GroupCompanyId"}],
"details": {
"date": "2016-01-01T00:00:00.000Z",
"customer": {
"email": "joe@smith.com",
"name": "joesmith"
},
"items":[
{
"name": "bottles",
"price": 12
}, {
"name": "caps",
"price": 20
}
]
}
}]
The SELECT
and WHERE
clauses of the AddInDataSearch object use a special notation to describe an object path. If we wanted to modify the call in Example 4 to retrieve just the customer name from the AddInData object, we would add the following path notation to the SELECT
clause of the AddInDataSearch object:
customer.name
api.call("Get",
{
"typeName": "AddInData",
"search": {
"addInId": "a2C4ABQuLFkepPVf6-4OKAQ",
"selectClause": "customer.name"
}
});
The returned AddInData object will contain a value of "joesmith" in its data property.
If you have an array in the path, it must be indicated by a [] after the name of the array property.
For example, if you wanted to modify Example 4 to select all item names, we would add the following to the SELECT
clause of the AddInDataSearch object:
items.[].name
api.call("Get",
{
"typeName": "AddInData",
"search": {
"addInId": "a2C4ABQuLFkepPVf6-4OKAQ",
"selectClause": "items.[].name"
}
});
The same notation is used for the WHERE
clause. This notation can be used to drill down to as many objects as you want.
The WHERE
clause of the AddInDataSearch object supports the following operators:
Operator | Description |
---|---|
= | Equal to |
< | Less than |
> | Greater than |
<= | Less than or equal to |
>= | Greater than or equal to |
These can be used with the object path notation explained above.
For example, if you want to get all items with a price less than 20, the appropriate WHERE
clause will be:
items.[].price < 20
Note: The data type of the value on the right side of the operator is important. String values will need to be enclosed in quotation marks and properly escaped.
To get all customers with the name "joesmith", the appropriate WHERE
clause will be:
customer.name = "joesmith"
SELECT
clause must be included if the WHERE
clause is specified, otherwise the entire data object will be returned. GET
operation always returns an Array of AddInData objects, each with a unique value in the data property.customer.name = "JoeSmith"
will not return any results.SELECT
and WHERE
clauses will be in the scope of the entire AddInData object. To have a search return separate matches, the independent pieces of content must be added to separate AddInData objects using the ADD
operation.To update stored content, use the SET
method on an AddInData object while specifying its AddInId ID. The return value is always null
.
As an example, use the API Runner tool to perform the following operation:
api.call("Set",
{
"typeName": "AddInData",
"entity": {
"addInId": "a2C4ABQuLFkepPVf6-4OKAQ",
"id": "a6tClQu4iFkuroNPqOQsydg",
"groups": [{
"id": "GroupCompanyId"
}],
"details": {
"date": "2016-01-01T00:00:00.000Z",
"items": [{
"name": "bottles",
"price": 12
}],
"customer": {
"name": "joesmith",
"email": "joe@smith.com"
}
}
}
});
An AddInData object is deleted when you specify its ID. The return value is always null
.
api.call("Remove", {
"typeName": "AddInData",
"entity": {
"id": "a6tClQu4iFkuroNPqOQsydg"
}
});
The following are the only restrictions on the JSON stored within AddInData objects:
The AddInData object has been available as a beta feature through several releases and as such, we've made improvements through time. Now that we are officially releasing this feature in 2101, a legacy property we are looking to get rid of is the 'Data' property. This is a string property that is not deserialized as an object when sent over JSON. The newer property, 'Details', deserializes as an object and should be used instead (you do not need to call JSON.parse() on this property). Partners that have designed their applications to work with the 'Data' property should transition to using 'Details'. In a future release, the 'Data' property will be completely removed.
All objects properties stored in the JSON can be modified but not deleted.
Example (Replacing):
{"customer": {"name": "joe", "email": "joe@smith.com"}}
With
{"customer": {"apple": "fruit", "salmon": "meat"}}
Results in a merged dataset instead of a deletion of the previous content
{"customer": {"name": "joe", "email": "joe@smith.com", "apple": "fruit", "salmon": "meat"}}
Workarounds to this issue would be to either:
Currently there is no support for fuzzy string matching.
The WHERE
clause cannot perform conjunctions or disjunctions.
Security Clearance limitations allow the following API methods:
While it's possible to create a single AddInData object with an array of details, this approach is less scalable. First is contending with the mandatory limit of 10,000 characters. Second is that it can cause unduly large objects to deal with which can be less memory efficient. Third is that if there is an array of entries and you need to remove one, you will have to remove the whole object and add a new one with the updated list of details. In general, we have found it more useful to treat the AddInData as a simple object which there can be many of.