Skip to main content
Version: 5.xx.xx

Migrating from 4.x.x to 5.x.x

Motivation Behind the Release​

Refine v5 removes deprecated APIs and legacy systems, upgrades to TanStack Query v5, and adds React 19 support. The result is a cleaner, faster codebase with better developer experience.

Migrating Your Refine Project: 3.x.x β†’ 4.x.x β†’ 5.x.x:

Before upgrading to Refine 5, your project must first be migrated from Refine 3.x.x to 4.x.x. This is an essential step, as Refine 5 builds upon the changes introduced in version 4.

β†’ Please refer to the migration guide for details on upgrading from 3.x.x to 4.x.x

Once your project is on Refine 4.x.x, you can proceed with the upgrade to Refine 5.x.x using the steps below.

Migration Steps

1. Upgrade All Refine Dependencies to v5​

Package Version Changes​

All Refine packages have been bumped to the next major version as a coordinated release. This ensures maximum stability and compatibility when using packages together - all Refine v5 packages are tested as a complete ecosystem.

Packagev4 Versionv5 Version
@refinedev/core4.x.x5.x.x
react17 or 1818 or 19
TanStack React Query4.x.x5.x.x
@refinedev/antd5.x.x6.x.x
@refinedev/mui6.x.x7.x.x
@refinedev/mantine2.x.x3.x.x
@refinedev/chakra-ui2.x.x3.x.x
@refinedev/react-hook-form4.x.x5.x.x
@refinedev/react-table5.x.x6.x.x
@refinedev/react-router1.x.x2.x.x
@refinedev/nextjs-router6.x.x7.x.x
@refinedev/remix-router3.x.x4.x.x
@refinedev/inferencer5.x.x6.x.x
@refinedev/devtools1.x.x2.x.x

⚑️ You can easily update Refine packages with the Refine CLI update command.

npm run refine update

How to add Refine CLI to an existing project?

Once dependencies are updated, proceed with the following breaking changes:


2. Remove All Deprecated APIs from Refine v4​

All deprecated APIs marked for removal in v4 have been completely removed in v5.

πŸͺ„ Migrating your project automatically with refine-codemod ✨ (recommended)

The @refinedev/codemod package handles the breaking changes for your project and automatically migrates it from 4.x.x to 5.x.x.

Simply cd into the root folder of your project (where the package.json is located) and run this command:

npx @refinedev/codemod@latest refine4-to-refine5

πŸ‘‰ Hook Return Type Changes: The codemod updates most standard cases, but may miss complex destructuring, conditional logic, or custom wrappers. Please review Data & Mutation Hooks: Return Type Breaking Changes if you use these patterns.

⚠️ Changes not handled by the codemod

Unfortunately, the codemod cannot cover every case. While it automates most of the migration, some changes still require manual updates. Below is the list of removed or modified APIs that you'll need to adjust yourself.

useNavigation β†’ useGo

🚨 Affects: Navigation helpers (push, replace, goBack)

The return values from useNavigation have been removed. You should now use useGo for navigation:

- import { useNavigation } from "@refinedev/core";
+ import { useGo } from "@refinedev/core";

- const { replace, push } = useNavigation();
- replace("/tasks/new");
+ const go = useGo();
+ go({ to: "/tasks/new", type: "replace" });
+ go({ to: "/tasks/new", type: "push" });

For backward navigation (goBack), use your router’s native API instead.

- import { useNavigation } from "@refinedev/core";
+ import { useNavigate } from "react-router";

- const { goBack } = useNavigation();
+ const navigate = useNavigate();
- goBack();
+ navigate(-1);

ITreeMenu β†’ TreeMenuItem & list field changes

🚨 Affects: useMenu, custom sider renderers

  • ITreeMenu has been removed β†’ use TreeMenuItem instead (codemod updates this).
  • list is now always a string route. πŸ‘‰ list.path is gone and list is no longer a function.

Why:
Previously, you could define a React component in the <Refine /> resource as list. This is no longer supported. Routes/components must be defined in your router. Because of this, list is now just a route string, not a function or object with path.

- const { menuItems, selectedKey } = useMenu();
- menuItems.map((item: ITreeMenu) => {
- const { key, list } = item;
- const route =
- typeof list === "string"
- ? list
- : typeof list !== "function"
- ? list?.path
- : key;
- });
+ const { menuItems, selectedKey } = useMenu();
+ menuItems.map((item: TreeMenuItem) => {
+ const { list } = item;
+ const route = list ?? key; // always a string route now
+ });

3. Refactor Legacy Router Provider to use new Router Provider​

