Docs
Integrating Puck
External Data Sources

External Data Sources

There are several different approaches for loading external data into a Puck component.

It's possible for Puck components to load their own data internally on the client, or on the server using React server components. This doesn't require any Puck configuration.

If you want to provide the user a way to select the data, you can use the external field type.

Selecting external data

The external field type allows users to select tabular data from a third-party data source, like a headless CMS. This will load the data once and save it into the data payload.

Interactive Demo
Example
No data selected
const config = {
  components: {
    Example: {
      fields: {
        data: {
          type: "external",
          fetchList: async () => {
            // Query an API for a list of items
            const items = await fetch(`/api/items`).then((res) => res.json());
            // [
            //   { title: "Hello, world", description: "Lorem ipsum 1" },
            //   { title: "Goodbye, world", description: "Lorem ipsum 2" },
            // ];
 
            return items;
          },
        },
      },
      render: ({ data }) => {
        if (!data) {
          return "No data selected";
        }
 
        return (
          <>
            <b>{data.title}</b>
            <p>{data.description}</p>
          </>
        );
      },
    },
  },
};

You can also use the showSearch parameter to show a search input to the user.

Data syncing

To keep the data in sync with the external source, we can combine the external field with the resolveData function.

This technique re-fetches the content every time the page is loaded, or the resolveAllData utility is called.

const config = {
  components: {
    Example: {
      fields: {
        data: {
          type: "external",
          fetchList: async () => {
            // Query an API for a list of items
            const items = await fetch(`/api/items`).then((res) => res.json());
            // [
            //   { title: "Hello, world", id: 0 },
            //   { title: "Goodbye, world", id: 1 },
            // ];
 
            return items;
          },
        },
      },
      resolveData: async ({ props }, { changed }) => {
        if (!props.data) return { props };
 
        // Don't query unless `data` has changed since resolveData was last run
        if (!changed.data) return { props };
 
        // Re-query the API for a particular item
        const latestData = await fetch(`/api/items/${props.data.id}`).then(
          (res) => res.json()
        );
        // { title: "Hello, world", description: "Lorem ipsum 1", id: 0 }
 
        return {
          props: {
            // Update the value for `data`
            data: latestData,
          },
        };
      },
      // ...
    },
  },
};

Hybrid authoring

Hybrid authoring enables users to edit fields inline, or populate those fields with data from an external source.

Interactive Demo
Example

This can be achieved by mapping the data from data.title to title in resolveData, and marking the field as read-only.

const config = {
  components: {
    Example: {
      fields: {
        data: {
          // ...
        },
        title: {
          type: "text",
        },
      },
      resolveData: async ({ props }, { changed }) => {
        // Remove read-only from the title field if `data` is empty
        if (!props.data) return { props, readOnly: { title: false } };
 
        // Don't query unless `data` has changed since resolveData was last run
        if (!changed.data) return { props };
 
        return {
          props: {
            title: props.data.title,
            readOnly: { title: true },
          },
        };
      },
      render: ({ title }) => <b>{title}</b>,
    },
  },
};

External data packages

We provide helper packages to load data from common data sources.

Further reading