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/${ produdct.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: 'Lorem Ipsum'
description: 'Dolor sit amet'
---
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: 'Lorem Ipsum',
meta: {
date: '2020-10-10',
tags: ['dolor', 'sit', 'amet']
}
}
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: 'orage' },
{ 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.