Use live previews to render edits in your data model without publishing them.
Although you can fetch Builder's Data Models using the Content API directly like a typical API resource, you can also use features such as live editing, previewing, and A/B testing of your Data Models within the Builder Visual Editor, all while using standard JS/TS syntax.
To get the most out of this document, you should already be familiar with:
Live preview is crucial when working with custom fields, data models, and structured data in Builder. It offers several benefits:
- Real-time updates by displaying changes immediately in the Visual Editor without publishing.
- Improved user experience to meet your expectations with an intuitive experience.
- Iterate more quickly on your content and designs more efficiently.
- Make sure your Sections, Pages, and structured data are exactly as intended before publishing.
Live previewing can support a smoother experience, with changes in Custom Fields, Data Models, or Structured Data updates in real-time.
- Specify the name of your data model in
BuilderContentwith themodelprop. - Use a render prop pattern with the required
data, an optionalloading, andfullDataparameters in case of SSR. - Add code that accesses your data in the
return()statement. This code varies and depends on your use case. - Set the Preview URL on the data model. For detailed instructions on setting a Preview URL on a model, visit the Setting a persistent Preview URL on a model in Editing and Previewing Your Site.
- Test the live preview by editing your data model in the Builder Visual Editor and checking that the changes are reflected in your application.
The following snippet shows this structure.
// Add your data model's name
<BuilderContent model="YOUR_DATA_MODEL">
// add function to render data
{(data, loading, fullData) => {
if (loading) return <div>Loading...</div>;
return (
// Add your code to access your data
);
}}
</BuilderContent>This example uses a custom data model named blog-article, which includes a title, author, handle, and publishedDate fields, each of type text.
The component calls builder.get() to retrieve the published entry from the blog-article model by matching the urlPath to the route parameter.
The retrieved data is passed to BlogArticle component as a prop.
import { useEffect } from "react";
import { builder, BuilderContent, useIsPreviewing } from "@builder.io/react";
import { useParams } from "react-router";
import BlogArticle from "./BlogArticle";
builder.init(/* ADD YOUR PUBLIC API KEY HERE */);
type ArticleData = {
title: string;
description: string;
author: string;
};
type Article = BuilderContent & {
data: ArticleData;
};
export default function BuilderPage() {
const isPreviewingInBuilder = useIsPreviewing();
const [notFound, setNotFound] = React.useState(false);
const [content, setContent] = React.useState<Article | null>(null);
const { slug } = useParams();
// get the page content from Builder
useEffect(() => {
async function fetchContent() {
const content = await builder
.get("blog-article", {
url: slug,
})
.promise();
setContent(content);
setNotFound(!content);
if (content?.data.title) {
document.title = content.data.title;
}
}
fetchContent();
}, [slug]);
if (content === null) {
return;
}
if (notFound && !isPreviewingInBuilder) {
return <div>404 Page Not Found</div>;
}
return (
<>
{/* Render the Builder page */}
<BlogContent article={content} />
</>
);
}The fetched article data is passed to the <BuilderContent> component for rendering. The inline function used within <BuilderContent> accepts the following parameters:
data(required) – The resolved data from theblog-articlemodel. If A/B testing is active,<BuilderContent>automatically serves the winning variant without additional setup.loading(optional) – A boolean indicating whether the data is still loading.fullData(required for SSR) – Includes all raw data from the Content API, such as A/B variations and metadata. In client-side rendering,<BuilderContent>fetches the most recently published entry of the specified model if the content prop is not provided.
//app/src/components/BlogArticle.tsx
import {
BuilderComponent,
BuilderContent,
useIsPreviewing,
} from "@builder.io/react";
type ArticleData = {
title: string;
description: string;
author: string;
};
type Article = BuilderContent & {
data: ArticleData;
};
export default function BlogContent({ article }: { article: Article }) {
const isPreviewing = useIsPreviewing();
if (!isPreviewing && !article) {
return (
<>
<div>404 - Page Not Found</div>
</>
);
}
return (
<>
<BuilderContent
content={article}
options={{ includeRefs: true }}
model="blog-article"
>
{(data, loading, fullData) => {
return (
<>
<!-- data coming from the blog-article data model -->
<div>Blog Title: {data.title}</div>
<div>Blog Author: {data.author}</div>
<div>Blog handle: {data.handle}</div>
{loading && <div>Loading...</div>}
<!-- You can render builder components from Page/section model -->
<BuilderComponent
content={article}
model="page"
options={{ includeRefs: true }}
/>
<div>Published Date: {data.publishedDate.Default}</div>
</>
);
}}
</BuilderContent>
</>
);
}Fields such as title, author, handle, and publishedDate update in real time within the Visual Editor and do not require a publish action.
The <BuilderContent> component enables dynamic visual layouts for content entries and supports drag-and-drop editing.
This example uses a custom data model called site-settings, which includes a navigationLinks field of type list. Each link contains a linkURL and linkText sub-field.
import { BuilderContent } from "@builder.io/react";
export const Navigation = (props) => {
return (
<BuilderContent model="site-settings">
{(data, loading, fullData) => {
if (loading) {
return <div>Loading...</div>;
} else {
return (
<>
<ul>
{data?.navigationLinks?.map((link) => {
return (
<div key={link.linkUrl}>
<a target="_blank" href={link.linkUrl}>
{link.linkText}
</a>
</div>
);
})}
</ul>
{props.children}
</>
);
}
}}
</BuilderContent>
);
};The BuilderContent component uses the site-settings data model to fetch the most recent published entry of the provided model for rendering.
The inline function provides three parameters:
data(required): The resolved data from thesite-settingsmodel is the structured data from the most recent entry. If A/B testing is active,<BuilderContent>automatically serves the winning variant without additional setup.loading(optional): A boolean indicating whether the data is still loading.fullContent(required for SSR): Includes all raw data from the Content API, such as A/B variations and metadata. In client-side rendering,<BuilderContent>fetches the most recently published entry of the specified model if the content prop is not passed.
The video below shows using this example data model in the Visual Editor:
This component uses the useState() hook to handle side effects, including fetching initial data and subscribing to live updates.
The fetchOneEntry() function is called with the blog-article model and API key to fetch data from Builder. If successful, setContent() updates the content state.
/* src/BlogArticle.tsx */
import {
fetchOneEntry,
subscribeToEditor,
BuilderContent,
getBuilderSearchParams,
} from "@builder.io/sdk-react";
import { useEffect, useState } from "react";
import { useParams } from "react-router";
function BlogArticle() {
const [content, setContent] = useState<BuilderContent | null>(null);
const [loading, setLoading] = useState(true);
const { slug } = useParams();
useEffect(() => {
fetchOneEntry({
model: "blog-article",
apiKey: /* ADD YOUR PUBLIC API KEY HERE */,
userAttributes: {
slug: slug,
},
options: getBuilderSearchParams(new URL(location.href).searchParams),
})
.then((data) => {
setContent(data);
setLoading(false);
})
.catch((err) => {
console.error(
"something went wrong while fetching Builder Content: ",
err
);
});
// subscribe to live updates
const unsubscribe = subscribeToEditor({
model: 'blog-article',
apiKey: /* ADD YOUR PUBLIC API KEY */,
callback: (data) => {
setContent(data);
},
});
return () => {
unsubscribe();
};
}, [slug]);
if (!content && !loading) {
return <div>Loading Data...</div>;
}
return (
<>
<div>Blog Title: {content?.data?.title}</div>
<div>Blog Author: {content?.data?.author}</div>
<div>Blog handle: {content?.data?.author}</div>
<div>Blog published date: {content?.data?.publishedDate}</div>
</>
);
}
export default BlogArticle;For real-time content preview, the component subscribes to the Visual Editor using subscribeToEditor(). When the state changes, setContent() updates the data, allowing content changes to appear instantly without a page refresh or publishing.
The useEffect() hook returns an unsubscribe() function to clean up the subscription when the component unmounts, preventing memory leaks.
If content is not available, the component displays a loading state. Once fetched, it renders key blog details such as title, author, handle, and publishedDate, dynamically pulling data from the blog-article model.
- Specify the name of your data model in
BuilderContentwith themodelprop. - Use a render prop pattern with the required
data, an optionalloading, andfullDataparameters in case of SSR. - Add code that accesses your data in the
return()statement. This code varies and depends on your use case. - Set the Preview URL on the data model. For detailed instructions on setting a Preview URL on a model, visit the Setting a persistent Preview URL on a model in Editing and Previewing Your Site.
- Test the live preview by editing your data model in the Builder Visual Editor and checking that the changes are reflected in your application.
The following snippet shows this structure.
// Add your data model's name
<BuilderContent model="YOUR_DATA_MODEL">
// add function to render data
{(data, loading, fullData) => {
if (loading) return <div>Loading...</div>;
return (
// Add your code to access your data
);
}}
</BuilderContent>This example uses a custom data model named blog-article, which includes a title, author, handle, and publishedDate fields, each of type text.
The component calls builder.get() to retrieve the published entry from the blog-article model by matching the urlPath to the route parameter.
The retrieved data is passed to BlogArticle component as a prop.
import { useEffect } from "react";
import { builder, BuilderContent, useIsPreviewing } from "@builder.io/react";
import { useParams } from "react-router";
import BlogArticle from "./BlogArticle";
builder.init(/* ADD YOUR PUBLIC API KEY HERE */);
type ArticleData = {
title: string;
description: string;
author: string;
};
type Article = BuilderContent & {
data: ArticleData;
};
export default function BuilderPage() {
const isPreviewingInBuilder = useIsPreviewing();
const [notFound, setNotFound] = React.useState(false);
const [content, setContent] = React.useState<Article | null>(null);
const { slug } = useParams();
// get the page content from Builder
useEffect(() => {
async function fetchContent() {
const content = await builder
.get("blog-article", {
url: slug,
})
.promise();
setContent(content);
setNotFound(!content);
if (content?.data.title) {
document.title = content.data.title;
}
}
fetchContent();
}, [slug]);
if (content === null) {
return;
}
if (notFound && !isPreviewingInBuilder) {
return <div>404 Page Not Found</div>;
}
return (
<>
{/* Render the Builder page */}
<BlogContent article={content} />
</>
);
}The fetched article data is passed to the <BuilderContent> component for rendering. The inline function used within <BuilderContent> accepts the following parameters:
data(required) – The resolved data from theblog-articlemodel. If A/B testing is active,<BuilderContent>automatically serves the winning variant without additional setup.loading(optional) – A boolean indicating whether the data is still loading.fullData(required for SSR) – Includes all raw data from the Content API, such as A/B variations and metadata. In client-side rendering,<BuilderContent>fetches the most recently published entry of the specified model if the content prop is not provided.
//app/src/components/BlogArticle.tsx
import {
BuilderComponent,
BuilderContent,
useIsPreviewing,
} from "@builder.io/react";
type ArticleData = {
title: string;
description: string;
author: string;
};
type Article = BuilderContent & {
data: ArticleData;
};
export default function BlogContent({ article }: { article: Article }) {
const isPreviewing = useIsPreviewing();
if (!isPreviewing && !article) {
return (
<>
<div>404 - Page Not Found</div>
</>
);
}
return (
<>
<BuilderContent
content={article}
options={{ includeRefs: true }}
model="blog-article"
>
{(data, loading, fullData) => {
return (
<>
<!-- data coming from the blog-article data model -->
<div>Blog Title: {data.title}</div>
<div>Blog Author: {data.author}</div>
<div>Blog handle: {data.handle}</div>
{loading && <div>Loading...</div>}
<!-- You can render builder components from Page/section model -->
<BuilderComponent
content={article}
model="page"
options={{ includeRefs: true }}
/>
<div>Published Date: {data.publishedDate.Default}</div>
</>
);
}}
</BuilderContent>
</>
);
}Fields such as title, author, handle, and publishedDate update in real time within the Visual Editor and do not require a publish action.
The <BuilderContent> component enables dynamic visual layouts for content entries and supports drag-and-drop editing.
This example uses a custom data model called site-settings, which includes a navigationLinks field of type list. Each link contains a linkURL and linkText sub-field.
import { BuilderContent } from "@builder.io/react";
export const Navigation = (props) => {
return (
<BuilderContent model="site-settings">
{(data, loading, fullData) => {
if (loading) {
return <div>Loading...</div>;
} else {
return (
<>
<ul>
{data?.navigationLinks?.map((link) => {
return (
<div key={link.linkUrl}>
<a target="_blank" href={link.linkUrl}>
{link.linkText}
</a>
</div>
);
})}
</ul>
{props.children}
</>
);
}
}}
</BuilderContent>
);
};The BuilderContent component uses the site-settings data model to fetch the most recent published entry of the provided model for rendering.
The inline function provides three parameters:
data(required): The resolved data from thesite-settingsmodel is the structured data from the most recent entry. If A/B testing is active,<BuilderContent>automatically serves the winning variant without additional setup.loading(optional): A boolean indicating whether the data is still loading.fullContent(required for SSR): Includes all raw data from the Content API, such as A/B variations and metadata. In client-side rendering,<BuilderContent>fetches the most recently published entry of the specified model if the content prop is not passed.
The video below shows using this example data model in the Visual Editor:
This component uses the useState() hook to handle side effects, including fetching initial data and subscribing to live updates.
The fetchOneEntry() function is called with the blog-article model and API key to fetch data from Builder. If successful, setContent() updates the content state.
/* src/BlogArticle.tsx */
import {
fetchOneEntry,
subscribeToEditor,
BuilderContent,
getBuilderSearchParams,
} from "@builder.io/sdk-react";
import { useEffect, useState } from "react";
import { useParams } from "react-router";
function BlogArticle() {
const [content, setContent] = useState<BuilderContent | null>(null);
const [loading, setLoading] = useState(true);
const { slug } = useParams();
useEffect(() => {
fetchOneEntry({
model: "blog-article",
apiKey: /* ADD YOUR PUBLIC API KEY HERE */,
userAttributes: {
slug: slug,
},
options: getBuilderSearchParams(new URL(location.href).searchParams),
})
.then((data) => {
setContent(data);
setLoading(false);
})
.catch((err) => {
console.error(
"something went wrong while fetching Builder Content: ",
err
);
});
// subscribe to live updates
const unsubscribe = subscribeToEditor({
model: 'blog-article',
apiKey: /* ADD YOUR PUBLIC API KEY */,
callback: (data) => {
setContent(data);
},
});
return () => {
unsubscribe();
};
}, [slug]);
if (!content && !loading) {
return <div>Loading Data...</div>;
}
return (
<>
<div>Blog Title: {content?.data?.title}</div>
<div>Blog Author: {content?.data?.author}</div>
<div>Blog handle: {content?.data?.author}</div>
<div>Blog published date: {content?.data?.publishedDate}</div>
</>
);
}
export default BlogArticle;For real-time content preview, the component subscribes to the Visual Editor using subscribeToEditor(). When the state changes, setContent() updates the data, allowing content changes to appear instantly without a page refresh or publishing.
The useEffect() hook returns an unsubscribe() function to clean up the subscription when the component unmounts, preventing memory leaks.
If content is not available, the component displays a loading state. Once fetched, it renders key blog details such as title, author, handle, and publishedDate, dynamically pulling data from the blog-article model.
- Specify the name of your data model in
BuilderContentwith themodelprop. - Use a render prop pattern with the required
data, an optionalloading, andfullDataparameters in case of SSR. - Add code that accesses your data in the
return()statement. This code varies and depends on your use case. - Set the Preview URL on the data model. For detailed instructions on setting a Preview URL on a model, visit the Setting a persistent Preview URL on a model in Editing and Previewing Your Site.
- Test the live preview by editing your data model in the Builder Visual Editor and checking that the changes are reflected in your application.
The following snippet shows this structure.
// Add your data model's name
<BuilderContent model="YOUR_DATA_MODEL">
// add function to render data
{(data, loading, fullData) => {
if (loading) return <div>Loading...</div>;
return (
// Add your code to access your data
);
}}
</BuilderContent>This example uses a custom data model named blog-article, which includes a title, author, handle, and publishedDate fields, each of type text.
The component calls builder.get() to retrieve the published entry from the blog-article model by matching the urlPath to the route parameter.
The retrieved data is passed to BlogArticle component as a prop.
import { useEffect } from "react";
import { builder, BuilderContent, useIsPreviewing } from "@builder.io/react";
import { useParams } from "react-router";
import BlogArticle from "./BlogArticle";
builder.init(/* ADD YOUR PUBLIC API KEY HERE */);
type ArticleData = {
title: string;
description: string;
author: string;
};
type Article = BuilderContent & {
data: ArticleData;
};
export default function BuilderPage() {
const isPreviewingInBuilder = useIsPreviewing();
const [notFound, setNotFound] = React.useState(false);
const [content, setContent] = React.useState<Article | null>(null);
const { slug } = useParams();
// get the page content from Builder
useEffect(() => {
async function fetchContent() {
const content = await builder
.get("blog-article", {
url: slug,
})
.promise();
setContent(content);
setNotFound(!content);
if (content?.data.title) {
document.title = content.data.title;
}
}
fetchContent();
}, [slug]);
if (content === null) {
return;
}
if (notFound && !isPreviewingInBuilder) {
return <div>404 Page Not Found</div>;
}
return (
<>
{/* Render the Builder page */}
<BlogContent article={content} />
</>
);
}The fetched article data is passed to the <BuilderContent> component for rendering. The inline function used within <BuilderContent> accepts the following parameters:
data(required) – The resolved data from theblog-articlemodel. If A/B testing is active,<BuilderContent>automatically serves the winning variant without additional setup.loading(optional) – A boolean indicating whether the data is still loading.fullData(required for SSR) – Includes all raw data from the Content API, such as A/B variations and metadata. In client-side rendering,<BuilderContent>fetches the most recently published entry of the specified model if the content prop is not provided.
//app/src/components/BlogArticle.tsx
import {
BuilderComponent,
BuilderContent,
useIsPreviewing,
} from "@builder.io/react";
type ArticleData = {
title: string;
description: string;
author: string;
};
type Article = BuilderContent & {
data: ArticleData;
};
export default function BlogContent({ article }: { article: Article }) {
const isPreviewing = useIsPreviewing();
if (!isPreviewing && !article) {
return (
<>
<div>404 - Page Not Found</div>
</>
);
}
return (
<>
<BuilderContent
content={article}
options={{ includeRefs: true }}
model="blog-article"
>
{(data, loading, fullData) => {
return (
<>
<!-- data coming from the blog-article data model -->
<div>Blog Title: {data.title}</div>
<div>Blog Author: {data.author}</div>
<div>Blog handle: {data.handle}</div>
{loading && <div>Loading...</div>}
<!-- You can render builder components from Page/section model -->
<BuilderComponent
content={article}
model="page"
options={{ includeRefs: true }}
/>
<div>Published Date: {data.publishedDate.Default}</div>
</>
);
}}
</BuilderContent>
</>
);
}Fields such as title, author, handle, and publishedDate update in real time within the Visual Editor and do not require a publish action.
The <BuilderContent> component enables dynamic visual layouts for content entries and supports drag-and-drop editing.
This example uses a custom data model called site-settings, which includes a navigationLinks field of type list. Each link contains a linkURL and linkText sub-field.
import { BuilderContent } from "@builder.io/react";
export const Navigation = (props) => {
return (
<BuilderContent model="site-settings">
{(data, loading, fullData) => {
if (loading) {
return <div>Loading...</div>;
} else {
return (
<>
<ul>
{data?.navigationLinks?.map((link) => {
return (
<div key={link.linkUrl}>
<a target="_blank" href={link.linkUrl}>
{link.linkText}
</a>
</div>
);
})}
</ul>
{props.children}
</>
);
}
}}
</BuilderContent>
);
};The BuilderContent component uses the site-settings data model to fetch the most recent published entry of the provided model for rendering.
The inline function provides three parameters:
data(required): The resolved data from thesite-settingsmodel is the structured data from the most recent entry. If A/B testing is active,<BuilderContent>automatically serves the winning variant without additional setup.loading(optional): A boolean indicating whether the data is still loading.fullContent(required for SSR): Includes all raw data from the Content API, such as A/B variations and metadata. In client-side rendering,<BuilderContent>fetches the most recently published entry of the specified model if the content prop is not passed.
The video below shows using this example data model in the Visual Editor:
This component uses the useState() hook to handle side effects, including fetching initial data and subscribing to live updates.
The fetchOneEntry() function is called with the blog-article model and API key to fetch data from Builder. If successful, setContent() updates the content state.
/* src/BlogArticle.tsx */
import {
fetchOneEntry,
subscribeToEditor,
BuilderContent,
getBuilderSearchParams,
} from "@builder.io/sdk-react";
import { useEffect, useState } from "react";
import { useParams } from "react-router";
function BlogArticle() {
const [content, setContent] = useState<BuilderContent | null>(null);
const [loading, setLoading] = useState(true);
const { slug } = useParams();
useEffect(() => {
fetchOneEntry({
model: "blog-article",
apiKey: /* ADD YOUR PUBLIC API KEY HERE */,
userAttributes: {
slug: slug,
},
options: getBuilderSearchParams(new URL(location.href).searchParams),
})
.then((data) => {
setContent(data);
setLoading(false);
})
.catch((err) => {
console.error(
"something went wrong while fetching Builder Content: ",
err
);
});
// subscribe to live updates
const unsubscribe = subscribeToEditor({
model: 'blog-article',
apiKey: /* ADD YOUR PUBLIC API KEY */,
callback: (data) => {
setContent(data);
},
});
return () => {
unsubscribe();
};
}, [slug]);
if (!content && !loading) {
return <div>Loading Data...</div>;
}
return (
<>
<div>Blog Title: {content?.data?.title}</div>
<div>Blog Author: {content?.data?.author}</div>
<div>Blog handle: {content?.data?.author}</div>
<div>Blog published date: {content?.data?.publishedDate}</div>
</>
);
}
export default BlogArticle;For real-time content preview, the component subscribes to the Visual Editor using subscribeToEditor(). When the state changes, setContent() updates the data, allowing content changes to appear instantly without a page refresh or publishing.
The useEffect() hook returns an unsubscribe() function to clean up the subscription when the component unmounts, preventing memory leaks.
If content is not available, the component displays a loading state. Once fetched, it renders key blog details such as title, author, handle, and publishedDate, dynamically pulling data from the blog-article model.
This component uses the useState() hook to handle side effects, including fetching initial data and subscribing to live updates.
The fetchOneEntry() function is called with the blog-article model and API key to fetch data from Builder. If successful, setContent() updates the content state.
/* src/BlogArticle.tsx */
import {
fetchOneEntry,
subscribeToEditor,
BuilderContent,
getBuilderSearchParams,
} from "@builder.io/sdk-react";
import { useEffect, useState } from "react";
import { useParams } from "react-router";
function BlogArticle() {
const [content, setContent] = useState<BuilderContent | null>(null);
const [loading, setLoading] = useState(true);
const { slug } = useParams();
useEffect(() => {
fetchOneEntry({
model: "blog-article",
apiKey: /* ADD YOUR PUBLIC API KEY HERE */,
userAttributes: {
slug: slug,
},
options: getBuilderSearchParams(new URL(location.href).searchParams),
})
.then((data) => {
setContent(data);
setLoading(false);
})
.catch((err) => {
console.error(
"something went wrong while fetching Builder Content: ",
err
);
});
// subscribe to live updates
const unsubscribe = subscribeToEditor({
model: 'blog-article',
apiKey: /* ADD YOUR PUBLIC API KEY */,
callback: (data) => {
setContent(data);
},
});
return () => {
unsubscribe();
};
}, [slug]);
if (!content && !loading) {
return <div>Loading Data...</div>;
}
return (
<>
<div>Blog Title: {content?.data?.title}</div>
<div>Blog Author: {content?.data?.author}</div>
<div>Blog handle: {content?.data?.author}</div>
<div>Blog published date: {content?.data?.publishedDate}</div>
</>
);
}
export default BlogArticle;For real-time content preview, the component subscribes to the Visual Editor using subscribeToEditor(). When the state changes, setContent() updates the data, allowing content changes to appear instantly without a page refresh or publishing.
The useEffect() hook returns an unsubscribe() function to clean up the subscription when the component unmounts, preventing memory leaks.
If content is not available, the component displays a loading state. Once fetched, it renders key blog details such as title, author, handle, and publishedDate, dynamically pulling data from the blog-article model.
- Specify the name of your data model in
BuilderContentwith themodelprop. - Use a render prop pattern with the required
data, an optionalloading, andfullDataparameters in case of SSR. - Add code that accesses your data in the
return()statement. This code varies and depends on your use case. - Set the Preview URL on the data model. For detailed instructions on setting a Preview URL on a model, visit the Setting a persistent Preview URL on a model in Editing and Previewing Your Site.
- Test the live preview by editing your data model in the Builder Visual Editor and checking that the changes are reflected in your application.
The following snippet shows this structure.
// Add your data model's name
<BuilderContent model="YOUR_DATA_MODEL">
// add function to render data
{(data, loading, fullData) => {
if (loading) return <div>Loading...</div>;
return (
// Add your code to access your data
);
}}
</BuilderContent>This example uses a custom data model named blog-article, which includes a title, author, handle, and publishedDate fields, each of type text.
The component calls builder.get() to retrieve the published entry from the blog-article model by matching the urlPath to the route parameter.
The retrieved data is passed to BlogArticle component as a prop.
import { useEffect } from "react";
import { builder, BuilderContent, useIsPreviewing } from "@builder.io/react";
import { useParams } from "react-router";
import BlogArticle from "./BlogArticle";
builder.init(/* ADD YOUR PUBLIC API KEY HERE */);
type ArticleData = {
title: string;
description: string;
author: string;
};
type Article = BuilderContent & {
data: ArticleData;
};
export default function BuilderPage() {
const isPreviewingInBuilder = useIsPreviewing();
const [notFound, setNotFound] = React.useState(false);
const [content, setContent] = React.useState<Article | null>(null);
const { slug } = useParams();
// get the page content from Builder
useEffect(() => {
async function fetchContent() {
const content = await builder
.get("blog-article", {
url: slug,
})
.promise();
setContent(content);
setNotFound(!content);
if (content?.data.title) {
document.title = content.data.title;
}
}
fetchContent();
}, [slug]);
if (content === null) {
return;
}
if (notFound && !isPreviewingInBuilder) {
return <div>404 Page Not Found</div>;
}
return (
<>
{/* Render the Builder page */}
<BlogContent article={content} />
</>
);
}The fetched article data is passed to the <BuilderContent> component for rendering. The inline function used within <BuilderContent> accepts the following parameters:
data(required) – The resolved data from theblog-articlemodel. If A/B testing is active,<BuilderContent>automatically serves the winning variant without additional setup.loading(optional) – A boolean indicating whether the data is still loading.fullData(required for SSR) – Includes all raw data from the Content API, such as A/B variations and metadata. In client-side rendering,<BuilderContent>fetches the most recently published entry of the specified model if the content prop is not provided.
//app/src/components/BlogArticle.tsx
import {
BuilderComponent,
BuilderContent,
useIsPreviewing,
} from "@builder.io/react";
type ArticleData = {
title: string;
description: string;
author: string;
};
type Article = BuilderContent & {
data: ArticleData;
};
export default function BlogContent({ article }: { article: Article }) {
const isPreviewing = useIsPreviewing();
if (!isPreviewing && !article) {
return (
<>
<div>404 - Page Not Found</div>
</>
);
}
return (
<>
<BuilderContent
content={article}
options={{ includeRefs: true }}
model="blog-article"
>
{(data, loading, fullData) => {
return (
<>
<!-- data coming from the blog-article data model -->
<div>Blog Title: {data.title}</div>
<div>Blog Author: {data.author}</div>
<div>Blog handle: {data.handle}</div>
{loading && <div>Loading...</div>}
<!-- You can render builder components from Page/section model -->
<BuilderComponent
content={article}
model="page"
options={{ includeRefs: true }}
/>
<div>Published Date: {data.publishedDate.Default}</div>
</>
);
}}
</BuilderContent>
</>
);
}Fields such as title, author, handle, and publishedDate update in real time within the Visual Editor and do not require a publish action.
The <BuilderContent> component enables dynamic visual layouts for content entries and supports drag-and-drop editing.
This example uses a custom data model called site-settings, which includes a navigationLinks field of type list. Each link contains a linkURL and linkText sub-field.
import { BuilderContent } from "@builder.io/react";
export const Navigation = (props) => {
return (
<BuilderContent model="site-settings">
{(data, loading, fullData) => {
if (loading) {
return <div>Loading...</div>;
} else {
return (
<>
<ul>
{data?.navigationLinks?.map((link) => {
return (
<div key={link.linkUrl}>
<a target="_blank" href={link.linkUrl}>
{link.linkText}
</a>
</div>
);
})}
</ul>
{props.children}
</>
);
}
}}
</BuilderContent>
);
};The BuilderContent component uses the site-settings data model to fetch the most recent published entry of the provided model for rendering.
The inline function provides three parameters:
data(required): The resolved data from thesite-settingsmodel is the structured data from the most recent entry. If A/B testing is active,<BuilderContent>automatically serves the winning variant without additional setup.loading(optional): A boolean indicating whether the data is still loading.fullContent(required for SSR): Includes all raw data from the Content API, such as A/B variations and metadata. In client-side rendering,<BuilderContent>fetches the most recently published entry of the specified model if the content prop is not passed.
The video below shows using this example data model in the Visual Editor:
This component uses the useState() hook to handle side effects, including fetching initial data and subscribing to live updates.
The fetchOneEntry() function is called with the blog-article model and API key to fetch data from Builder. If successful, setContent() updates the content state.
/* src/BlogArticle.tsx */
import {
fetchOneEntry,
subscribeToEditor,
BuilderContent,
getBuilderSearchParams,
} from "@builder.io/sdk-react";
import { useEffect, useState } from "react";
import { useParams } from "react-router";
function BlogArticle() {
const [content, setContent] = useState<BuilderContent | null>(null);
const [loading, setLoading] = useState(true);
const { slug } = useParams();
useEffect(() => {
fetchOneEntry({
model: "blog-article",
apiKey: /* ADD YOUR PUBLIC API KEY HERE */,
userAttributes: {
slug: slug,
},
options: getBuilderSearchParams(new URL(location.href).searchParams),
})
.then((data) => {
setContent(data);
setLoading(false);
})
.catch((err) => {
console.error(
"something went wrong while fetching Builder Content: ",
err
);
});
// subscribe to live updates
const unsubscribe = subscribeToEditor({
model: 'blog-article',
apiKey: /* ADD YOUR PUBLIC API KEY */,
callback: (data) => {
setContent(data);
},
});
return () => {
unsubscribe();
};
}, [slug]);
if (!content && !loading) {
return <div>Loading Data...</div>;
}
return (
<>
<div>Blog Title: {content?.data?.title}</div>
<div>Blog Author: {content?.data?.author}</div>
<div>Blog handle: {content?.data?.author}</div>
<div>Blog published date: {content?.data?.publishedDate}</div>
</>
);
}
export default BlogArticle;For real-time content preview, the component subscribes to the Visual Editor using subscribeToEditor(). When the state changes, setContent() updates the data, allowing content changes to appear instantly without a page refresh or publishing.
The useEffect() hook returns an unsubscribe() function to clean up the subscription when the component unmounts, preventing memory leaks.
If content is not available, the component displays a loading state. Once fetched, it renders key blog details such as title, author, handle, and publishedDate, dynamically pulling data from the blog-article model.
- Specify the name of your data model in
BuilderContentwith themodelprop. - Use a render prop pattern with the required
data, an optionalloading, andfullDataparameters in case of SSR. - Add code that accesses your data in the
return()statement. This code varies and depends on your use case. - Set the Preview URL on the data model. For detailed instructions on setting a Preview URL on a model, visit the Setting a persistent Preview URL on a model in Editing and Previewing Your Site.
- Test the live preview by editing your data model in the Builder Visual Editor and checking that the changes are reflected in your application.
The following snippet shows this structure.
// Add your data model's name
<BuilderContent model="YOUR_DATA_MODEL">
// add function to render data
{(data, loading, fullData) => {
if (loading) return <div>Loading...</div>;
return (
// Add your code to access your data
);
}}
</BuilderContent>This example uses a custom data model named blog-article, which includes a title, author, handle, and publishedDate fields, each of type text.
The component calls builder.get() to retrieve the published entry from the blog-article model by matching the urlPath to the route parameter.
The retrieved data is passed to BlogArticle component as a prop.
import { useEffect } from "react";
import { builder, BuilderContent, useIsPreviewing } from "@builder.io/react";
import { useParams } from "react-router";
import BlogArticle from "./BlogArticle";
builder.init(/* ADD YOUR PUBLIC API KEY HERE */);
type ArticleData = {
title: string;
description: string;
author: string;
};
type Article = BuilderContent & {
data: ArticleData;
};
export default function BuilderPage() {
const isPreviewingInBuilder = useIsPreviewing();
const [notFound, setNotFound] = React.useState(false);
const [content, setContent] = React.useState<Article | null>(null);
const { slug } = useParams();
// get the page content from Builder
useEffect(() => {
async function fetchContent() {
const content = await builder
.get("blog-article", {
url: slug,
})
.promise();
setContent(content);
setNotFound(!content);
if (content?.data.title) {
document.title = content.data.title;
}
}
fetchContent();
}, [slug]);
if (content === null) {
return;
}
if (notFound && !isPreviewingInBuilder) {
return <div>404 Page Not Found</div>;
}
return (
<>
{/* Render the Builder page */}
<BlogContent article={content} />
</>
);
}The fetched article data is passed to the <BuilderContent> component for rendering. The inline function used within <BuilderContent> accepts the following parameters:
data(required) – The resolved data from theblog-articlemodel. If A/B testing is active,<BuilderContent>automatically serves the winning variant without additional setup.loading(optional) – A boolean indicating whether the data is still loading.fullData(required for SSR) – Includes all raw data from the Content API, such as A/B variations and metadata. In client-side rendering,<BuilderContent>fetches the most recently published entry of the specified model if the content prop is not provided.
//app/src/components/BlogArticle.tsx
import {
BuilderComponent,
BuilderContent,
useIsPreviewing,
} from "@builder.io/react";
type ArticleData = {
title: string;
description: string;
author: string;
};
type Article = BuilderContent & {
data: ArticleData;
};
export default function BlogContent({ article }: { article: Article }) {
const isPreviewing = useIsPreviewing();
if (!isPreviewing && !article) {
return (
<>
<div>404 - Page Not Found</div>
</>
);
}
return (
<>
<BuilderContent
content={article}
options={{ includeRefs: true }}
model="blog-article"
>
{(data, loading, fullData) => {
return (
<>
<!-- data coming from the blog-article data model -->
<div>Blog Title: {data.title}</div>
<div>Blog Author: {data.author}</div>
<div>Blog handle: {data.handle}</div>
{loading && <div>Loading...</div>}
<!-- You can render builder components from Page/section model -->
<BuilderComponent
content={article}
model="page"
options={{ includeRefs: true }}
/>
<div>Published Date: {data.publishedDate.Default}</div>
</>
);
}}
</BuilderContent>
</>
);
}Fields such as title, author, handle, and publishedDate update in real time within the Visual Editor and do not require a publish action.
The <BuilderContent> component enables dynamic visual layouts for content entries and supports drag-and-drop editing.
This example uses a custom data model called site-settings, which includes a navigationLinks field of type list. Each link contains a linkURL and linkText sub-field.
import { BuilderContent } from "@builder.io/react";
export const Navigation = (props) => {
return (
<BuilderContent model="site-settings">
{(data, loading, fullData) => {
if (loading) {
return <div>Loading...</div>;
} else {
return (
<>
<ul>
{data?.navigationLinks?.map((link) => {
return (
<div key={link.linkUrl}>
<a target="_blank" href={link.linkUrl}>
{link.linkText}
</a>
</div>
);
})}
</ul>
{props.children}
</>
);
}
}}
</BuilderContent>
);
};The BuilderContent component uses the site-settings data model to fetch the most recent published entry of the provided model for rendering.
The inline function provides three parameters:
data(required): The resolved data from thesite-settingsmodel is the structured data from the most recent entry. If A/B testing is active,<BuilderContent>automatically serves the winning variant without additional setup.loading(optional): A boolean indicating whether the data is still loading.fullContent(required for SSR): Includes all raw data from the Content API, such as A/B variations and metadata. In client-side rendering,<BuilderContent>fetches the most recently published entry of the specified model if the content prop is not passed.
The video below shows using this example data model in the Visual Editor:
In the example below, the code subscribes to live updates from the Builder Visual Editor using the subscribeToEditor() function.
Whenever changes are made in the editor, the callback function updates the content variable with the new data. The onMount() function returns a cleanup function that unsubscribes from live updates when the component is unmounted.
The fetchOneEntry() function runs immediately after the component mounts to fetch the initial data from Builder.
<script>
import { onMount } from "svelte";
import { fetchOneEntry, subscribeToEditor, getBuilderSearchParams } from "@builder.io/sdk";
import { page } from "$app/stores";
let content = null;
let loading = true;
const BUILDER_PUBLIC_API_KEY = import.meta.env.VITE_PUBLIC_BUILDER_KEY;
let slug;
$: slug = $page.params.slug;
onMount(async () => {
try {
const data = await fetchOneEntry({
model: "blog-article",
apiKey: BUILDER_PUBLIC_API_KEY,
userAttributes: { slug },
options: getBuilderSearchParams(new URL(location.href).searchParams),
});
content = data;
loading = false;
} catch (err) {
console.error("Error fetching Builder Content:", err);
}
const unsubscribe = subscribeToEditor({
model: 'blog-article',
apiKey: /* ADD YOUR PUBLIC API KEY */,
callback: (data) => {
setContent(data);
},
});
return () => unsubscribe();
});
</script>
{#if loading}
<p>Loading Data...</p>
{:else}
<div>Blog Title: {content?.data?.title}</div>
<div>Blog Author: {content?.data?.author}</div>
<div>Blog Handle: {content?.data?.handle}</div>
<div>Blog Published Date: {content?.data?.publishedDate}</div>
{/if}In the template, the fetched data populates content, and the component renders the title, author, handle, and publishedDate using the data property of the content object.
The rendered content updates automatically whenever changes are made in the Builder Visual Editor, thanks to the reactive nature of the content variable and the live subscription set up in onMount().
In the example below, the code subscribes to live updates from the Builder Visual Editor using the subscribeToEditor() function.
Whenever changes are made in the editor, the callback function updates the content variable with the new data. The onMount() function returns a cleanup function that unsubscribes from live updates when the component is unmounted.
The fetchOneEntry() function runs immediately after the component mounts to fetch the initial data from Builder.
<script>
import { onMount } from "svelte";
import { fetchOneEntry, subscribeToEditor, getBuilderSearchParams } from "@builder.io/sdk";
import { page } from "$app/stores";
let content = null;
let loading = true;
const BUILDER_PUBLIC_API_KEY = import.meta.env.VITE_PUBLIC_BUILDER_KEY;
let slug;
$: slug = $page.params.slug;
onMount(async () => {
try {
const data = await fetchOneEntry({
model: "blog-article",
apiKey: BUILDER_PUBLIC_API_KEY,
userAttributes: { slug },
options: getBuilderSearchParams(new URL(location.href).searchParams),
});
content = data;
loading = false;
} catch (err) {
console.error("Error fetching Builder Content:", err);
}
const unsubscribe = subscribeToEditor({
model: 'blog-article',
apiKey: /* ADD YOUR PUBLIC API KEY */,
callback: (data) => {
setContent(data);
},
});
return () => unsubscribe();
});
</script>
{#if loading}
<p>Loading Data...</p>
{:else}
<div>Blog Title: {content?.data?.title}</div>
<div>Blog Author: {content?.data?.author}</div>
<div>Blog Handle: {content?.data?.author}</div>
<div>Blog Published Date: {content?.data?.publishedDate}</div>
{/if}For real-time content preview, the component subscribes to the Visual Editor using subscribeToEditor(). When the state changes, setContent() updates the data, allowing content changes to appear instantly without a page refresh or publishing.
The useEffect() hook returns an unsubscribe() function to clean up the subscription when the component unmounts, preventing memory leaks.
If content is not available, the component displays a loading state. Once fetched, it renders key blog details such as title, author, handle, and publishedDate, dynamically pulling data from the blog-article model.
In this example, the component defines two reactive properties:
content– Initialized asnull, this property stores the fetched data from Builder.unsubscribeFromEditor()– Initialized as an empty function, this property is assigned the unsubscribe function returned bysubscribeToEditor().
The fetched data is assigned to the content property using content.value. The component also subscribes to live updates from the Visual Editor using subscribeToEditor().
Whenever changes are made in the Visual Editor, the callback function updates the content property with the new data. When the component unmounts, unsubscribeFromEditor() is called to clean up the subscription.
<script setup>
import { ref, onMounted, onUnmounted } from "vue";
import { useRoute } from "vue-router";
import { fetchOneEntry, subscribeToEditor, getBuilderSearchParams } from "@builder.io/sdk";
const route = useRoute();
const BUILDER_PUBLIC_API_KEY = import.meta.env.VITE_PUBLIC_BUILDER_KEY;
const content = ref(null);
const loading = ref(true);
onMounted(async () => {
try {
const data = await fetchOneEntry({
model: "blog-article",
apiKey: BUILDER_PUBLIC_API_KEY,
userAttributes: { slug: route.params.slug },
options: getBuilderSearchParams(new URL(location.href).searchParams),
});
content.value = data;
loading.value = false;
} catch (err) {
console.error("Error fetching Builder Content:", err);
}
const unsubscribe = subscribeToEditor({
model: 'blog-article',
apiKey: /* ADD YOUR PUBLIC API KEY */,
callback: (data) => {
setContent(data);
},
});
onUnmounted(() => {
unsubscribeFromEditor();
});
});
</script>
<template>
<div v-if="loading">Loading Data...</div>
<div v-else>
<div>Blog Title: {{ content?.data?.title }}</div>
<div>Blog Author: {{ content?.data?.author }}</div>
<div>Blog Handle: {{ content?.data?.author }}</div>
<div>Blog Published Date: {{ content?.data?.publishedDate }}</div>
</div>
</template>In this example, the component defines two reactive properties:
content– Initialized asnull, this property stores the fetched data from Builder.unsubscribeFromEditor()– Initialized as an empty function, this property is assigned the unsubscribe function returned bysubscribeToEditor().
The fetched data is assigned to the content property using content.value. The component also subscribes to live updates from the Visual Editor using subscribeToEditor().
Whenever changes are made in the Visual Editor, the callback function updates the content property with the new data. When the component unmounts, unsubscribeFromEditor() is called to clean up the subscription.
<script setup>
import { useRoute } from "vue-router";
import { fetchOneEntry, subscribeToEditor, getBuilderSearchParams } from "@builder.io/sdk";
const route = useRoute();
const BUILDER_PUBLIC_API_KEY = import.meta.env.VITE_PUBLIC_BUILDER_KEY;
const { data: content, pending: loading } = await useAsyncData(() =>
fetchOneEntry({
model: "blog-article",
apiKey: BUILDER_PUBLIC_API_KEY,
userAttributes: { slug: route.params.slug },
options: getBuilderSearchParams(new URL(location.href).searchParams),
})
);
onMounted(() => {
const unsubscribe = subscribeToEditor({
model: 'blog-article',
apiKey: /* ADD YOUR PUBLIC API KEY */,
callback: (data) => {
setContent(data);
},
});
onUnmounted(() => {
unsubscribe();
});
});
</script>
<template>
<div v-if="loading">Loading Data...</div>
<div v-else>
<div>Blog Title: {{ content?.data?.title }}</div>
<div>Blog Author: {{ content?.data?.author }}</div>
<div>Blog Handle: {{ content?.data?.author }}</div>
<div>Blog Published Date: {{ content?.data?.publishedDate }}</div>
</div>
</template>The following example code defines a server-side data loader function called useBuilderContentLoader using routeLoader$, which fetches data from Builder based on the specified model and API key. The fetched data is then passed to the default component, where it is stored in a signal called content.
The component subscribes to live updates from the Builder Visual Editor using subscribeToEditor within the useVisibleTask$ lifecycle hook.
import { component$, useSignal, useVisibleTask$ } from '@builder.io/qwik';
import { routeLoader$ } from '@builder.io/qwik-city';
import { fetchOneEntry, subscribeToEditor } from '@builder.io/sdk-qwik';
export const apiKey = /* add your Public API Key here */;
export const useBuilderContentLoader = routeLoader$(async event => {
const data = await fetchOneEntry({
model: 'coffee',
apiKey: apiKey,
});
if (!data) {
throw event.error(404, 'page not found');
}
return data;
});
export default component$(() => {
const fetchedContent = useBuilderContentLoader();
const content = useSignal(fetchedContent.value);
useVisibleTask$(() => {
const unsubscribe = subscribeToEditor({
model: 'blog-article',
apiKey: /* ADD YOUR PUBLIC API KEY */,
callback: (data) => {
setContent(data);
},
});
return () => unsubscribe();
});
return content.value ? (
<>
<div>coffee name: {content.value.data?.name}</div>
<div>coffee info: {content.value.data?.info}</div>
</>
) : (
<div>Loading...</div>
);
});Whenever changes are made in the Visual Editor, the content signal is reactively updated, triggering a re-render of the component with the latest data.
The component conditionally renders the coffee name and info based on the availability of the content value, displaying a loading message while the data is being fetched.
This component uses the useState() hook to handle side effects, including fetching initial data and subscribing to live updates.
The fetchOneEntry() function is called with the blog-article model and API key to fetch data from Builder. If successful, setContent() updates the content state.
import {
component$,
useSignal,
useTask$,
useVisibleTask$,
} from "@builder.io/qwik";
import { useLocation } from "@builder.io/qwik-city";
import {
fetchOneEntry,
subscribeToEditor,
BuilderContent,
getBuilderSearchParams,
} from "@builder.io/sdk-qwik";
export default component$(() => {
const content = useSignal<BuilderContent | null>(null);
const loading = useSignal(true);
const location = useLocation();
const slug = location.params.slug;
useTask$(async ({ track }) => {
track(() => slug);
if (!slug) return;
try {
const data = await fetchOneEntry({
model: "blog-article",
apiKey: /* ADD YOUR PUBLIC API KEY HERE */,
userAttributes: {
slug: slug,
},
options: getBuilderSearchParams(new URL(location.url.href).searchParams),
});
content.value = data;
loading.value = false;
} catch (err) {
console.error(
"something went wrong while fetching Builder Content: ",
err
);
loading.value = false;
}
});
useVisibleTask$(() => {
// subscribe to live updates
const unsubscribe = subscribeToEditor("blog-article", (data) => {
content.value = data;
});
return () => {
unsubscribe();
};
});
if (!content.value && !loading.value) {
return <div>Loading Data...</div>;
}
return (
<>
<div>Blog Title: {content.value?.data?.title}</div>
<div>Blog Author: {content.value?.data?.author}</div>
<div>Blog handle: {content.value?.data?.author}</div>
<div>Blog published date: {content.value?.data?.publishedDate}</div>
</>
);
});For real-time content preview, the component subscribes to the Visual Editor using subscribeToEditor(). Whenever changes are made in the Visual Editor, the content signal is reactively updated, triggering a re-render of the component with the latest data.
If content is not available, the component displays a loading state. Once fetched, it renders key blog details such as title, author, handle, and publishedDate, dynamically pulling data from the blog-article model.
In this example, <BlogArticle/> is a functional component that uses the useState() hook to manage content retrieved from the model.
The useEffect() hook handles side effects, including fetching initial data and subscribing to live updates.
The fetchOneEntry() function is called with the blog-article model and API key to fetch data from Builder. If successful, setContent() updates the content state.
import { Component, OnInit, OnDestroy } from "@angular/core";
import { ActivatedRoute } from "@angular/router";
import { fetchOneEntry, subscribeToEditor, getBuilderSearchParams } from "@builder.io/sdk";
const BUILDER_PUBLIC_API_KEY = "YOUR_PUBLIC_API_KEY";
@Component({
selector: "app-blog-article",
templateUrl: "./blog-article.component.html",
styleUrls: ["./blog-article.component.css"],
})
export class BlogArticleComponent implements OnInit, OnDestroy {
content: any = null;
loading: boolean = true;
private unsubscribe: () => void = () => {};
constructor(private route: ActivatedRoute) {}
ngOnInit(): void {
const slug = this.route.snapshot.paramMap.get("slug");
fetchOneEntry({
model: "blog-article",
apiKey: BUILDER_PUBLIC_API_KEY,
userAttributes: { slug },
options: getBuilderSearchParams(new URL(location.href).searchParams),
})
.then((data) => {
this.content = data;
this.loading = false;
})
.catch((err) => console.error("Error fetching content:", err));
this.unsubscribe = subscribeToEditor({
model: 'blog-article',
apiKey: /* ADD YOUR PUBLIC API KEY */,
callback: (data) => {
this.content = data;
},
});
}
ngOnDestroy(): void {
this.unsubscribe();
}
}For real-time content preview, the component subscribes to the Visual Editor using subscribeToEditor(). When the state changes, setContent() updates the data, allowing content changes to appear instantly without a page refresh or publishing.
The useEffect() hook returns an unsubscribe() function to clean up the subscription when the component unmounts, preventing memory leaks.
If content is not available, the component displays a loading state. Once fetched, it renders key blog details such as title, author, handle, and publishedDate, dynamically pulling data from the blog-article model.
In this example, <BlogArticle/> is a functional component that uses the useState() hook to manage content retrieved from the model.
The useEffect() hook handles side effects, including fetching initial data and subscribing to live updates.
The fetchOneEntry() function is called with the blog-article model and API key to fetch data from Builder. If successful, setContent() updates the content state.
import { Component, OnInit, OnDestroy } from "@angular/core";
import { ActivatedRoute } from "@angular/router";
import { fetchOneEntry, subscribeToEditor, getBuilderSearchParams } from "@builder.io/sdk";
const BUILDER_PUBLIC_API_KEY = "YOUR_PUBLIC_API_KEY";
@Component({
selector: "app-blog-article",
templateUrl: "./blog-article.component.html",
styleUrls: ["./blog-article.component.css"],
})
export class BlogArticleComponent implements OnInit, OnDestroy {
content: any = null;
loading: boolean = true;
private unsubscribe: () => void = () => {};
constructor(private route: ActivatedRoute) {}
ngOnInit(): void {
const slug = this.route.snapshot.paramMap.get("slug");
fetchOneEntry({
model: "blog-article",
apiKey: BUILDER_PUBLIC_API_KEY,
userAttributes: { slug },
options: getBuilderSearchParams(new URL(location.href).searchParams),
})
.then((data) => {
this.content = data;
this.loading = false;
})
.catch((err) => console.error("Error fetching content:", err));
this.unsubscribe = subscribeToEditor({
model: 'blog-article',
apiKey: /* ADD YOUR PUBLIC API KEY */,
callback: (data) => {
this.content = data;
},
});
}
ngOnDestroy(): void {
this.unsubscribe();
}
}For real-time content preview, the component subscribes to the Visual Editor using subscribeToEditor(). When the state changes, setContent() updates the data, allowing content changes to appear instantly without a page refresh or publishing.
The useEffect() hook returns an unsubscribe() function to clean up the subscription when the component unmounts, preventing memory leaks.
If content is not available, the component displays a loading state. Once fetched, it renders key blog details such as title, author, handle, and publishedDate, dynamically pulling data from the blog-article model.
- Specify the name of your data model in
BuilderContentwith themodelprop. - Use a render prop pattern with the required
data, an optionalloading, andfullDataparameters in case of SSR. - Add code that accesses your data in the
return()statement. This code varies and depends on your use case. - Set the Preview URL on the data model. For detailed instructions on setting a Preview URL on a model, visit the Setting a persistent Preview URL on a model in Editing and Previewing Your Site.
- Test the live preview by editing your data model in the Builder Visual Editor and checking that the changes are reflected in your application.
The following snippet shows this structure.
// Add your data model's name
<BuilderContent model="YOUR_DATA_MODEL">
// add function to render data
{(data, loading, fullData) => {
if (loading) return <div>Loading...</div>;
return (
// Add your code to access your data
);
}}
</BuilderContent>This example uses a custom data model named blog-article, which includes a title, author, handle, and publishedDate fields, each of type text.
The component calls builder.get() to retrieve the published entry from the blog-article model by matching the urlPath to the route parameter.
The retrieved data is passed to BlogArticle component as a prop.
import { useEffect } from "react";
import { builder, BuilderContent, useIsPreviewing } from "@builder.io/react";
import { useParams } from "react-router";
import BlogArticle from "./BlogArticle";
builder.init(/* ADD YOUR PUBLIC API KEY HERE */);
type ArticleData = {
title: string;
description: string;
author: string;
};
type Article = BuilderContent & {
data: ArticleData;
};
export default function BuilderPage() {
const isPreviewingInBuilder = useIsPreviewing();
const [notFound, setNotFound] = React.useState(false);
const [content, setContent] = React.useState<Article | null>(null);
const { slug } = useParams();
// get the page content from Builder
useEffect(() => {
async function fetchContent() {
const content = await builder
.get("blog-article", {
url: slug,
})
.promise();
setContent(content);
setNotFound(!content);
if (content?.data.title) {
document.title = content.data.title;
}
}
fetchContent();
}, [slug]);
if (content === null) {
return;
}
if (notFound && !isPreviewingInBuilder) {
return <div>404 Page Not Found</div>;
}
return (
<>
{/* Render the Builder page */}
<BlogContent article={content} />
</>
);
}The fetched article data is passed to the <BuilderContent> component for rendering. The inline function used within <BuilderContent> accepts the following parameters:
data(required) – The resolved data from theblog-articlemodel. If A/B testing is active,<BuilderContent>automatically serves the winning variant without additional setup.loading(optional) – A boolean indicating whether the data is still loading.fullData(required for SSR) – Includes all raw data from the Content API, such as A/B variations and metadata. In client-side rendering,<BuilderContent>fetches the most recently published entry of the specified model if the content prop is not provided.
//app/src/components/BlogArticle.tsx
import {
BuilderComponent,
BuilderContent,
useIsPreviewing,
} from "@builder.io/react";
type ArticleData = {
title: string;
description: string;
author: string;
};
type Article = BuilderContent & {
data: ArticleData;
};
export default function BlogContent({ article }: { article: Article }) {
const isPreviewing = useIsPreviewing();
if (!isPreviewing && !article) {
return (
<>
<div>404 - Page Not Found</div>
</>
);
}
return (
<>
<BuilderContent
content={article}
options={{ includeRefs: true }}
model="blog-article"
>
{(data, loading, fullData) => {
return (
<>
<!-- data coming from the blog-article data model -->
<div>Blog Title: {data.title}</div>
<div>Blog Author: {data.author}</div>
<div>Blog handle: {data.handle}</div>
{loading && <div>Loading...</div>}
<!-- You can render builder components from Page/section model -->
<BuilderComponent
content={article}
model="page"
options={{ includeRefs: true }}
/>
<div>Published Date: {data.publishedDate.Default}</div>
</>
);
}}
</BuilderContent>
</>
);
}Fields such as title, author, handle, and publishedDate update in real time within the Visual Editor and do not require a publish action.
The <BuilderContent> component enables dynamic visual layouts for content entries and supports drag-and-drop editing.
This example uses a custom data model called site-settings, which includes a navigationLinks field of type list. Each link contains a linkURL and linkText sub-field.
import { BuilderContent } from "@builder.io/react";
export const Navigation = (props) => {
return (
<BuilderContent model="site-settings">
{(data, loading, fullData) => {
if (loading) {
return <div>Loading...</div>;
} else {
return (
<>
<ul>
{data?.navigationLinks?.map((link) => {
return (
<div key={link.linkUrl}>
<a target="_blank" href={link.linkUrl}>
{link.linkText}
</a>
</div>
);
})}
</ul>
{props.children}
</>
);
}
}}
</BuilderContent>
);
};The BuilderContent component uses the site-settings data model to fetch the most recent published entry of the provided model for rendering.
The inline function provides three parameters:
data(required): The resolved data from thesite-settingsmodel is the structured data from the most recent entry. If A/B testing is active,<BuilderContent>automatically serves the winning variant without additional setup.loading(optional): A boolean indicating whether the data is still loading.fullContent(required for SSR): Includes all raw data from the Content API, such as A/B variations and metadata. In client-side rendering,<BuilderContent>fetches the most recently published entry of the specified model if the content prop is not passed.
The video below shows using this example data model in the Visual Editor:
This component uses the useState() hook to handle side effects, including fetching initial data and subscribing to live updates.
The fetchOneEntry() function is called with the blog-article model and API key to fetch data from Builder. If successful, setContent() updates the content state.
/* src/BlogArticle.tsx */
import {
fetchOneEntry,
subscribeToEditor,
BuilderContent,
getBuilderSearchParams,
} from "@builder.io/sdk-react";
import { useEffect, useState } from "react";
import { useParams } from "react-router";
function BlogArticle() {
const [content, setContent] = useState<BuilderContent | null>(null);
const [loading, setLoading] = useState(true);
const { slug } = useParams();
useEffect(() => {
fetchOneEntry({
model: "blog-article",
apiKey: /* ADD YOUR PUBLIC API KEY HERE */,
userAttributes: {
slug: slug,
},
options: getBuilderSearchParams(new URL(location.href).searchParams),
})
.then((data) => {
setContent(data);
setLoading(false);
})
.catch((err) => {
console.error(
"something went wrong while fetching Builder Content: ",
err
);
});
// subscribe to live updates
const unsubscribe = subscribeToEditor({
model: 'blog-article',
apiKey: /* ADD YOUR PUBLIC API KEY */,
callback: (data) => {
setContent(data);
},
});
return () => {
unsubscribe();
};
}, [slug]);
if (!content && !loading) {
return <div>Loading Data...</div>;
}
return (
<>
<div>Blog Title: {content?.data?.title}</div>
<div>Blog Author: {content?.data?.author}</div>
<div>Blog handle: {content?.data?.author}</div>
<div>Blog published date: {content?.data?.publishedDate}</div>
</>
);
}
export default BlogArticle;For real-time content preview, the component subscribes to the Visual Editor using subscribeToEditor(). When the state changes, setContent() updates the data, allowing content changes to appear instantly without a page refresh or publishing.
The useEffect() hook returns an unsubscribe() function to clean up the subscription when the component unmounts, preventing memory leaks.
If content is not available, the component displays a loading state. Once fetched, it renders key blog details such as title, author, handle, and publishedDate, dynamically pulling data from the blog-article model.
- Specify the name of your data model in
BuilderContentwith themodelprop. - Use a render prop pattern with the required
dataparameter, an optionalloadingparameter, and acontentparameter if you're using SSR. - In the
return()statement, add code that accesses your data. This code varies and depends on your use case. - Set the Preview URL on the data model. For detailed instructions on setting a Preview URL on a model, visit the Setting a persistent Preview URL on a model in Editing and Previewing Your Site.
- Test the live preview by editing your data model in the Builder Visual Editor and checking that the changes are reflected in your application.
The following snippet shows this structure.
// Add your data model's name
<BuilderContent model="YOUR_DATA_MODEL">
// add function to render data
{(data, loading, content) => {
if (loading) return <div>Loading...</div>;
return (
// Add your code to access your data
);
}}
</BuilderContent>This example uses the blog-article model, which includes title, author, handle, and publishedDate fields.
The component fetches the published entry from data model using builder.get(), matching the route parameter to urlPath. If found, it updates the document title and passes the data to BlogArticleComponent for rendering.
import { Component, OnDestroy, OnInit } from "@angular/core";
import { ActivatedRoute } from "@angular/router";
import { builder, BuilderContent } from "@builder.io/sdk";
import { Subscription } from "rxjs";
import { BlogArticleComponent } from "../blog-article/blog-article.component";
builder.init("YOUR_PUBLIC_API_KEY");
interface ArticleData {
title: string;
description: string;
author: string;
}
interface Article extends BuilderContent {
data: ArticleData;
}
@Component({
selector: "app-builder-page",
templateUrl: "./builder-page.component.html",
styleUrls: ["./builder-page.component.css"],
})
export class BuilderPageComponent implements OnInit, OnDestroy {
content: Article | null = null;
notFound: boolean = false;
isPreviewingInBuilder: boolean = false;
private routeSub: Subscription = new Subscription();
constructor(private route: ActivatedRoute) {}
ngOnInit(): void {
this.routeSub = this.route.params.subscribe((params) => {
const slug = params["slug"];
this.fetchContent(slug);
});
}
async fetchContent(slug: string): Promise<void> {
try {
const content = await builder
.get("blog-article", {
url: slug,
})
.promise();
this.content = content;
this.notFound = !content;
if (content?.data?.title) {
document.title = content.data.title;
}
} catch (error) {
console.error("Error fetching Builder content:", error);
this.notFound = true;
}
}
ngOnDestroy(): void {
this.routeSub.unsubscribe();
}
}On the client, the fetched article data is passed to the <builder-content> component to render. The component gets three parameters:
data(required): The resolved data from theblog-articlemodel. If A/B testing is active,<builder-content>automatically serves the winning variant without additional setup.loading(optional): A boolean indicating whether the data is still loading.fullData(required for SSR): Includes all raw data from the Content API, such as A/B variations and metadata. In client-side rendering,<builder-content>fetches the most recently published entry of the specified model if thecontentprop is not passed.
import { Component, Input, OnInit } from "@angular/core";
import { Builder, BuilderContent } from "@builder.io/sdk-angular";
interface ArticleData {
title: string;
description: string;
author: string;
handle?: string;
publishedDate?: { Default: string };
}
interface Article extends BuilderContent {
data: ArticleData;
}
@Component({
selector: "app-blog-content",
template: `
<ng-container *ngIf="!isPreviewing && !article; else contentTemplate">
<div>404 - Page Not Found</div>
</ng-container>
<ng-template #contentTemplate>
<builder-content
[content]="article"
[options]="{ includeRefs: true }"
model="blog-article"
let-data
let-loading="loading"
let-fullData
>
<div>Blog Title: {{ data?.title }}</div>
<div>Blog Author: {{ data?.author }}</div>
<div>Blog Handle: {{ data?.handle }}</div>
<div *ngIf="loading">Loading...</div>
<!-- Render Builder Components from Page/Section Model -->
<builder-component
[content]="article"
model="page"
[options]="{ includeRefs: true }"
></builder-component>
<div>Published Date: {{ data?.publishedDate?.Default }}</div>
</builder-content>
</ng-template>
`,
styleUrls: ["./blog-content.component.css"],
})
export class BlogContentComponent implements OnInit {
@Input() article: Article | null = null;
isPreviewing: boolean = false;
ngOnInit(): void {
this.isPreviewing = Builder.isPreviewing;
}
}
Fields such as title, author, and publishedDate update in real time within the Visual Editor and do not require a publish action.
The <builder-content> component enables dynamic visual layouts for content entries and supports drag-and-drop editing.
This example uses a custom data model called site-settings, which includes a navigationLinks field of type list. Each link contains a linkURL and linkText sub-field.
import { BuilderContent } from "@builder.io/angular";
export const Navigation = (props) => {
return (
<BuilderContent model="site-settings">
{(data, loading, fullContent) => {
if (loading) {
return <div>Loading...</div>;
} else {
return (
<>
<ul>
{data?.navigationLinks?.map((link) => {
return (
<div key={link.linkUrl}>
<a target="_blank" href={link.linkUrl}>
{link.linkText}
</a>
</div>
);
})}
</ul>
{props.children}
</>
);
}
}}
</BuilderContent>
);
};The BuilderContent component uses the site-settings data model to fetch the most recent published entry of the site-settings model for rendering. The inline function takes three parameters, data, loading, and fullContent.
data(required): the resolved data from the Builder content. in this case is the structured data from the most recent entry of thesite-settingsmodel. If you’re using A/B testing,BuilderContentautomatically returns the winning variant without additional configuration.loading(optional): boolean indicating if thedatais still loading.fullContent(required for SSR): the raw data that the Content API returns, containing all A/B variations and metadata. If you use client-side rendering and don’t pass in acontentprop toBuilderContent,BuilderContentselects the most recent published entry of the model specified.
The video below shows using this example data model in the Visual Editor:
For more information on the variety of custom fields, visit Custom Fields.