diff --git a/docs/data/data-grid/filtering-recipes/FilteringLocalStorage.js b/docs/data/data-grid/filtering-recipes/FilteringLocalStorage.js
new file mode 100644
index 0000000000000..290e5ce9e6084
--- /dev/null
+++ b/docs/data/data-grid/filtering-recipes/FilteringLocalStorage.js
@@ -0,0 +1,85 @@
+import * as React from 'react';
+import { DataGrid, GridToolbar } from '@mui/x-data-grid';
+import { useDemoData } from '@mui/x-data-grid-generator';
+
+const VISIBLE_FIELDS = ['name', 'rating', 'country', 'dateCreated', 'isAdmin'];
+
+const createFilterModelStore = () => {
+ let listeners = [];
+ const lsKey = 'gridFilterModel';
+ const emptyModel = 'null';
+
+ return {
+ subscribe: (callback) => {
+ listeners.push(callback);
+ return () => {
+ listeners = listeners.filter((listener) => listener !== callback);
+ };
+ },
+ getSnapshot: () => {
+ try {
+ return localStorage.getItem(lsKey) || emptyModel;
+ } catch (error) {
+ return emptyModel;
+ }
+ },
+ getServerSnapshot: () => {
+ return emptyModel;
+ },
+ update: (filterModel) => {
+ localStorage.setItem(lsKey, JSON.stringify(filterModel));
+ listeners.forEach((listener) => listener());
+ },
+ };
+};
+
+const usePersistedFilterModel = () => {
+ const [filterModelStore] = React.useState(createFilterModelStore);
+
+ const filterModelString = React.useSyncExternalStore(
+ filterModelStore.subscribe,
+ filterModelStore.getSnapshot,
+ filterModelStore.getServerSnapshot,
+ );
+
+ const filterModel = React.useMemo(() => {
+ try {
+ return JSON.parse(filterModelString) || undefined;
+ } catch (error) {
+ return undefined;
+ }
+ }, [filterModelString]);
+
+ return React.useMemo(
+ () => [filterModel, filterModelStore.update],
+ [filterModel, filterModelStore.update],
+ );
+};
+
+export default function FilteringLocalStorage() {
+ const { data } = useDemoData({
+ dataSet: 'Employee',
+ visibleFields: VISIBLE_FIELDS,
+ rowLength: 100,
+ });
+
+ const [filterModel, setFilterModel] = usePersistedFilterModel();
+
+ const onFilterModelChange = React.useCallback(
+ (newFilterModel) => {
+ setFilterModel(newFilterModel);
+ },
+ [setFilterModel],
+ );
+
+ return (
+
+
+
+ );
+}
diff --git a/docs/data/data-grid/filtering-recipes/FilteringLocalStorage.tsx b/docs/data/data-grid/filtering-recipes/FilteringLocalStorage.tsx
new file mode 100644
index 0000000000000..f0d4089961706
--- /dev/null
+++ b/docs/data/data-grid/filtering-recipes/FilteringLocalStorage.tsx
@@ -0,0 +1,94 @@
+import * as React from 'react';
+import {
+ DataGrid,
+ DataGridProps,
+ GridFilterModel,
+ GridToolbar,
+} from '@mui/x-data-grid';
+import { useDemoData } from '@mui/x-data-grid-generator';
+
+const VISIBLE_FIELDS = ['name', 'rating', 'country', 'dateCreated', 'isAdmin'];
+
+const createFilterModelStore = () => {
+ let listeners: Array<() => void> = [];
+ const lsKey = 'gridFilterModel';
+ const emptyModel = 'null';
+
+ return {
+ subscribe: (callback: () => void) => {
+ listeners.push(callback);
+ return () => {
+ listeners = listeners.filter((listener) => listener !== callback);
+ };
+ },
+ getSnapshot: () => {
+ try {
+ return localStorage.getItem(lsKey) || emptyModel;
+ } catch (error) {
+ return emptyModel;
+ }
+ },
+
+ getServerSnapshot: () => {
+ return emptyModel;
+ },
+
+ update: (filterModel: GridFilterModel) => {
+ localStorage.setItem(lsKey, JSON.stringify(filterModel));
+ listeners.forEach((listener) => listener());
+ },
+ };
+};
+
+const usePersistedFilterModel = () => {
+ const [filterModelStore] = React.useState(createFilterModelStore);
+
+ const filterModelString = React.useSyncExternalStore(
+ filterModelStore.subscribe,
+ filterModelStore.getSnapshot,
+ filterModelStore.getServerSnapshot,
+ );
+
+ const filterModel = React.useMemo(() => {
+ try {
+ return (JSON.parse(filterModelString) as GridFilterModel) || undefined;
+ } catch (error) {
+ return undefined;
+ }
+ }, [filterModelString]);
+
+ return React.useMemo(
+ () => [filterModel, filterModelStore.update] as const,
+ [filterModel, filterModelStore.update],
+ );
+};
+
+export default function FilteringLocalStorage() {
+ const { data } = useDemoData({
+ dataSet: 'Employee',
+ visibleFields: VISIBLE_FIELDS,
+ rowLength: 100,
+ });
+
+ const [filterModel, setFilterModel] = usePersistedFilterModel();
+
+ const onFilterModelChange = React.useCallback<
+ NonNullable
+ >(
+ (newFilterModel) => {
+ setFilterModel(newFilterModel);
+ },
+ [setFilterModel],
+ );
+
+ return (
+
+
+
+ );
+}
diff --git a/docs/data/data-grid/filtering-recipes/FilteringLocalStorage.tsx.preview b/docs/data/data-grid/filtering-recipes/FilteringLocalStorage.tsx.preview
new file mode 100644
index 0000000000000..cdca9c3dde546
--- /dev/null
+++ b/docs/data/data-grid/filtering-recipes/FilteringLocalStorage.tsx.preview
@@ -0,0 +1,6 @@
+
\ No newline at end of file
diff --git a/docs/data/data-grid/filtering-recipes/filtering-recipes.md b/docs/data/data-grid/filtering-recipes/filtering-recipes.md
index fa5322aa7f224..9265e2dd36f9a 100644
--- a/docs/data/data-grid/filtering-recipes/filtering-recipes.md
+++ b/docs/data/data-grid/filtering-recipes/filtering-recipes.md
@@ -6,6 +6,14 @@ title: Data Grid - Filtering customization recipes
Advanced filtering customization recipes.
+## Persisting filters in local storage
+
+You can persist the filters in the local storage to keep the filters applied after the page is reloaded.
+
+In the demo below, the [`React.useSyncExternalStore` hook](https://react.dev/reference/react/useSyncExternalStore) is used to synchronize the filters with the local storage.
+
+{{"demo": "FilteringLocalStorage.js", "bg": "inline", "defaultCodeOpen": false}}
+
## Quick filter outside of the grid
The [Quick Filter](/x/react-data-grid/filtering/quick-filter/) component is typically used in the Data Grid's Toolbar component slot.