Sanity logosanity.ioAll Systems Operational© Sanity 2026
Change Site Theme
Sanity logo

Documentation

    • Overview
    • Platform introduction
    • Next.js quickstart
    • Nuxt.js quickstart
    • Astro quickstart
    • React Router quickstart
    • Studio quickstart
    • Build with AI
    • Content Lake
    • Functions
    • APIs and SDKs
    • Agent Actions
    • Visual Editing
    • Blueprints
    • Platform management
    • Dashboard
    • Studio
    • Canvas
    • Media Library
    • App SDK
    • Content Agent
    • HTTP API
    • CLI
    • Libraries
    • Specifications
    • Changelog
    • User guides
    • Developer guides
    • Courses and certifications
    • Join the community
    • Templates
Content Lake (Datastore)
Overview

  • Understanding Content Lake

    Technical limits
    API Versioning
    API CDN

  • Datasets and documents

    Datasets
    Documents
    Drafts
    IDs and Paths
    Perspectives
    Attribute limit
    Hot swap
    Cloud clone
    Backups

  • Querying with GROQ

    Introduction
    How queries work
    Custom functions
    Query cheat sheet
    Vision plugin
    Syntax reference
    GROQ @ Sanity Learn

  • Querying with GraphQL

    Introduction
    GROQ and GraphQL

  • Mutations

    Introduction to document mutations
    Mutate documents with actions
    Transactions
    Patches
    Document mutation patterns

  • Assets

    Introduction
    Upload, query, and delete assets
    Metadata
    Transformations
    IIIF

  • Content Operations

    Importing Data
    Restore a deleted dataset from a backup
    Migrating your schema and content
    Content migration cheat sheet
    Schema migration principles
    Schema change course @ Sanity Learn

  • Real-time and dynamic content

    Perspectives for preview and presentation
    Live Content API
    Listening API

  • Webhooks

    Introduction
    Best practices
    HTTP API reference
    Toolkit (JS)

  • Embeddings (Experimental)

    Introduction
    Create and query an embeddings index
    Embeddings Index CLI reference
    HTTP API reference

  • Security

    Access Your Data (CORS)
    Browser security & CORS
    Keeping your data safe
    Activity Feed

  • Reference

    GROQ Specification
    Common Sanity document types
    HTTP API
    Roles and permissions
    URL Format
    Authentication
    Using JSONMatch
    IP addresses used by Sanity

On this page

Previous

Perspectives

Next

Hot swap

Was this page helpful?

On this page

  • What is the attribute limit?
  • What counts as an attribute?
  • Best practices
  • Use arrays for page building
  • Avoid excessive nesting and recursive data structures
  • Focus on meaning, not presentation
  • Beware of multipliers in translation/localization
  • What to do if you hit the limit?
  • Export your data
  • Get unblocked
  • Restructure your content
  • Track your progress
  • Closing remarks
Content Lake (Datastore)Last updated January 9, 2026

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 attribute and datatype you can have in your dataset. Depending on what plan your project is on, your limit is one of the following:

  • Free: 2k attributes
  • Growth: 10k attributes
  • Enterprise: Custom # 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 attribute and datatype. An alternative way to think about them is as the different paths through your content.

Let's take a basic data structure:

{
  "foo": [
    {
      "bar":…,
      "baz":…
    },
    {
      "bar":…,
      "baz":…
    },
    {
      "bat": {
        "bar":…
      }
    }
  ]
}

This structure contains six unique paths or attributes:

  • foo -> an array
  • foo[] -> an object
  • foo[].bar -> a string
  • foo[].baz -> a string
  • foo[].bat -> an object
  • foo[].bat.bar -> a string

Paths only count towards 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 Sanity 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 blockContent array, with inside of it:
• blocks objects, with inside of them:
• markDefs and children arrays, with inside of them:
• span types, with inside of them:
• a marks array and a text field

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 amount 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 basically 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

Things get worse when subsequently the same block content configuration is used for any block content fields inside the custom objects, so editors can endlessly nest the entire page builder inside itself.

Focus on meaning, not presentation

Before responsive web 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 front-end'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 i18n/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 basically 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 instead. To differentiate between the different languages and more easily query for them, you can consider adding a (hidden) internationalization field to your document type and/or add the language to the document ID. 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 your Sanity 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 dataset export command in your terminal. For example:

sanity dataset 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 optimised. 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 towards the attribute limit.

Restructure your content

How to restructure your content depends on your content model and is therefore different per project. However, there are a bunch of examples to get you started. Please note that in all cases, it is highly recommended to run a full dataset export before proceeding.

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.

{
  "foo": [
    {
      "bar":…,
      "baz":…
    },
    {
      "bar":…,
      "baz":…
    },
    {
      "bat": {
        "bar":…
      }
    }
  ]
}
{
 "de": {
  ...
 }
 "en": {
  ...
 }
}