Blog
Releases

Upgrading to Puck 0.20

Fede Bonel TozziFede Bonel Tozzi
Aug 14, 2025

The improvements in Puck 0.20 introduce a few breaking changes and some deprecations, but the upgrade path is straightforward and shouldn’t affect most apps.

This guide covers everything you need to know to upgrade safely and take advantage of what’s new:

If you’d like to help test Puck 0.20, follow this guide and report any issues you encounter.

For a full overview of what’s new in Puck 0.20, check out the release blog post.

Installing Puck 0.20

Run the following command to install the latest version of Puck:

npm install --save-exact @measured/puck@^0.20.0

Breaking changes

usePuck.get has been removed

In Puck 0.19, useGetPuck was introduced to access the Puck state outside the render lifecycle.

At the same time, usePuck.get was also mistakenly included. This function was never meant to be public and caused re-renders on every state change.

As a result, usePuck.get has been removed. Use useGetPuck instead to achieve the same behavior.

// Before: import { createUsePuck } from "@measured/puck"; const usePuck = createUsePuck(); const SaveDataButton = () => { const get = usePuck((s) => s.get); const handleSave = useCallback(() => { const { appState } = get(); savePage(appState.data); }, [get]); return <button onClick={handleSave}>Save</button>; };
// After: import { useGetPuck } from "@measured/puck"; const SaveDataButton = () => { const getPuck = useGetPuck(); const handleSave = useCallback(() => { const { appState } = getPuck(); savePage(appState.data); }, [getPuck]); return <button onClick={handleSave}>Save</button>; };

New deprecations

Deprecated: overrides.components

The components override has been renamed and is now deprecated. Using it will show a warning:

The `components` override has been deprecated and renamed to `drawer`

To fix this warning, replace your components override with drawer:

// Before: <Puck data={data} config={config} overrides={{ components: ({ children }) => ( <div style={{ display: "flex", flexWrap: "wrap" }}>{children}</div> ), }} />
// After: <Puck data={data} config={config} overrides={{ drawer: ({ children }) => ( <div style={{ display: "flex", flexWrap: "wrap" }}>{children}</div> ), }} />

Deprecated: overrides.componentItem

The componentItem override has been renamed and is now deprecated. Using it will show a warning:

The `componentItem` override has been deprecated and renamed to `drawerItem`

To fix this warning, replace your componentItem override with drawerItem:

// Before: <Puck data={data} config={config} overrides={{ componentItem: ({ children }) => ( <div style={{ border: "1px solid red" }}>{children}</div> ), }} />
// After: <Puck data={data} config={config} overrides={{ drawerItem: ({ children }) => ( <div style={{ border: "1px solid red" }}>{children}</div> ), }} />

Deprecated: Config type generics

Passing multiple generics to type your Puck config has been deprecated.

To migrate, use a single generic object instead:

All properties on the object are optional—you can pass only what you need.

// Before: type Components = { Heading: { title: string }; }; type RootProps = { title: string }; type Categories = "typography" | "layout" | "other"; const config: Config<Components, RootProps, Categories> = { //... };
// After: type Components = { Heading: { title: string }; }; type RootProps = { title: string }; type Categories = ["typography", "layout", "other"]; type MyFields = { checkbox: { type: "checkbox" } }; const config: Config<{ components: Components; root: RootProps; categories: Categories; fields: MyFields; }> = { //... };

Deprecated: ComponentConfig type generics

Passing multiple generics to type your component configs has been deprecated.

To migrate, use a single generic object instead:

All properties on the object are optional—you can pass only what you need.

// Before: type Props = { title: string; }; const Heading: ComponentConfig<Props> = { // ... };
// After: type Props = { title: string; }; type CustomFields = { checkbox: { type: "checkbox" } }; const Heading: ComponentConfig<{ props: Props; fields: CustomFields; }> = { // ... };

Notable changes

Migrating dynamic zones to slots

Previously, migrate would fail with an error when used with DropZone data containing dynamic zone names:

Could not migrate DropZone "DropZoneComponent-123:dynamicZone-0" to slot field. No slot exists with the name "dynamicZone-0".

To handle these cases, you can now use the migrateDynamicZonesForComponent option.

migrateDynamicZonesForComponent is an object that maps component names to custom migration functions.

Each function receives:

  • The current component props
  • The current component zones that don’t match a slot field definition

It should return the component’s updated props with the migrated data.

const legacyData = { root: { props: { title: "Legacy Zones Migration", }, }, content: [ { type: "Columns", props: { id: "Columns-123", columns: [{}], }, }, ], zones: { "Columns-123:column-0": [ { type: "Header", props: { id: "Header-123", title: "Drop zone 0", }, }, ], }, }; const newData = migrate(legacyData, config, { migrateDynamicZonesForComponent: { // zones = { column-0: [{ type: "Header", props: {...} }] } Columns: (props, zones) => { return { ...props, columns: Object.values(zones).map((zone) => ({ column: zone, })), }; }, }, }); console.log(newData); // { // root: { // props: { // title: "Legacy Zones Migration" // }, // }, // content: [ // { // type: "Columns", // props: { // id: "Columns-123", // columns: [ // { // column: [ // { // type: "Header", // props: { // id: "Header-123", // text: "Drop zone 0", // }, // }, // ], // }, // ], // }, // }, // ], // },

Full changelog

Check out the full changelog, including known issues, on the GitHub release.

Learn more about Puck

If you’re interested in learning more about Puck, check out the demo or read the docs. If you like what you see, please give us a star on GitHub to help others find Puck too!