If your project is still using the legacyRouterProvider provider, you'll need to migrate to the new router system. The new router provider offers greater flexibility and better integration with modern routing patterns.

Please refer these guides to refactor your project:

4. Refactor Legacy Auth Provider to use new Auth Provider​

If your project is still using the legacy auth provider legacyAuthProvider or auth hooks with v3LegacyAuthProviderCompatible: true, you must migrate to the modern auth provider structure because these are completely removed.

For complete migration instructions, please refer to the Auth Provider Migration Guide.

useLogin({
- v3LegacyAuthProviderCompatible: true,
});

<Refine
- legacyAuthProvider={legacyAuthProvider}
+ authProvider={authProvider}
/>

5. Upgrade TanStack Query to v5​

You'll need to upgrade TanStack Query from v4 to v5. Please refer to the TanStack Query migration guide for detailed instructions on this upgrade.

6. Upgrade React to v19 (optional)​

Refine v5 supports both React 18 and React 19. If you want to take advantage of the latest React features, you can optionally upgrade to React 19. Please refer to the React 19 release notes for more information about the new features and migration considerations.

Data & Mutation Hooks: Return Type Breaking Changes​

🚨 Affects: All data and mutation hooks (useList, useTable, useInfiniteList, useOne, useMany, useForm, useCreate, useUpdate, etc.)

Return types of data and mutation hooks were refactored for clarity and consistency. Query state (isLoading, isError, error, etc.) and mutation state (isPending, isError, error, etc.) are now grouped under query and mutation objects respectively, while normalized values (data, total, etc.) are returned under a result object. This change:

  • Unifies the shape of return types across all hooks.
  • Eliminates nested property access (e.g., data?.data).
  • Improves type safety and developer experience.
  • Groups all TanStack Query APIs (isLoading, isError, refetch, etc.) under the query object, making them easier to discover and use consistently.

The following sections show hook-specific breaking changes with before/after usage examples.

useList​

const {
- data,
- isLoading,
- isError,
} = useList();

- const posts = data.data
- const total = data.total

const {
+ result,
+ query: { isLoading, isError },
} = useList();

+ const posts = result.data;
+ const total = result.total;

useOne, useMany, useShow​

All three hooks follow the same query and result pattern.

const {
- data,
- isLoading,
- isError,
} = useOne({
resource: "users",
id: 1,
});

- const user = data.data;

const {
+ result,
+ query: { isLoading, isError },
} = useOne({
resource: "users",
id: 1,
});

+ const user = result;

useTable from @refinedev/react-table​

🚨 Affects: TanStack Table properties grouping

TanStack Table properties are now grouped under a reactTable object for better organization.

const {
- getHeaderGroups,
- getRowModel,
- setOptions,
- getState,
- setPageIndex,
refineCore: { filters, setCurrentPage, setFilters },
} = useTable({
columns,
});

const {
+ reactTable: {
+ getHeaderGroups,
+ getRowModel,
+ setOptions,
+ getState,
+ setPageIndex,
+ },
refineCore: { filters, setCurrentPage, setFilters },
} = useTable({
columns,
});

useTable from @refinedev/core​

βœ… There are no breaking changes in useTable. However, we introduced a new result property to make data access easier and keep consistency across all hooks.

const {
tableQuery,
+ result: {
+ data,
+ total,
+ },
} = useTable();

+ const posts = result.data;
+ const total = result.total;

useTable from @refinedev/antd​

βœ… There are no breaking changes in useTable. However, we introduced a new result property to make data access easier and keep consistency across all hooks.

const {
tableProps,
+ result: {
+ data,
+ total,
+ },
} = useTable();

+ const posts = result.data;
+ const total = result.total;

useDataGrid from @refinedev/mui​

βœ… There are no breaking changes in useDataGrid. However, we introduced a new result property to make data access easier and keep consistency across all hooks.

const {
tableQuery,
dataGridProps,
+ result: {
+ data,
+ total,
+ },
} = useDataGrid();

+ const posts = result.data;
+ const total = result.total;

useSimpleList from @refinedev/antd​

βœ… There are no breaking changes in useSimpleList. However, we introduced a new result property to make data access easier and keep consistency across all hooks.

Migration:

const {
listProps,
queryResult,
+ query,
+ result: {
+ data,
+ total,
+ },
} = useSimpleList();

useInfiniteList​

const {
- data,
- isLoading,
- isError,
- fetchNextPage,
- hasNextPage,
} = useInfiniteList({
resource: "posts",
});

- const posts = data?.data;

const {
+ result,
+ query: { isLoading, isError, fetchNextPage, hasNextPage },
} = useInfiniteList({ resource: "posts" });

