diff --git a/packages/desktop-client/src/components/filters/AppliedFilters.tsx b/packages/desktop-client/src/components/filters/AppliedFilters.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..1f5ead431ea702da4d00c318ed82bf6e01fb99bd
--- /dev/null
+++ b/packages/desktop-client/src/components/filters/AppliedFilters.tsx
@@ -0,0 +1,55 @@
+import React from 'react';
+
+import { type RuleConditionEntity } from 'loot-core/types/models';
+
+import { View } from '../common/View';
+
+import { FilterExpression } from './FilterExpression';
+import { CondOpMenu } from './SavedFilters';
+
+type AppliedFiltersProps = {
+  filters: RuleConditionEntity[];
+  onUpdate: (
+    filter: RuleConditionEntity,
+    newFilter: RuleConditionEntity,
+  ) => RuleConditionEntity;
+  onDelete: (filter: RuleConditionEntity) => void;
+  conditionsOp: string;
+  onCondOpChange: () => void;
+};
+
+export function AppliedFilters({
+  filters,
+  onUpdate,
+  onDelete,
+  conditionsOp,
+  onCondOpChange,
+}: AppliedFiltersProps) {
+  return (
+    <View
+      style={{
+        flexDirection: 'row',
+        alignItems: 'flex-start',
+        flexWrap: 'wrap',
+      }}
+    >
+      <CondOpMenu
+        conditionsOp={conditionsOp}
+        onCondOpChange={onCondOpChange}
+        filters={filters}
+      />
+      {filters.map((filter: RuleConditionEntity, i: number) => (
+        <FilterExpression
+          key={i}
+          customName={filter.customName}
+          field={filter.field}
+          op={filter.op}
+          value={filter.value}
+          options={filter.options}
+          onChange={newFilter => onUpdate(filter, newFilter)}
+          onDelete={() => onDelete(filter)}
+        />
+      ))}
+    </View>
+  );
+}
diff --git a/packages/desktop-client/src/components/filters/CompactFiltersButton.tsx b/packages/desktop-client/src/components/filters/CompactFiltersButton.tsx
index 8848ce8bd043187e4815b09aa6e3796d6aa59ab5..7da1dc8fa7d6a02afd499d33f47c6fdb660147af 100644
--- a/packages/desktop-client/src/components/filters/CompactFiltersButton.tsx
+++ b/packages/desktop-client/src/components/filters/CompactFiltersButton.tsx
@@ -1,14 +1,9 @@
-// @ts-strict-ignore
 import React from 'react';
 
 import { SvgFilter } from '../../icons/v1';
 import { Button } from '../common/Button';
 
