Skip to content

Nested custom attributes

This page covers nested custom attributes, which allow you to define a set of attributes as a property of another attribute. In other words, when you define a custom attribute object, you can define a set of additional attributes for that object.

About nested attributes

Nested attributes let you build richer segments and personalize messages with data from a single custom attribute object.

In the following example, the custom attribute favorite_book contains the nested attributes title, author, and publishing_date. This object can be used to target users by author, filter by publishing date, or insert the book title directly into a message:

1
2
3
4
5
"favorite_book": {
  "title": "The Hobbit",
  "author": "J.R.R. Tolkien",
  "publishing_date": "1937"
}

Supported data types

The following data types are supported:

Considerations

  • Nested custom attributes are intended for custom attributes sent through the Braze SDK or API.
  • Objects have a maximum size of 100 KB. If an update causes the object to exceed 100 KB, Braze drops the update, and the attribute is unchanged.
  • Key names and string values have a size limit of 255 characters.
  • Key names cannot contain spaces.
  • Periods (.) and dollar signs ($) aren’t supported characters in an API payload if you’re attempting to send a nested custom attribute to a user profile.
  • Not all Braze Partners support nested custom attributes. Refer to the Partner documentation to confirm if specific partner integrations support this feature.
  • Nested custom attributes cannot be used as a filter when making a Connected Audience API call.

API example

The following is a /users/track example with a “Most Played Song” object. To capture the properties of the song, we’ll send an API request that lists most_played_song as an object, along with a set of object properties.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
  "attributes": [
    {
      "external_id": "user_id",
      "most_played_song": {
        "song_name": "Solea",
        "artist_name": "Miles Davis",
        "album_name": "Sketches of Spain",
        "genre": "Jazz",
        "play_analytics": {
            "count": 1000,
            "top_10_listeners": true
        }
      }
    }
  ]
}

To update an existing object, send a POST to users/track with the _merge_objects parameter in the request. This will deep merge your update with the existing object data. Deep merging ensures that all levels of an object are merged into another object instead of only the first level. In this example, we already have a most_played_song object in Braze, and now we’re adding a new field, year_released, to the most_played_song object.

1
2
3
4
5
6
7
8
9
10
11
{
  "attributes": [
    {
      "external_id": "user_id",
      "_merge_objects": true,
      "most_played_song": {
          "year_released": 1960
      }
    }
  ]
}

After this request is received, the custom attribute object will now look like the following:

1
2
3
4
5
6
7
8
9
10
11
{"most_played_song": {
  "song_name": "Solea",
  "artist_name" : "Miles Davis",
  "album_name": "Sketches of Spain",
  "year_released": 1960,
  "genre": "Jazz",
  "play_analytics": {
     "count": 1000,
     "top_10_listeners": true
  }
}}

To delete a custom attribute object, send a POST to users/track with the custom attribute object set to null.

1
2
3
4
5
6
7
8
{
  "attributes": [
    {
      "external_id": "user_id",
      "most_played_song": null
    }
  ]
}

SDK example

Create

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
val json = JSONObject()
    .put("song_name", "Solea")
    .put("artist_name", "Miles Davis")
    .put("album_name", "Sketches of Spain")
    .put("genre", "Jazz")
    .put(
        "play_analytics",
        JSONObject()
            .put("count", 1000)
            .put("top_10_listeners", true)
    )

braze.getCurrentUser { user ->
    user.setCustomUserAttribute("most_played_song", json)
}

Update

1
2
3
4
5
6
val json = JSONObject()
    .put("year_released", 1960)

braze.getCurrentUser { user ->
    user.setCustomUserAttribute("most_played_song", json, true)
}

Delete

1
2
3
braze.getCurrentUser { user ->
    user.unsetCustomUserAttribute("most_played_song")
}

Create

1
2
3
4
5
6
7
8
9
10
11
12
let json: [String: Any?] = [
  "song_name": "Solea",
  "artist_name": "Miles Davis",
  "album_name": "Sketches of Spain",
  "genre": "Jazz",
  "play_analytics": [
    "count": 1000,
    "top_10_listeners": true,
  ],
]

braze.user.setCustomAttribute(key: "most_played_song", dictionary: json)

Update

1
2
3
4
5
let json: [String: Any?] = [
  "year_released": 1960
]

braze.user.setCustomAttribute(key: "most_played_song", dictionary: json, merge: true)

Delete

1
braze.user.unsetCustomAttribute(key: "most_played_song")

Create