+ const posts = result.data;

Authentication Hooks​

🚨 Affects: All authentication hooks (useLogin, useLogout, useRegister, useForgotPassword, useUpdatePassword)

Authentication hooks now follow the same mutation pattern as other mutation hooks:

const {
- isPending,
- isError,
mutate,
} = useLogin();

const {
+ mutation: { isPending, isError },
mutate,
} = useLogin();

Mutation Hooks​

🚨 Affects: All other mutation hooks (useUpdate, useDelete, useCreateMany, useUpdateMany, useDeleteMany, useCustomMutation)

All remaining mutation hooks follow the same pattern as useUpdate.

const {
- isPending,
- isError,
mutate,
mutateAsync,
} = useUpdate({ resource: "posts" });

const {
+ mutation: { isPending, isError },
mutate,
mutateAsync,
} = useUpdate({ resource: "posts" });

List of All Breaking Changes​

metaData β†’ meta​

🚨 Affects: All data hooks, useForm, useTable, useDataGrid, useSelect, etc.

The metaData parameter has been renamed to meta across all hooks:

useList({
- metaData: { foo: "bar" },
+ meta: { foo: "bar" },
})

useOne({
- metaData: { headers: { "Authorization": "Bearer token" } },
+ meta: { headers: { "Authorization": "Bearer token" } },
})

useCreate({
- metaData: { endpoint: "custom" },
+ meta: { endpoint: "custom" },
})

AuthBindings β†’ AuthProvider (Type Imports)​

🚨 Affects: Type imports from @refinedev/core

Type interfaces have been renamed in @refinedev/core. When importing these types, you'll need to update the import names while preserving usage with aliases:

// AuthBindings β†’ AuthProvider
- import { type AuthBindings } from "@refinedev/core";
+ import { type AuthProvider } from "@refinedev/core";

RouterBindings β†’ RouterProvider (Type Imports)​

🚨 Affects: Type imports from @refinedev/core

Type interfaces have been renamed in @refinedev/core. When importing these types, you'll need to update the import names while preserving usage with aliases:


- import type { RouterBindings } from "@refinedev/core";
+ import type { RouterProvider } from "@refinedev/core";

sorter/sort β†’ sorters​

🚨 Affects: useList, useInfiniteList, useTable, useDataGrid, useSelect

The sorter and sort parameters have been renamed to sorters:

useList({
- sort: [{ field: "title", order: "asc" }],
+ sorters: [{ field: "title", order: "asc" }],
})

useTable({
- initialSorter: [{ field: "createdAt", order: "desc" }],
+ sorters: {
+ initial: [{ field: "createdAt", order: "desc" }]
+ },
})

filters Updates​

🚨 Affects: useList, useTable, useDataGrid, useSelect

Filter configuration has been simplified and moved out of config objects:

useList({
- config: {
- filters: [{ field: "status", operator: "eq", value: "published" }],
- },
+ filters: [{ field: "status", operator: "eq", value: "published" }],
})

useTable({
- initialFilter: [{ field: "category", operator: "eq", value: "tech" }],
- permanentFilter: [{ field: "status", operator: "eq", value: "active" }],
+ filters: {
+ initial: [{ field: "category", operator: "eq", value: "tech" }],
+ permanent: [{ field: "status", operator: "eq", value: "active" }]
+ },
})

pagination Updates​

🚨 Affects: useList, useTable, useDataGrid, useSelect

Pagination configuration has been restructured:

useList({
- hasPagination: false,
+ pagination: { mode: "off" },
})

useTable({
- initialCurrent: 1,
- initialPageSize: 20,
- hasPagination: false,
+ pagination: { mode: "off", currentPage: 1, pageSize: 20 },
})

###Β pagination.current -> pagination.currentPage

🚨 Affects: useTable, useDataGrid, useSimpleList, useSubscription, useList, useCheckboxGroup, useSelect

useTable({
- pagination: { current: 1 },
+ pagination: { currentPage: 1 },
})

###Β setCurrent -> setCurrentPage

🚨 Affects: useTable, useDataGrid, useSimpleList

The setCurrent function has been renamed to setCurrentPage:

const {
- setCurrent,
- current,
+ currentPage,
+ setCurrentPage,
} = useTable();

Resource options β†’ meta​

🚨 Affects: Resource definitions in <Refine> component

Resource options have been renamed to meta:

<Refine
resources={[
{
name: "posts",
- options: { label: "Blog Posts" },
+ meta: { label: "Blog Posts" },
},
]}
/>

resourceName/resourceNameOrRouteName β†’ resource​

🚨 Affects: useImport, useExport, All Button components

