Attribute limit
Everything about the attribute limit: what it is, how to avoid it, and what to do if you hit the limit on one of your projects.
What is the attribute limit?
The attribute limit determines how many unique combinations of path and data type you can have in your dataset. Depending on what plan your project is on, your limit is one of the following:
- Free: 2,000 attributes
- Growth: 10,000 attributes
- Enterprise: custom number of attributes
Gotcha
The attribute limit is a hard technical limit right now. For this reason, we do not currently offer a pay-as-you-go option for extra attributes.
What counts as an attribute?
As shown above, an attribute is officially defined as a unique combination of path and data type. An alternative way to think about them is as the different paths through your content.
Let's take a basic data structure:
{
"sections": [
{
"heading":…,
"body":…
},
{
"heading":…,
"body":…
},
{
"callout": {
"heading":…
}
}
]
}This structure contains six unique paths or attributes:
sections-> an arraysections[]-> an objectsections[].heading-> a stringsections[].body-> a stringsections[].callout-> an objectsections[].callout.heading-> a string
Paths only count toward your attribute limit when they hold actual content. Solely changing your schema definitions will not affect the attribute count. Schema definitions define the structure of your content, a bit like a blueprint defines the structure of a building. Until you add or remove content using the Studio or the HTTP API, your attribute count will remain unchanged.
Each unique path is counted once, no matter how often it is used. Removing a path from your attribute count requires deleting every piece of content on that path across all documents.
In short, your attribute count:
- Goes up when you first add content on a path.
- Goes down when a path no longer holds any content.
- Stays the same regardless of whether a path is used once or many times.
Best practices
When structuring your content, there are a few pitfalls to keep in mind to avoid hitting the attribute limit. Although this is not an exhaustive list, following the best practices below should go a long way in keeping your attribute count in check.
Use arrays for page building
A common use case for Sanity is using structured content for page building. In setting up a page builder, it may be tempting to use the block content type as the editor gives a lot of flexibility and allows adding any number of custom objects that can then be used inline.
However, a block content field has quite an extensive data structure by default:
- a
blockContentarray, with inside of it: blocksobjects, with inside of them:markDefsandchildrenarrays; thechildrenarray containsspanobjects, each with amarksarray and atextfield, whilemarkDefsholds annotation objects (such as links)
This nested structure is further extended by any custom types you add to it, all with their own unique paths. A block content field with many custom objects may therefore lead to a hefty number of attributes.
Another issue with this approach is that people sometimes want to use block content fields inside of custom objects. This is likely to lead to even more attributes as a result of now having the above structure embedded in the same structure. Moreover, when the exact same block content component is used, allowing this type of nesting gives editors the freedom to nest to an arbitrarily deep level, which can then drag a project over the attribute limit.
To avoid any of these challenges and keep the attribute count as low as possible, we recommend using arrays for page building. In addition to fewer attributes, greater control over the exact content structure, and reduced risk of getting into nesting situations, this approach has the added advantage of not having to deal with serializers for complex custom objects.
Avoid excessive nesting and recursive data structures
Nesting compounds your attribute count because every additional level introduces a new set of unique paths for the same fields. Recursive structures are the extreme case: if the page builder described above uses the same block content configuration for block content fields inside its custom objects, editors can nest the entire page builder inside itself, and each level of nesting adds another full set of attributes. To stay in control, limit nesting to a fixed depth in your schema definitions, and avoid structures that can contain themselves, directly or indirectly.
Focus on meaning, not presentation
Before responsive web design made its entrance and people started optimizing for different devices, it was customary to mix content with presentation. A headline could be blue, have font size 24px, line-height 30px, and a bottom padding of 10px. Although it may still be tempting today to offer that same level of control to editors, there are several downsides to this approach. For one, whenever you want to change your frontend's design, editors will have to review all relevant content.
Most importantly for this guide, adding all these presentational attributes is likely to boost your attribute count significantly as they would exist for nearly every piece of content.
Instead of mimicking CSS properties in your schema definitions, we recommend a separation of concerns. Leave the presentational aspects to wherever you implement your content and instead stick to semantics in your content structure. In other words, focus on the meaning of your content.
Beware of multipliers in translation/localization
There is a variety of internationalization (i18n) and localization (l10n) approaches out there, some of which have a greater impact on your attribute count than others. For example, one approach suggests wrapping all your fields inside a language object, so you get the following structure:
{
"de": {
...
},
"en": {
...
}
}This multiplies the number of attributes by the number of languages added, as all fields get duplicated on a language path. Adding more than a few languages this way means trouble.
Instead of duplicating the fields inside a document, thereby creating all these extra paths, a more frugal approach is to duplicate the document. To differentiate between the different languages and more easily query for them, you can consider adding a (hidden) internationalization field to your document type, adding the language to the document ID, or both. As you will be reusing the same fields across different documents, adding an extra language no longer affects your attribute count at all.
What to do if you hit the limit?
If you inadvertently hit the attribute limit on one of your datasets, you will see the following error when opening the Studio: Total attribute count exceeds limit.
Export your data
Before deleting any content or changing your data structure, we highly recommend running a full export of your dataset to prevent any unintended data loss. To do so, you can run the datasets export command in your terminal. For example:
sanity datasets export production production.tar.gz
Get unblocked
The first step after exporting your data is to get unblocked so you and other users on your project can work in the Studio again. In other words, the challenge is to get back below the attribute limit.
Perhaps there is a heavily nested structure with block content and translations that could be optimized. Or maybe you have singletons for different pages that could be folded into a single page type instead to further reduce the number of unique paths.
A final note is that it also helps to remove any unused content from schema revisions. For example, if you used to have a particular document type with a bunch of documents, but later removed that type, or even some fields within a type, make sure to clean up the content so there are no leftovers in the datastore that will count toward the attribute limit.
Restructure your content
How to restructure your content depends on your content model and is therefore different per project. However, the two examples below show common ways to reduce the attribute count. Please note that in all cases, it is highly recommended to run a full dataset export before proceeding.
For example, say you enrich product information with a separate string field for each specification:
{
"product": {
"color": "Blue",
"material": "Cotton",
"weight": "230 g"
}
}This structure already uses four attributes (product, product.color, product.material, and product.weight), and every new specification adds another one. Restructuring the specifications into an array of name and value pairs caps the count:
{
"product": {
"specifications": [
{ "name": "Color", "value": "Blue" },
{ "name": "Material", "value": "Cotton" },
{ "name": "Weight", "value": "230 g" }
]
}
}This version starts slightly higher at five attributes (product, product.specifications, product.specifications[], product.specifications[].name, and product.specifications[].value), but the count stays the same no matter how many specifications you add, because each unique path is counted once regardless of how often it is used.
The same idea applies at the document level. If every page is its own document type with uniquely named fields, each page type introduces its own set of paths:
[
{ "_type": "homePage", "homeHeading": "Welcome", "homeIntro": "…" },
{ "_type": "aboutPage", "aboutHeading": "About us", "aboutIntro": "…" }
]These two documents use four attributes between them, and every new page type adds more. Folding them into a single shared page type keeps the paths constant:
[
{ "_type": "page", "heading": "Welcome", "intro": "…" },
{ "_type": "page", "heading": "About us", "intro": "…" }
]Both documents now share the same two attributes (heading and intro), so adding more pages no longer affects the count.
Track your progress
To keep an eye on your attribute limit while restructuring your content, you can use this URL: https://<projectId>.api.sanity.io/v1/data/stats/<datasetName>
The attribute count is the value of fields.count.value, and the limit is inside fields.count.limit.
Closing remarks
Although this guide was specifically about the attribute limit, the principles outlined above are best practices that are likely to lead to a more solid, flexible, and future-proof content model in any situation.
To keep going, learn more about content modeling, review the datasets export command, or explore localization approaches for handling multiple languages.