Visual editing /Visual Editor /

Sitemap navigator

Populating the sitemap navigator allows you to use it for navigating website pages. The sitemap navigator provides a single point from which to access all editable pages on a website.

sitemap navigator

The sitemap provides two ways to navigate:

# Tree view

The sitemap is built in a hierarchical way using the urlPath from page documents. (More on this below.)

It is collapsed by default, so that only top-level pages are visible. Each page can be expanded and collapsed as needed.

sitemap navigator toggle

# Search pages

On larger sites, it's usually easier to search for a page. The search field is automatically focused when opening the sitemap. Search values query a page document's title and urlPath properties.

sitemap navigator search

# Populate the sitemap

The sitemap will be populated by default, according to your content schema. If the slug of the page is not part of the content schema, you need to override the default sitemap behavior.

# Default sitemap from pages

By default, the sitemap is populated by retrieving the urlPath value from all documents of models with the type property set to page.

If your content source uses inferred schemas (most headless CMSs) or does not have the concept of pages, you need to extend the models that represent pages.

Make sure the name of the page model object corresponds to the name in the json schema that appears in the headless CMS.

// stackbit.config.ts
import { defineStackbitConfig } from "@stackbit/types";

export default defineStackbitConfig({
  contentSources: [
    // ...
  ],
  modelExtensions: [
    { name: "page", type: "page", urlPath: "/{slug}" },
    { name: "post", type: "page", urlPath: "/blog/{slug}" }
  ]
});

In this case, all pages and posts will show up in the sitemap.

# Custom sitemap

To override default behavior, use the siteMap property.

The siteMap property provides an array of SiteMapEntry objects. These objects can specify URL paths or be tied directly to a document. We suggest using TypeScript to introspect the expected properties.

Here’s an example that implements a similar approach to the default sitemap behavior. To ensure stableId is set to a value that will not change, the example includes logic to set a unique pageId for each page, if one doesn’t exist already. The pageId is then used for the stableId when generating the siteMap.

// stackbit.config.ts
import {
  defineStackbitConfig,
  getLocalizedFieldForLocale,
  SiteMapEntry
} from "@stackbit/types";

export default defineStackbitConfig({
  stackbitVersion: "~0.6.0",
  contentSources: [
    // ...
  ],
  // 1: add the `pageId` field here
  modelExtensions: [
    {
      name: "page",
      type: "page",
      urlPath: "/{slug}",
      fields: [{ name: "pageId", type: "string", hidden: true }]
    }
  ],

  // 2: add this method to create the ID when creating a page
  async onContentCreate({ object, model }) {
    if (model.type !== "page") {
      return object;
    }
    // for pages that already have a pageId field, use that value; if not, generate one
    const hasPageIdField = !!model.fields?.find(
      field => field.name === "pageId"
    );
    if (hasPageIdField && !object.pageId) {
      object.pageId = Date.now().toString();
    }

    return object;
  },

  siteMap: ({ documents, models }) => {
    const pageModels = models.filter(m => m.type === "page").map(m => m.name);
    return documents
      .filter(d => pageModels.includes(d.modelName))
      .map(document => {
        // 3: use the pageId value for the stableId
        const slugField =
          document.fields.slug.type === "slug"
            ? document.fields.slug
            : undefined;
        const pageIdField =
          document.fields.pageId.type === "string"
            ? document.fields.pageId
            : undefined;

        const slug = getLocalizedFieldForLocale(slugField);
        const pageId = getLocalizedFieldForLocale(pageIdField);

        if (!slug.value || !pageId.value) return null;

        const urlPath = "/" + slug.value.replace(/^\/+/, "");

        return {
          stableId: pageId.value,
          urlPath,
          document,
          isHomePage: urlPath === "/"
        };
      })
      .filter(Boolean) as SiteMapEntry[];
  }
});