useImport({
- resourceName: "posts",
+ resource: "posts",
})

<CreateButton
- resourceNameOrRouteName="posts"
+ resource="posts"
/>

config Object Removal​

🚨 Affects: useList, useInfiniteList

The config parameter has been flattened in data hooks:

useList({
- config: {
- pagination: { currentPage: 1, pageSize: 10 },
- sorters: [{ field: "title", order: "asc" }],
- filters: [{ field: "status", operator: "eq", value: "published" }],
- hasPagination: false,
- sort: [{ field: "title", order: "asc" }],
- metaData: { foo: "bar" },
- },
+ pagination: { currentPage: 1, pageSize: 10, mode: "off" },
+ sorters: [{ field: "title", order: "asc" }],
+ filters: [{ field: "status", operator: "eq", value: "published" }],
+ meta: { foo: "bar" },
})

useTable Hook Restructuring​

🚨 Affects: useTable, useSimpleList, useDataGrid

The useTable hook and its UI variants (useDataGrid from @refinedev/mui, useSimpleList) have been significantly restructured:

useTable({
- initialCurrent: 1,
- initialPageSize: 10,
- hasPagination: false,
+ pagination: { currentPage: 1, pageSize: 10 , mode: "off" },
- setCurrent,
+ setCurrentPage,

- initialSorter: [{ field: "title", order: "asc" }],
- permanentSorter: [{ field: "status", order: "asc" }],
+ sorters: {
+ initial: [{ field: "title", order: "asc" }],
+ permanent: [{ field: "status", order: "asc" }]
+ },

- initialFilter: [{ field: "status", operator: "eq", value: "published" }],
- permanentFilter: [{ field: "category", operator: "eq", value: "tech" }],
- defaultSetFilterBehavior: "replace",
+ filters: {
+ initial: [{ field: "status", operator: "eq", value: "published" }],
+ permanent: [{ field: "category", operator: "eq", value: "tech" }],
+ defaultBehavior: "replace"
+ },

})

// Return values also changed
const {
- sorter,
- setSorter,
- tableQueryResult,
- current,
+ currentPage,
+ sorters,
+ setSorters,
+ tableQuery,
} = useTable();

queryResult β†’ query​

🚨 Affects: useForm, useSelect, useShow, useSimpleList, useMany

const {
- queryResult,
+ query,
} = useShow();

const {
- queryResult,
+ query,
} = useForm();

defaultValueQueryResult β†’ defaultValueQuery​

🚨 Affects: useSelect

const {
- defaultValueQueryResult,
+ defaultValueQuery,
} = useSelect();

tableQueryResult β†’ tableQuery​

🚨 Affects: useTable, useDataGrid

const {
- tableQueryResult,
+ tableQuery,
} = useTable();

mutationResult β†’ mutation​

🚨 Affects: useCreate, useUpdate, useDelete, useCreateMany, useUpdateMany, useDeleteMany, useCustomMutation


const {
- mutationResult,
+ mutation,
} = useForm();

isLoading β†’ isPending​

🚨 Affects: useCreate, useUpdate, useDelete, useCreateMany, useUpdateMany, useDeleteMany, useCustomMutation

For mutation hooks, the loading state property has been updated:

const { mutate, mutation: { isPending } } = useCreate();

- if (isLoading) return <Spinner />;
+ if (isPending) return <Spinner />;

useNavigation β†’ useGo, useBack​

The useNavigation hook has been replaced with individual hooks:

- import { useNavigation } from "@refinedev/core";
+ import { useGo, useBack } from "@refinedev/core";

const MyComponent = () => {
- const { push, goBack, replace } = useNavigation();
+ const go = useGo();
+ const back = useBack();

- push("/posts");
+ go({ to: "/posts" });

- goBack();
+ back();

- replace("/posts");
+ go({ to: "/posts", type: "replace" });
};

useResource β†’ useResourceParams​

The useResource hook has been removed in favor of useResourceParams. The new useResourceParams hook offers the same functionality as useResource, while introducing additional features and a more streamlined API. To reduce confusion and improve consistency, all resource-related logic should now use useResourceParams exclusively.

- import { useResource } from "@refinedev/core";
+ import { useResourceParams } from "@refinedev/core";


- useResource("posts");
+ useResourceParams({ resource: "posts" });

ignoreAccessControlProvider β†’ accessControl​

<CreateButton
- ignoreAccessControlProvider
+ accessControl={{ enabled: false }}
- resourceNameOrRouteName="posts"
+ resource="posts"
/>

Resource options β†’ meta​

The options prop has been moved to meta:

