diff --git a/packages/pluggableWidgets/datagrid-date-filter-web/src/DatagridDateFilter.editorConfig.ts b/packages/pluggableWidgets/datagrid-date-filter-web/src/DatagridDateFilter.editorConfig.ts
index 134c00a117..27bcfee192 100644
--- a/packages/pluggableWidgets/datagrid-date-filter-web/src/DatagridDateFilter.editorConfig.ts
+++ b/packages/pluggableWidgets/datagrid-date-filter-web/src/DatagridDateFilter.editorConfig.ts
@@ -1,3 +1,4 @@
+import { hidePropertiesIn, hidePropertyIn, Properties } from "@mendix/pluggable-widgets-tools";
 import {
     betweenIcon,
     betweenIconDark,
@@ -23,22 +24,23 @@ import {
 import {
     ContainerProps,
     ImageProps,
+    structurePreviewPalette,
     StructurePreviewProps,
-    text,
-    structurePreviewPalette
+    text
 } from "@mendix/widget-plugin-platform/preview/structure-preview-api";
-import { hidePropertiesIn, hidePropertyIn, Properties } from "@mendix/pluggable-widgets-tools";
 
 import { DatagridDateFilterPreviewProps, DefaultFilterEnum } from "../typings/DatagridDateFilterProps";
 
-export function getProperties(
-    values: DatagridDateFilterPreviewProps,
-    defaultProperties: Properties,
-    platform: "web" | "desktop"
-): Properties {
+export function getProperties(values: DatagridDateFilterPreviewProps, defaultProperties: Properties): Properties {
     if (!values.adjustable) {
         hidePropertyIn(defaultProperties, values, "screenReaderButtonCaption");
     }
+
+    if (values.attrChoice === "auto") {
+        hidePropertyIn(defaultProperties, values, "attributes");
+        hidePropertyIn(defaultProperties, {} as { linkedDs: unknown }, "linkedDs");
+    }
+
     if (values.defaultFilter !== "between") {
         hidePropertiesIn(defaultProperties, values, [
             "defaultStartDate",
@@ -49,13 +51,7 @@ export function getProperties(
     } else {
         hidePropertiesIn(defaultProperties, values, ["defaultValue", "valueAttribute"]);
     }
-    if (platform === "web") {
-        if (!values.advanced) {
-            hidePropertiesIn(defaultProperties, values, ["onChange", "valueAttribute"]);
-        }
-    } else {
-        hidePropertyIn(defaultProperties, values, "advanced");
-    }
+
     return defaultProperties;
 }
 
diff --git a/packages/pluggableWidgets/datagrid-date-filter-web/src/DatagridDateFilter.tsx b/packages/pluggableWidgets/datagrid-date-filter-web/src/DatagridDateFilter.tsx
index d3e977451d..3b5ec9cc5d 100644
--- a/packages/pluggableWidgets/datagrid-date-filter-web/src/DatagridDateFilter.tsx
+++ b/packages/pluggableWidgets/datagrid-date-filter-web/src/DatagridDateFilter.tsx
@@ -4,10 +4,18 @@ import { DatagridDateFilterContainerProps } from "../typings/DatagridDateFilterP
 import { Container } from "./components/DateFilterContainer";
 import { withDateFilterAPI } from "./hocs/withDateFilterAPI";
 import { isLoadingDefaultValues } from "./utils/widget-utils";
+import { withDateLinkedAttributes } from "./hocs/withDateLinkedAttributes";
 
 const container = withPreloader(Container, isLoadingDefaultValues);
-const Widget = withDateFilterAPI(container);
+const FilterAuto = withDateFilterAPI(container);
+const FilterLinked = withDateLinkedAttributes(container);
 
 export default function DatagridDateFilter(props: DatagridDateFilterContainerProps): ReactElement | null {
-    return <Widget {...props} />;
+    const isAuto = props.attrChoice === "auto";
+
+    if (isAuto) {
+        return <FilterAuto {...props} />;
+    }
+
+    return <FilterLinked {...props} />;
 }
diff --git a/packages/pluggableWidgets/datagrid-date-filter-web/src/DatagridDateFilter.xml b/packages/pluggableWidgets/datagrid-date-filter-web/src/DatagridDateFilter.xml
index 39ed04bf46..bc9800a53f 100644
--- a/packages/pluggableWidgets/datagrid-date-filter-web/src/DatagridDateFilter.xml
+++ b/packages/pluggableWidgets/datagrid-date-filter-web/src/DatagridDateFilter.xml
@@ -8,9 +8,32 @@
     <properties>
         <propertyGroup caption="General">
             <propertyGroup caption="General">
-                <property key="advanced" type="boolean" defaultValue="false">
-                    <caption>Enable advanced options</caption>
+                <property key="attrChoice" type="enumeration" defaultValue="auto">
+                    <caption>Filter attributes</caption>
                     <description />
+                    <enumerationValues>
+                        <enumerationValue key="auto">Auto</enumerationValue>
+                        <enumerationValue key="linked">Custom</enumerationValue>
+                    </enumerationValues>
+                </property>
+                <property key="linkedDs" type="datasource" isLinked="true" isList="true">
+                    <caption>Datasource to Filter</caption>
+                    <description />
+                </property>
+                <property key="attributes" type="object" isList="true" required="false">
+                    <caption>Attributes</caption>
+                    <description>Select the attributes that the end-user may use for filtering.</description>
+                    <properties>
+                        <propertyGroup caption="General">
+                            <property key="attribute" type="attribute" dataSource="../linkedDs" isMetaData="true" required="true">
+                                <caption>Attribute</caption>
+                                <description />
+                                <attributeTypes>
+                                    <attributeType name="DateTime" />
+                                </attributeTypes>
+                            </property>
+                        </propertyGroup>
+                    </properties>
                 </property>
                 <property key="defaultValue" type="expression" required="false">
                     <caption>Default value</caption>
diff --git a/packages/pluggableWidgets/datagrid-date-filter-web/src/components/__tests__/DatagridDateFilter.spec.tsx b/packages/pluggableWidgets/datagrid-date-filter-web/src/components/__tests__/DatagridDateFilter.spec.tsx
index 6cfbed5d16..3deebdf5fe 100644
--- a/packages/pluggableWidgets/datagrid-date-filter-web/src/components/__tests__/DatagridDateFilter.spec.tsx
+++ b/packages/pluggableWidgets/datagrid-date-filter-web/src/components/__tests__/DatagridDateFilter.spec.tsx
@@ -1,8 +1,8 @@
 import "@testing-library/jest-dom";
-import { FilterAPIv2 } from "@mendix/widget-plugin-filtering/context";
+import { FilterAPI } from "@mendix/widget-plugin-filtering/context";
 import {
     HeaderFiltersStore,
-    HeaderFiltersStoreProps
+    HeaderFiltersStoreSpec
 } from "@mendix/widget-plugin-filtering/stores/generic/HeaderFiltersStore";
 import {
     actionValue,
@@ -15,11 +15,7 @@ import { createContext, createElement } from "react";
 import DatagridDateFilter from "../../DatagridDateFilter";
 import { DatagridDateFilterContainerProps } from "../../../typings/DatagridDateFilterProps";
 import { MXGlobalObject, MXSessionConfig } from "../../../typings/global";
-
-interface StaticInfo {
-    name: string;
-    filtersChannelName: string;
-}
+import { FilterObserver } from "@mendix/widget-plugin-filtering/typings/FilterObserver";
 
 function createMXObjectMock(
     code: string,
@@ -54,13 +50,17 @@ const commonProps: DatagridDateFilterContainerProps = {
     advanced: false
 };
 
-const headerFilterStoreInfo: StaticInfo = {
-    name: commonProps.name,
-    filtersChannelName: ""
-};
-
 const mxObject = createMXObjectMock("en_US", "en-US");
 
+const mockSpec = (spec: Partial<HeaderFiltersStoreSpec>): HeaderFiltersStoreSpec => ({
+    filterList: [],
+    filterChannelName: "datagrid/1",
+    headerInitFilter: [],
+    sharedInitFilter: [],
+    customFilterHost: {} as FilterObserver,
+    ...spec
+});
+
 describe("Date Filter", () => {
     describe("with single instance", () => {
         afterEach(() => {
@@ -69,13 +69,13 @@ describe("Date Filter", () => {
 
         describe("with single attribute", () => {
             beforeEach(() => {
-                const props: HeaderFiltersStoreProps = {
+                const spec = mockSpec({
                     filterList: [
                         { filter: new ListAttributeValueBuilder().withType("DateTime").withFilterable(true).build() }
                     ]
-                };
-                const headerFilterStore = new HeaderFiltersStore(props, headerFilterStoreInfo, null);
-                (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext<FilterAPIv2>(
+                });
+                const headerFilterStore = new HeaderFiltersStore(spec);
+                (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext<FilterAPI>(
                     headerFilterStore.context
                 );
                 (window as any).mx = mxObject;
@@ -144,7 +144,7 @@ describe("Date Filter", () => {
 
         describe("with double attributes", () => {
             beforeAll(() => {
-                const props: HeaderFiltersStoreProps = {
+                const spec = mockSpec({
                     filterList: [
                         {
                             filter: new ListAttributeValueBuilder()
@@ -161,9 +161,9 @@ describe("Date Filter", () => {
                                 .build()
                         }
                     ]
-                };
-                const headerFilterStore = new HeaderFiltersStore(props, headerFilterStoreInfo, null);
-                (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext<FilterAPIv2>(
+                });
+                const headerFilterStore = new HeaderFiltersStore(spec);
+                (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext<FilterAPI>(
                     headerFilterStore.context
                 );
                 (window as any).mx = mxObject;
@@ -184,13 +184,13 @@ describe("Date Filter", () => {
 
         describe("with wrong attribute's type", () => {
             beforeAll(() => {
-                const props: HeaderFiltersStoreProps = {
+                const spec = mockSpec({
                     filterList: [
                         { filter: new ListAttributeValueBuilder().withType("Decimal").withFilterable(true).build() }
                     ]
-                };
-                const headerFilterStore = new HeaderFiltersStore(props, headerFilterStoreInfo, null);
-                (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext<FilterAPIv2>(
+                });
+                const headerFilterStore = new HeaderFiltersStore(spec);
+                (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext<FilterAPI>(
                     headerFilterStore.context
                 );
                 (window as any).mx = mxObject;
@@ -211,7 +211,7 @@ describe("Date Filter", () => {
 
         describe("with wrong multiple attributes' types", () => {
             beforeAll(() => {
-                const props: HeaderFiltersStoreProps = {
+                const spec = mockSpec({
                     filterList: [
                         {
                             filter: new ListAttributeValueBuilder()
@@ -228,9 +228,9 @@ describe("Date Filter", () => {
                                 .build()
                         }
                     ]
-                };
-                const headerFilterStore = new HeaderFiltersStore(props, headerFilterStoreInfo, null);
-                (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext<FilterAPIv2>(
+                });
+                const headerFilterStore = new HeaderFiltersStore(spec);
+                (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext<FilterAPI>(
                     headerFilterStore.context
                 );
                 (window as any).mx = mxObject;
@@ -267,13 +267,13 @@ describe("Date Filter", () => {
 
     describe("with multiple instances", () => {
         beforeAll(() => {
-            const props: HeaderFiltersStoreProps = {
+            const spec = mockSpec({
                 filterList: [
                     { filter: new ListAttributeValueBuilder().withType("DateTime").withFilterable(true).build() }
                 ]
-            };
-            const headerFilterStore = new HeaderFiltersStore(props, headerFilterStoreInfo, null);
-            (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext<FilterAPIv2>(
+            });
+            const headerFilterStore = new HeaderFiltersStore(spec);
+            (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext<FilterAPI>(
                 headerFilterStore.context
             );
             (window as any).mx = mxObject;
@@ -296,13 +296,13 @@ describe("Date Filter", () => {
 
     describe("with session config", () => {
         beforeEach(() => {
-            const props: HeaderFiltersStoreProps = {
+            const spec = mockSpec({
                 filterList: [
                     { filter: new ListAttributeValueBuilder().withType("DateTime").withFilterable(true).build() }
                 ]
-            };
-            const headerFilterStore = new HeaderFiltersStore(props, headerFilterStoreInfo, null);
-            (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext<FilterAPIv2>(
+            });
+            const headerFilterStore = new HeaderFiltersStore(spec);
+            (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext<FilterAPI>(
                 headerFilterStore.context
             );
         });
diff --git a/packages/pluggableWidgets/datagrid-date-filter-web/src/hocs/withDateFilterAPI.tsx b/packages/pluggableWidgets/datagrid-date-filter-web/src/hocs/withDateFilterAPI.tsx
index 38b85272f1..38f9c77860 100644
--- a/packages/pluggableWidgets/datagrid-date-filter-web/src/hocs/withDateFilterAPI.tsx
+++ b/packages/pluggableWidgets/datagrid-date-filter-web/src/hocs/withDateFilterAPI.tsx
@@ -6,7 +6,7 @@ export function withDateFilterAPI<P extends object>(
     Component: (props: P & Date_FilterAPIv2) => React.ReactElement
 ): (props: P) => React.ReactElement {
     return function FilterAPIProvider(props) {
-        const api = useDateFilterAPI("");
+        const api = useDateFilterAPI();
 
         if (api.hasError) {
             return <Alert bootstrapStyle="danger">{api.error.message}</Alert>;
diff --git a/packages/pluggableWidgets/datagrid-date-filter-web/src/hocs/withDateLinkedAttributes.tsx b/packages/pluggableWidgets/datagrid-date-filter-web/src/hocs/withDateLinkedAttributes.tsx
new file mode 100644
index 0000000000..abd3175921
--- /dev/null
+++ b/packages/pluggableWidgets/datagrid-date-filter-web/src/hocs/withDateLinkedAttributes.tsx
@@ -0,0 +1,71 @@
+import { createElement } from "react";
+import { AttributeMetaData } from "mendix";
+import { useFilterAPI } from "@mendix/widget-plugin-filtering/context";
+import { APIError } from "@mendix/widget-plugin-filtering/errors";
+import { error, value, Result } from "@mendix/widget-plugin-filtering/result-meta";
+import { Date_InputFilterInterface } from "@mendix/widget-plugin-filtering/typings/InputFilterInterface";
+import { Alert } from "@mendix/widget-plugin-component-kit/Alert";
+import { useConst } from "@mendix/widget-plugin-mobx-kit/react/useConst";
+import { useSetup } from "@mendix/widget-plugin-mobx-kit/react/useSetup";
+import { DateStoreProvider } from "@mendix/widget-plugin-filtering/custom-filter-api/DateStoreProvider";
+import { ISetupable } from "@mendix/widget-plugin-mobx-kit/setupable";
+
+interface RequiredProps {
+    attributes: Array<{
+        attribute: AttributeMetaData<Date>;
+    }>;
+    name: string;
+}
+
+interface StoreProvider extends ISetupable {
+    store: Date_InputFilterInterface;
+}
+
+type Component<P extends object> = (props: P) => React.ReactElement;
+
+export function withDateLinkedAttributes<P extends RequiredProps>(
+    component: Component<P & InjectableFilterAPI>
+): Component<P> {
+    const StoreInjector = withInjectedStore(component);
+
+    return function FilterAPIProvider(props) {
+        const api = useStoreProvider(props);
+
+        if (api.hasError) {
+            return <Alert bootstrapStyle="danger">{api.error.message}</Alert>;
+        }
+
+        return <StoreInjector {...props} {...api.value} />;
+    };
+}
+
+function withInjectedStore<P extends object>(
+    Component: Component<P & InjectableFilterAPI>
+): Component<P & { provider: StoreProvider; channel: string }> {
+    return function StoreInjector(props) {
+        const provider = useSetup(() => props.provider);
+        return <Component {...props} filterStore={provider.store} parentChannelName={props.channel} />;
+    };
+}
+
+interface InjectableFilterAPI {
+    filterStore: Date_InputFilterInterface;
+    parentChannelName?: string;
+}
+
+function useStoreProvider(props: RequiredProps): Result<{ provider: StoreProvider; channel: string }, APIError> {
+    const filterAPI = useFilterAPI();
+    return useConst(() => {
+        if (filterAPI.hasError) {
+            return error(filterAPI.error);
+        }
+
+        return value({
+            provider: new DateStoreProvider(filterAPI.value, {
+                attributes: props.attributes.map(obj => obj.attribute),
+                dataKey: props.name
+            }),
+            channel: filterAPI.value.parentChannelName
+        });
+    });
+}
diff --git a/packages/pluggableWidgets/datagrid-date-filter-web/typings/DatagridDateFilterProps.d.ts b/packages/pluggableWidgets/datagrid-date-filter-web/typings/DatagridDateFilterProps.d.ts
index 2855ea37b7..970ae14f7d 100644
--- a/packages/pluggableWidgets/datagrid-date-filter-web/typings/DatagridDateFilterProps.d.ts
+++ b/packages/pluggableWidgets/datagrid-date-filter-web/typings/DatagridDateFilterProps.d.ts
@@ -4,16 +4,27 @@
  * @author Mendix Widgets Framework Team
  */
 import { CSSProperties } from "react";
-import { ActionValue, DynamicValue, EditableValue } from "mendix";
+import { ActionValue, AttributeMetaData, DynamicValue, EditableValue } from "mendix";
+
+export type AttrChoiceEnum = "auto" | "linked";
+
+export interface AttributesType {
+    attribute: AttributeMetaData<Date>;
+}
 
 export type DefaultFilterEnum = "between" | "greater" | "greaterEqual" | "equal" | "notEqual" | "smaller" | "smallerEqual" | "empty" | "notEmpty";
 
+export interface AttributesPreviewType {
+    attribute: string;
+}
+
 export interface DatagridDateFilterContainerProps {
     name: string;
     class: string;
     style?: CSSProperties;
     tabIndex?: number;
-    advanced: boolean;
+    attrChoice: AttrChoiceEnum;
+    attributes: AttributesType[];
     defaultValue?: DynamicValue<Date>;
     defaultStartDate?: DynamicValue<Date>;
     defaultEndDate?: DynamicValue<Date>;
@@ -40,7 +51,8 @@ export interface DatagridDateFilterPreviewProps {
     readOnly: boolean;
     renderMode: "design" | "xray" | "structure";
     translate: (text: string) => string;
-    advanced: boolean;
+    attrChoice: AttrChoiceEnum;
+    attributes: AttributesPreviewType[];
     defaultValue: string;
     defaultStartDate: string;
     defaultEndDate: string;
diff --git a/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/components/__tests__/DataGridDropdownFilter.spec.tsx b/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/components/__tests__/DataGridDropdownFilter.spec.tsx
index 2306dd4a38..784ade5f13 100644
--- a/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/components/__tests__/DataGridDropdownFilter.spec.tsx
+++ b/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/components/__tests__/DataGridDropdownFilter.spec.tsx
@@ -1,18 +1,14 @@
 import "@testing-library/jest-dom";
-import { FilterAPIv2 } from "@mendix/widget-plugin-filtering/context";
+import { FilterAPI } from "@mendix/widget-plugin-filtering/context";
 import {
     HeaderFiltersStore,
-    HeaderFiltersStoreProps
+    HeaderFiltersStoreSpec
 } from "@mendix/widget-plugin-filtering/stores/generic/HeaderFiltersStore";
 import { dynamicValue, ListAttributeValueBuilder } from "@mendix/widget-plugin-test-utils";
 import { createContext, createElement } from "react";
 import DatagridDropdownFilter from "../../DatagridDropdownFilter";
 import { fireEvent, render, screen, waitFor } from "@testing-library/react";
-
-interface StaticInfo {
-    name: string;
-    filtersChannelName: string;
-}
+import { FilterObserver } from "@mendix/widget-plugin-filtering/typings/FilterObserver";
 
 const commonProps = {
     class: "filter-custom-class",
@@ -26,10 +22,14 @@ const commonProps = {
     selectedItemsStyle: "text" as const
 };
 
-const headerFilterStoreInfo: StaticInfo = {
-    name: commonProps.name,
-    filtersChannelName: ""
-};
+const mockSpec = (spec: Partial<HeaderFiltersStoreSpec>): HeaderFiltersStoreSpec => ({
+    filterList: [],
+    filterChannelName: "datagrid/1",
+    headerInitFilter: [],
+    sharedInitFilter: [],
+    customFilterHost: {} as FilterObserver,
+    ...spec
+});
 
 const consoleError = global.console.error;
 jest.spyOn(global.console, "error").mockImplementation((...args: any[]) => {
@@ -50,7 +50,7 @@ describe("Dropdown Filter", () => {
 
         describe("with single attribute", () => {
             function mockCtx(universe: string[]): void {
-                const props: HeaderFiltersStoreProps = {
+                const spec = mockSpec({
                     filterList: [
                         {
                             filter: new ListAttributeValueBuilder()
@@ -64,9 +64,9 @@ describe("Dropdown Filter", () => {
                                 .build()
                         }
                     ]
-                };
-                const headerFilterStore = new HeaderFiltersStore(props, headerFilterStoreInfo, null);
-                (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext<FilterAPIv2>(
+                });
+                const headerFilterStore = new HeaderFiltersStore(spec);
+                (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext<FilterAPI>(
                     headerFilterStore.context
                 );
             }
@@ -207,7 +207,7 @@ describe("Dropdown Filter", () => {
 
         describe("with multiple attributes", () => {
             beforeAll(() => {
-                const props: HeaderFiltersStoreProps = {
+                const spec = mockSpec({
                     filterList: [
                         {
                             filter: new ListAttributeValueBuilder()
@@ -234,9 +234,9 @@ describe("Dropdown Filter", () => {
                                 .build()
                         }
                     ]
-                };
-                const headerFilterStore = new HeaderFiltersStore(props, headerFilterStoreInfo, null);
-                (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext<FilterAPIv2>(
+                });
+                const headerFilterStore = new HeaderFiltersStore(spec);
+                (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext<FilterAPI>(
                     headerFilterStore.context
                 );
             });
@@ -267,15 +267,15 @@ describe("Dropdown Filter", () => {
 
         describe("with wrong attribute's type", () => {
             beforeAll(() => {
-                const props: HeaderFiltersStoreProps = {
+                const spec = mockSpec({
                     filterList: [
                         {
                             filter: new ListAttributeValueBuilder().withType("String").withFilterable(true).build()
                         }
                     ]
-                };
-                const headerFilterStore = new HeaderFiltersStore(props, headerFilterStoreInfo, null);
-                (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext<FilterAPIv2>(
+                });
+                const headerFilterStore = new HeaderFiltersStore(spec);
+                (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext<FilterAPI>(
                     headerFilterStore.context
                 );
             });
@@ -297,7 +297,7 @@ describe("Dropdown Filter", () => {
 
         describe("with wrong multiple attributes' types", () => {
             beforeAll(() => {
-                const props: HeaderFiltersStoreProps = {
+                const spec = mockSpec({
                     filterList: [
                         {
                             filter: new ListAttributeValueBuilder()
@@ -314,9 +314,9 @@ describe("Dropdown Filter", () => {
                                 .build()
                         }
                     ]
-                };
-                const headerFilterStore = new HeaderFiltersStore(props, headerFilterStoreInfo, null);
-                (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext<FilterAPIv2>(
+                });
+                const headerFilterStore = new HeaderFiltersStore(spec);
+                (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext<FilterAPI>(
                     headerFilterStore.context
                 );
             });
@@ -354,7 +354,7 @@ describe("Dropdown Filter", () => {
 
         describe("with invalid values", () => {
             beforeAll(() => {
-                const props: HeaderFiltersStoreProps = {
+                const spec = mockSpec({
                     filterList: [
                         {
                             filter: new ListAttributeValueBuilder()
@@ -364,9 +364,9 @@ describe("Dropdown Filter", () => {
                                 .build()
                         }
                     ]
-                };
-                const headerFilterStore = new HeaderFiltersStore(props, headerFilterStoreInfo, null);
-                (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext<FilterAPIv2>(
+                });
+                const headerFilterStore = new HeaderFiltersStore(spec);
+                (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext<FilterAPI>(
                     headerFilterStore.context
                 );
             });
@@ -392,7 +392,7 @@ describe("Dropdown Filter", () => {
 
         describe("with multiple invalid values", () => {
             beforeAll(() => {
-                const props: HeaderFiltersStoreProps = {
+                const spec = mockSpec({
                     filterList: [
                         {
                             filter: new ListAttributeValueBuilder()
@@ -409,9 +409,9 @@ describe("Dropdown Filter", () => {
                                 .build()
                         }
                     ]
-                };
-                const headerFilterStore = new HeaderFiltersStore(props, headerFilterStoreInfo, null);
-                (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext<FilterAPIv2>(
+                });
+                const headerFilterStore = new HeaderFiltersStore(spec);
+                (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext<FilterAPI>(
                     headerFilterStore.context
                 );
             });
@@ -442,7 +442,7 @@ describe("Dropdown Filter", () => {
 
     describe("with multiple instances", () => {
         beforeAll(() => {
-            const props: HeaderFiltersStoreProps = {
+            const spec = mockSpec({
                 filterList: [
                     {
                         filter: new ListAttributeValueBuilder()
@@ -456,9 +456,9 @@ describe("Dropdown Filter", () => {
                             .build()
                     }
                 ]
-            };
-            const headerFilterStore = new HeaderFiltersStore(props, headerFilterStoreInfo, null);
-            (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext<FilterAPIv2>(
+            });
+            const headerFilterStore = new HeaderFiltersStore(spec);
+            (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext<FilterAPI>(
                 headerFilterStore.context
             );
         });
diff --git a/packages/pluggableWidgets/datagrid-number-filter-web/src/DatagridNumberFilter.editorConfig.ts b/packages/pluggableWidgets/datagrid-number-filter-web/src/DatagridNumberFilter.editorConfig.ts
index 4c52aa7205..a6c2f1d30f 100644
--- a/packages/pluggableWidgets/datagrid-number-filter-web/src/DatagridNumberFilter.editorConfig.ts
+++ b/packages/pluggableWidgets/datagrid-number-filter-web/src/DatagridNumberFilter.editorConfig.ts
@@ -1,10 +1,4 @@
-import {
-    ContainerProps,
-    ImageProps,
-    structurePreviewPalette,
-    StructurePreviewProps,
-    text
-} from "@mendix/widget-plugin-platform/preview/structure-preview-api";
+import { hidePropertyIn, Properties } from "@mendix/pluggable-widgets-tools";
 import {
     emptyIcon,
     emptyIconDark,
@@ -23,25 +17,26 @@ import {
     smallerThanIcon,
     smallerThanIconDark
 } from "@mendix/widget-plugin-filtering/preview/editor-preview-icons";
-import { hidePropertiesIn, hidePropertyIn, Properties } from "@mendix/pluggable-widgets-tools";
+import {
+    ContainerProps,
+    ImageProps,
+    structurePreviewPalette,
+    StructurePreviewProps,
+    text
+} from "@mendix/widget-plugin-platform/preview/structure-preview-api";
 
 import { DatagridNumberFilterPreviewProps, DefaultFilterEnum } from "../typings/DatagridNumberFilterProps";
 
-export function getProperties(
-    values: DatagridNumberFilterPreviewProps,
-    defaultProperties: Properties,
-    platform: "web" | "desktop"
-): Properties {
+export function getProperties(values: DatagridNumberFilterPreviewProps, defaultProperties: Properties): Properties {
     if (!values.adjustable) {
         hidePropertyIn(defaultProperties, values, "screenReaderButtonCaption");
     }
-    if (platform === "web") {
-        if (!values.advanced) {
-            hidePropertiesIn(defaultProperties, values, ["onChange", "valueAttribute"]);
-        }
-    } else {
-        hidePropertyIn(defaultProperties, values, "advanced");
+
+    if (values.attrChoice === "auto") {
+        hidePropertyIn(defaultProperties, values, "attributes");
+        hidePropertyIn(defaultProperties, {} as { linkedDs: unknown }, "linkedDs");
     }
+
     return defaultProperties;
 }
 
diff --git a/packages/pluggableWidgets/datagrid-number-filter-web/src/DatagridNumberFilter.tsx b/packages/pluggableWidgets/datagrid-number-filter-web/src/DatagridNumberFilter.tsx
index f306da389c..8743b68cdd 100644
--- a/packages/pluggableWidgets/datagrid-number-filter-web/src/DatagridNumberFilter.tsx
+++ b/packages/pluggableWidgets/datagrid-number-filter-web/src/DatagridNumberFilter.tsx
@@ -4,10 +4,18 @@ import { DatagridNumberFilterContainerProps } from "../typings/DatagridNumberFil
 import { NumberFilterContainer } from "./components/NumberFilterContainer";
 import { isLoadingDefaultValues } from "./utils/widget-utils";
 import { withNumberFilterAPI } from "./hocs/withNumberFilterAPI";
+import { withLinkedAttributes } from "./hocs/withLinkedAttributes";
 
 const container = withPreloader<DatagridNumberFilterContainerProps>(NumberFilterContainer, isLoadingDefaultValues);
-const Widget = withNumberFilterAPI(container);
+const FilterAuto = withNumberFilterAPI(container);
+const FilterLinked = withLinkedAttributes(container);
 
 export default function DatagridNumberFilter(props: DatagridNumberFilterContainerProps): ReactElement {
-    return <Widget {...props} />;
+    const isAuto = props.attrChoice === "auto";
+
+    if (isAuto) {
+        return <FilterAuto {...props} />;
+    }
+
+    return <FilterLinked {...props} />;
 }
diff --git a/packages/pluggableWidgets/datagrid-number-filter-web/src/DatagridNumberFilter.xml b/packages/pluggableWidgets/datagrid-number-filter-web/src/DatagridNumberFilter.xml
index 5c024b076f..e628073200 100644
--- a/packages/pluggableWidgets/datagrid-number-filter-web/src/DatagridNumberFilter.xml
+++ b/packages/pluggableWidgets/datagrid-number-filter-web/src/DatagridNumberFilter.xml
@@ -8,9 +8,35 @@
     <properties>
         <propertyGroup caption="General">
             <propertyGroup caption="General">
-                <property key="advanced" type="boolean" defaultValue="false">
-                    <caption>Enable advanced options</caption>
+                <property key="attrChoice" type="enumeration" defaultValue="auto">
+                    <caption>Filter attributes</caption>
                     <description />
+                    <enumerationValues>
+                        <enumerationValue key="auto">Auto</enumerationValue>
+                        <enumerationValue key="linked">Custom</enumerationValue>
+                    </enumerationValues>
+                </property>
+                <property key="linkedDs" type="datasource" isLinked="true" isList="true">
+                    <caption>Datasource to Filter</caption>
+                    <description />
+                </property>
+                <property key="attributes" type="object" isList="true" required="false">
+                    <caption>Attributes</caption>
+                    <description>Select the attributes that the end-user may use for filtering.</description>
+                    <properties>
+                        <propertyGroup caption="General">
+                            <property key="attribute" type="attribute" dataSource="../linkedDs" isMetaData="true" required="true">
+                                <caption>Attribute</caption>
+                                <description />
+                                <attributeTypes>
+                                    <attributeType name="AutoNumber" />
+                                    <attributeType name="Decimal" />
+                                    <attributeType name="Integer" />
+                                    <attributeType name="Long" />
+                                </attributeTypes>
+                            </property>
+                        </propertyGroup>
+                    </properties>
                 </property>
                 <property key="defaultValue" type="expression" required="false">
                     <caption>Default value</caption>
diff --git a/packages/pluggableWidgets/datagrid-number-filter-web/src/components/__tests__/DatagridNumberFilter.spec.tsx b/packages/pluggableWidgets/datagrid-number-filter-web/src/components/__tests__/DatagridNumberFilter.spec.tsx
index c97e9d1525..92767d5ff2 100644
--- a/packages/pluggableWidgets/datagrid-number-filter-web/src/components/__tests__/DatagridNumberFilter.spec.tsx
+++ b/packages/pluggableWidgets/datagrid-number-filter-web/src/components/__tests__/DatagridNumberFilter.spec.tsx
@@ -1,9 +1,9 @@
 import "@testing-library/jest-dom";
-import { FilterAPIv2 } from "@mendix/widget-plugin-filtering/context";
+import { FilterAPI } from "@mendix/widget-plugin-filtering/context";
 import { requirePlugin } from "@mendix/widget-plugin-external-events/plugin";
 import {
     HeaderFiltersStore,
-    HeaderFiltersStoreProps
+    HeaderFiltersStoreSpec
 } from "@mendix/widget-plugin-filtering/stores/generic/HeaderFiltersStore";
 import {
     actionValue,
@@ -20,11 +20,7 @@ import DatagridNumberFilter from "../../DatagridNumberFilter";
 import { Big } from "big.js";
 import { DatagridNumberFilterContainerProps } from "../../../typings/DatagridNumberFilterProps";
 import { resetIdCounter } from "downshift";
-
-interface StaticInfo {
-    name: string;
-    filtersChannelName: string;
-}
+import { FilterObserver } from "@mendix/widget-plugin-filtering/typings/FilterObserver";
 
 const commonProps: DatagridNumberFilterContainerProps = {
     class: "filter-custom-class",
@@ -36,13 +32,17 @@ const commonProps: DatagridNumberFilterContainerProps = {
     delay: 1000
 };
 
-const headerFilterStoreInfo: StaticInfo = {
-    name: commonProps.name,
-    filtersChannelName: "datagrid1"
-};
-
 jest.useFakeTimers();
 
+const mockSpec = (spec: Partial<HeaderFiltersStoreSpec>): HeaderFiltersStoreSpec => ({
+    filterList: [],
+    filterChannelName: "datagrid1",
+    headerInitFilter: [],
+    sharedInitFilter: [],
+    customFilterHost: {} as FilterObserver,
+    ...spec
+});
+
 beforeEach(() => {
     jest.spyOn(console, "warn").mockImplementation(() => {
         // noop
@@ -60,23 +60,23 @@ describe("Number Filter", () => {
 
         describe("with single attribute", () => {
             beforeEach(() => {
-                const props: HeaderFiltersStoreProps = {
-                    filterList: [
-                        {
-                            filter: new ListAttributeValueBuilder()
-                                .withType("Long")
-                                .withFormatter(
-                                    value => (value ? value.toString() : ""),
-                                    (value: string) => ({ valid: true, value })
-                                )
-                                .withFilterable(true)
-                                .build()
-                        }
-                    ],
-                    parentChannelName: headerFilterStoreInfo.filtersChannelName
-                };
-                const headerFilterStore = new HeaderFiltersStore(props, headerFilterStoreInfo, null);
-                (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext<FilterAPIv2>(
+                const headerFilterStore = new HeaderFiltersStore(
+                    mockSpec({
+                        filterList: [
+                            {
+                                filter: new ListAttributeValueBuilder()
+                                    .withType("Long")
+                                    .withFormatter(
+                                        value => (value ? value.toString() : ""),
+                                        (value: string) => ({ valid: true, value })
+                                    )
+                                    .withFilterable(true)
+                                    .build()
+                            }
+                        ]
+                    })
+                );
+                (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext<FilterAPI>(
                     headerFilterStore.context
                 );
             });
@@ -194,39 +194,40 @@ describe("Number Filter", () => {
 
         describe("with multiple attributes", () => {
             beforeEach(() => {
-                const props: HeaderFiltersStoreProps = {
-                    filterList: [
-                        {
-                            filter: new ListAttributeValueBuilder()
-                                .withId("attribute1")
-                                .withType("Long")
-                                .withFormatter(
-                                    value => value,
-                                    () => {
-                                        // noop
-                                    }
-                                )
-                                .withFilterable(true)
-                                .build()
-                        },
-                        {
-                            filter: new ListAttributeValueBuilder()
-                                .withId("attribute2")
-                                .withType("Decimal")
-                                .withFormatter(
-                                    value => value,
-                                    () => {
-                                        // noop
-                                    }
-                                )
-                                .withFilterable(true)
-                                .build()
-                        }
-                    ],
-                    parentChannelName: headerFilterStoreInfo.filtersChannelName
-                };
-                const headerFilterStore = new HeaderFiltersStore(props, headerFilterStoreInfo, null);
-                (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext<FilterAPIv2>(
+                const headerFilterStore = new HeaderFiltersStore(
+                    mockSpec({
+                        filterList: [
+                            {
+                                filter: new ListAttributeValueBuilder()
+                                    .withId("attribute1")
+                                    .withType("Long")
+                                    .withFormatter(
+                                        value => value,
+                                        () => {
+                                            // noop
+                                        }
+                                    )
+                                    .withFilterable(true)
+                                    .build()
+                            },
+                            {
+                                filter: new ListAttributeValueBuilder()
+                                    .withId("attribute2")
+                                    .withType("Decimal")
+                                    .withFormatter(
+                                        value => value,
+                                        () => {
+                                            // noop
+                                        }
+                                    )
+                                    .withFilterable(true)
+                                    .build()
+                            }
+                        ]
+                    })
+                );
+
+                (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext<FilterAPI>(
                     headerFilterStore.context
                 );
             });
@@ -296,13 +297,14 @@ describe("Number Filter", () => {
 
         describe("with wrong attribute's type", () => {
             beforeAll(() => {
-                const props: HeaderFiltersStoreProps = {
+                const spec = mockSpec({
                     filterList: [
                         { filter: new ListAttributeValueBuilder().withType("Boolean").withFilterable(true).build() }
                     ]
-                };
-                const headerFilterStore = new HeaderFiltersStore(props, headerFilterStoreInfo, null);
-                (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext<FilterAPIv2>(
+                });
+
+                const headerFilterStore = new HeaderFiltersStore(spec);
+                (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext<FilterAPI>(
                     headerFilterStore.context
                 );
             });
@@ -320,7 +322,7 @@ describe("Number Filter", () => {
 
         describe("with wrong multiple attributes' types", () => {
             beforeAll(() => {
-                const props: HeaderFiltersStoreProps = {
+                const spec = mockSpec({
                     filterList: [
                         {
                             filter: new ListAttributeValueBuilder()
@@ -337,9 +339,10 @@ describe("Number Filter", () => {
                                 .build()
                         }
                     ]
-                };
-                const headerFilterStore = new HeaderFiltersStore(props, headerFilterStoreInfo, null);
-                (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext<FilterAPIv2>(
+                });
+
+                const headerFilterStore = new HeaderFiltersStore(spec);
+                (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext<FilterAPI>(
                     headerFilterStore.context
                 );
             });
@@ -370,7 +373,7 @@ describe("Number Filter", () => {
 
     describe("with multiple instances", () => {
         beforeEach(() => {
-            const props: HeaderFiltersStoreProps = {
+            const spec = mockSpec({
                 filterList: [
                     {
                         filter: new ListAttributeValueBuilder()
@@ -385,9 +388,9 @@ describe("Number Filter", () => {
                             .build()
                     }
                 ]
-            };
-            const headerFilterStore = new HeaderFiltersStore(props, headerFilterStoreInfo, null);
-            (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext<FilterAPIv2>(
+            });
+            const headerFilterStore = new HeaderFiltersStore(spec);
+            (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext<FilterAPI>(
                 headerFilterStore.context
             );
         });
diff --git a/packages/pluggableWidgets/datagrid-number-filter-web/src/hocs/withLinkedAttributes.tsx b/packages/pluggableWidgets/datagrid-number-filter-web/src/hocs/withLinkedAttributes.tsx
new file mode 100644
index 0000000000..3ec11b8f0d
--- /dev/null
+++ b/packages/pluggableWidgets/datagrid-number-filter-web/src/hocs/withLinkedAttributes.tsx
@@ -0,0 +1,72 @@
+import { createElement } from "react";
+import { AttributeMetaData } from "mendix";
+import { useFilterAPI } from "@mendix/widget-plugin-filtering/context";
+import { APIError } from "@mendix/widget-plugin-filtering/errors";
+import { error, value, Result } from "@mendix/widget-plugin-filtering/result-meta";
+import { Number_InputFilterInterface } from "@mendix/widget-plugin-filtering/typings/InputFilterInterface";
+import { Alert } from "@mendix/widget-plugin-component-kit/Alert";
+import { useConst } from "@mendix/widget-plugin-mobx-kit/react/useConst";
+import { useSetup } from "@mendix/widget-plugin-mobx-kit/react/useSetup";
+import { NumberStoreProvider } from "@mendix/widget-plugin-filtering/custom-filter-api/NumberStoreProvider";
+import { ISetupable } from "@mendix/widget-plugin-mobx-kit/setupable";
+import { Big } from "big.js";
+
+interface RequiredProps {
+    attributes: Array<{
+        attribute: AttributeMetaData<Big>;
+    }>;
+    name: string;
+}
+
+interface StoreProvider extends ISetupable {
+    store: Number_InputFilterInterface;
+}
+
+type Component<P extends object> = (props: P) => React.ReactElement;
+
+export function withLinkedAttributes<P extends RequiredProps>(
+    component: Component<P & InjectableFilterAPI>
+): Component<P> {
+    const StoreInjector = withInjectedStore(component);
+
+    return function FilterAPIProvider(props) {
+        const api = useStoreProvider(props);
+
+        if (api.hasError) {
+            return <Alert bootstrapStyle="danger">{api.error.message}</Alert>;
+        }
+
+        return <StoreInjector {...props} {...api.value} />;
+    };
+}
+
+function withInjectedStore<P extends object>(
+    Component: Component<P & InjectableFilterAPI>
+): Component<P & { provider: StoreProvider; channel: string }> {
+    return function StoreInjector(props) {
+        const provider = useSetup(() => props.provider);
+        return <Component {...props} filterStore={provider.store} parentChannelName={props.channel} />;
+    };
+}
+
+interface InjectableFilterAPI {
+    filterStore: Number_InputFilterInterface;
+    parentChannelName?: string;
+}
+
+function useStoreProvider(props: RequiredProps): Result<{ provider: StoreProvider; channel: string }, APIError> {
+    const filterAPI = useFilterAPI();
+    return useConst(() => {
+        if (filterAPI.hasError) {
+            return error(filterAPI.error);
+        }
+
+        return value({
+            provider: new NumberStoreProvider(filterAPI.value, {
+                attributes: props.attributes.map(obj => obj.attribute),
+                dataKey: props.name
+            }),
+            channel: filterAPI.value.parentChannelName
+        });
+    });
+}
diff --git a/packages/pluggableWidgets/datagrid-number-filter-web/src/hocs/withNumberFilterAPI.tsx b/packages/pluggableWidgets/datagrid-number-filter-web/src/hocs/withNumberFilterAPI.tsx
index e937dfd70a..73c78450ea 100644
--- a/packages/pluggableWidgets/datagrid-number-filter-web/src/hocs/withNumberFilterAPI.tsx
+++ b/packages/pluggableWidgets/datagrid-number-filter-web/src/hocs/withNumberFilterAPI.tsx
@@ -6,7 +6,7 @@ export function withNumberFilterAPI<P extends object>(
     Component: (props: P & Number_FilterAPIv2) => React.ReactElement
 ): (props: P) => React.ReactElement {
     return function FilterAPIProvider(props) {
-        const api = useNumberFilterAPI("");
+        const api = useNumberFilterAPI();
 
         if (api.hasError) {
             return <Alert bootstrapStyle="danger">{api.error.message}</Alert>;
diff --git a/packages/pluggableWidgets/datagrid-number-filter-web/typings/DatagridNumberFilterProps.d.ts b/packages/pluggableWidgets/datagrid-number-filter-web/typings/DatagridNumberFilterProps.d.ts
index 0c2f405af5..8b9a872dff 100644
--- a/packages/pluggableWidgets/datagrid-number-filter-web/typings/DatagridNumberFilterProps.d.ts
+++ b/packages/pluggableWidgets/datagrid-number-filter-web/typings/DatagridNumberFilterProps.d.ts
@@ -4,17 +4,28 @@
  * @author Mendix Widgets Framework Team
  */
 import { CSSProperties } from "react";
-import { ActionValue, DynamicValue, EditableValue } from "mendix";
+import { ActionValue, AttributeMetaData, DynamicValue, EditableValue } from "mendix";
 import { Big } from "big.js";
 
+export type AttrChoiceEnum = "auto" | "linked";
+
+export interface AttributesType {
+    attribute: AttributeMetaData<Big>;
+}
+
 export type DefaultFilterEnum = "greater" | "greaterEqual" | "equal" | "notEqual" | "smaller" | "smallerEqual" | "empty" | "notEmpty";
 
+export interface AttributesPreviewType {
+    attribute: string;
+}
+
 export interface DatagridNumberFilterContainerProps {
     name: string;
     class: string;
     style?: CSSProperties;
     tabIndex?: number;
-    advanced: boolean;
+    attrChoice: AttrChoiceEnum;
+    attributes: AttributesType[];
     defaultValue?: DynamicValue<Big>;
     defaultFilter: DefaultFilterEnum;
     placeholder?: DynamicValue<string>;
@@ -37,7 +48,8 @@ export interface DatagridNumberFilterPreviewProps {
     readOnly: boolean;
     renderMode: "design" | "xray" | "structure";
     translate: (text: string) => string;
-    advanced: boolean;
+    attrChoice: AttrChoiceEnum;
+    attributes: AttributesPreviewType[];
     defaultValue: string;
     defaultFilter: DefaultFilterEnum;
     placeholder: string;
diff --git a/packages/pluggableWidgets/datagrid-text-filter-web/package.json b/packages/pluggableWidgets/datagrid-text-filter-web/package.json
index 0aecea7bf8..0efe6bab88 100644
--- a/packages/pluggableWidgets/datagrid-text-filter-web/package.json
+++ b/packages/pluggableWidgets/datagrid-text-filter-web/package.json
@@ -45,6 +45,7 @@
         "@mendix/widget-plugin-external-events": "workspace:*",
         "@mendix/widget-plugin-filtering": "workspace:*",
         "@mendix/widget-plugin-hooks": "workspace:*",
+        "@mendix/widget-plugin-mobx-kit": "workspace:^",
         "@mendix/widget-plugin-platform": "workspace:*",
         "classnames": "^2.3.2",
         "mobx": "6.12.3",
diff --git a/packages/pluggableWidgets/datagrid-text-filter-web/src/DatagridTextFilter.editorConfig.ts b/packages/pluggableWidgets/datagrid-text-filter-web/src/DatagridTextFilter.editorConfig.ts
index 2414af2eae..787ae0b8c9 100644
--- a/packages/pluggableWidgets/datagrid-text-filter-web/src/DatagridTextFilter.editorConfig.ts
+++ b/packages/pluggableWidgets/datagrid-text-filter-web/src/DatagridTextFilter.editorConfig.ts
@@ -1,10 +1,4 @@
-import {
-    ContainerProps,
-    ImageProps,
-    StructurePreviewProps,
-    text,
-    structurePreviewPalette
-} from "@mendix/widget-plugin-platform/preview/structure-preview-api";
+import { hidePropertyIn, Properties } from "@mendix/pluggable-widgets-tools";
 import {
     containsIcon,
     containsIconDark,
@@ -29,25 +23,27 @@ import {
     startsWithIcon,
     startsWithIconDark
 } from "@mendix/widget-plugin-filtering/preview/editor-preview-icons";
-import { hidePropertiesIn, hidePropertyIn, Properties } from "@mendix/pluggable-widgets-tools";
+import {
+    ContainerProps,
+    ImageProps,
+    structurePreviewPalette,
+    StructurePreviewProps,
+    text
+} from "@mendix/widget-plugin-platform/preview/structure-preview-api";
 
 import { DatagridTextFilterPreviewProps, DefaultFilterEnum } from "../typings/DatagridTextFilterProps";
 
-export function getProperties(
-    values: DatagridTextFilterPreviewProps,
-    defaultProperties: Properties,
-    platform: "web" | "desktop"
-): Properties {
+export function getProperties(values: DatagridTextFilterPreviewProps, defaultProperties: Properties): Properties {
     if (!values.adjustable) {
         hidePropertyIn(defaultProperties, values, "screenReaderButtonCaption");
     }
-    if (platform === "web") {
-        if (!values.advanced) {
-            hidePropertiesIn(defaultProperties, values, ["onChange", "valueAttribute"]);
-        }
-    } else {
-        hidePropertyIn(defaultProperties, values, "advanced");
+
+    if (values.attrChoice === "auto") {
+        hidePropertyIn(defaultProperties, values, "attributes");
     }
+
+    hidePropertyIn(defaultProperties, {} as { linkedDs: unknown }, "linkedDs");
+
     return defaultProperties;
 }
 
diff --git a/packages/pluggableWidgets/datagrid-text-filter-web/src/DatagridTextFilter.tsx b/packages/pluggableWidgets/datagrid-text-filter-web/src/DatagridTextFilter.tsx
index 5772f0eb2f..f47d4f1bcc 100644
--- a/packages/pluggableWidgets/datagrid-text-filter-web/src/DatagridTextFilter.tsx
+++ b/packages/pluggableWidgets/datagrid-text-filter-web/src/DatagridTextFilter.tsx
@@ -4,10 +4,18 @@ import { DatagridTextFilterContainerProps } from "../typings/DatagridTextFilterP
 import { TextFilterContainer } from "./components/TextFilterContainer";
 import { withTextFilterAPI } from "./hocs/withTextFilterAPI";
 import { isLoadingDefaultValues } from "./utils/widget-utils";
+import { withLinkedAttributes } from "./hocs/withLinkedAttributes";
 
 const container = withPreloader<DatagridTextFilterContainerProps>(TextFilterContainer, isLoadingDefaultValues);
-const Widget = withTextFilterAPI(container);
+const FilterAuto = withTextFilterAPI(container);
+const FilterLinked = withLinkedAttributes(container);
 
 export default function DatagridTextFilter(props: DatagridTextFilterContainerProps): ReactElement {
-    return <Widget {...props} />;
+    const isAuto = props.attrChoice === "auto";
+
+    if (isAuto) {
+        return <FilterAuto {...props} />;
+    }
+
+    return <FilterLinked {...props} />;
 }
diff --git a/packages/pluggableWidgets/datagrid-text-filter-web/src/DatagridTextFilter.xml b/packages/pluggableWidgets/datagrid-text-filter-web/src/DatagridTextFilter.xml
index d8ed8c4cfc..893a20f660 100644
--- a/packages/pluggableWidgets/datagrid-text-filter-web/src/DatagridTextFilter.xml
+++ b/packages/pluggableWidgets/datagrid-text-filter-web/src/DatagridTextFilter.xml
@@ -8,9 +8,32 @@
     <properties>
         <propertyGroup caption="General">
             <propertyGroup caption="General">
-                <property key="advanced" type="boolean" defaultValue="false">
-                    <caption>Enable advanced options</caption>
+                <property key="attrChoice" type="enumeration" defaultValue="auto">
+                    <caption>Filter attributes</caption>
                     <description />
+                    <enumerationValues>
+                        <enumerationValue key="auto">Auto</enumerationValue>
+                        <enumerationValue key="linked">Custom</enumerationValue>
+                    </enumerationValues>
+                </property>
+                <property key="linkedDs" type="datasource" isLinked="true" isList="true">
+                    <caption>Datasource to Filter</caption>
+                    <description />
+                </property>
+                <property key="attributes" type="object" isList="true" required="false">
+                    <caption>Attributes</caption>
+                    <description>Select the attributes that the end-user may use for filtering.</description>
+                    <properties>
+                        <propertyGroup caption="General">
+                            <property key="attribute" type="attribute" dataSource="../linkedDs" isMetaData="true" required="true">
+                                <caption>Attribute</caption>
+                                <description />
+                                <attributeTypes>
+                                    <attributeType name="String" />
+                                </attributeTypes>
+                            </property>
+                        </propertyGroup>
+                    </properties>
                 </property>
                 <property key="defaultValue" type="expression" required="false">
                     <caption>Default value</caption>
diff --git a/packages/pluggableWidgets/datagrid-text-filter-web/src/components/__tests__/DatagridTextFilter.spec.tsx b/packages/pluggableWidgets/datagrid-text-filter-web/src/components/__tests__/DatagridTextFilter.spec.tsx
index 7726d55c48..3b8ae18f20 100644
--- a/packages/pluggableWidgets/datagrid-text-filter-web/src/components/__tests__/DatagridTextFilter.spec.tsx
+++ b/packages/pluggableWidgets/datagrid-text-filter-web/src/components/__tests__/DatagridTextFilter.spec.tsx
@@ -1,8 +1,7 @@
 import "@testing-library/jest-dom";
-import { FilterAPIv2 } from "@mendix/widget-plugin-filtering/context";
 import {
     HeaderFiltersStore,
-    HeaderFiltersStoreProps
+    HeaderFiltersStoreSpec
 } from "@mendix/widget-plugin-filtering/stores/generic/HeaderFiltersStore";
 import {
     actionValue,
@@ -17,11 +16,8 @@ import { createContext, createElement } from "react";
 import DatagridTextFilter from "../../DatagridTextFilter";
 import { DatagridTextFilterContainerProps } from "../../../typings/DatagridTextFilterProps";
 import { resetIdCounter } from "downshift";
-
-interface StaticInfo {
-    name: string;
-    filtersChannelName: string;
-}
+import { FilterObserver } from "@mendix/widget-plugin-filtering/typings/FilterObserver";
+import { FilterAPI } from "@mendix/widget-plugin-filtering/context";
 
 const commonProps: DatagridTextFilterContainerProps = {
     class: "filter-custom-class",
@@ -29,13 +25,9 @@ const commonProps: DatagridTextFilterContainerProps = {
     name: "filter-test",
     defaultFilter: "equal" as const,
     adjustable: true,
-    advanced: false,
-    delay: 1000
-};
-
-const headerFilterStoreInfo: StaticInfo = {
-    name: commonProps.name,
-    filtersChannelName: "datagrid1"
+    delay: 1000,
+    attrChoice: "auto",
+    attributes: []
 };
 
 jest.useFakeTimers();
@@ -57,7 +49,7 @@ describe("Text Filter", () => {
 
         describe("with defaultValue prop", () => {
             beforeEach(() => {
-                const props: HeaderFiltersStoreProps = {
+                const spec: HeaderFiltersStoreSpec = {
                     filterList: [
                         {
                             filter: new ListAttributeValueBuilder()
@@ -70,10 +62,13 @@ describe("Text Filter", () => {
                                 .build()
                         }
                     ],
-                    parentChannelName: "datagrid1"
+                    filterChannelName: "datagrid1",
+                    sharedInitFilter: [],
+                    headerInitFilter: [],
+                    customFilterHost: {} as FilterObserver
                 };
-                const headerFilterStore = new HeaderFiltersStore(props, headerFilterStoreInfo, null);
-                (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext<FilterAPIv2>(
+                const headerFilterStore = new HeaderFiltersStore(spec);
+                (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext<FilterAPI>(
                     headerFilterStore.context
                 );
             });
@@ -170,7 +165,7 @@ describe("Text Filter", () => {
 
         describe("with single attribute", () => {
             beforeAll(() => {
-                const props: HeaderFiltersStoreProps = {
+                const spec: HeaderFiltersStoreSpec = {
                     filterList: [
                         {
                             filter: new ListAttributeValueBuilder()
@@ -183,10 +178,13 @@ describe("Text Filter", () => {
                                 .build()
                         }
                     ],
-                    parentChannelName: "datagrid1"
+                    filterChannelName: "datagrid1",
+                    sharedInitFilter: [],
+                    headerInitFilter: [],
+                    customFilterHost: {} as FilterObserver
                 };
-                const headerFilterStore = new HeaderFiltersStore(props, headerFilterStoreInfo, null);
-                (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext<FilterAPIv2>(
+                const headerFilterStore = new HeaderFiltersStore(spec);
+                (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext<FilterAPI>(
                     headerFilterStore.context
                 );
             });
@@ -256,7 +254,7 @@ describe("Text Filter", () => {
 
         describe("with multiple attributes", () => {
             beforeAll(() => {
-                const props: HeaderFiltersStoreProps = {
+                const spec: HeaderFiltersStoreSpec = {
                     filterList: [
                         {
                             filter: new ListAttributeValueBuilder()
@@ -284,10 +282,14 @@ describe("Text Filter", () => {
                                 .withFilterable(true)
                                 .build()
                         }
-                    ]
+                    ],
+                    filterChannelName: "datagrid1",
+                    sharedInitFilter: [],
+                    headerInitFilter: [],
+                    customFilterHost: {} as FilterObserver
                 };
-                const headerFilterStore = new HeaderFiltersStore(props, headerFilterStoreInfo, null);
-                (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext<FilterAPIv2>(
+                const headerFilterStore = new HeaderFiltersStore(spec);
+                (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext<FilterAPI>(
                     headerFilterStore.context
                 );
             });
@@ -305,13 +307,17 @@ describe("Text Filter", () => {
 
         describe("with wrong attribute's type", () => {
             beforeAll(() => {
-                const props: HeaderFiltersStoreProps = {
+                const spec: HeaderFiltersStoreSpec = {
                     filterList: [
                         { filter: new ListAttributeValueBuilder().withType("Decimal").withFilterable(true).build() }
-                    ]
+                    ],
+                    filterChannelName: "datagrid1",
+                    sharedInitFilter: [],
+                    headerInitFilter: [],
+                    customFilterHost: {} as FilterObserver
                 };
-                const headerFilterStore = new HeaderFiltersStore(props, headerFilterStoreInfo, null);
-                (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext<FilterAPIv2>(
+                const headerFilterStore = new HeaderFiltersStore(spec);
+                (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext<FilterAPI>(
                     headerFilterStore.context
                 );
             });
@@ -329,7 +335,7 @@ describe("Text Filter", () => {
 
         describe("with wrong multiple attributes' types", () => {
             beforeAll(() => {
-                const props: HeaderFiltersStoreProps = {
+                const spec: HeaderFiltersStoreSpec = {
                     filterList: [
                         {
                             filter: new ListAttributeValueBuilder()
@@ -345,10 +351,14 @@ describe("Text Filter", () => {
                                 .withFilterable(true)
                                 .build()
                         }
-                    ]
+                    ],
+                    filterChannelName: "datagrid1",
+                    sharedInitFilter: [],
+                    headerInitFilter: [],
+                    customFilterHost: {} as FilterObserver
                 };
-                const headerFilterStore = new HeaderFiltersStore(props, headerFilterStoreInfo, null);
-                (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext<FilterAPIv2>(
+                const headerFilterStore = new HeaderFiltersStore(spec);
+                (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext<FilterAPI>(
                     headerFilterStore.context
                 );
             });
@@ -379,7 +389,7 @@ describe("Text Filter", () => {
 
     describe("with multiple instances", () => {
         beforeAll(() => {
-            const props: HeaderFiltersStoreProps = {
+            const spec: HeaderFiltersStoreSpec = {
                 filterList: [
                     {
                         filter: new ListAttributeValueBuilder()
@@ -393,10 +403,14 @@ describe("Text Filter", () => {
                             .withFilterable(true)
                             .build()
                     }
-                ]
+                ],
+                filterChannelName: "datagrid1",
+                sharedInitFilter: [],
+                headerInitFilter: [],
+                customFilterHost: {} as FilterObserver
             };
-            const headerFilterStore = new HeaderFiltersStore(props, headerFilterStoreInfo, null);
-            (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext<FilterAPIv2>(
+            const headerFilterStore = new HeaderFiltersStore(spec);
+            (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext<FilterAPI>(
                 headerFilterStore.context
             );
         });
diff --git a/packages/pluggableWidgets/datagrid-text-filter-web/src/hocs/withLinkedAttributes.tsx b/packages/pluggableWidgets/datagrid-text-filter-web/src/hocs/withLinkedAttributes.tsx
new file mode 100644
index 0000000000..839892a368
--- /dev/null
+++ b/packages/pluggableWidgets/datagrid-text-filter-web/src/hocs/withLinkedAttributes.tsx
@@ -0,0 +1,71 @@
+import { createElement } from "react";
+import { AttributeMetaData } from "mendix";
+import { useFilterAPI } from "@mendix/widget-plugin-filtering/context";
+import { APIError } from "@mendix/widget-plugin-filtering/errors";
+import { error, value, Result } from "@mendix/widget-plugin-filtering/result-meta";
+import { String_InputFilterInterface } from "@mendix/widget-plugin-filtering/typings/InputFilterInterface";
+import { Alert } from "@mendix/widget-plugin-component-kit/Alert";
+import { useConst } from "@mendix/widget-plugin-mobx-kit/react/useConst";
+import { useSetup } from "@mendix/widget-plugin-mobx-kit/react/useSetup";
+import { StringStoreProvider } from "@mendix/widget-plugin-filtering/custom-filter-api/StringStoreProvider";
+import { ISetupable } from "@mendix/widget-plugin-mobx-kit/setupable";
+
+interface RequiredProps {
+    attributes: Array<{
+        attribute: AttributeMetaData<string>;
+    }>;
+    name: string;
+}
+
+interface StoreProvider extends ISetupable {
+    store: String_InputFilterInterface;
+}
+
+type Component<P extends object> = (props: P) => React.ReactElement;
+
+export function withLinkedAttributes<P extends RequiredProps>(
+    component: Component<P & InjectableFilterAPI>
+): Component<P> {
+    const StoreInjector = withInjectedStore(component);
+
+    return function FilterAPIProvider(props) {
+        const api = useStoreProvider(props);
+
+        if (api.hasError) {
+            return <Alert bootstrapStyle="danger">{api.error.message}</Alert>;
+        }
+
+        return <StoreInjector {...props} {...api.value} />;
+    };
+}
+
+function withInjectedStore<P extends object>(
+    Component: Component<P & InjectableFilterAPI>
+): Component<P & { provider: StoreProvider; channel: string }> {
+    return function StoreInjector(props) {
+        const provider = useSetup(() => props.provider);
+        return <Component {...props} filterStore={provider.store} parentChannelName={props.channel} />;
+    };
+}
+
+interface InjectableFilterAPI {
+    filterStore: String_InputFilterInterface;
+    parentChannelName?: string;
+}
+
+function useStoreProvider(props: RequiredProps): Result<{ provider: StoreProvider; channel: string }, APIError> {
+    const filterAPI = useFilterAPI();
+    return useConst(() => {
+        if (filterAPI.hasError) {
+            return error(filterAPI.error);
+        }
+
+        return value({
+            provider: new StringStoreProvider(filterAPI.value, {
+                attributes: props.attributes.map(obj => obj.attribute),
+                dataKey: props.name
+            }),
+            channel: filterAPI.value.parentChannelName
+        });
+    });
+}
diff --git a/packages/pluggableWidgets/datagrid-text-filter-web/src/hocs/withTextFilterAPI.tsx b/packages/pluggableWidgets/datagrid-text-filter-web/src/hocs/withTextFilterAPI.tsx
index c54deea135..726f91c054 100644
--- a/packages/pluggableWidgets/datagrid-text-filter-web/src/hocs/withTextFilterAPI.tsx
+++ b/packages/pluggableWidgets/datagrid-text-filter-web/src/hocs/withTextFilterAPI.tsx
@@ -6,7 +6,7 @@ export function withTextFilterAPI<P extends object>(
     Component: (props: P & String_FilterAPIv2) => React.ReactElement
 ): (props: P) => React.ReactElement {
     return function FilterAPIProvider(props) {
-        const api = useStringFilterAPI("");
+        const api = useStringFilterAPI();
 
         if (api.hasError) {
             return <Alert bootstrapStyle="danger">{api.error.message}</Alert>;
diff --git a/packages/pluggableWidgets/datagrid-text-filter-web/typings/DatagridTextFilterProps.d.ts b/packages/pluggableWidgets/datagrid-text-filter-web/typings/DatagridTextFilterProps.d.ts
index dfd4193e6a..170eb35aa8 100644
--- a/packages/pluggableWidgets/datagrid-text-filter-web/typings/DatagridTextFilterProps.d.ts
+++ b/packages/pluggableWidgets/datagrid-text-filter-web/typings/DatagridTextFilterProps.d.ts
@@ -4,16 +4,27 @@
  * @author Mendix Widgets Framework Team
  */
 import { CSSProperties } from "react";
-import { ActionValue, DynamicValue, EditableValue } from "mendix";
+import { ActionValue, AttributeMetaData, DynamicValue, EditableValue } from "mendix";
+
+export type AttrChoiceEnum = "auto" | "linked";
+
+export interface AttributesType {
+    attribute: AttributeMetaData<string>;
+}
 
 export type DefaultFilterEnum = "contains" | "startsWith" | "endsWith" | "greater" | "greaterEqual" | "equal" | "notEqual" | "smaller" | "smallerEqual" | "empty" | "notEmpty";
 
+export interface AttributesPreviewType {
+    attribute: string;
+}
+
 export interface DatagridTextFilterContainerProps {
     name: string;
     class: string;
     style?: CSSProperties;
     tabIndex?: number;
-    advanced: boolean;
+    attrChoice: AttrChoiceEnum;
+    attributes: AttributesType[];
     defaultValue?: DynamicValue<string>;
     defaultFilter: DefaultFilterEnum;
     placeholder?: DynamicValue<string>;
@@ -36,7 +47,8 @@ export interface DatagridTextFilterPreviewProps {
     readOnly: boolean;
     renderMode: "design" | "xray" | "structure";
     translate: (text: string) => string;
-    advanced: boolean;
+    attrChoice: AttrChoiceEnum;
+    attributes: AttributesPreviewType[];
     defaultValue: string;
     defaultFilter: DefaultFilterEnum;
     placeholder: string;
diff --git a/packages/pluggableWidgets/datagrid-web/e2e/DataGrid.spec.js b/packages/pluggableWidgets/datagrid-web/e2e/DataGrid.spec.js
index 5402ff52cd..321616b7d9 100644
--- a/packages/pluggableWidgets/datagrid-web/e2e/DataGrid.spec.js
+++ b/packages/pluggableWidgets/datagrid-web/e2e/DataGrid.spec.js
@@ -113,14 +113,14 @@ test.describe("capabilities: hiding", () => {
         const textAreaValue = await textArea.inputValue();
         expect(JSON.parse(textAreaValue)).toEqual({
             name: "datagrid5",
-            schemaVersion: 2,
+            schemaVersion: 3,
             settingsHash: "1530160614",
             columns: [
                 { columnId: "0", hidden: true },
                 { columnId: "1", hidden: false }
             ],
             columnFilters: [],
-            groupFilters: [],
+            customFilters: [],
             sortOrder: [],
             columnOrder: ["0", "1"]
         });
diff --git a/packages/pluggableWidgets/datagrid-web/src/controllers/StateSyncController.ts b/packages/pluggableWidgets/datagrid-web/src/controllers/DatasourceParamsController.ts
similarity index 65%
rename from packages/pluggableWidgets/datagrid-web/src/controllers/StateSyncController.ts
rename to packages/pluggableWidgets/datagrid-web/src/controllers/DatasourceParamsController.ts
index 397c73e3c6..9a79deb4e0 100644
--- a/packages/pluggableWidgets/datagrid-web/src/controllers/StateSyncController.ts
+++ b/packages/pluggableWidgets/datagrid-web/src/controllers/DatasourceParamsController.ts
@@ -12,34 +12,41 @@ interface Columns {
     sortInstructions: SortInstruction[] | undefined;
 }
 
-interface Header {
+interface FiltersInput {
     conditions: Array<FilterCondition | undefined>;
 }
 
-type StateSyncControllerSpec = {
+type DatasourceParamsControllerSpec = {
     query: QueryController;
     columns: Columns;
-    header: Header;
+    header: FiltersInput;
+    customFilters: FiltersInput;
 };
 
-export class StateSyncController implements ReactiveController {
+export class DatasourceParamsController implements ReactiveController {
     private columns: Columns;
-    private header: Header;
+    private header: FiltersInput;
     private query: QueryController;
+    private customFilters: FiltersInput;
 
-    constructor(host: ReactiveControllerHost, spec: StateSyncControllerSpec) {
+    constructor(host: ReactiveControllerHost, spec: DatasourceParamsControllerSpec) {
         host.addController(this);
         this.columns = spec.columns;
         this.header = spec.header;
         this.query = spec.query;
+        this.customFilters = spec.customFilters;
 
         makeAutoObservable(this, { setup: false });
     }
 
     private get derivedFilter(): FilterCondition | undefined {
-        const { columns, header } = this;
+        const { columns, header, customFilters } = this;
 
-        return and(compactArray(columns.conditions), compactArray(header.conditions));
+        return and(
+            compactArray(columns.conditions),
+            compactArray(header.conditions),
+            compactArray(customFilters.conditions)
+        );
     }
 
     private get derivedSortOrder(): SortInstruction[] | undefined {
@@ -68,17 +75,22 @@ export class StateSyncController implements ReactiveController {
 
     static unzipFilter(
         filter?: FilterCondition
-    ): [columns: Array<FilterCondition | undefined>, header: Array<FilterCondition | undefined>] {
+    ): [
+        columns: Array<FilterCondition | undefined>,
+        header: Array<FilterCondition | undefined>,
+        sharedFilter: Array<FilterCondition | undefined>
+    ] {
         if (!filter) {
-            return [[], []];
+            return [[], [], []];
         }
         if (!isAnd(filter)) {
-            return [[], []];
+            return [[], [], []];
         }
-        if (filter.args.length !== 2) {
-            return [[], []];
+        if (filter.args.length !== 3) {
+            return [[], [], []];
         }
-        const [columns, header] = filter.args;
-        return [fromCompactArray(columns), fromCompactArray(header)];
+
+        const [columns, header, shared] = filter.args;
+        return [fromCompactArray(columns), fromCompactArray(header), fromCompactArray(shared)];
     }
 }
diff --git a/packages/pluggableWidgets/datagrid-web/src/helpers/state/ColumnGroupStore.ts b/packages/pluggableWidgets/datagrid-web/src/helpers/state/ColumnGroupStore.ts
index efc8dd67ad..ae76ecf01b 100644
--- a/packages/pluggableWidgets/datagrid-web/src/helpers/state/ColumnGroupStore.ts
+++ b/packages/pluggableWidgets/datagrid-web/src/helpers/state/ColumnGroupStore.ts
@@ -13,7 +13,7 @@ import {
     sortInstructionsToSortRules,
     sortRulesToSortInstructions
 } from "./ColumnsSortingStore";
-import { ColumnFilterStore } from "./column/ColumnFilterStore";
+import { ColumnFilterStore, ObserverBag } from "./column/ColumnFilterStore";
 import { ColumnStore } from "./column/ColumnStore";
 
 export interface IColumnGroupStore {
@@ -46,17 +46,18 @@ export class ColumnGroupStore implements IColumnGroupStore, IColumnParentStore {
     constructor(
         props: Pick<DatagridContainerProps, "columns" | "datasource">,
         info: StaticInfo,
-        dsViewState: Array<FilterCondition | undefined> | null
+        initFilter: Array<FilterCondition | undefined>,
+        observerBag: ObserverBag
     ) {
         this._allColumns = [];
         this.columnFilters = [];
 
         props.columns.forEach((columnProps, i) => {
-            const initCond = dsViewState?.at(i) ?? null;
+            const initCond = initFilter.at(i) ?? null;
             const column = new ColumnStore(i, columnProps, this);
             this._allColumnsById.set(column.columnId, column);
             this._allColumns[i] = column;
-            this.columnFilters[i] = new ColumnFilterStore(columnProps, info, initCond);
+            this.columnFilters[i] = new ColumnFilterStore(columnProps, info, initCond, observerBag);
         });
 
         this.sorting = new ColumnsSortingStore(
diff --git a/packages/pluggableWidgets/datagrid-web/src/helpers/state/GridPersonalizationStore.ts b/packages/pluggableWidgets/datagrid-web/src/helpers/state/GridPersonalizationStore.ts
index 21c8024aca..c9a062b6ac 100644
--- a/packages/pluggableWidgets/datagrid-web/src/helpers/state/GridPersonalizationStore.ts
+++ b/packages/pluggableWidgets/datagrid-web/src/helpers/state/GridPersonalizationStore.ts
@@ -1,5 +1,5 @@
 import { error, Result, value } from "@mendix/widget-plugin-filtering/result-meta";
-import { HeaderFiltersStore } from "@mendix/widget-plugin-filtering/stores/generic/HeaderFiltersStore";
+import { FilterObserver } from "@mendix/widget-plugin-filtering/typings/FilterObserver";
 import { FiltersSettingsMap } from "@mendix/widget-plugin-filtering/typings/settings";
 import { action, comparer, computed, IReactionDisposer, makeObservable, reaction } from "mobx";
 import { DatagridContainerProps } from "../../../typings/DatagridProps";
@@ -17,7 +17,7 @@ import { ColumnGroupStore } from "./ColumnGroupStore";
 export class GridPersonalizationStore {
     private readonly gridName: string;
     private readonly gridColumnsHash: string;
-    private readonly schemaVersion: GridPersonalizationStorageSettings["schemaVersion"] = 2;
+    private readonly schemaVersion: GridPersonalizationStorageSettings["schemaVersion"] = 3;
     private readonly storeFilters: boolean;
 
     private storage: PersonalizationStorage;
@@ -27,7 +27,7 @@ export class GridPersonalizationStore {
     constructor(
         props: DatagridContainerProps,
         private columnsStore: ColumnGroupStore,
-        private headerFilters: HeaderFiltersStore
+        private customFilters: FilterObserver
     ) {
         this.gridName = props.name;
         this.gridColumnsHash = getHash(this.columnsStore._allColumns, this.gridName);
@@ -35,7 +35,6 @@ export class GridPersonalizationStore {
 
         makeObservable<GridPersonalizationStore, "applySettings">(this, {
             settings: computed,
-
             applySettings: action
         });
 
@@ -95,6 +94,7 @@ export class GridPersonalizationStore {
     private applySettings(settings: GridPersonalizationStorageSettings): void {
         this.columnsStore.setColumnSettings(toColumnSettings(settings));
         this.columnsStore.setColumnFilterSettings(settings.columnFilters);
+        this.customFilters.settings = new Map(settings.customFilters);
     }
 
     private readSettings(
@@ -137,7 +137,7 @@ export class GridPersonalizationStore {
             this.gridColumnsHash,
             this.columnsStore.columnSettings,
             this.storeFilters ? this.columnsStore.filterSettings : new Map(),
-            this.storeFilters ? this.headerFilters.settings : new Map()
+            this.storeFilters ? this.customFilters.settings : new Map()
         );
     }
 }
@@ -164,7 +164,7 @@ function toStorageFormat(
     gridColumnsHash: string,
     columnsSettings: ColumnPersonalizationSettings[],
     columnFilters: FiltersSettingsMap<ColumnId>,
-    groupFilters: FiltersSettingsMap<string>
+    customFilters: FiltersSettingsMap<string>
 ): GridPersonalizationStorageSettings {
     const sortOrder = columnsSettings
         .filter(c => c.sortDir && c.sortWeight !== undefined)
@@ -175,7 +175,7 @@ function toStorageFormat(
 
     return {
         name: gridName,
-        schemaVersion: 2,
+        schemaVersion: 3,
         settingsHash: gridColumnsHash,
         columns: columnsSettings.map(c => ({
             columnId: c.columnId,
@@ -185,7 +185,7 @@ function toStorageFormat(
         })),
 
         columnFilters: Array.from(columnFilters),
-        groupFilters: Array.from(groupFilters),
+        customFilters: Array.from(customFilters),
 
         sortOrder,
         columnOrder
diff --git a/packages/pluggableWidgets/datagrid-web/src/helpers/state/RootGridStore.ts b/packages/pluggableWidgets/datagrid-web/src/helpers/state/RootGridStore.ts
index a98cbf7eff..1e03c6b941 100644
--- a/packages/pluggableWidgets/datagrid-web/src/helpers/state/RootGridStore.ts
+++ b/packages/pluggableWidgets/datagrid-web/src/helpers/state/RootGridStore.ts
@@ -1,3 +1,4 @@
+import { CustomFilterHost } from "@mendix/widget-plugin-filtering/stores/generic/CustomFilterHost";
 import { HeaderFiltersStore } from "@mendix/widget-plugin-filtering/stores/generic/HeaderFiltersStore";
 import { BaseControllerHost } from "@mendix/widget-plugin-mobx-kit/BaseControllerHost";
 import { disposeBatch } from "@mendix/widget-plugin-mobx-kit/disposeBatch";
@@ -6,10 +7,10 @@ import { generateUUID } from "@mendix/widget-plugin-platform/framework/generate-
 import { autorun, computed } from "mobx";
 import { DatagridContainerProps } from "../../../typings/DatagridProps";
 import { DatasourceController } from "../../controllers/DatasourceController";
+import { DatasourceParamsController } from "../../controllers/DatasourceParamsController";
 import { DerivedLoaderController } from "../../controllers/DerivedLoaderController";
 import { PaginationController } from "../../controllers/PaginationController";
 import { RefreshController } from "../../controllers/RefreshController";
-import { StateSyncController } from "../../controllers/StateSyncController";
 import { ProgressStore } from "../../features/data-export/ProgressStore";
 import { StaticInfo } from "../../typings/static-info";
 import { ColumnGroupStore } from "./ColumnGroupStore";
@@ -37,24 +38,37 @@ export class RootGridStore extends BaseControllerHost {
         super();
 
         const { props } = gate;
-        const [columnsViewState, headerViewState] = StateSyncController.unzipFilter(props.datasource.filter);
+        const [columnsInitFilter, headerInitFilter, sharedInitFilter] = DatasourceParamsController.unzipFilter(
+            props.datasource.filter
+        );
 
         this.gate = gate;
         this.staticInfo = {
             name: props.name,
             filtersChannelName: `datagrid/${generateUUID()}`
         };
+        const customFilterHost = new CustomFilterHost();
         const query = new DatasourceController(this, { gate });
-        const columns = (this.columnsStore = new ColumnGroupStore(props, this.staticInfo, columnsViewState));
-        const header = (this.headerFiltersStore = new HeaderFiltersStore(props, this.staticInfo, headerViewState));
-        this.settingsStore = new GridPersonalizationStore(props, this.columnsStore, this.headerFiltersStore);
+        const columns = (this.columnsStore = new ColumnGroupStore(props, this.staticInfo, columnsInitFilter, {
+            customFilterHost,
+            sharedInitFilter
+        }));
+        const header = (this.headerFiltersStore = new HeaderFiltersStore({
+            filterList: props.filterList,
+            filterChannelName: this.staticInfo.filtersChannelName,
+            headerInitFilter,
+            sharedInitFilter,
+            customFilterHost
+        }));
+        this.settingsStore = new GridPersonalizationStore(props, this.columnsStore, customFilterHost);
         this.paginationCtrl = new PaginationController(this, { gate, query });
         this.exportProgressCtrl = exportCtrl;
 
-        new StateSyncController(this, {
+        new DatasourceParamsController(this, {
             query,
             columns,
-            header
+            header,
+            customFilters: customFilterHost
         });
 
         new RefreshController(this, {
@@ -73,7 +87,7 @@ export class RootGridStore extends BaseControllerHost {
         const [add, disposeAll] = disposeBatch();
         add(super.setup());
         add(this.columnsStore.setup());
-        add(this.headerFiltersStore.setup() ?? (() => {}));
+        add(this.headerFiltersStore.setup());
         add(() => this.settingsStore.dispose());
         add(autorun(() => this.updateProps(this.gate.props)));
 
diff --git a/packages/pluggableWidgets/datagrid-web/src/helpers/state/column/ColumnFilterStore.tsx b/packages/pluggableWidgets/datagrid-web/src/helpers/state/column/ColumnFilterStore.tsx
index 9d0d9ae7cb..cf930ae8a3 100644
--- a/packages/pluggableWidgets/datagrid-web/src/helpers/state/column/ColumnFilterStore.tsx
+++ b/packages/pluggableWidgets/datagrid-web/src/helpers/state/column/ColumnFilterStore.tsx
@@ -1,4 +1,4 @@
-import { FilterAPIv2, getGlobalFilterContextObject } from "@mendix/widget-plugin-filtering/context";
+import { FilterAPI, getGlobalFilterContextObject } from "@mendix/widget-plugin-filtering/context";
 import { RefFilterStore, RefFilterStoreProps } from "@mendix/widget-plugin-filtering/stores/picker/RefFilterStore";
 import { StaticSelectFilterStore } from "@mendix/widget-plugin-filtering/stores/picker/StaticSelectFilterStore";
 import { InputFilterStore, attrgroupFilterStore } from "@mendix/widget-plugin-filtering/stores/input/store-utils";
@@ -12,6 +12,7 @@ import { StaticInfo } from "../../../typings/static-info";
 import { FilterData } from "@mendix/widget-plugin-filtering/typings/settings";
 import { value } from "@mendix/widget-plugin-filtering/result-meta";
 import { disposeFx } from "@mendix/widget-plugin-filtering/mobx-utils";
+import { FilterObserver } from "@mendix/widget-plugin-filtering/typings/FilterObserver";
 export interface IColumnFilterStore {
     renderFilterWidgets(): ReactNode;
 }
@@ -23,9 +24,11 @@ const { Provider } = getGlobalFilterContextObject();
 export class ColumnFilterStore implements IColumnFilterStore {
     private _widget: ReactNode;
     private _filterStore: FilterStore | null = null;
-    private _context: FilterAPIv2;
+    private _context: FilterAPI;
+    private _observerBag: ObserverBag;
 
-    constructor(props: ColumnsType, info: StaticInfo, dsViewState: FilterCondition | null) {
+    constructor(props: ColumnsType, info: StaticInfo, dsViewState: FilterCondition | null, observerBag: ObserverBag) {
+        this._observerBag = observerBag;
         this._widget = props.filter;
         this._filterStore = this.createFilterStore(props, dsViewState);
         this._context = this.createContext(this._filterStore, info);
@@ -92,14 +95,16 @@ export class ColumnFilterStore implements IColumnFilterStore {
         return null;
     }
 
-    private createContext(store: FilterStore | null, info: StaticInfo): FilterAPIv2 {
+    private createContext(store: FilterStore | null, info: StaticInfo): FilterAPI {
         return {
-            version: 2,
+            version: 3,
             parentChannelName: info.filtersChannelName,
             provider: value({
                 type: "direct",
                 store
-            })
+            }),
+            filterObserver: this._observerBag.customFilterHost,
+            sharedInitFilter: this._observerBag.sharedInitFilter
         };
     }
 
@@ -132,3 +137,8 @@ const isListAttributeValue = (
 
 const errorMessage = (propName: string): string =>
     `Can't map ColumnsType to AssociationProperties: ${propName} is undefined`;
+
+export interface ObserverBag {
+    customFilterHost: FilterObserver;
+    sharedInitFilter: Array<FilterCondition | undefined>;
+}
diff --git a/packages/pluggableWidgets/datagrid-web/src/typings/personalization-settings.ts b/packages/pluggableWidgets/datagrid-web/src/typings/personalization-settings.ts
index f51a4f9b4e..6c64391741 100644
--- a/packages/pluggableWidgets/datagrid-web/src/typings/personalization-settings.ts
+++ b/packages/pluggableWidgets/datagrid-web/src/typings/personalization-settings.ts
@@ -19,14 +19,14 @@ interface ColumnPersonalizationStorageSettings {
 
 export type ColumnFilterSettings = Array<[key: ColumnId, data: FilterData]>;
 
-export type GroupFilterSettings = Array<[key: string, data: FilterData]>;
+export type CustomFilterSettings = Array<[key: string, data: FilterData]>;
 
 export interface GridPersonalizationStorageSettings {
     name: string;
-    schemaVersion: 2;
+    schemaVersion: 3;
     settingsHash: string;
     columns: ColumnPersonalizationStorageSettings[];
-    groupFilters: GroupFilterSettings;
+    customFilters: CustomFilterSettings;
     columnFilters: ColumnFilterSettings;
     columnOrder: ColumnId[];
     sortOrder: SortRule[];
diff --git a/packages/pluggableWidgets/dropdown-sort-web/src/components/__test__/DropdownSort.spec.tsx b/packages/pluggableWidgets/dropdown-sort-web/src/components/__test__/DropdownSort.spec.tsx
index 099b39208f..bd7ead1a62 100644
--- a/packages/pluggableWidgets/dropdown-sort-web/src/components/__test__/DropdownSort.spec.tsx
+++ b/packages/pluggableWidgets/dropdown-sort-web/src/components/__test__/DropdownSort.spec.tsx
@@ -1,7 +1,7 @@
-import { FilterAPIv2 } from "@mendix/widget-plugin-filtering/context";
+import { FilterAPI } from "@mendix/widget-plugin-filtering/context";
 import {
     HeaderFiltersStore,
-    HeaderFiltersStoreProps
+    HeaderFiltersStoreSpec
 } from "@mendix/widget-plugin-filtering/stores/generic/HeaderFiltersStore";
 import { SortAPI } from "@mendix/widget-plugin-sorting/context";
 import { SortAPIProvider, SortListType } from "@mendix/widget-plugin-sorting/providers/SortAPIProvider";
@@ -12,6 +12,7 @@ import { ListValue } from "mendix";
 import { createContext, createElement } from "react";
 import { DropdownSortContainerProps } from "../../../typings/DropdownSortProps";
 import { DropdownSort } from "../../DropdownSort";
+import { FilterObserver } from "@mendix/widget-plugin-filtering/typings/FilterObserver";
 
 const commonProps: DropdownSortContainerProps = {
     class: "filter-custom-class",
@@ -19,22 +20,16 @@ const commonProps: DropdownSortContainerProps = {
     name: "filter-test"
 };
 
-interface StaticInfo {
-    name: string;
-    filtersChannelName: string;
-}
-
-const headerFilterStoreInfo: StaticInfo = {
-    name: commonProps.name,
-    filtersChannelName: ""
+const spec: HeaderFiltersStoreSpec = {
+    filterList: [],
+    sharedInitFilter: [],
+    headerInitFilter: [],
+    filterChannelName: "datagrid",
+    customFilterHost: {} as FilterObserver
 };
 
-// CONTEXT
-const props: HeaderFiltersStoreProps = {
-    filterList: []
-};
-const headerFilterStore = new HeaderFiltersStore(props, headerFilterStoreInfo, null);
-(window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext<FilterAPIv2>(
+const headerFilterStore = new HeaderFiltersStore(spec);
+(window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext<FilterAPI>(
     headerFilterStore.context
 );
 
diff --git a/packages/pluggableWidgets/gallery-web/src/stores/RootGalleryStore.ts b/packages/pluggableWidgets/gallery-web/src/stores/RootGalleryStore.ts
index 0f9bf1ebf6..24aa253be7 100644
--- a/packages/pluggableWidgets/gallery-web/src/stores/RootGalleryStore.ts
+++ b/packages/pluggableWidgets/gallery-web/src/stores/RootGalleryStore.ts
@@ -1,4 +1,5 @@
 import { compactArray, fromCompactArray } from "@mendix/widget-plugin-filtering/condition-utils";
+import { CustomFilterHost } from "@mendix/widget-plugin-filtering/stores/generic/CustomFilterHost";
 import { HeaderFiltersStore } from "@mendix/widget-plugin-filtering/stores/generic/HeaderFiltersStore";
 import { generateUUID } from "@mendix/widget-plugin-platform/framework/generate-uuid";
 import { SortAPIProvider } from "@mendix/widget-plugin-sorting/providers/SortAPIProvider";
@@ -25,8 +26,13 @@ export class RootGalleryStore {
             filtersChannelName: `datagrid/${generateUUID()}`
         };
 
-        const headerViewState = this.getDsViewState(props);
-        this.headerFiltersStore = new HeaderFiltersStore(props, this.staticInfo, headerViewState);
+        this.headerFiltersStore = new HeaderFiltersStore({
+            filterList: props.filterList,
+            filterChannelName: this.staticInfo.filtersChannelName,
+            headerInitFilter: this.getDsViewState(props),
+            sharedInitFilter: [],
+            customFilterHost: new CustomFilterHost()
+        });
         this.sortProvider = new SortAPIProvider(props);
     }
 
diff --git a/packages/shared/widget-plugin-filtering/package.json b/packages/shared/widget-plugin-filtering/package.json
index 7a31cc4efa..e92778dc25 100644
--- a/packages/shared/widget-plugin-filtering/package.json
+++ b/packages/shared/widget-plugin-filtering/package.json
@@ -36,6 +36,7 @@
         "@floating-ui/react-dom": "^2.1.2",
         "@mendix/widget-plugin-external-events": "workspace:*",
         "@mendix/widget-plugin-hooks": "workspace:*",
+        "@mendix/widget-plugin-mobx-kit": "workspace:^",
         "@mendix/widget-plugin-platform": "workspace:*",
         "downshift": "^9.0.8",
         "mendix": "^10.16.49747",
diff --git a/packages/shared/widget-plugin-filtering/src/__tests__/condition-utils.spec.ts b/packages/shared/widget-plugin-filtering/src/__tests__/condition-utils.spec.ts
index 5e2484ec08..39e062ec82 100644
--- a/packages/shared/widget-plugin-filtering/src/__tests__/condition-utils.spec.ts
+++ b/packages/shared/widget-plugin-filtering/src/__tests__/condition-utils.spec.ts
@@ -1,27 +1,33 @@
 jest.mock("mendix/filters/builders");
 
+import { AndCondition } from "mendix/filters";
 import { equals, literal } from "mendix/filters/builders";
 import { compactArray, fromCompactArray, tag } from "../condition-utils";
-import { AndCondition } from "mendix/filters";
 
 describe("condition-utils", () => {
     describe("compactArray", () => {
-        it("returns 'and' condition for zero array", () => {
+        it("returns 'tag' condition for zero array", () => {
             const result = compactArray([]);
-            expect(result).toMatchObject({ name: "and", type: "function" });
-            expect((result as AndCondition).args).toHaveLength(2);
+            expect(result).toMatchObject({
+                name: "!=",
+                type: "function",
+                arg1: { value: "[0,[]]", valueType: "String" }
+            });
         });
 
-        it("returns 'and' condition for empty array", () => {
+        it("returns 'tag' condition for array of undefined", () => {
             const result = compactArray([undefined, undefined, undefined]);
-            expect(result).toMatchObject({ name: "and", type: "function" });
-            expect((result as AndCondition).args).toHaveLength(2);
+            expect(result).toMatchObject({
+                name: "!=",
+                type: "function",
+                arg1: { value: "[3,[]]", valueType: "String" }
+            });
         });
 
-        it("returns 'and' condition with 4 args", () => {
+        it("returns 'and' condition with 3 args", () => {
             const result = compactArray([tag("0"), undefined, tag("2")]);
             expect(result).toMatchObject({ name: "and", type: "function" });
-            expect((result as AndCondition).args).toHaveLength(4);
+            expect((result as AndCondition).args).toHaveLength(3);
         });
     });
 
diff --git a/packages/shared/widget-plugin-filtering/src/condition-utils.ts b/packages/shared/widget-plugin-filtering/src/condition-utils.ts
index 7f14ecbdc4..976da416b5 100644
--- a/packages/shared/widget-plugin-filtering/src/condition-utils.ts
+++ b/packages/shared/widget-plugin-filtering/src/condition-utils.ts
@@ -1,12 +1,12 @@
 import {
-    FilterCondition,
     AndCondition,
-    OrCondition,
-    LiteralExpression,
     ContainsCondition,
-    EqualsCondition
+    EqualsCondition,
+    FilterCondition,
+    LiteralExpression,
+    OrCondition
 } from "mendix/filters";
-import { equals, literal, and } from "mendix/filters/builders";
+import { and, literal, notEqual } from "mendix/filters/builders";
 
 type BinaryExpression<T = FilterCondition> = T extends { arg1: unknown; arg2: object } ? T : never;
 type Func<T> = T extends { name: infer Fn } ? Fn : never;
@@ -40,25 +40,33 @@ interface TagName {
     readonly valueType: "string";
 }
 
+const MARKER = "#";
+
+interface TagMarker {
+    readonly type: "literal";
+    readonly value: typeof MARKER;
+    readonly valueType: "string";
+}
+
 interface TagCond {
     readonly type: "function";
-    readonly name: "=";
+    readonly name: "!=";
     readonly arg1: TagName;
-    readonly arg2: TagName;
+    readonly arg2: TagMarker;
 }
 
 export function tag(name: string): TagCond {
-    return equals(literal(name), literal(name)) as TagCond;
+    return notEqual(literal(name), literal(MARKER)) as TagCond;
 }
 
 export function isTag(cond: FilterCondition): cond is TagCond {
     return (
-        cond.name === "=" &&
+        cond.name === "!=" &&
         cond.arg1.type === "literal" &&
         cond.arg2.type === "literal" &&
         /string/i.test(cond.arg1.valueType) &&
         /string/i.test(cond.arg2.valueType) &&
-        cond.arg1.value === cond.arg2.value
+        cond.arg2.value === MARKER
     );
 }
 
@@ -88,20 +96,19 @@ function shrink<T>(array: Array<T | undefined>): [indexes: number[], items: T[]]
 
 export function compactArray(input: Array<FilterCondition | undefined>): FilterCondition {
     const [indexes, items] = shrink(input);
-    const arrayMeta = [input.length, indexes] as const;
-    const metaTag = tag(arrayTag(arrayMeta));
-    // As 'and' requires at least 2 args, we add a placeholder
-    const placeholder = tag("_");
-    return and(metaTag, placeholder, ...items);
+    const metaTag = tag(arrayTag([input.length, indexes] as const));
+
+    if (items.length === 0) {
+        return metaTag;
+    }
+
+    return and(metaTag, ...items);
 }
 
 export function fromCompactArray(cond: FilterCondition): Array<FilterCondition | undefined> {
-    if (!isAnd(cond)) {
-        return [];
-    }
+    const tag = isAnd(cond) ? cond.args[0] : cond;
 
-    const [metaTag] = cond.args;
-    const arrayMeta = isTag(metaTag) ? fromArrayTag(metaTag.arg1.value) : undefined;
+    const arrayMeta = isTag(tag) ? fromArrayTag(tag.arg1.value) : undefined;
 
     if (!arrayMeta) {
         return [];
@@ -109,7 +116,12 @@ export function fromCompactArray(cond: FilterCondition): Array<FilterCondition |
 
     const [length, indexes] = arrayMeta;
     const arr: Array<FilterCondition | undefined> = Array(length).fill(undefined);
-    cond.args.slice(2).forEach((cond, i) => {
+
+    if (!isAnd(cond)) {
+        return arr;
+    }
+
+    cond.args.slice(1).forEach((cond, i) => {
         arr[indexes[i]] = cond;
     });
 
diff --git a/packages/shared/widget-plugin-filtering/src/context.ts b/packages/shared/widget-plugin-filtering/src/context.ts
index 2a44be0ed0..e34c9f5375 100644
--- a/packages/shared/widget-plugin-filtering/src/context.ts
+++ b/packages/shared/widget-plugin-filtering/src/context.ts
@@ -1,13 +1,17 @@
+import { FilterCondition } from "mendix/filters/index.js";
 import { Context, createContext, useContext } from "react";
 import { APIError, ENOCONTEXT } from "./errors.js";
 import { Result, error, value } from "./result-meta.js";
+import { FilterObserver } from "./typings/FilterObserver.js";
 import { InputFilterInterface } from "./typings/InputFilterInterface.js";
 import { PickerFilterStore } from "./typings/PickerFilterStore.js";
 
-export interface FilterAPIv2 {
-    version: 2;
+export interface FilterAPI {
+    version: 3;
     parentChannelName: string;
     provider: Result<FilterStoreProvider, APIError>;
+    filterObserver: FilterObserver;
+    sharedInitFilter: Array<FilterCondition | undefined>;
 }
 
 /** @deprecated */
@@ -18,7 +22,7 @@ export enum FilterType {
     DATE = "date"
 }
 
-export type FilterStoreProvider = DirectProvider | KeyProvider | LegacyProvider;
+export type FilterStoreProvider = DirectProvider | LegacyProvider;
 
 export type FilterStore = InputFilterInterface | PickerFilterStore;
 
@@ -27,32 +31,27 @@ interface DirectProvider {
     store: FilterStore | null;
 }
 
-export interface KeyProvider {
-    type: "key-value";
-    get: (key: string) => FilterStore | null;
-}
-
 /** @deprecated */
 export interface LegacyProvider {
     type: "legacy";
     get: (type: FilterType) => FilterStore | null;
 }
 
-type Context_v2 = Context<FilterAPIv2 | null>;
+type FilterAPIContext = Context<FilterAPI | null>;
 
 const CONTEXT_OBJECT_PATH = "com.mendix.widgets.web.filterable.filterContext.v2" as const;
 
 declare global {
     interface Window {
-        [CONTEXT_OBJECT_PATH]: Context_v2 | undefined;
+        [CONTEXT_OBJECT_PATH]: FilterAPIContext | undefined;
     }
 }
 
-export function getGlobalFilterContextObject(): Context_v2 {
-    return (window[CONTEXT_OBJECT_PATH] ??= createContext<FilterAPIv2 | null>(null));
+export function getGlobalFilterContextObject(): FilterAPIContext {
+    return (window[CONTEXT_OBJECT_PATH] ??= createContext<FilterAPI | null>(null));
 }
 
-export function useFilterContextValue(): Result<FilterAPIv2, APIError> {
+export function useFilterAPI(): Result<FilterAPI, APIError> {
     const context = getGlobalFilterContextObject();
     const contextValue = useContext(context);
 
@@ -63,12 +62,13 @@ export function useFilterContextValue(): Result<FilterAPIv2, APIError> {
     return value(contextValue);
 }
 
-export function getFilterStore(provider: FilterStoreProvider, legacyType: FilterType, key: string): FilterStore | null {
+/** @deprecated This hook is renamed, use `useFilterAPI` instead. */
+export const useFilterContextValue = useFilterAPI;
+
+export function getFilterStore(provider: FilterStoreProvider, legacyType: FilterType): FilterStore | null {
     switch (provider.type) {
         case "direct":
             return provider.store;
-        case "key-value":
-            return provider.get(key);
         case "legacy":
             return provider.get(legacyType);
         default:
diff --git a/packages/shared/widget-plugin-filtering/src/custom-filter-api/BaseStoreProvider.ts b/packages/shared/widget-plugin-filtering/src/custom-filter-api/BaseStoreProvider.ts
new file mode 100644
index 0000000000..883290b619
--- /dev/null
+++ b/packages/shared/widget-plugin-filtering/src/custom-filter-api/BaseStoreProvider.ts
@@ -0,0 +1,32 @@
+import { disposeBatch } from "@mendix/widget-plugin-mobx-kit/disposeBatch";
+import { ISetupable } from "@mendix/widget-plugin-mobx-kit/setupable";
+import { FilterCondition } from "mendix/filters";
+import { isAnd, isTag } from "../condition-utils";
+import { FilterAPI } from "../context";
+import { Filter } from "../typings/FilterObserver";
+
+export abstract class BaseStoreProvider<S extends Filter> implements ISetupable {
+    protected abstract _store: S;
+    protected abstract filterAPI: FilterAPI;
+    abstract readonly dataKey: string;
+
+    protected findInitFilter(conditions: Array<FilterCondition | undefined>, key: string): FilterCondition | null {
+        for (const cond of conditions) {
+            if (cond && isAnd(cond)) {
+                const [tag, initFilter] = cond.args;
+                if (isTag(tag) && tag.arg1.value === key) {
+                    return initFilter;
+                }
+            }
+        }
+        return null;
+    }
+
+    setup(): () => void {
+        const [add, disposeAll] = disposeBatch();
+        this.filterAPI.filterObserver.observe(this.dataKey, this._store);
+        add(() => this.filterAPI.filterObserver.unobserve(this.dataKey));
+        add(this._store.setup?.());
+        return disposeAll;
+    }
+}
diff --git a/packages/shared/widget-plugin-filtering/src/custom-filter-api/DateStoreProvider.ts b/packages/shared/widget-plugin-filtering/src/custom-filter-api/DateStoreProvider.ts
new file mode 100644
index 0000000000..65cfe580d5
--- /dev/null
+++ b/packages/shared/widget-plugin-filtering/src/custom-filter-api/DateStoreProvider.ts
@@ -0,0 +1,25 @@
+import { FilterAPI } from "../context";
+import { DateInputFilterStore } from "../stores/input/DateInputFilterStore";
+import { Date_InputFilterInterface } from "../typings/InputFilterInterface";
+import { BaseStoreProvider } from "./BaseStoreProvider";
+import { FilterSpec } from "./typings";
+
+export class DateStoreProvider extends BaseStoreProvider<DateInputFilterStore> {
+    protected _store: DateInputFilterStore;
+    protected filterAPI: FilterAPI;
+    readonly dataKey: string;
+
+    constructor(filterAPI: FilterAPI, spec: FilterSpec<Date>) {
+        super();
+        this.filterAPI = filterAPI;
+        this.dataKey = spec.dataKey;
+        this._store = new DateInputFilterStore(
+            spec.attributes,
+            this.findInitFilter(filterAPI.sharedInitFilter, this.dataKey)
+        );
+    }
+
+    get store(): Date_InputFilterInterface {
+        return this._store;
+    }
+}
diff --git a/packages/shared/widget-plugin-filtering/src/custom-filter-api/NumberStoreProvider.ts b/packages/shared/widget-plugin-filtering/src/custom-filter-api/NumberStoreProvider.ts
new file mode 100644
index 0000000000..da7f77e639
--- /dev/null
+++ b/packages/shared/widget-plugin-filtering/src/custom-filter-api/NumberStoreProvider.ts
@@ -0,0 +1,25 @@
+import { FilterAPI } from "../context";
+import { NumberInputFilterStore } from "../stores/input/NumberInputFilterStore";
+import { Number_InputFilterInterface } from "../typings/InputFilterInterface";
+import { BaseStoreProvider } from "./BaseStoreProvider";
+import { FilterSpec } from "./typings";
+
+export class NumberStoreProvider extends BaseStoreProvider<NumberInputFilterStore> {
+    protected _store: NumberInputFilterStore;
+    protected filterAPI: FilterAPI;
+    readonly dataKey: string;
+
+    constructor(filterAPI: FilterAPI, spec: FilterSpec<Big>) {
+        super();
+        this.filterAPI = filterAPI;
+        this.dataKey = spec.dataKey;
+        this._store = new NumberInputFilterStore(
+            spec.attributes,
+            this.findInitFilter(filterAPI.sharedInitFilter, this.dataKey)
+        );
+    }
+
+    get store(): Number_InputFilterInterface {
+        return this._store;
+    }
+}
diff --git a/packages/shared/widget-plugin-filtering/src/custom-filter-api/StringStoreProvider.ts b/packages/shared/widget-plugin-filtering/src/custom-filter-api/StringStoreProvider.ts
new file mode 100644
index 0000000000..b1270ac3e3
--- /dev/null
+++ b/packages/shared/widget-plugin-filtering/src/custom-filter-api/StringStoreProvider.ts
@@ -0,0 +1,25 @@
+import { FilterAPI } from "../context";
+import { StringInputFilterStore } from "../stores/input/StringInputFilterStore";
+import { String_InputFilterInterface } from "../typings/InputFilterInterface";
+import { BaseStoreProvider } from "./BaseStoreProvider";
+import { FilterSpec } from "./typings";
+
+export class StringStoreProvider extends BaseStoreProvider<StringInputFilterStore> {
+    protected _store: StringInputFilterStore;
+    protected filterAPI: FilterAPI;
+    readonly dataKey: string;
+
+    constructor(filterAPI: FilterAPI, spec: FilterSpec<string>) {
+        super();
+        this.filterAPI = filterAPI;
+        this.dataKey = spec.dataKey;
+        this._store = new StringInputFilterStore(
+            spec.attributes,
+            this.findInitFilter(filterAPI.sharedInitFilter, this.dataKey)
+        );
+    }
+
+    get store(): String_InputFilterInterface {
+        return this._store;
+    }
+}
diff --git a/packages/shared/widget-plugin-filtering/src/custom-filter-api/typings.ts b/packages/shared/widget-plugin-filtering/src/custom-filter-api/typings.ts
new file mode 100644
index 0000000000..5cf80885d2
--- /dev/null
+++ b/packages/shared/widget-plugin-filtering/src/custom-filter-api/typings.ts
@@ -0,0 +1,8 @@
+import { AttributeMetaData, EditableValue } from "mendix";
+
+type AttributeValue_2 = EditableValue["value"];
+
+export interface FilterSpec<T extends AttributeValue_2> {
+    attributes: Array<AttributeMetaData<T>>;
+    dataKey: string;
+}
diff --git a/packages/shared/widget-plugin-filtering/src/helpers/useDateFilterAPI.ts b/packages/shared/widget-plugin-filtering/src/helpers/useDateFilterAPI.ts
index d2391337f3..d6cdab88bf 100644
--- a/packages/shared/widget-plugin-filtering/src/helpers/useDateFilterAPI.ts
+++ b/packages/shared/widget-plugin-filtering/src/helpers/useDateFilterAPI.ts
@@ -1,6 +1,6 @@
 import { useRef } from "react";
 import { FilterType, getFilterStore, useFilterContextValue } from "../context";
-import { APIError, EKEYMISSING, EMISSINGSTORE, EStoreTypeMisMatch } from "../errors";
+import { APIError, EMISSINGSTORE, EStoreTypeMisMatch } from "../errors";
 import { error, Result, value } from "../result-meta";
 import { isDateFilter } from "../stores/input/store-utils";
 import { Date_InputFilterInterface } from "../typings/InputFilterInterface";
@@ -10,7 +10,7 @@ export interface Date_FilterAPIv2 {
     parentChannelName?: string;
 }
 
-export function useDateFilterAPI(key: string): Result<Date_FilterAPIv2, APIError> {
+export function useDateFilterAPI(): Result<Date_FilterAPIv2, APIError> {
     const ctx = useFilterContextValue();
     const dateAPI = useRef<Date_FilterAPIv2>();
 
@@ -24,11 +24,7 @@ export function useDateFilterAPI(key: string): Result<Date_FilterAPIv2, APIError
         return error(api.provider.error);
     }
 
-    if (api.provider.value.type === "key-value" && key === "") {
-        return error(EKEYMISSING);
-    }
-
-    const store = getFilterStore(api.provider.value, FilterType.DATE, key);
+    const store = getFilterStore(api.provider.value, FilterType.DATE);
 
     if (store === null) {
         return error(EMISSINGSTORE);
diff --git a/packages/shared/widget-plugin-filtering/src/helpers/useNumberFilterAPI.ts b/packages/shared/widget-plugin-filtering/src/helpers/useNumberFilterAPI.ts
index 4635732692..c1790cc83c 100644
--- a/packages/shared/widget-plugin-filtering/src/helpers/useNumberFilterAPI.ts
+++ b/packages/shared/widget-plugin-filtering/src/helpers/useNumberFilterAPI.ts
@@ -1,6 +1,6 @@
 import { useRef } from "react";
 import { FilterType, getFilterStore, useFilterContextValue } from "../context";
-import { APIError, EKEYMISSING, EMISSINGSTORE, EStoreTypeMisMatch } from "../errors";
+import { APIError, EMISSINGSTORE, EStoreTypeMisMatch } from "../errors";
 import { error, Result, value } from "../result-meta";
 import { isNumberFilter } from "../stores/input/store-utils";
 import { Number_InputFilterInterface } from "../typings/InputFilterInterface";
@@ -10,7 +10,7 @@ export interface Number_FilterAPIv2 {
     parentChannelName?: string;
 }
 
-export function useNumberFilterAPI(key: string): Result<Number_FilterAPIv2, APIError> {
+export function useNumberFilterAPI(): Result<Number_FilterAPIv2, APIError> {
     const ctx = useFilterContextValue();
     const numAPI = useRef<Number_FilterAPIv2>();
 
@@ -24,11 +24,7 @@ export function useNumberFilterAPI(key: string): Result<Number_FilterAPIv2, APIE
         return error(api.provider.error);
     }
 
-    if (api.provider.value.type === "key-value" && key === "") {
-        return error(EKEYMISSING);
-    }
-
-    const store = getFilterStore(api.provider.value, FilterType.NUMBER, key);
+    const store = getFilterStore(api.provider.value, FilterType.NUMBER);
 
     if (store === null) {
         return error(EMISSINGSTORE);
diff --git a/packages/shared/widget-plugin-filtering/src/helpers/useSelectFilterAPI.ts b/packages/shared/widget-plugin-filtering/src/helpers/useSelectFilterAPI.ts
index b3a675806e..24c7d9fa57 100644
--- a/packages/shared/widget-plugin-filtering/src/helpers/useSelectFilterAPI.ts
+++ b/packages/shared/widget-plugin-filtering/src/helpers/useSelectFilterAPI.ts
@@ -27,7 +27,7 @@ export function useSelectFilterAPI({ filterable }: Props): Result<Select_FilterA
         return error(api.provider.error);
     }
 
-    const store = getFilterStore(api.provider.value, FilterType.ENUMERATION, "");
+    const store = getFilterStore(api.provider.value, FilterType.ENUMERATION);
 
     if (store === null) {
         return error(EMISSINGSTORE);
diff --git a/packages/shared/widget-plugin-filtering/src/helpers/useStringFilterAPI.ts b/packages/shared/widget-plugin-filtering/src/helpers/useStringFilterAPI.ts
index 377ff90477..f3c11fbcda 100644
--- a/packages/shared/widget-plugin-filtering/src/helpers/useStringFilterAPI.ts
+++ b/packages/shared/widget-plugin-filtering/src/helpers/useStringFilterAPI.ts
@@ -1,6 +1,6 @@
 import { useRef } from "react";
 import { FilterType, getFilterStore, useFilterContextValue } from "../context";
-import { APIError, EKEYMISSING, EMISSINGSTORE, EStoreTypeMisMatch } from "../errors";
+import { APIError, EMISSINGSTORE, EStoreTypeMisMatch } from "../errors";
 import { error, Result, value } from "../result-meta";
 import { isStringFilter } from "../stores/input/store-utils";
 import { String_InputFilterInterface } from "../typings/InputFilterInterface";
@@ -10,7 +10,7 @@ export interface String_FilterAPIv2 {
     parentChannelName?: string;
 }
 
-export function useStringFilterAPI(key: string): Result<String_FilterAPIv2, APIError> {
+export function useStringFilterAPI(): Result<String_FilterAPIv2, APIError> {
     const ctx = useFilterContextValue();
     const strAPI = useRef<String_FilterAPIv2>();
 
@@ -24,11 +24,7 @@ export function useStringFilterAPI(key: string): Result<String_FilterAPIv2, APIE
         return error(api.provider.error);
     }
 
-    if (api.provider.value.type === "key-value" && key === "") {
-        return error(EKEYMISSING);
-    }
-
-    const store = getFilterStore(api.provider.value, FilterType.STRING, key);
+    const store = getFilterStore(api.provider.value, FilterType.STRING);
 
     if (store === null) {
         return error(EMISSINGSTORE);
diff --git a/packages/shared/widget-plugin-filtering/src/stores/generic/CustomFilterHost.ts b/packages/shared/widget-plugin-filtering/src/stores/generic/CustomFilterHost.ts
new file mode 100644
index 0000000000..b798603b73
--- /dev/null
+++ b/packages/shared/widget-plugin-filtering/src/stores/generic/CustomFilterHost.ts
@@ -0,0 +1,46 @@
+import { FilterCondition } from "mendix/filters";
+import { and } from "mendix/filters/builders";
+import { autorun, makeAutoObservable } from "mobx";
+import { tag } from "../../condition-utils";
+import { Filter, FilterObserver } from "../../typings/FilterObserver";
+import { FiltersSettingsMap } from "../../typings/settings";
+
+export class CustomFilterHost implements FilterObserver {
+    private filters: Map<string, Filter> = new Map();
+    private settingsBuffer: FiltersSettingsMap<string> = new Map();
+    private disposeMap: Map<string, () => void> = new Map();
+
+    constructor() {
+        makeAutoObservable(this);
+    }
+
+    get settings(): FiltersSettingsMap<string> {
+        return new Map([...this.filters].map(([key, filter]) => [key, filter.toJSON()]));
+    }
+
+    set settings(data: FiltersSettingsMap<string>) {
+        this.settingsBuffer = data;
+    }
+
+    get conditions(): Array<FilterCondition | undefined> {
+        return [...this.filters].map(([key, { condition }]) => {
+            return condition ? and(tag(key), condition) : undefined;
+        });
+    }
+
+    observe(key: string, filter: Filter): void {
+        const dispose = autorun(() => {
+            if (this.settingsBuffer.has(key)) {
+                filter.fromJSON(this.settingsBuffer.get(key));
+            }
+        });
+        this.disposeMap.set(key, dispose);
+        this.filters.set(key, filter);
+    }
+
+    unobserve(key: string): void {
+        this.disposeMap.get(key)?.();
+        this.disposeMap.delete(key);
+        this.filters.delete(key);
+    }
+}
diff --git a/packages/shared/widget-plugin-filtering/src/stores/generic/HeaderFiltersStore.ts b/packages/shared/widget-plugin-filtering/src/stores/generic/HeaderFiltersStore.ts
index 3836716e43..f627172731 100644
--- a/packages/shared/widget-plugin-filtering/src/stores/generic/HeaderFiltersStore.ts
+++ b/packages/shared/widget-plugin-filtering/src/stores/generic/HeaderFiltersStore.ts
@@ -1,40 +1,43 @@
 import { ListAttributeValue } from "mendix";
 import { FilterCondition } from "mendix/filters";
 import { computed, makeObservable } from "mobx";
-import { FilterAPIv2 } from "../../context";
+import { FilterAPI } from "../../context";
 import { APIError } from "../../errors";
 import { LegacyPv } from "../../providers/LegacyPv";
 import { Result, value } from "../../result-meta";
+import { FilterObserver } from "../../typings/FilterObserver";
 import { FiltersSettingsMap } from "../../typings/settings";
 
 export interface FilterListType {
     filter: ListAttributeValue<string | Big | boolean | Date>;
 }
 
-export interface HeaderFiltersStoreProps {
+export interface HeaderFiltersStoreSpec {
     filterList: FilterListType[];
-    parentChannelName?: string;
-}
-
-export interface StaticInfo {
-    name: string;
-    filtersChannelName: string;
+    filterChannelName: string;
+    headerInitFilter: Array<FilterCondition | undefined>;
+    sharedInitFilter: Array<FilterCondition | undefined>;
+    customFilterHost: FilterObserver;
 }
 
 export class HeaderFiltersStore {
     private provider: Result<LegacyPv, APIError>;
-    context: FilterAPIv2;
+    context: FilterAPI;
 
-    constructor(
-        props: HeaderFiltersStoreProps,
-        info: StaticInfo,
-        dsViewState: Array<FilterCondition | undefined> | null
-    ) {
-        this.provider = this.createProvider(props, dsViewState);
+    constructor({
+        filterList,
+        filterChannelName,
+        headerInitFilter,
+        sharedInitFilter,
+        customFilterHost: filterObserver
+    }: HeaderFiltersStoreSpec) {
+        this.provider = this.createProvider(filterList, headerInitFilter);
         this.context = {
-            version: 2,
-            parentChannelName: info.filtersChannelName ?? "",
-            provider: this.provider
+            version: 3,
+            parentChannelName: filterChannelName,
+            provider: this.provider,
+            sharedInitFilter,
+            filterObserver
         };
         makeObservable(this, {
             conditions: computed,
@@ -59,13 +62,13 @@ export class HeaderFiltersStore {
     }
 
     createProvider(
-        props: HeaderFiltersStoreProps,
-        dsViewState: Array<FilterCondition | undefined> | null
+        filterList: FilterListType[],
+        initFilter: Array<FilterCondition | undefined>
     ): Result<LegacyPv, APIError> {
         return value(
             new LegacyPv(
-                props.filterList.map(f => f.filter),
-                dsViewState
+                filterList.map(f => f.filter),
+                initFilter
             )
         );
     }
@@ -77,6 +80,4 @@ export class HeaderFiltersStore {
 
         return this.provider.value.setup();
     }
-
-    updateProps(): void {}
 }
diff --git a/packages/shared/widget-plugin-filtering/src/stores/input/BaseInputFilterStore.ts b/packages/shared/widget-plugin-filtering/src/stores/input/BaseInputFilterStore.ts
index 8dc62bfa6c..08c21e5cfc 100644
--- a/packages/shared/widget-plugin-filtering/src/stores/input/BaseInputFilterStore.ts
+++ b/packages/shared/widget-plugin-filtering/src/stores/input/BaseInputFilterStore.ts
@@ -1,5 +1,5 @@
 import { Big } from "big.js";
-import { ListAttributeValue } from "mendix";
+import { AttributeMetaData } from "mendix";
 import { FilterCondition } from "mendix/filters";
 import {
     and,
@@ -23,8 +23,9 @@ import { Argument } from "./Argument";
 
 type StateTuple<Fn, V> = [Fn] | [Fn, V] | [Fn, V, V];
 type Val<A extends Argument> = A["value"];
+
 export class BaseInputFilterStore<A extends Argument, Fn extends AllFunctions> {
-    protected _attributes: ListAttributeValue[] = [];
+    protected _attributes: AttributeMetaData[] = [];
     private _filterFunction: Fn;
     private _isFilterFunctionAdjustable: boolean = true;
     arg1: A;
@@ -32,7 +33,7 @@ export class BaseInputFilterStore<A extends Argument, Fn extends AllFunctions> {
     isInitialized = false;
     defaultState: StateTuple<Fn, Val<A>>;
 
-    constructor(arg1: A, arg2: A, initFn: Fn, attributes: ListAttributeValue[]) {
+    constructor(arg1: A, arg2: A, initFn: Fn, attributes: AttributeMetaData[]) {
         this._attributes = attributes;
         this.defaultState = [initFn];
         this._filterFunction = initFn;
@@ -49,7 +50,9 @@ export class BaseInputFilterStore<A extends Argument, Fn extends AllFunctions> {
             setState: action,
             UNSAFE_setDefaults: action,
             UNSAFE_overwriteFilterFunction: action,
-            filterFunction: computed
+            filterFunction: computed,
+            clear: action,
+            reset: action
         });
     }
     get filterFunction(): Fn {
@@ -116,7 +119,7 @@ export class BaseInputFilterStore<A extends Argument, Fn extends AllFunctions> {
 }
 
 function getFilterCondition<T extends string | Big | Date>(
-    listAttribute: ListAttributeValue,
+    listAttribute: AttributeMetaData,
     value: T | undefined,
     valueR: T | undefined,
     operation: AllFunctions
diff --git a/packages/shared/widget-plugin-filtering/src/stores/input/DateInputFilterStore.ts b/packages/shared/widget-plugin-filtering/src/stores/input/DateInputFilterStore.ts
index d04bf14a31..7a8807a803 100644
--- a/packages/shared/widget-plugin-filtering/src/stores/input/DateInputFilterStore.ts
+++ b/packages/shared/widget-plugin-filtering/src/stores/input/DateInputFilterStore.ts
@@ -1,4 +1,4 @@
-import { DateTimeFormatter, ListAttributeValue } from "mendix";
+import { AttributeMetaData, DateTimeFormatter, ListAttributeValue, SimpleFormatter } from "mendix";
 import { AndCondition, FilterCondition, LiteralExpression } from "mendix/filters";
 import {
     and,
@@ -26,6 +26,7 @@ import { BaseInputFilterStore } from "./BaseInputFilterStore";
 type DateFns = FilterFunctionGeneric | FilterFunctionNonValue | FilterFunctionBinary;
 type StateTuple = [DateFns, Date | undefined, Date | undefined];
 type InitState = [DateFns, Date | undefined, Date | undefined] | [DateFns, Date | undefined];
+type AttrMeta = AttributeMetaData<Date> & { formatter?: SimpleFormatter<Date> };
 
 export class DateInputFilterStore
     extends BaseInputFilterStore<DateArgument, DateFns>
@@ -36,8 +37,8 @@ export class DateInputFilterStore
     private readonly rangeMarkerTag = "__RANGE_MARKER__";
     private computedState: StateTuple;
 
-    constructor(attributes: Array<ListAttributeValue<Date>>, initCond: FilterCondition | null) {
-        const { formatter } = attributes[0];
+    constructor(attributes: Array<AttributeMetaData<Date>>, initCond: FilterCondition | null) {
+        const formatter = getFormatter(attributes[0]);
         super(new DateArgument(formatter), new DateArgument(formatter), "equal", attributes);
         // NOTE: some fields already become observable in `super`.
         makeObservable<this, "computedState">(this, {
@@ -76,7 +77,7 @@ export class DateInputFilterStore
     updateProps(attributes: ListAttributeValue[]): void {
         if (!comparer.shallow(this._attributes, attributes)) {
             this._attributes = attributes;
-            const formatter = attributes.at(0)?.formatter;
+            const formatter = getFormatter(attributes[0] as AttributeMetaData<Date>);
             this.arg1.updateProps(formatter as DateTimeFormatter);
             this.arg2.updateProps(formatter as DateTimeFormatter);
         }
@@ -103,7 +104,7 @@ export class DateInputFilterStore
     }
 
     private getCondition(
-        attr: ListAttributeValue,
+        attr: AttributeMetaData,
         filterFn: DateFns,
         v1: Date | undefined,
         v2: Date | undefined
@@ -121,7 +122,7 @@ export class DateInputFilterStore
     }
 
     private getAttrCondition(
-        attr: ListAttributeValue,
+        attr: AttributeMetaData,
         filterFn: Exclude<DateFns, "between">,
         date: Date | undefined
     ): [FilterCondition] | [] {
@@ -153,7 +154,7 @@ export class DateInputFilterStore
         }
     }
 
-    private getRangeCondition(attr: ListAttributeValue, [start, end]: [Date, Date]): [FilterCondition] | [] {
+    private getRangeCondition(attr: AttributeMetaData, [start, end]: [Date, Date]): [FilterCondition] | [] {
         const attrExp = attribute(attr.id);
 
         return [
@@ -296,3 +297,23 @@ function subDay(date: Date): Date {
     newDate.setUTCDate(newDate.getUTCDate() - 1);
     return newDate;
 }
+
+function getFormatter(attr: AttrMeta): SimpleFormatter<Date> {
+    if (attr.formatter) {
+        return attr.formatter;
+    }
+
+    return {
+        format: v => v?.toString() ?? "",
+        parse: v => {
+            const date = Date.parse(v);
+            if (isNaN(date)) {
+                return { valid: false };
+            }
+            return {
+                valid: true,
+                value: new Date(date)
+            };
+        }
+    };
+}
diff --git a/packages/shared/widget-plugin-filtering/src/stores/input/NumberInputFilterStore.ts b/packages/shared/widget-plugin-filtering/src/stores/input/NumberInputFilterStore.ts
index a8fbbe08c5..18a302d02d 100644
--- a/packages/shared/widget-plugin-filtering/src/stores/input/NumberInputFilterStore.ts
+++ b/packages/shared/widget-plugin-filtering/src/stores/input/NumberInputFilterStore.ts
@@ -1,5 +1,5 @@
 import { Big } from "big.js";
-import { ListAttributeValue } from "mendix";
+import { AttributeMetaData, ListAttributeValue, SimpleFormatter } from "mendix";
 import { FilterCondition } from "mendix/filters";
 import { action, comparer, makeObservable } from "mobx";
 import { inputStateFromCond } from "../../condition-utils";
@@ -11,7 +11,8 @@ import { BaseInputFilterStore } from "./BaseInputFilterStore";
 import { baseNames } from "./fn-mappers";
 
 type NumFns = FilterFunctionGeneric | FilterFunctionNonValue | FilterFunctionBinary;
-type Formatter = ListAttributeValue<Big>["formatter"];
+type Formatter = SimpleFormatter<Big>;
+type AttrMeta = AttributeMetaData<Big> & { formatter?: SimpleFormatter<Big> };
 
 export class NumberInputFilterStore
     extends BaseInputFilterStore<NumberArgument, NumFns>
@@ -20,9 +21,8 @@ export class NumberInputFilterStore
     readonly storeType = "input";
     readonly type = "number";
 
-    constructor(attributes: Array<ListAttributeValue<Big>>, initCond: FilterCondition | null) {
-        let { formatter } = attributes[0];
-        formatter = formatterFix(formatter);
+    constructor(attributes: AttrMeta[], initCond: FilterCondition | null) {
+        const formatter = formatterFix(attributes[0].formatter);
         super(new NumberArgument(formatter), new NumberArgument(formatter), "equal", attributes);
         makeObservable(this, {
             updateProps: action,
@@ -79,11 +79,12 @@ export class NumberInputFilterStore
     }
 }
 
-function formatterFix(formatter: Formatter): Formatter {
+function formatterFix(formatter: Formatter | undefined): Formatter {
     // Check formatter.parse to see if it is a valid formatter.
-    if (formatter.parse("none")?.valid === false) {
+    if (formatter && formatter.parse("none")?.valid === false) {
         return formatter;
     }
+
     // Create a new formatter that will handle the autonumber values.
     return {
         format: (value: Big) => {
diff --git a/packages/shared/widget-plugin-filtering/src/stores/input/StringInputFilterStore.ts b/packages/shared/widget-plugin-filtering/src/stores/input/StringInputFilterStore.ts
index 45ab2a30d9..31c720798b 100644
--- a/packages/shared/widget-plugin-filtering/src/stores/input/StringInputFilterStore.ts
+++ b/packages/shared/widget-plugin-filtering/src/stores/input/StringInputFilterStore.ts
@@ -1,4 +1,4 @@
-import { ListAttributeValue } from "mendix";
+import { AttributeMetaData, ListAttributeValue, SimpleFormatter } from "mendix";
 import { FilterCondition } from "mendix/filters";
 import { action, comparer, makeObservable } from "mobx";
 import { inputStateFromCond } from "../../condition-utils";
@@ -15,6 +15,8 @@ import { BaseInputFilterStore } from "./BaseInputFilterStore";
 import { baseNames } from "./fn-mappers";
 
 type StrFns = FilterFunctionString | FilterFunctionGeneric | FilterFunctionNonValue | FilterFunctionBinary;
+type AttrMeta = AttributeMetaData<string> & { formatter?: SimpleFormatter<string> };
+
 export class StringInputFilterStore
     extends BaseInputFilterStore<StringArgument, StrFns>
     implements String_InputFilterInterface
@@ -22,8 +24,8 @@ export class StringInputFilterStore
     readonly storeType = "input";
     readonly type = "string";
 
-    constructor(attributes: Array<ListAttributeValue<string>>, initCond: FilterCondition | null) {
-        const { formatter } = attributes[0];
+    constructor(attributes: AttrMeta[], initCond: FilterCondition | null) {
+        const formatter = getFormatter<string>(attributes[0]);
         super(new StringArgument(formatter), new StringArgument(formatter), "equal", attributes);
         makeObservable(this, {
             updateProps: action,
@@ -91,3 +93,13 @@ export class StringInputFilterStore
         this.isInitialized = true;
     }
 }
+
+function getFormatter<T>(attr: { formatter?: SimpleFormatter<T> }): SimpleFormatter<T> {
+    return (
+        attr.formatter ??
+        ({
+            format: v => v ?? "",
+            parse: v => ({ valid: true, value: v ?? "" })
+        } as SimpleFormatter<T>)
+    );
+}
diff --git a/packages/shared/widget-plugin-filtering/src/stores/picker/BaseSelectStore.ts b/packages/shared/widget-plugin-filtering/src/stores/picker/BaseSelectStore.ts
index d31e08f06e..ca1b324fd0 100644
--- a/packages/shared/widget-plugin-filtering/src/stores/picker/BaseSelectStore.ts
+++ b/packages/shared/widget-plugin-filtering/src/stores/picker/BaseSelectStore.ts
@@ -40,7 +40,7 @@ export class BaseSelectStore {
     }
 
     fromJSON(json: FilterData): void {
-        if (json === null || isInputData(json)) {
+        if (json == null || isInputData(json)) {
             return;
         }
         this.setSelected(json);
diff --git a/packages/shared/widget-plugin-filtering/src/typings/FilterObserver.ts b/packages/shared/widget-plugin-filtering/src/typings/FilterObserver.ts
new file mode 100644
index 0000000000..c572ed5d38
--- /dev/null
+++ b/packages/shared/widget-plugin-filtering/src/typings/FilterObserver.ts
@@ -0,0 +1,17 @@
+import { FilterCondition } from "mendix/filters";
+import { FilterData, FiltersSettingsMap } from "./settings";
+
+export interface Filter {
+    toJSON(): FilterData;
+    fromJSON(data: FilterData): void;
+    condition: FilterCondition | undefined;
+    setup?: () => void | void;
+}
+
+export interface FilterObserver {
+    get settings(): FiltersSettingsMap<string>;
+    set settings(settings: FiltersSettingsMap<string>);
+    conditions: Array<FilterCondition | undefined>;
+    observe(key: string, filter: Filter): void;
+    unobserve(key: string): void;
+}
diff --git a/packages/shared/widget-plugin-filtering/src/typings/settings.ts b/packages/shared/widget-plugin-filtering/src/typings/settings.ts
index 7f4f3b6614..1b6cb7a373 100644
--- a/packages/shared/widget-plugin-filtering/src/typings/settings.ts
+++ b/packages/shared/widget-plugin-filtering/src/typings/settings.ts
@@ -4,6 +4,6 @@ export type InputData<Fn = AllFunctions> = [Fn, string | null, string | null];
 
 export type SelectData = string[];
 
-export type FilterData = InputData | SelectData | null;
+export type FilterData = InputData | SelectData | null | undefined;
 
 export type FiltersSettingsMap<T> = Map<T, FilterData>;
diff --git a/packages/shared/widget-plugin-filtering/src/useDefaultValue.ts b/packages/shared/widget-plugin-filtering/src/useDefaultValue.ts
deleted file mode 100644
index d61d99ac45..0000000000
--- a/packages/shared/widget-plugin-filtering/src/useDefaultValue.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-import { DynamicValue } from "mendix";
-import { useRef } from "react";
-
-export function useDefaultValue<T>(defaultValueProp?: DynamicValue<T>): T | undefined | null {
-    const defaultValueRef = useRef<T | undefined | null>(null);
-
-    if (defaultValueProp?.status !== "loading" && defaultValueRef.current === null) {
-        defaultValueRef.current = defaultValueProp?.value;
-    }
-
-    return defaultValueRef.current;
-}
diff --git a/packages/shared/widget-plugin-mobx-kit/src/disposeBatch.ts b/packages/shared/widget-plugin-mobx-kit/src/disposeBatch.ts
index b3d899c210..7b0caf4fe4 100644
--- a/packages/shared/widget-plugin-mobx-kit/src/disposeBatch.ts
+++ b/packages/shared/widget-plugin-mobx-kit/src/disposeBatch.ts
@@ -1,8 +1,12 @@
-export function disposeBatch(): [add: (fn: () => void) => void, disposeAll: () => void] {
+type MaybeFn = (() => void) | void;
+
+export function disposeBatch(): [add: (fn: MaybeFn) => void, disposeAll: () => void] {
     const disposers = new Set<() => void>();
 
-    const add = (fn: () => void): void => {
-        disposers.add(fn);
+    const add = (fn: MaybeFn): void => {
+        if (fn) {
+            disposers.add(fn);
+        }
     };
 
     const disposeAll = (): void => {
diff --git a/packages/shared/widget-plugin-mobx-kit/src/setupable.ts b/packages/shared/widget-plugin-mobx-kit/src/setupable.ts
new file mode 100644
index 0000000000..cb29e7ccb9
--- /dev/null
+++ b/packages/shared/widget-plugin-mobx-kit/src/setupable.ts
@@ -0,0 +1,3 @@
+export interface ISetupable {
+    setup(): void | (() => void);
+}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 4ef7c3a92f..88c5933250 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -1271,6 +1271,9 @@ importers:
       '@mendix/widget-plugin-hooks':
         specifier: workspace:*
         version: link:../../shared/widget-plugin-hooks
+      '@mendix/widget-plugin-mobx-kit':
+        specifier: workspace:^
+        version: link:../../shared/widget-plugin-mobx-kit
       '@mendix/widget-plugin-platform':
         specifier: workspace:*
         version: link:../../shared/widget-plugin-platform
@@ -2608,6 +2611,9 @@ importers:
       '@mendix/widget-plugin-hooks':
         specifier: workspace:*
         version: link:../widget-plugin-hooks
+      '@mendix/widget-plugin-mobx-kit':
+        specifier: workspace:^
+        version: link:../widget-plugin-mobx-kit
       '@mendix/widget-plugin-platform':
         specifier: workspace:*
         version: link:../widget-plugin-platform