You can use Builder CMS Data to create structured reusable data across your site. You can manage the data schema, add, and remove fields within the UI and teammates can create and remove data items in auto-generated structured forms.
Examples include:
- Header navigation links
- Product Details
- Blog authors
- Structured rich content such as user profiles
This tutorial shows you how to add editable navigation links to your site header.
To follow along with this tutorial, you should have the following:
- a Builder account
- an app in the framework of your choice with the appropriate SDK installed
Create a page with the following contents, replacing YOUR_API_KEY with your account's Public API Key:
// pages/your-page.jsx
import { builder } from "@builder.io/react";
import { GetStaticProps, GetStaticPaths } from "next";
// TO DO: Replace with your Public API Key.
builder.init(YOUR_API_KEY);
export async function getStaticProps() {
const links = await builder.getAll("nav-link", {
// Add options for queries, sorting, and targeting here
});
return {
props: {
links: links || null,
},
};
}
export default function Home({ links }) {
return (
<>
<header>
<nav>
{links.map((link, index) => (
<a key={index} href={link.data.url}>
{link.data.label}
</a>
))}
</nav>
</header>
{/* <RestOfYourPage /> */}
</>
);
}
Create a page with the following contents, replacing YOUR_API_KEY with your account's Public API Key:
import { fetchEntries } from '@builder.io/sdk-react';
import { GetStaticPaths, GetStaticProps } from 'next';
// Replace with your Public API Key
const YOUR_API_KEY = /* Add Your Public API Key here */;
// Define a function that fetches the Builder content for a given page
export const getStaticProps: GetStaticProps = async ({ params }) => {
const urlPath = '/' + (Array.isArray(params?.page) ? params.page.join('/') : params?.page || '');
// Fetch the builder content for the given page
const links = await fetchEntries({
apiKey: YOUR_API_KEY,
model: 'nav-link',
userAttributes: { urlPath },
});
return {
// Return the page content as props
props: { links },
// Revalidate the content every 5 seconds
revalidate: 5,
};
};
// Define a function that generates the
// static paths for all pages in Builder
export const getStaticPaths: GetStaticPaths = async () => {
return {
paths: [],
fallback: 'blocking',
};
};
export default function Page({ links }) {
return (
<>
<header>
<nav>
{links?.map((link, index) => (
<a key={index} href={link.data.url}>
{link.data.label}
</a>
))}
</nav>
</header>
<RestOfYourPage />
</>
);
}Create a page, for example in app/[[...page]]/page.tsx, with the following contents, replacing YOUR_API_KEY with your account's Public API Key:
import { builder } from "@builder.io/sdk";
// Replace with your Public API Key.
builder.init(YOUR_API_KEY);
export default async function Home() {
const links = await builder.getAll("nav-link", { prerender: false });
return (
<>
<header>
<nav>
{links.map((link, index) => (
<a key={index} href={link.data.url}>
{link.data.label}
</a>
))}
</nav>
</header>
{/* <RestOfYourPage /> */}
</>
);
}Be sure to add use client to your builder.tsx:
// builder.tsx
"use client"; Create a page, for example in app/[...page]/page.tsx, with the following contents, replacing YOUR_PUBLIC_API_KEY with your account's Public API Key:
// Example file structure, app/[...slug]/page.tsx
// You could alternatively use src/app/[...slug]/page.tsx
import { fetchEntries, getBuilderSearchParams } from "@builder.io/sdk-react";
interface PageProps {
params: {
slug: string[];
};
}
export default async function Page(props: PageProps) {
const urlPath = "/" + (props.params?.slug?.join("/") || "");
// Fetch the builder content for the given page
const links = await fetchEntries({
apiKey: YOUR_API_KEY, //TO DO: Add your Public API Key
model: "nav-link",
options: getBuilderSearchParams(props.searchParams),
userAttributes: { urlPath },
});
return (
<>
<header>
<nav>
{links?.map((link, index) => (
<a key={index} href={link.data.url}>
{link.data.label}
</a>
))}
</nav>
</header>
</>
);
}Create a page with the following contents, replacing YOUR_API_KEY with your account's Public API Key:
import { useEffect, useState } from "react";
import { builder } from "@builder.io/react";
// Put your API key here
builder.init(YOUR_API_KEY);
export default function App() {
const [links, setLinks] = useState([]);
// Get the CMS data from Builder
useEffect(() => {
async function fetchContent() {
const links = await builder.getAll("nav-links", {
// You can use options for queries, sorting, and targeting here
// https://github.com/BuilderIO/builder/blob/main/packages/core/docs/interfaces/GetContentOptions.md
});
setLinks(links);
}
fetchContent();
}, []);
return (
<>
<header>
<nav>
{links.map((link, index) => (
<a key={index} href={link.data.url}>
{link.data.label}
</a>
))}
</nav>
</header>
{/* Put the rest of your page here */}
{/* <RestOfYourPage /> */}
</>
);
}Create a page with the following contents, replacing YOUR_API_KEY with your account's Public API Key:
import { fetchOneEntry } from '@builder.io/sdk-react';
import { useEffect, useState, getBuilderSearchParams } from 'react';
// TODO: enter your public API key
const YOUR_API_KEY = /* Add Your Public API Key here */;
function App() {
const [links, setLinks] = useState(null);
useEffect(() => {
const urlPath = '/' + (params?.page?.join('/') || '');
fetchOneEntry({
model: 'nav-link',
apiKey: YOUR_API_KEY,
options: getBuilderSearchParams(new URL(location.href).searchParams),
userAttributes: { urlPath },
}).then(links => setLinks(links));
}, []);
return (
<>
<header>
<nav>
{links?.map((link, index) => (
<a key={index} href={link.data.url}>
{link.data.label}
</a>
))}
</nav>
</header>
<RestOfYourPage />
</>
);
}
export default App;Paste the following code into a new file within the routes directory called $.tsx, making sure to replace YOUR_API_KEY with your Public API Key.
//For example, in routes/nav.tsx
import { json } from "@remix-run/node";
import { useLoaderData } from "@remix-run/react";
import { fetchEntries, getBuilderSearchParams } from "@builder.io/sdk-react";
// TO DO: Add your Public API Key
const YOUR_API_KEY = YOUR_PUBLIC_API_KEY;
export const loader = async () => {
const links = await fetchEntries({
model: "nav-link",
apiKey: YOUR_API_KEY,
options: getBuilderSearchParams(new URL(request.url).searchParams),
// Adjust userAttributes as necessary for your targeting needs
});
return json(links);
};
export default function Nav() {
const links = useLoaderData();
console.log(links); // Add this line
return (
<header>
<nav>
{links?.map((link, index) => (
<a key={index} href={link.data.url}>
{link.data.label}
</a>
))}
</nav>
</header>
);
}
Want the latest and greatest of Remix with Builder? We recommend using Gen 2.
Paste the following code into a new file within the routes directory called $.tsx, making sure to replace YOUR_API_KEY with your Public API Key.
import { builder } from '@builder.io/react';
import type { BuilderContent } from '@builder.io/sdk';
import { useLoaderData } from '@remix-run/react';
import type { LoaderFunction } from '@remix-run/node';
// TO DO: add your Public API Key here
builder.init(YOUR_PUBLIC_API_KEY);
export const loader: LoaderFunction = async ({ params }) => {
// Get the CMS data from Builder
const links = await builder.getAll('nav-links', {
// You can use options for queries, sorting, and targeting here
// https://github.com/BuilderIO/builder/blob/main/packages/core/docs/interfaces/GetContentOptions.md
});
// If there's data, return that data
if (links && links.length) return {links};
// otherwise, if there's no data, return an empty array
return {
links: []
};
};
interface LoaderData {
links: BuilderContent[];
}
// useLoaderData retrieves data from the server during server-side rendering
export default function Page() {
const {links}: LoaderData = useLoaderData<typeof loader>();
// Use this data to render a nav menu with the links
return (
<>
<header>
<nav>
{links.map((link, index) => (
<a key={index} href={link.data.url}>
{link.data.label}
</a>
))}
</nav>
</header>
{/* Put the rest of your page here */}
{/* <RestOfYourPage /> */}
</>
);
}Paste the following code into a new file within the routes directory called $.tsx, making sure to replace YOUR_API_KEY with your Public API Key.
//For example, in routes/nav.tsx
import { json } from '@remix-run/node';
import { useLoaderData } from '@remix-run/react';
import { fetchOneEntry, getBuilderSearchParams } from '@builder.io/sdk-react';
// TO DO: Add your Public API Key
const YOUR_API_KEY = YOUR_PUBLIC_API_KEY;
export const loader = async () => {
const links = await fetchOneEntry({
model: 'nav-link',
apiKey: YOUR_API_KEY,
options: getBuilderSearchParams(event.url.searchParams)
// Adjust userAttributes as necessary for your targeting needs
});
return json(links);
};
export default function Nav() {
const links = useLoaderData();
return (
<header>
<nav>
{links?.map((link, index) => (
<a key={index} href={link.data.url}>{link.data.label}</a>
))}
</nav>
</header>
);
}
Want the latest and greatest of Hydrogen with Builder? We recommend using Gen 2.
Paste the following code into a new file within the routes directory called $.tsx, making sure to replace YOUR_API_KEY with your Public API Key.
import { builder } from '@builder.io/react';
import type { BuilderContent } from '@builder.io/sdk';
import { useLoaderData } from '@remix-run/react';
import type { LoaderFunction } from '@remix-run/node';
// Initialize the Builder client and pass in the Public API Key
builder.init('YOUR_PUBLIC_API_KEY'); // <-- add your Public API Key here
export const loader: LoaderFunction = async ({ params }) => {
// Get the CMS data from Builder
const links = await builder.getAll('nav-links', {
// You can use options for queries, sorting, and targeting here
// https://github.com/BuilderIO/builder/blob/main/packages/core/docs/interfaces/GetContentOptions.md
});
// If there's data, return that data
if (links && links.length) return {links};
// otherwise, if there's no data, return an empty array
return {
links: []
};
};
interface LoaderData {
links: BuilderContent[];
}
// useLoaderData retrieves data from the server during server-side rendering
export default function Page() {
const {links}: LoaderData = useLoaderData<typeof loader>();
// Use this data to render a nav menu with the links
return (
<>
<header>
<nav>
{links.map((link, index) => (
<a key={index} href={link.data.url}>
{link.data.label}
</a>
))}
</nav>
</header>
{/* Put the rest of your page here */}
{/* <RestOfYourPage /> */}
</>
);
}Add a load() function to your script in +page.server.js to get the data from Builder:
import { fetchEntries, getBuilderSearchParams } from '@builder.io/sdk-svelte';
/** @type {import('./$types').PageServerLoad} */
export async function load(event) {
...
// get content from other models if needed
...
// fetch the nav links
// and provide your Public API Key
const links = await fetchEntries({
model: 'nav-link',
apiKey: YOUR_API_KEY,
options: getBuilderSearchParams(event.url.searchParams)
// You can use options for queries, sorting, and targeting here
});
return {
props: { links }
}
}Add links to your script and an each block to iterate through the links in +page.svelte to list the nav links.
<script>
// This data is received directly as props from
// the `load` function in `+page.server.js`
export let links = [];
</script>
<main>
<h1>Navigation Links</h1>
<ul>
{#each links as link (link.id)}
<li><a href={link.data.url}>{link.data.label}</a></li>
{/each}
</ul>
</main>
For an example app that you can add this code to, see the Builder SvelteKit example on GitHub.
Create a page with the following contents. Replace YOUR_API_KEY with your Public API Key:
// pages/your-page.vue
<template>
<header>
<nav>
<a v-for="link in links" :href="link.data.url">
{{ link.data.label }}
</a>
</nav>
</header>
<!-- Put the rest of your page here. -->
</template>
<script>
import Vue from 'vue';
import { fetchEntries, getBuilderSearchParams } from '@builder.io/sdk-vue';
export default {
data() {
return {
links: [],
};
},
// Use the created lifecycle hook for Vue 2 or setup function for Vue 3 if you're using the Composition API
created() {
this.fetchLinks();
},
methods: {
async fetchLinks() {
try {
// Replace 'YOUR_PUBLIC_API_KEY' with your actual Builder.io public API key and 'nav-link' with your model name
const results = await fetchEntries({
model: 'nav-link',
apiKey: 'YOUR_PUBLIC_API_KEY',
options: getBuilderSearchParams(new URL(location.href).searchParams)
});
this.links = results;
} catch (error) {
console.error('Error fetching links:', error);
}
},
},
};
</script>Create a page with the following contents. Replace YOUR_API_KEY with your account's Public API Key:
// pages/your-page.vue
<template>
<header>
<nav>
<a v-for="link in links" :href="link.data.url">
{{ link.data.label }}
</a>
</nav>
</header>
<!-- Put the rest of your page here. -->
</template>
<script>
import Vue from 'vue';
import { fetchEntries } from '@builder.io/sdk-vue';
export default ({
async asyncData() {
try {
// Replace 'YOUR_API_KEY' with your Public API Key
// and 'nav-link' with your model name
const links = await fetchEntries({
model: 'nav-link',
apiKey: YOUR_API_KEY,
});
return { links };
} catch (error) {
console.error('Error fetching links:', error);
return { links: [] };
}
},
};
</script>Replace src/routes/index.ts with the following contents, replacing YOUR_API_KEY with your account's Public API Key:
import { component$, Resource, useResource$ } from '@builder.io/qwik';
import { fetchEntries } from '@builder.io/sdk-qwik';
// TO DO: Add your Public API Key
export const apiKey = /* Add your Public API Key here */;
export default component$(() => {
const linksResource = useResource$(() =>
fetchEntries({
model: 'nav-link',
apiKey: apiKey,
options: getBuilderSearchParams(url.searchParams),
})
);
return (
<Resource
value={linksResource}
onPending={() => <>Loading...</>}
onRejected={error => <>Error: {error.message}</>}
onResolved={links => (
<header>
<h1>My Integrated Data</h1>
<nav>
{links?.map((link, index) => (
<a key={index} href={link.data.url}>
{link.data.label}
</a>
))}
</nav>
</header>
)}
/>
);
});Create a standalone component to display navigation links, using CLI:
ng g c navBarIn app.routes.ts, import NavBarComponent at the top with the other JavaScript imports and define the path for the component and pass it into routes array.
// src/app/app.routes.ts
import { Routes } from '@angular/router';
import { NavBarComponent } from './nav-bar/nav-bar.component'; // <-- import at top
...
export const routes: Routes = [
{
path: 'your-path',
component: NavBarComponent,
},
];In nav-bar.component.ts:
- Declare a
linksarray to hold the Builder links. - Inside
@Component(), addNavLinksComponentto the imports array. - In the template, use
NavLinksComponentand bind thelinksdata. - Fetch the links data using
fetchEntries()insidengOnInit()lifecycle hook.
// app/nav-bar/nav-bar.components.ts
import { CommonModule } from '@angular/common';
import { Component } from '@angular/core';
import { fetchEntries, type BuilderContent } from '@builder.io/sdk-angular';
import { NavLinksComponent } from './nav-links/nav-links.component';
@Component({
selector: 'app-nav-bar',
standalone: true,
imports: [CommonModule, NavLinksComponent],
template: `
<nav>
<app-nav-links [links]="links" />
<!-- <RestOfYourPage /> -->
</nav>
`,
})
export class NavBarComponent {
links: BuilderContent[] = [];
async ngOnInit() {
this.links =
(await fetchEntries({
model: 'nav-link',
apiKey: /* ADD YOUR PUBLIC API KEY HERE */,
})) || this.links;
}
}Create another component, NavLinksComponent, and replace the placeholder template with a *ngFor, which iterates through the fetched Builder links.
// app/nav-bar/nav-links/nav-links.components.ts
import { CommonModule } from '@angular/common';
import { Component, Input } from '@angular/core';
import { type BuilderContent } from '@builder.io/sdk-angular';
@Component({
selector: 'app-nav-links',
standalone: true,
imports: [CommonModule],
template: `
<ul style="display: flex; gap: 20px; list-style: none;">
<li *ngFor="let link of links">
<a [href]="link.data?.['url']" style="text-decoration: none;">{{
link.data?.['label']
}}</a>
</li>
</ul>
`,
})
export class NavLinksComponent {
@Input() links: BuilderContent[] = [];
}Add a new component for this demo NavBarComponent, for the Builder Navigation links data to load in your app.
ng g c navBarIn app.routes.ts, import NavBarComponent at the top with the other JavaScript imports and define the path for the component and pass it into routes array.
// src/app/app.routes.ts
import { Routes } from '@angular/router';
import { NavBarComponent } from './nav-bar/nav-bar.component';
import { navBarResolver } from './nav-bar/nav-bar.resolver'; // <-- import at top
...
export const routes: Routes = [
{
path: 'your-path',
component: NavBarComponent,
resolve: { navLinks: navBarResolver },
},
];In nav-bar.component.ts:
- Declare a
linksarray to hold the Builder links. - Inside
@Component(), addNavLinksComponentto theimportsarray. - Inside the constructor, define a
routeof typeActivatedRoute. - In the template, use
NavLinksComponentand bind thelinksdata. - Fetch the data present at the route from the resolver with the
ngOnInit()lifecycle hook.
// app/nav-bar/nav-bar.component.ts
import { CommonModule } from '@angular/common';
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { BuilderContent } from '@builder.io/sdk-angular';
import { NavLinksComponent } from './nav-links/nav-links.component';
@Component({
selector: 'app-nav-bar',
standalone: true,
imports: [CommonModule, NavLinksComponent],
template: `
<nav>
<app-nav-links [links]="links" />
</nav>
<!-- <RestOfYourPage /> -->
`,
})
export class NavBarComponent implements OnInit {
links: BuilderContent[] = [];
constructor(private route: ActivatedRoute) {}
ngOnInit() {
this.route.data.subscribe((data: any) => {
this.links = data.navLinks;
});
}
}In the navBarResolver, fetch the links data from the navigation-links model.
// app/nav-bar/nav-bar.resolver.ts
import { ResolveFn } from '@angular/router';
import { fetchEntries, type BuilderContent } from '@builder.io/sdk-angular';
export const navBarResolver: ResolveFn<BuilderContent[]> = async () => {
const links = await fetchEntries({
model: 'nav-link',
apiKey: /* ADD YOUR PUBLIC API KEY HERE */,
});
return links;
};
Create another component, NavLinksComponent, and replace the placeholder template with an *ngFor, which iterates through the fetched Builder links.
import { CommonModule } from '@angular/common';
import { Component, Input } from '@angular/core';
import { type BuilderContent } from '@builder.io/sdk-angular';
@Component({
selector: 'app-nav-links',
standalone: true,
imports: [CommonModule],
template: `
<ul style="display: flex; gap: 20px; list-style: none;">
<li *ngFor="let link of links">
<a [href]="link.data?.['url']" style="text-decoration: none;">{{
link.data?.['label']
}}</a>
</li>
</ul>
`,
})
export class NavLinksComponent {
@Input() links: BuilderContent[] = [];
}Edit your gatsby-config.js file to configure the Builder SDK, replacing YOUR_API_KEY with your account's Public API Key:
// gatsby-config.js
module.exports = {
...,
plugins: [
{
resolve: '@builder.io/gatsby',
options: {
// Replace with your Public API Key.
publicAPIKey: YOUR_API_KEY,
},
},
],
};Create a page with the following content:
// src/pages/your-page.jsx
import * as React from 'react';
import { graphql } from 'gatsby';
function YourPage({ data }) {
const links = data?.allBuilderModels.navLink;
return (
<>
<header>
<nav>
{links.map((link, index) => (
<a key={index} href={link.content.data.url}>
{link.content.data.label}
</a>
))}
</nav>
</header>
{/* Put the rest of your page here. */}
<RestOfYourPage />
</>
)
}
export default YourPage;
export const linksQuery = graphql`
query {
allBuilderModels {
navLink {
content
}
}
}
`;
Fetch your links data from Builder's Content API. Then use that data to create a nav bar within your app's page template.
The example below uses Express.js. Replace YOUR_API_KEY with your account's Public API Key:
// your-app.js
app.get("/", async (req, res) => {
const apiKey = YOUR_API_KEY;
const { results } = await fetch(
// You can also query, sort, and target this content.
// See full docs on our content API: https://www.builder.io/c/docs/query-api
`https://cdn.builder.io/api/v2/content/nav-link?apiKey=${apiKey}`
).then((res) => res.json());
res.send(`
<html>
<head> <!-- Your head content here --> </head>
<body>
<header>
${results
.map((link) => `<a href="${link.url}">${link.title}</a>`)
.join('')}
</header>
<main>
<!-- Your main content here -->
</main>
</body>
</html>
`);
});
Add a new component for this demo: NavLinkComponent, for the Builder Nav link data to load in your app.
ng generate component NavLinkReplace the placeholder content in nav-link.component.html with an *ngFor, which iterates through the Builder links the component fetches in the next step:
<nav>
<a *ngFor="let link of links" [href]="link.data.url">
{{link.data.label}}
</a>
</nav>
<!-- Put the rest of your page here. -->In nav-link.component.ts, declare a links array to hold the Builder links, inject BuilderServiceand, fetch the data:
import { BuilderService } from '@builder.io/angular';
export class NavLinkComponent implements OnInit {
links: any[] = [];
constructor(private builder: BuilderService) { }
async ngOnInit(): Promise<void> {
this.links = await this.builder.getAll('nav-link');
}
}In the Builder UI, create a Data model so you can create navigation links.
- In the Models section of Builder, Click +Create Model.
- Select Data.
- Enter Nav link as the name for your new Data model.
- Click +New Field.
- Name the first field
labeland give it a type ofText. - Repeat steps 3 and 4 to make a second label named
Urlwith typeurl. - Click Save.
Use the new Data model to create Nav Link content entries.
- Go to the Content section of Builder.
- Click +New.
- Select Nav Link.
- Give it a label, url, and name.
- Click Publish.
To make more links so that you can iterate through the links in your nav list, click the three dots and select Duplicate. Repeat steps 2-4 for each link you create.
The video below shows how to make three Nav Link entries.
Go back to your website and refresh the page to see your nav links. After your links are rendering, try adding new content entries in Builder. For each new entry, the new link populates the nav.
When working with structured data, it's important to set up live previewing to see real-time updates in the Visual Editor without publishing. This is especially useful for dynamic content like navigation links.
For detailed information and examples, read Live Previewing Data Models and Custom Fields.
For more information on how to work with Models in Builder, refer to Understanding Content Models.