<Refine
resources={[
{
name: "posts",
- options: {
- label: "Blog Posts",
- icon: <PostIcon />,
- route: "my-posts",
- auditLog: {
- permissions: ["list", "create"],
- },
- hide: false,
- dataProviderName: "default",
- },
- canDelete: true,
+ meta: {
+ label: "Blog Posts",
+ icon: <PostIcon />,
+ parent: "categories",
+ canDelete: true,
+ audit: ["list", "create"],
+ hide: false,
+ dataProviderName: "default",
+ },
},
]}
/>

DataProvider getList and custom Method Updates​

export const dataProvider = {
getList: ({
resource,
pagination: {
+ mode: "off" | "server" | "client",
},
- hasPagination,
+ sorters,
- sort,
filters,
+ meta,
- metaData,
}) => {
// ...
},

custom: ({
// ...
+ sorters,
- sort,
}) => {
// ...
},
};

useImport and useExport Hook Updates​

The useImport and useExport hooks have additional parameter updates:

useImport({
- resourceName: "posts",
+ resource: "posts",
- metaData: { foo: "bar" },
+ meta: { foo: "bar" },
// These are now used as direct props instead of nested config
- mapData: (item) => item,
- paparseConfig: {},
- batchSize: 1000,
+ mapData: (item) => item,
+ paparseConfig: {},
+ batchSize: 1000,
})

useExport({
- resourceName: "posts",
+ resource: "posts",
- sorter: [{ field: "title", order: "asc" }],
+ sorters: [{ field: "title", order: "asc" }],
- metaData: { foo: "bar" },
+ meta: { foo: "bar" },
- exportOptions: {},
+ unparseConfig: {},
// These are now used as direct props
- mapData: (item) => item,
- maxItemCount: 1000,
- pageSize: 20,
+ mapData: (item) => item,
+ maxItemCount: 1000,
+ pageSize: 20,
})

queryKeys -> keys​

🚨 Affects: Custom implementations using Refine helpers

// queryKeys helper updates
- import { queryKeys } from "@refinedev/core";
+ import { keys } from "@refinedev/core";

// Usage updates
- queryKeys.data().resource("posts").action("list").get();
+ keys().data().resource("posts").action("list").get();

useNavigation β†’ useGo​

🚨 Affects: Navigation helpers (push, replace, goBack)

The return values from useNavigation have been removed. You should now use useGo for navigation:

- import { useNavigation } from "@refinedev/core";
+ import { useGo } from "@refinedev/core";

- const { replace, push } = useNavigation();
- replace("/tasks/new");
+ const go = useGo();
+ go({ to: "/tasks/new", type: "replace" });
+ go({ to: "/tasks/new", type: "push" });

For backward navigation (goBack), use your router’s native API instead.

- import { useNavigation } from "@refinedev/core";
+ import { useNavigate } from "react-router";

- const { goBack } = useNavigation();
+ const navigate = useNavigate();
- goBack();
+ navigate(-1);

ITreeMenu β†’ TreeMenuItem & list field changes​

🚨 Affects: useMenu, custom sider renderers

  • ITreeMenu has been removed β†’ use TreeMenuItem instead (codemod updates this).
  • list is now always a string route. πŸ‘‰ list.path is gone and list is no longer a function.

Why:
Previously, you could define a React component in the <Refine /> resource as list. This is no longer supported. Routes/components must be defined in your router. Because of this, list is now just a route string, not a function or object with path.

- const { menuItems, selectedKey } = useMenu();
- menuItems.map((item: ITreeMenu) => {
- const { key, list } = item;
- const route =
- typeof list === "string"
- ? list
- : typeof list !== "function"
- ? list?.path
- : key;
- });
+ const { menuItems, selectedKey } = useMenu();
+ menuItems.map((item: TreeMenuItem) => {
+ const { list } = item;
+ const route = list ?? key; // always a string route now
+ });

ThemedLayoutV2 β†’ ThemedLayout​

🚨 Affects: Layout components across all UI packages

The V2 layout components have been renamed to remove the V2 suffix across all UI packages (@refinedev/antd, @refinedev/mui, @refinedev/mantine, @refinedev/chakra-ui).

Components affected:

  • ThemedLayoutV2 β†’ ThemedLayout
  • ThemedTitleV2 β†’ ThemedTitle
  • ThemedSiderV2 β†’ ThemedSider
  • ThemedHeaderV2 β†’ ThemedHeader
- import { ThemedLayoutV2, ThemedTitleV2, ThemedSiderV2, ThemedHeaderV2 } from "@refinedev/antd";
+ import { ThemedLayout, ThemedTitle, ThemedSider, ThemedHeader } from "@refinedev/antd";