-type CompactFiltersButtonProps = {
-  onClick: (newValue) => void;
-};
-
-export function CompactFiltersButton({ onClick }: CompactFiltersButtonProps) {
+export function CompactFiltersButton({ onClick }: { onClick: () => void }) {
   return (
     <Button type="bare" onClick={onClick}>
       <SvgFilter width={15} height={15} />
diff --git a/packages/desktop-client/src/components/filters/FilterExpression.tsx b/packages/desktop-client/src/components/filters/FilterExpression.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..08bb15645602f8c248b863ad39961e44efb74550
--- /dev/null
+++ b/packages/desktop-client/src/components/filters/FilterExpression.tsx
@@ -0,0 +1,108 @@
+import React, { useState } from 'react';
+
+import { mapField, friendlyOp } from 'loot-core/src/shared/rules';
+import { integerToCurrency } from 'loot-core/src/shared/util';
+import {
+  type RuleConditionOp,
+  type RuleConditionEntity,
+} from 'loot-core/src/types/models';
+
+import { SvgDelete } from '../../icons/v0';
+import { type CSSProperties, theme } from '../../style';
+import { Button } from '../common/Button';
+import { Text } from '../common/Text';
+import { View } from '../common/View';
+import { Value } from '../rules/Value';
+
+import { FilterEditor } from './FiltersMenu';
+import { subfieldFromFilter } from './subfieldFromFilter';
+
+type FilterExpressionProps = {
+  field: string | undefined;
+  customName: string | undefined;
+  op: RuleConditionOp | undefined;
+  value: string | string[] | number | boolean | undefined;
+  options: RuleConditionEntity['options'];
+  style?: CSSProperties;
+  onChange: (cond: RuleConditionEntity) => RuleConditionEntity;
+  onDelete: () => void;
+};
+
+export function FilterExpression({
+  field: originalField,
+  customName,
+  op,
+  value,
+  options,
+  style,
+  onChange,
+  onDelete,
+}: FilterExpressionProps) {
+  const [editing, setEditing] = useState(false);
+
+  const field = subfieldFromFilter({ field: originalField, value });
+
+  return (
+    <View
+      style={{
+        backgroundColor: theme.pillBackground,
+        borderRadius: 4,
+        flexDirection: 'row',
+        alignItems: 'center',
+        marginRight: 10,
+        marginTop: 10,
+        ...style,
+      }}
+    >
+      <Button
+        type="bare"
+        disabled={customName != null}
+        onClick={() => setEditing(true)}
+        style={{ marginRight: -7 }}
+      >
+        <div style={{ paddingBlock: 1, paddingLeft: 5, paddingRight: 2 }}>
+          {customName ? (
+            <Text style={{ color: theme.pageTextPositive }}>{customName}</Text>
+          ) : (
+            <>
+              <Text style={{ color: theme.pageTextPositive }}>
+                {mapField(field, options)}
+              </Text>{' '}
+              <Text>{friendlyOp(op, null)}</Text>{' '}
+              <Value
+                value={value}
+                field={field}
+                inline={true}
+                valueIsRaw={op === 'contains' || op === 'doesNotContain'}
+              />
+            </>
+          )}
+        </div>
+      </Button>
+      <Button type="bare" onClick={onDelete} aria-label="Delete filter">
+        <SvgDelete
+          style={{
+            width: 8,
+            height: 8,
+            margin: 5,
+            marginLeft: 3,
+          }}
+        />
+      </Button>
+      {editing && (
+        <FilterEditor
+          field={originalField}
+          op={op}
+          value={
+            field === 'amount' && typeof value === 'number'
+              ? integerToCurrency(value)
+              : value
+          }
+          options={options}
+          onSave={onChange}
+          onClose={() => setEditing(false)}
+        />
+      )}
+    </View>
+  );
+}
diff --git a/packages/desktop-client/src/components/filters/FiltersButton.tsx b/packages/desktop-client/src/components/filters/FiltersButton.tsx
index f6f420f48894f8c56f29fcf926674d11d340af14..c2491b95132122f41f0da4199464631b68668831 100644
--- a/packages/desktop-client/src/components/filters/FiltersButton.tsx
+++ b/packages/desktop-client/src/components/filters/FiltersButton.tsx
@@ -1,14 +1,9 @@
-// @ts-strict-ignore
 import React from 'react';
 
 import { SvgSettingsSliderAlternate } from '../../icons/v2';
 import { Button } from '../common/Button';
 
-type FiltersButtonProps = {
-  onClick: (newValue) => void;
-};
-
-export function FiltersButton({ onClick }: FiltersButtonProps) {
+export function FiltersButton({ onClick }: { onClick: () => void }) {
   return (
     <Button type="bare" onClick={onClick} title="Filters">
       <SvgSettingsSliderAlternate
diff --git a/packages/desktop-client/src/components/filters/FiltersMenu.jsx b/packages/desktop-client/src/components/filters/FiltersMenu.jsx
index 2d40d6375c1bc34210a8eba93c548e6d717f0119..f4650c5a44fa68e3a6be3edce75496eda84eb551 100644
--- a/packages/desktop-client/src/components/filters/FiltersMenu.jsx
+++ b/packages/desktop-client/src/components/filters/FiltersMenu.jsx
@@ -13,17 +13,14 @@ import { send } from 'loot-core/src/platform/client/fetch';
 import { getMonthYearFormat } from 'loot-core/src/shared/months';
 import {
   mapField,
-  friendlyOp,
   deserializeField,
   getFieldError,
   unparse,
-  makeValue,
   FIELD_TYPES,
   TYPE_INFO,
 } from 'loot-core/src/shared/rules';
-import { titleFirst, integerToCurrency } from 'loot-core/src/shared/util';
+import { titleFirst } from 'loot-core/src/shared/util';
 
-import { SvgDelete } from '../../icons/v0';
 import { theme } from '../../style';
 import { Button } from '../common/Button';
 import { HoverTarget } from '../common/HoverTarget';
@@ -32,13 +29,15 @@ import { Select } from '../common/Select';
 import { Stack } from '../common/Stack';
 import { Text } from '../common/Text';
 import { View } from '../common/View';
-import { Value } from '../rules/Value';
 import { Tooltip } from '../tooltips';
 import { GenericInput } from '../util/GenericInput';
 
 import { CompactFiltersButton } from './CompactFiltersButton';
 import { FiltersButton } from './FiltersButton';
-import { CondOpMenu } from './SavedFilters';
+import { OpButton } from './OpButton';
+import { subfieldFromFilter } from './subfieldFromFilter';
+import { subfieldToOptions } from './subfieldToOptions';
+import { updateFilterReducer } from './updateFilterReducer';
 
 const filterFields = [
   'date',
@@ -52,100 +51,6 @@ const filterFields = [
   'saved',
 ].map(field => [field, mapField(field)]);
 
-function subfieldFromFilter({ field, options, value }) {
-  if (field === 'date') {
-    if (value.length === 7) {
-      return 'month';
-    } else if (value.length === 4) {
-      return 'year';
-    }
-  } else if (field === 'amount') {
-    if (options && options.inflow) {
-      return 'amount-inflow';
-    } else if (options && options.outflow) {
-      return 'amount-outflow';
-    }
-  }
-  return field;
-}
-
-function subfieldToOptions(field, subfield) {
-  switch (field) {
-    case 'amount':
-      switch (subfield) {
-        case 'amount-inflow':
-          return { inflow: true };
-        case 'amount-outflow':
-          return { outflow: true };
-        default:
-          return null;
-      }
-    case 'date':
-      switch (subfield) {
-        case 'month':
-          return { month: true };
-        case 'year':
-          return { year: true };
-        default:
-          return null;
-      }
-    default:
-      return null;
-  }
-}
-
-function OpButton({ op, selected, style, onClick }) {
-  return (
-    <Button
-      type="bare"
-      style={{
-        backgroundColor: theme.pillBackground,
-        marginBottom: 5,
-        ...style,
-        ...(selected && {
-          color: theme.buttonNormalSelectedText,
-          '&,:hover,:active': {
-            backgroundColor: theme.buttonNormalSelectedBackground,
-            color: theme.buttonNormalSelectedText,
-          },
-        }),
-      }}
-      onClick={onClick}
-    >
-      {friendlyOp(op)}
-    </Button>
-  );
-}
-
-function updateFilterReducer(state, action) {
-  switch (action.type) {
-    case 'set-op': {
-      const type = FIELD_TYPES.get(state.field);
-      let value = state.value;
-      if (
-        (type === 'id' || type === 'string') &&
-        (action.op === 'contains' ||
-          action.op === 'is' ||
-          action.op === 'doesNotContain' ||
-          action.op === 'isNot')
-      ) {
-        // Clear out the value if switching between contains or
-        // is/oneof for the id or string type
-        value = null;
-      }
-      return { ...state, op: action.op, value };
-    }
-    case 'set-value': {
-      const { value } = makeValue(action.value, {
-        type: FIELD_TYPES.get(state.field),
-      });
-      return { ...state, value };
-    }
-    default:
-      throw new Error(`Unhandled action type: ${action.type}`);
-  }
-}
-
 function ConfigureField({
   field,
   initialSubfield = field,
@@ -478,7 +383,7 @@ export function FilterButton({ onApply, compact, hover }) {
   );
 }
 
-function FilterEditor({ field, op, value, options, onSave, onClose }) {
+export function FilterEditor({ field, op, value, options, onSave, onClose }) {
   const [state, dispatch] = useReducer(
     (state, action) => {
       switch (action.type) {
@@ -508,119 +413,3 @@ function FilterEditor({ field, op, value, options, onSave, onClose }) {
     />
   );
 }
-
-function FilterExpression({
-  field: originalField,
-  customName,
-  op,
-  value,
-  options,
-  stage,
-  style,
-  onChange,
-  onDelete,
-}) {
-  const [editing, setEditing] = useState(false);
-
-  const field = subfieldFromFilter({ field: originalField, value });
-
-  return (
-    <View
-      style={{
-        backgroundColor: theme.pillBackground,
-        borderRadius: 4,
-        flexDirection: 'row',
-        alignItems: 'center',
-        marginRight: 10,
-        marginTop: 10,
-        ...style,
-      }}
-    >
-      <Button
-        type="bare"
-        disabled={customName != null}
-        onClick={() => setEditing(true)}
-        style={{ marginRight: -7 }}
-      >
-        <div style={{ paddingBlock: 1, paddingLeft: 5, paddingRight: 2 }}>
-          {customName ? (
-            <Text style={{ color: theme.pageTextPositive }}>{customName}</Text>
-          ) : (
-            <>
-              <Text style={{ color: theme.pageTextPositive }}>
-                {mapField(field, options)}
-              </Text>{' '}
-              <Text>{friendlyOp(op, null)}</Text>{' '}
-              <Value
-                value={value}
-                field={field}
-                inline={true}
-                valueIsRaw={op === 'contains' || op === 'doesNotContain'}
-              />
-            </>
-          )}
-        </div>
-      </Button>
-      <Button type="bare" onClick={onDelete} aria-label="Delete filter">
-        <SvgDelete
-          style={{
-            width: 8,
-            height: 8,
-            margin: 5,
-            marginLeft: 3,
-          }}
-        />
-      </Button>
-      {editing && (
-        <FilterEditor
-          field={originalField}
-          customName={customName}
-          op={op}
-          value={field === 'amount' ? integerToCurrency(value) : value}
-          options={options}
-          stage={stage}
-          onSave={onChange}
-          onClose={() => setEditing(false)}
-        />
-      )}
-    </View>
-  );
-}
-
-export function AppliedFilters({
-  filters,
-  editingFilter,
-  onUpdate,
-  onDelete,
-  conditionsOp,
-  onCondOpChange,
-}) {
-  return (
-    <View
-      style={{
-        flexDirection: 'row',
-        alignItems: 'flex-start',
-        flexWrap: 'wrap',
-      }}
-    >
-      <CondOpMenu
-        conditionsOp={conditionsOp}
-        onCondOpChange={onCondOpChange}
-        filters={filters}
-      />
-      {filters.map((filter, i) => (
-        <FilterExpression
-          key={i}
-          customName={filter.customName}
-          field={filter.field}
-          op={filter.op}
-          value={filter.value}
-          options={filter.options}
-          editing={editingFilter === filter}
-          onChange={newFilter => onUpdate(filter, newFilter)}
-          onDelete={() => onDelete(filter)}
-        />
-      ))}
-    </View>
-  );
-}
diff --git a/packages/desktop-client/src/components/filters/OpButton.tsx b/packages/desktop-client/src/components/filters/OpButton.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..f061cb0c81afb48c14e14c2f7d9a23e63e9fc0ef
--- /dev/null
+++ b/packages/desktop-client/src/components/filters/OpButton.tsx
@@ -0,0 +1,36 @@
+import React from 'react';
+
+import { friendlyOp } from 'loot-core/src/shared/rules';
+
+import { type CSSProperties, theme } from '../../style';
+import { Button } from '../common/Button';
+
+type OpButtonProps = {
+  op: string;
+  selected: boolean;
+  onClick: () => void;
+  style?: CSSProperties;
+};
+
+export function OpButton({ op, selected, style, onClick }: OpButtonProps) {
+  return (
+    <Button
+      type="bare"
+      style={{
+        backgroundColor: theme.pillBackground,
+        marginBottom: 5,
+        ...style,
+        ...(selected && {
+          color: theme.buttonNormalSelectedText,
+          '&,:hover,:active': {
+            backgroundColor: theme.buttonNormalSelectedBackground,
+            color: theme.buttonNormalSelectedText,
+          },
+        }),
+      }}
+      onClick={onClick}
+    >
+      {friendlyOp(op)}
+    </Button>
+  );
+}
diff --git a/packages/desktop-client/src/components/filters/SavedFilters.jsx b/packages/desktop-client/src/components/filters/SavedFilters.jsx
index 497ccd14ef61e833ec507a18431cf2f4a3555f6d..1fd0be01155f92e8ec92c04a70c44ae742faee61 100644
--- a/packages/desktop-client/src/components/filters/SavedFilters.jsx
+++ b/packages/desktop-client/src/components/filters/SavedFilters.jsx
@@ -14,7 +14,7 @@ import { FormField, FormLabel } from '../forms';
 import { FieldSelect } from '../modals/EditRule';
 import { GenericInput } from '../util/GenericInput';
 
-import { AppliedFilters } from './FiltersMenu';
+import { AppliedFilters } from './AppliedFilters';
 
 function FilterMenu({ onClose, filterId, onFilterMenuSelect }) {
   return (
@@ -285,21 +285,21 @@ function SavedFilterMenuButton({
 }
 
 export function CondOpMenu({ conditionsOp, onCondOpChange, filters }) {
-  return (
-    filters.length > 1 && (
-      <Text style={{ color: theme.pageText, marginTop: 11, marginRight: 5 }}>
-        <FieldSelect
-          style={{ display: 'inline-flex' }}
-          fields={[
-            ['and', 'all'],
-            ['or', 'any'],
-          ]}
-          value={conditionsOp}
-          onChange={(name, value) => onCondOpChange(value, filters)}
-        />
-        of:
-      </Text>
-    )
+  return filters.length > 1 ? (
+    <Text style={{ color: theme.pageText, marginTop: 11, marginRight: 5 }}>
+      <FieldSelect
+        style={{ display: 'inline-flex' }}
+        fields={[
+          ['and', 'all'],
+          ['or', 'any'],
+        ]}
+        value={conditionsOp}
+        onChange={(name, value) => onCondOpChange(value, filters)}
+      />
+      of:
+    </Text>
+  ) : (
+    <View />
   );
 }
 
diff --git a/packages/desktop-client/src/components/filters/subfieldFromFilter.ts b/packages/desktop-client/src/components/filters/subfieldFromFilter.ts
new file mode 100644
index 0000000000000000000000000000000000000000..d4f60dfe2028de349c06e53e1c1b4d9815d426f7
--- /dev/null
+++ b/packages/desktop-client/src/components/filters/subfieldFromFilter.ts
@@ -0,0 +1,27 @@
+import { type RuleConditionEntity } from 'loot-core/src/types/models';
+
+export function subfieldFromFilter({
+  field,
+  options,
+  value,
+}: RuleConditionEntity) {
+  if (field === 'date') {
+    if (typeof value === 'string') {
+      if (value.length === 7) {
+        return 'month';
+      } else if (value.length === 4) {
+        return 'year';
+      }
+    }
+  }
+
+  if (field === 'amount') {
+    if (options && options.inflow) {
+      return 'amount-inflow';
+    } else if (options && options.outflow) {
+      return 'amount-outflow';
+    }
+  }
+
+  return field;
+}
diff --git a/packages/desktop-client/src/components/filters/subfieldToOptions.ts b/packages/desktop-client/src/components/filters/subfieldToOptions.ts
new file mode 100644
index 0000000000000000000000000000000000000000..60ac78746e8cc2860d476fe9b298312d21bb1de2
--- /dev/null
+++ b/packages/desktop-client/src/components/filters/subfieldToOptions.ts
@@ -0,0 +1,34 @@
+import { type RuleConditionEntity } from 'loot-core/src/types/models';
+
+export function subfieldToOptions(field: string, subfield: string) {
+  let setOptions: RuleConditionEntity['options'];
+  switch (field) {
+    case 'amount':
+      switch (subfield) {
+        case 'amount-inflow':
+          setOptions = { inflow: true };
+          break;
+        case 'amount-outflow':
+          setOptions = { outflow: true };
+          break;
+        default:
+          break;
+      }
+      break;
+    case 'date':
+      switch (subfield) {
+        case 'month':
+          setOptions = { month: true };
+          break;
+        case 'year':
+          setOptions = { year: true };
+          break;
+        default:
+          break;
+      }
+      break;
+    default:
+      break;
+  }
+  return setOptions;
+}
diff --git a/packages/desktop-client/src/components/filters/updateFilterReducer.ts b/packages/desktop-client/src/components/filters/updateFilterReducer.ts
new file mode 100644
index 0000000000000000000000000000000000000000..8bbca3ce85055e832bedb59aa25b61a866a43ba9
--- /dev/null
+++ b/packages/desktop-client/src/components/filters/updateFilterReducer.ts
@@ -0,0 +1,34 @@
+import { makeValue, FIELD_TYPES } from 'loot-core/src/shared/rules';
+import { type RuleConditionEntity } from 'loot-core/src/types/models';
+
+export function updateFilterReducer(
+  state: { field: string; value: string | string[] | number | boolean | null },
+  action: RuleConditionEntity,
+) {
+  switch (action.type) {
+    case 'set-op': {
+      const type = FIELD_TYPES.get(state.field);
+      let value = state.value;
+      if (
+        (type === 'id' || type === 'string') &&
+        (action.op === 'contains' ||
+          action.op === 'is' ||
+          action.op === 'doesNotContain' ||
+          action.op === 'isNot')
+      ) {
+        // Clear out the value if switching between contains or
+        // is/oneof for the id or string type
+        value = null;
+      }
+      return { ...state, op: action.op, value };
+    }
+    case 'set-value': {
+      const { value } = makeValue(action.value, {
+        type: FIELD_TYPES.get(state.field),
+      });
+      return { ...state, value };
+    }
+    default:
+      throw new Error(`Unhandled action type: ${action.type}`);
+  }
+}
diff --git a/packages/desktop-client/src/components/reports/Header.jsx b/packages/desktop-client/src/components/reports/Header.jsx
index e3231f8d696eac8f7b29bd4ef32eefad3f2a0ecd..aaad46b7d0517859355c0ba0eebbb205fa49b604 100644
--- a/packages/desktop-client/src/components/reports/Header.jsx
+++ b/packages/desktop-client/src/components/reports/Header.jsx
@@ -8,7 +8,8 @@ import { Button } from '../common/Button';
 import { ButtonLink } from '../common/ButtonLink';
 import { Select } from '../common/Select';
 import { View } from '../common/View';
-import { FilterButton, AppliedFilters } from '../filters/FiltersMenu';
+import { AppliedFilters } from '../filters/AppliedFilters';
+import { FilterButton } from '../filters/FiltersMenu';
 
 export function validateStart(allMonths, start, end) {
   const earliest = allMonths[allMonths.length - 1].name;
diff --git a/packages/desktop-client/src/components/reports/reports/CustomReport.jsx b/packages/desktop-client/src/components/reports/reports/CustomReport.jsx
index ff49e56cb4c5b4b5162bf7b65ab536aa5a657c1e..a9bf769cb0b08a463fc126433900fd02c660ef9a 100644
--- a/packages/desktop-client/src/components/reports/reports/CustomReport.jsx
+++ b/packages/desktop-client/src/components/reports/reports/CustomReport.jsx
@@ -18,7 +18,7 @@ import { AlignedText } from '../../common/AlignedText';
 import { Block } from '../../common/Block';
 import { Text } from '../../common/Text';
 import { View } from '../../common/View';
-import { AppliedFilters } from '../../filters/FiltersMenu';
+import { AppliedFilters } from '../../filters/AppliedFilters';
 import { PrivacyFilter } from '../../PrivacyFilter';
 import { ChooseGraph } from '../ChooseGraph';
 import { Header } from '../Header';
diff --git a/packages/loot-core/src/types/models/rule.d.ts b/packages/loot-core/src/types/models/rule.d.ts
index 6b9eb147f55fb23913799a1ad524cb0b64d1931e..ce2b384f66592b89f0b8ca47ae7bec8d4c65e6be 100644
--- a/packages/loot-core/src/types/models/rule.d.ts
+++ b/packages/loot-core/src/types/models/rule.d.ts
@@ -9,24 +9,31 @@ export interface RuleEntity {
   tombstone?: boolean;
 }
 
+export type RuleConditionOp =
+  | 'is'
+  | 'isNot'
+  | 'oneOf'
+  | 'notOneOf'
+  | 'isapprox'
+  | 'isbetween'
+  | 'gt'
+  | 'gte'
+  | 'lt'
+  | 'lte'
+  | 'contains'
+  | 'doesNotContain';
+
 export interface RuleConditionEntity {
-  field: unknown;
-  op:
-    | 'is'
-    | 'isNot'
-    | 'oneOf'
-    | 'notOneOf'
-    | 'isapprox'
-    | 'isbetween'
-    | 'gt'
-    | 'gte'
-    | 'lt'
-    | 'lte'
-    | 'contains'
-    | 'doesNotContain';
-  value: unknown;
-  options?: unknown;
-  conditionsOp?: unknown;
+  field?: string;
+  op?: RuleConditionOp;
+  value?: string | string[] | number | boolean;
+  options?: {
+    inflow?: boolean;
+    outflow?: boolean;
+    month?: boolean;
+    year?: boolean;
+  };
+  conditionsOp?: string;
   type?: string;
   customName?: string;
 }
diff --git a/upcoming-release-notes/2231.md b/upcoming-release-notes/2231.md
new file mode 100644
index 0000000000000000000000000000000000000000..fd198f7f20a4e50bc623417f13f57a195b904586
--- /dev/null
+++ b/upcoming-release-notes/2231.md
@@ -0,0 +1,6 @@
+---
+category: Maintenance
+authors: [carkom]
+---
+
+Split out mega-file FiltersMenu.jsx into separate elements and converted them all to Typescript.