1
2
3
4
5
6
7
8
9
10
11
12
import * as braze from "@braze/web-sdk";
const json = {
  "song_name": "Solea",
  "artist_name": "Miles Davis",
  "album_name": "Sketches of Spain",
  "genre": "Jazz",
  "play_analytics": {
    "count": 1000,
    "top_10_listeners": true
  }
};
braze.getUser().setCustomUserAttribute("most_played_song", json);

Update

1
2
3
4
5
6
import * as braze from "@braze/web-sdk";
const json = {
  "year_released": 1960
};
braze.getUser().setCustomUserAttribute("most_played_song", json, true);

Delete

1
2
import * as braze from "@braze/web-sdk";
braze.getUser().setCustomUserAttribute("most_played_song", null);

Capturing dates as object properties

To capture dates as object properties, you must use the $time key. In the following example, an “Important Dates” object is used to capture the set of object properties, birthday and wedding_anniversary. The value for these dates is an object with a $time key, which cannot be a null value.

1
2
3
4
5
6
7
8
9
10
11
{
  "attributes": [ 
    {
      "external_id": "time_with_nca_test",
      "important_dates": {
        "birthday": {"$time" : "1980-01-01"},
        "wedding_anniversary": {"$time" : "2020-05-28"}
      }
    }
  ]
}

Liquid templating

The following Liquid templating example shows how to reference the custom attribute object properties saved from the preceding API request and use them in your messaging.

Use the custom_attribute personalization tag and dot notation to access properties on an object. Specify the name of the object (and position in array if referencing an array of objects), followed by a dot (period), followed by the property name.

{{custom_attribute.${most_played_song}[0].artist_name}} — “Miles Davis”
{{custom_attribute.${most_played_song}[0].song_name}} — “Solea”
{{custom_attribute.${most_played_song}[0].play_analytics.count}} — “1000”

Using Liquid to template a song name and the number of times a listener has played that song into a message

Personalization

Using the Add Personalization modal, you can also insert nested custom attributes into your messaging. Select Nested Custom Attributes as the personalization type. Next, select the top-level attribute and attribute key.

For example, in the personalization modal below, this inserts the nested custom attribute of a local neighborhood office based on a user’s preferences.

Regenerate schemas

After a schema has been generated, it can be regenerated once every 24 hours. This section describes how to regenerate your schema. For more detailed information on schemas, see Generate a schema using the nested object explorer.

To regenerate the schema for your nested custom attribute:

  1. Go to Data Settings > Custom Attributes.
  2. Search for your nested custom attribute.
  3. In the Attribute Name column for your attribute, select to manage the schema.
  4. A modal will appear. Select Regenerate Schema.

The option to regenerate schema will be disabled if it has been less than 24 hours since the schema was last regenerated. Regenerating the schema will only detect new objects and will not delete objects that currently exist in the schema.

If data doesn’t appear as expected after regenerating the schema, the attribute may not be ingested often enough. User data is sampled on previous data sent to Braze for the given nested attribute. If the attribute isn’t ingested enough, it won’t be picked up for the schema.

Trigger nested custom attribute changes

You can trigger when a nested custom attribute object changes. This option isn’t available for changes to object arrays. If you don’t see an option to view the path explorer, check that you’ve generated a schema.

For example, in an action-based campaign, you can add a new trigger action for Change Custom Attribute Value to target users who have changed their neighborhood office preferences.

Action-based campaign delivery settings with a Change Custom Attribute Value trigger for nested preferences.

Segmentation behavior with arrays of objects

When you use multiple Nested Custom Attribute filters with AND logic to segment on an array of objects, each filter is evaluated independently across all items in the array. A user qualifies for the segment if any item in the array satisfies each individual filter—the filters don’t have to match the same item.

For example, suppose a user has the following array:

1
2
3
4
5
6
{
  "orders": [
    {"product": "Shoes", "price": 80},
    {"product": "Hat", "price": 25}
  ]
}

A segment with the following AND filters:

  • orders[].price is greater than 50
  • orders[].price is less than 30

This user would qualify because the first filter matches the “Shoes” item (80 > 50) and the second filter matches the “Hat” item (25 < 30). Even though no single item satisfies both conditions, the user still enters the segment.

If you need all conditions to match the same item within an array, use multi-criteria segmentation on the same path, or restructure your data to avoid cross-item matching.

Data points

Any key that is sent consumes a data point. For example, this object initialized in the user profile counts as seven (7) data points:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
  "attributes": [
    {
      "external_id": "user_id",
      "most_played_song": {
        "song_name": "Solea",
        "artist_name": "Miles Davis",
        "album_name": "Sketches of Spain",
        "year_released": 1960,
        "genre": "Jazz",
        "play_analytics": {
          "count": 1000,
          "top_10_listeners": true
        }
      }
    }
  ]
}
New Stuff!