data-sb-field-path is an annotation method used to tell Stackbit where model fields are rendered on the page. Fields specified in this attribute will be available for visual editing in Stackbit.

This attribute is typically used in conjunction with data-sb-object-id (link), which creates a data object scope, making it cleaner to add multiple fields from the same object. The value of this attribute should be the model field name of an object that was specified in the data-sb-object-id attribute of the closest ancestor element, or in the element itself.

Here's an example that scopes an element to a product data object and then makes its title and image_url fields editable through Stackbit.

<div data-sb-object-id={ product.id }>

  <!-- a field rendered inside the element -->
	<div data-sb-field-path="title">{ product.title }</div>

  <!-- a field rendered inside element's attribute
  <img data-sb-field-path="image_url#@src" src={ product.image_url } />

</div>

Field Path Syntax

Syntax

data-sb-field-path="field_name" (default)

data-sb-field-path="object_id:field_name#xpath_location"

Parameters

object_id (followed by a :) is an object ID to which this field path belongs. This is typically not required, as it is inferred from data-sb-object-id in ancestor elements. See Specifying Object IDs below for more information usage within the field path.

field_name is the only required part and assumed as the only part when the colon (:) and hash (#) are missing. See Mapping to Model Fields below for more information. Note that field names must use the original model fields.

xpath_location (prefixed by a #) is an optional string specifying the xpath to the location where the field value is used, relative to the current element. The default xpath_location is text(). Meaning that if the field value is rendered inside the element as its only text node child, you can omit the #text().

Example

Given the defaults above, the following three data-sb-field-path annotations are identical:

<div data-sb-object-id={product.id}>
  <div data-sb-field-path="title">{product.title}</div>
  <div data-sb-field-path="title#text()">{product.title}</div>
  <div data-sb-field-path={`${product.id}:title#text()`}>{product.title}</div>
</div>

And here's a more complete example that could represent a card displaying product information:

<a data-sb-object-id={ product.id }
   class="product-card"
   href={`/product/${ product.id }`}>
  <div data-sb-field-path="title">{ product.title }</div>
  <img data-sb-field-path="image_url#@src" src={ product.image_url } />
  <div data-sb-field-path="description">{ product.description }</div>
  <div>
    <span>$</span>
    <span data-sb-field-path="price">{ product.price }<span>
  </div>
</a>

The annotated HTML element should be the immediate parent of the rendered value. This way Stackbit knows exactly which content to highlight and make editable. In the example above, the price field is specified on the inner <span> element that renders the price value and not on its parent <div>. This way the dollar sign ($) won't be highlighted when editing visually, providing a better experience for your editors.

Using a Field Path within an Attribute

To specify that the field value is used within an attribute, use the attribute @ xpath specifier followed by the attribute name:

<a href={product.url} data-sb-field-path="url#@href">
  Product Info
</a>

Mapping to Model Fields

Field names specified with data-sb-field-path must match field names in the model, and not another variables or property name used to render the value.

A component may receive transformed data with properties names that are different from the underlying data.

For example, assume you have a data file at blog/post-1.md with the following content:

# /blog/post-1.md
---
title: 'Blog Post Title'
description: 'Description of the first blog post'
---
Hello World

And the model for that page looks like this in stackbit.yaml:

# stackbit.yaml
models:
  post:
    type: page
    name: Post
    folder: blog
    fields:
      - type: string
        name: title
      - type: text
        name: description

Now assume that somewhere inside your static site generator logic, for whatever reason, the description field is mapped to subtitle, and subtitle is the property that is passed to your Post component.

Therefore, even if the property name received by React component is subtitle the field path value should be the original field name, which is description:

function Post(props) {
  return (
    <article data-sb-object-id={props.post.id}>
      <h1 data-sb-field-path="title">{props.post.title}</h1>
      <div data-sb-field-path="description" href={props.post.subtitle} />
    </article>
  )
}

Annotating Elements with Multiple Children

In some cases, you might find yourself having an element that renders a field value as its direct text node child, but also includes other elements as children. In this case, use the xpath to specify the exact position of the rendered field value within the element.

<div data-sb-field-path="title#text()[1]">{ props.title }<span>...</span></div>

If the location of this child is determined dynamically, be sure to update the xpath with the appropriate logic. Here's an example:

const barXpath = `bar#text()[${props.showHardCodedText ? 2 : 1}]`
return (
  <div data-sb-field-path={barXpath}>
    {props.showHardCodedText ? 'Hard coded text' : null}
    <span>...</span>
    {props.bar}
  </div>
)

Which means when showHardCodedText === false, the HTML resolves to the following:

<!-- showHardCodedText === false -->
<div data-sb-field-path="bar#text()[1]">
  <span>...</span>
  bar value
</div>

And when showHardCodedText === true, we get this instead:

<!-- showHardCodedText === true -->
<div data-sb-field-path="bar#text()[2]">
  Hard coded text
  <span>...</span>
  bar value
</div>

Annotating Elements that Render Multiple Fields

In some cases, an element can render two fields from the same object. You can specify both fields separated by a space, applying the proper xpath as necessary.

Applying Fields to Attributes and Text

The following example shows a React component that renders the title and url inside an anchor element, where the url is applied to the href attribute, and the title is assumed to be the element's text.

function Link(props) {
  return (
    <a href={props.action.url} data-sb-field-path="action.url#@href action.title">
      {props.action.title}
    </a>
  )
}

Multiple Fields as the Same Child

Another example, is an element that renders author's first and last names. In this case you might do something like this:

<div class="author" data-sb-field-path="first_name last_name">
  {author.first_name} {author.last_name}
</div>

But it's much better to be more explicit by wrapping each field with an empty <span> element (with no styles). This is more declarative and will provide a better editing experience:

<div class="author">
  <span data-sb-field-path="first_name">{author.first_name}</span>
  <span data-sb-field-path="last_name">{author.last_name}</span>
</div>

Adding Multiple Classes from Multiple Fields

You can also specify that an element renders multiple fields within a single attribute. Here's an example:

const style = {
  color: props.banner.textColor,
  backgroundColor: props.banner.backgroundColor
}
return (
  <div className={style} data-sb-field-path="textColor#@class backgroundColor#@class text">
    {props.banner.text}
  </div>
)

Annotating Nested Objects

When your data has multiple levels of nesting, use the dot . to explicitly chain the parent to child field. Consider the following data object:

{
  id: 'post_123',
  title: 'My Blog Post',
  meta: {
    date: '2020-10-10',
    tags: ['red', 'yellow', 'green']
  }
}

If you wanted to directly annotate [meta.date](http://meta.date) and meta.tags you could use those properties explicitly, as shown in this example:

function Post(props) {
  return (
    <article data-sb-object-id={props.post.id}>
      <h1 data-sb-field-path="title">{props.post.title}</h1>
      <div>
        <div data-sb-field-path="meta.date">{props.post.meta.date}</div>
        <div data-sb-field-path="meta.tags">{props.post.meta.tags.join(' ')}</div>
      </div>
      ...
    </article>
  )
}

Inferred Relative Field Paths

Field paths can be specified relative to their parent fields defined in an ancestor element. To specify a field path relative to another one, prefix it with a dot .. This syntax helps shortening the field paths when a nested object has multiple properties.

Relative field paths only apply to fields of type object, list, model and reference.

Continuing the previous example, we can leverage the fact that the post's meta is rendered in its own <div> element and simplify the annotated code to use relative field paths:

function Post(props) {
  return (
    <article data-sb-object-id={props.post.id}>
      <h1 data-sb-field-path="title">{props.post.title}</h1>
      <div data-sb-field-path="meta">
        <div data-sb-field-path=".date">{post.meta.date}</div>
        <div data-sb-field-path=".tags">{post.meta.tags.join(' ')}</div>
      </div>
      ...
    </article>
  )
}

Specifying Object IDs within the Field Path

When an element isn't scoped with data-sb-object-id, you can scope it directly within the data-sb-field-path value. When doing so, separate the object ID from the field path by using colon :.

Using Multiple Objects within the Same Element

This is useful when an element renders field values of different objects. For example, the <title> element may render the title of the page but also the title of the site:

<head>
  <title data-sb-field-path={`${page.id}:title ${siteConfig.id}:title`}>
    {page.title} - {siteConfig.title}
  </title>
</head>

Nesting Fields within another Object's Scope

Another example is when you have a complex HTML hierarchy where fields of one object used within a descendant element scoped by another object. For example, in the following code the post.date is rendered within an element that is scoped by author.id:

function Post(props) {
  const author = getAuthorById(post.author_id)
  return (
    <div data-sb-object-id={post.id}>
      <div data-sb-field-path="title">{post.title}</div>
      <div data-sb-field-path="subtitle">{post.subtitle}</div>
      <div data-sb-object-id={author.id}>
        {'Written by '}
        <span data-sb-field-path="first_name">{author.first_name}</span>
        <span data-sb-field-path="last_name"> {author.last_name}</span>
        {'on '}
        <span data-sb-field-path={`${post.id}:date`}>{post.date.toDateString()}</span>
      </div>
    </div>
  )
}

Unable to Use a Parent Element

Another example for using object IDs within a field path is when specifying a parent object ID is not possible due to template constrains. For example, when using React Helmet, properties specified on the <Helmet> component may get stripped, preventing you from being able to specify the object ID on the <head> element:

function Post(props) {
  return (
    <article data-sb-object-id={ props.post.id }>
      <Helmet>
        <meta name="description"
              content={ props.post.seo.description }
              data-sb-field-path={`${ props.post.id }:seo.description`}>
        <meta name="keywords"
              content={ props.post.seo.keywords.join(', ') }
              data-sb-field-path={`${ props.post.id }:seo.keywords`}>
      </Helmet>
      <h1 data-sb-field-path="title">{ props.post.title }</h1>
      ...
    </article>
  );
}

Annotating List Fields

When you have an element that contains list of items, set the value of data-sb-field-path to the path of the list field. Every item in the list must be enclosed inside square brackets with its index items[n]. This informs Stackbit to render special list controls, such as adding, removing and reordering list fields.

For example, assume a page with list of products:

{
  id: 'page_123',
  title: 'Products',
  products: [
    { title: 'orange' },
    { title: 'apple' }
  ]
}

You can use the index from a map function to specify the appropriate index for the object. This is similar to how you might set the React key property.

<div data-sb-object-id={ props.page.id }>
	<h1 data-sb-field-path="title">{ props.page.title }</h1>
	<ul class="products" data-sb-field-path="products">
	  { props.page.products.map((product, index) => (
	    <li key={index} data-sb-field-path={`.[${index}]`}>
        <div data-sb-field-path=".title">{ product.title }</div>
        <div data-sb-field-path=".price">{ product.price }</div>
      </li>
	  )};
	</ul>
</div>

The element specifying a list field path, must be a direct parent of the elements containing the list items. In the above example, the <ul> element is the direct parent of the <li> elements containing the list items.