diff --git a/docs/Buttons.md b/docs/Buttons.md
index 1c7ea83bf69..2eaa00af61f 100644
--- a/docs/Buttons.md
+++ b/docs/Buttons.md
@@ -261,6 +261,10 @@ export const PostList = () => (
 
 Partially updates the selected rows. To be used inside [the `<Datagrid bulkActionButtons>` prop](./Datagrid.md#bulkactionbuttons).
 
+![Bulk Update button](./img/bulk-update-button.png)
+
+#### Usage
+
 {% raw %}
 ```jsx
 import * as React from 'react';
@@ -285,7 +289,7 @@ export const PostList = () => (
 ```
 {% endraw %}
 
-![Bulk Update button](./img/bulk-update-button.png)
+#### Props
 
 | Prop              | Required | Type           | Default            | Description                                                                                                         |
 |-------------------|----------|----------------|--------------------|---------------------------------------------------------------------------------------------------------------------|
@@ -299,6 +303,311 @@ export const PostList = () => (
 
 **Tip:** If you choose the `'pessimistic'` or `'optimistic'` mutation mode, a confirm dialog will be displayed to the user before the mutation is executed.
 
+### `<BulkUpdateFormButton>`
+
+This component, part of the [enterprise edition](https://marmelab.com/ra-enterprise/modules/ra-form-layout)<img class="icon" src="./img/premium.svg" />, lets users edit multiple records at once. To be used inside [the `<Datagrid bulkActionButtons>` prop](./Datagrid.md#bulkactionbuttons).
+
+The button opens a dialog containing the form passed as children. When the form is submitted, it will call the dataProvider's `updateMany` method with the ids of the selected records.
+
+<video controls autoplay playsinline muted loop>
+  <source src="./img/BulkUpdateButton-SimpleForm.webm" type="video/webm"/>
+  <source src="./img/BulkUpdateButton-SimpleForm.mp4" type="video/mp4"/>
+  Your browser does not support the video tag.
+</video>
+
+#### Usage
+
+`<BulkUpdateFormButton>` can be used inside `<Datagrid>`'s `bulkActionButtons`.
+
+```tsx
+import * as React from 'react';
+import {
+    Admin,
+    BooleanField,
+    BooleanInput,
+    Datagrid,
+    DateField,
+    DateInput,
+    List,
+    Resource,
+    SimpleForm,
+    TextField,
+} from 'react-admin';
+import { BulkUpdateFormButton } from '@react-admin/ra-form-layout';
+
+import { dataProvider } from './dataProvider';
+import { i18nProvider } from './i18nProvider';
+
+export const App = () => (
+    <Admin dataProvider={dataProvider} i18nProvider={i18nProvider}>
+        <Resource name="posts" list={PostList} />
+    </Admin>
+);
+
+const PostBulkUpdateButton = () => (
+    <BulkUpdateFormButton>
+        <SimpleForm>
+            <DateInput source="published_at" />
+            <BooleanInput source="is_public" />
+        </SimpleForm>
+    </BulkUpdateFormButton>
+);
+
+const PostList = () => (
+    <List>
+        <Datagrid bulkActionButtons={<PostBulkUpdateButton />}>
+            <TextField source="id" />
+            <TextField source="title" />
+            <DateField source="published_at" />
+            <BooleanField source="is_public" />
+        </Datagrid>
+    </List>
+);
+```
+
+**Tip:** You are not limited to using a `<SimpleForm>` as children. You can for instance use an `<InputSelectorForm>`, which allows to select the fields to update. Check out the [`<InputSelectorForm>`](#usage-with-inputselectorform) below for more information.
+
+#### Props
+
+| Prop              | Required     | Type     | Default         | Description                                                                                                                        |
+|-------------------|--------------|----------|-----------------|------------------------------------------------------------------------------------------------------------------------------------|
+| `children`        | Required (*) | Element  | -               | A form component to render inside the Dialog                                                                                       |
+| `DialogProps`     | -            | Object   | -               | Additional props to pass to the [MUI Dialog](https://mui.com/material-ui/react-dialog/)                                            |
+| `mutationMode`    | -            | `string` | `'pessimistic'` | The mutation mode (`'undoable'`, `'pessimistic'` or `'optimistic'`)                                                                |
+| `mutationOptions` | -            | Object   | -               | Mutation options passed to [react-query](https://tanstack.com/query/v3/docs/react/reference/useMutation) when calling `updateMany` |
+
+
+#### `children`
+
+`<BulkUpdateFormButton>` expects a form component as children, such as `<SimpleForm>` or `<InputSelectorForm>`.
+
+```tsx
+import { BulkUpdateFormButton } from '@react-admin/ra-form-layout';
+import * as React from 'react';
+import { BooleanInput, DateInput, SimpleForm } from 'react-admin';
+
+const PostBulkUpdateButton = () => (
+    <BulkUpdateFormButton>
+        <SimpleForm>
+            <DateInput source="published_at" />
+            <BooleanInput source="is_public" />
+        </SimpleForm>
+    </BulkUpdateFormButton>
+);
+```
+
+#### `DialogProps`
+
+The `DialogProps` prop can be used to pass additional props to the [MUI Dialog](https://mui.com/material-ui/react-dialog/).
+{% raw %}
+```tsx
+import { Slide } from '@mui/material';
+import { TransitionProps } from '@mui/material/transitions';
+import { BulkUpdateFormButton } from '@react-admin/ra-form-layout';
+import * as React from 'react';
+import { BooleanInput, DateInput, SimpleForm } from 'react-admin';
+
+const Transition = React.forwardRef(function Transition(
+    props: TransitionProps & {
+        children: React.ReactElement<any, any>;
+    },
+    ref: React.Ref<unknown>
+) {
+    return <Slide direction="left" ref={ref} {...props} />;
+});
+
+const PostBulkUpdateButtonWithTransition = () => (
+    <BulkUpdateFormButton DialogProps={{ TransitionComponent: Transition }}>
+        <SimpleForm>
+            <DateInput source="published_at" />
+            <BooleanInput source="is_public" />
+        </SimpleForm>
+    </BulkUpdateFormButton>
+);
+```
+{% endraw %}
+
+#### `mutationMode`
+
+Use the `mutationMode` prop to specify the [mutation mode](https://marmelab.com/react-admin/Edit.html#mutationmode).
+
+```tsx
+import { BulkUpdateFormButton } from '@react-admin/ra-form-layout';
+import * as React from 'react';
+import { BooleanInput, DateInput, SimpleForm } from 'react-admin';
+
+const PostBulkUpdateButton = () => (
+    <BulkUpdateFormButton mutationMode="undoable">
+        <SimpleForm>
+            <DateInput source="published_at" />
+            <BooleanInput source="is_public" />
+        </SimpleForm>
+    </BulkUpdateFormButton>
+);
+```
+
+#### `mutationOptions` and `meta`
+
+The `mutationOptions` prop can be used to pass options to the [react-query mutation](https://react-query.tanstack.com/reference/useMutation#options) used to call the dataProvider's `updateMany` method.
+
+{% raw %}
+```tsx
+import { BulkUpdateFormButton } from '@react-admin/ra-form-layout';
+import * as React from 'react';
+import { BooleanInput, DateInput, SimpleForm } from 'react-admin';
+
+const PostBulkUpdateButton = () => (
+    <BulkUpdateFormButton mutationOptions={{ retry: false }}>
+        <SimpleForm>
+            <DateInput source="published_at" />
+            <BooleanInput source="is_public" />
+        </SimpleForm>
+    </BulkUpdateFormButton>
+);
+```
+{% endraw %}
+
+You can also use this prop to pass a `meta` object, that will be passed to the dataProvider when calling `updateMany`.
+{% raw %}
+```tsx
+import { BulkUpdateFormButton } from '@react-admin/ra-form-layout';
+import * as React from 'react';
+import { BooleanInput, DateInput, SimpleForm } from 'react-admin';
+
+const PostBulkUpdateButton = () => (
+    <BulkUpdateFormButton mutationOptions={{ meta: { foo: 'bar' } }}>
+        <SimpleForm>
+            <DateInput source="published_at" />
+            <BooleanInput source="is_public" />
+        </SimpleForm>
+    </BulkUpdateFormButton>
+);
+```
+{% endraw %}
+
+#### Usage with `<TabbedForm>` or other location based form layouts
+
+`<BulkUpdateFormButton>` can be used with any form layout. However, for form layouts that are based on location by default, such as [`<TabbedForm>`](https://marmelab.com/react-admin/TabbedForm.html), you will need to disable the location syncing feature, as it may conflict with the Edit route declared by React Admin (`/<resource>/<id>`).
+
+For instance, with `<TabbedForm>`, you can use the `syncWithLocation` prop to disable it:
+
+```tsx
+import { BulkUpdateFormButton } from '@react-admin/ra-form-layout';
+import * as React from 'react';
+import { BooleanInput, DateInput, TabbedForm } from 'react-admin';
+
+const PostBulkUpdateButton = () => (
+    <BulkUpdateFormButton>
+        <TabbedForm syncWithLocation={false}>
+            <TabbedForm.Tab label="Publication">
+                <DateInput source="published_at" />
+            </TabbedForm.Tab>
+            <TabbedForm.Tab label="Visibility">
+                <BooleanInput source="is_public" />
+            </TabbedForm.Tab>
+        </TabbedForm>
+    </BulkUpdateFormButton>
+);
+```
+
+#### Usage With `<InputSelectorForm>`
+
+`<BulkUpdateFormButton>` works best with `<InputSelectorForm>`, which component renders a form allowing to select the fields to update in a record.
+
+<video controls autoplay playsinline muted loop>
+  <source src="./img/BulkUpdateButton-InputSelectorForm.webm" type="video/webm"/>
+  <source src="./img/BulkUpdateButton-InputSelectorForm.mp4" type="video/mp4"/>
+  Your browser does not support the video tag.
+</video>
+
+`<InputSelectorForm>` expects a list of inputs passed in the `inputs` prop. Each input must have a `label` and an `element`.
+
+```tsx
+import {
+    BulkUpdateFormButton,
+    InputSelectorForm,
+} from '@react-admin/ra-form-layout';
+import * as React from 'react';
+import { BooleanInput, DateInput } from 'react-admin';
+
+const PostBulkUpdateButton = () => (
+    <BulkUpdateFormButton>
+        <InputSelectorForm
+            inputs={[
+                {
+                    label: 'Published at',
+                    element: <DateInput source="published_at" />,
+                },
+                {
+                    label: 'Is public',
+                    element: <BooleanInput source="is_public" />,
+                },
+            ]}
+        />
+    </BulkUpdateFormButton>
+);
+```
+
+Use the `inputs` prop to specify the list of inputs from which the user can pick. Each input must have a `label` and an `element`.
+
+```tsx
+import { InputSelectorForm } from '@react-admin/ra-form-layout';
+import * as React from 'react';
+import {
+    BooleanInput,
+    DateInput,
+    SelectArrayInput,
+    TextInput,
+} from 'react-admin';
+
+const PostEdit = () => (
+    <InputSelectorForm
+        inputs={[
+            {
+                label: 'Title',
+                element: <TextInput source="title" />,
+            },
+            {
+                label: 'Body',
+                element: <TextInput source="body" multiline />,
+            },
+            {
+                label: 'Published at',
+                element: <DateInput source="published_at" />,
+            },
+            {
+                label: 'Is public',
+                element: <BooleanInput source="is_public" />,
+            },
+            {
+                label: 'Tags',
+                element: (
+                    <SelectArrayInput
+                        source="tags"
+                        choices={[
+                            { id: 'react', name: 'React' },
+                            { id: 'vue', name: 'Vue' },
+                            { id: 'solid', name: 'Solid' },
+                            { id: 'programming', name: 'Programming' },
+                        ]}
+                    />
+                ),
+            },
+        ]}
+    />
+);
+```
+
+#### Limitations
+
+If you look under the hood, you will see that `<BulkUpdateFormButton>` provides a `<SaveContext>` to its children, which allows them to call `updateMany` with the ids of the selected records.
+
+However since we are in the context of a list, there is no `<RecordContext>` available. Hence, the following inputs cannot work inside a `<BulkUpdateFormButton>`:
+
+- `<ReferenceOneInput>`
+- `<ReferenceManyInput>`
+- `<ReferenceManyToManyInput>`
+
 ### `<FilterButton>`
 
 This button is an internal component used by react-admin in [the Filter button/form combo](./FilteringTutorial.md#the-filter-buttonform-combo).
diff --git a/docs/Datagrid.md b/docs/Datagrid.md
index cb4c3472232..4cf1d2be4fd 100644
--- a/docs/Datagrid.md
+++ b/docs/Datagrid.md
@@ -147,7 +147,6 @@ Finally, `<Datagrid>` inspects children for props that indicate how it should be
   Your browser does not support the video tag.
 </video>
 
-
 Bulk action buttons are buttons that affect several records at once, like mass deletion for instance. In the `<Datagrid>` component, the bulk actions toolbar appears when a user ticks the checkboxes in the first column of the table. The user can then choose a button from the bulk actions toolbar. By default, all Datagrids have a single bulk action button, the bulk delete button. You can add other bulk action buttons by passing a custom element as the `bulkActionButtons` prop of the `<Datagrid>` component:
 
 ```jsx
@@ -173,7 +172,12 @@ export const PostList = () => (
 );
 ```
 
-**Tip**: React-admin provides three components that you can use in `bulkActionButtons`: [`<BulkDeleteButton>`](./Buttons.md#bulkdeletebutton), [`<BulkUpdateButton>`](./Buttons.md#bulkupdatebutton), and [`<BulkExportButton>`](./Buttons.md#bulkexportbutton).
+**Tip**: React-admin provides four components that you can use in `bulkActionButtons`:
+
+- [`<BulkDeleteButton>`](./Buttons.md#bulkdeletebutton) (enabled by default)
+- [`<BulkExportButton>`](./Buttons.md#bulkexportbutton) to export only the selection
+- [`<BulkUpdateButton>`](./Buttons.md#bulkupdatebutton) to immediately update the selection
+- [`<BulkUpdateFormButton>`](./Buttons.md#bulkupdateformbutton) to display a form allowing to update the selection
 
 **Tip**: You can also disable bulk actions altogether by passing `false` to the `bulkActionButtons` prop. In this case, the checkboxes column doesn't show up.
 
diff --git a/docs/img/BulkUpdateButton-InputSelectorForm.mp4 b/docs/img/BulkUpdateButton-InputSelectorForm.mp4
new file mode 100644
index 00000000000..2063f7436a1
Binary files /dev/null and b/docs/img/BulkUpdateButton-InputSelectorForm.mp4 differ
diff --git a/docs/img/BulkUpdateButton-InputSelectorForm.webm b/docs/img/BulkUpdateButton-InputSelectorForm.webm
new file mode 100644
index 00000000000..969e59c649a
Binary files /dev/null and b/docs/img/BulkUpdateButton-InputSelectorForm.webm differ
diff --git a/docs/img/BulkUpdateButton-SimpleForm.mp4 b/docs/img/BulkUpdateButton-SimpleForm.mp4
new file mode 100644
index 00000000000..5d1fa5b4659
Binary files /dev/null and b/docs/img/BulkUpdateButton-SimpleForm.mp4 differ
diff --git a/docs/img/BulkUpdateButton-SimpleForm.webm b/docs/img/BulkUpdateButton-SimpleForm.webm
new file mode 100644
index 00000000000..5eb74bb5684
Binary files /dev/null and b/docs/img/BulkUpdateButton-SimpleForm.webm differ