Commit 1651e582240 for woocommerce

commit 1651e582240ed70b41dbf076498e6dddbe181616
Author: Miroslav Mitev <m1r0@users.noreply.github.com>
Date:   Tue Sep 22 14:15:23 2026 +0300

    Add searchProps to CompareFilter and FilterPicker (#68816)

    * Add searchProps to CompareFilter and FilterPicker

    CompareFilter and FilterPicker filters passed type, autocompleter, and labels.placeholder to the inner Search component. Group them under searchProps (settings.searchProps for FilterPicker), so a config can pass any Search prop.

    The old props still work and log a deprecation notice. When set, they take precedence over searchProps, so extensions that change them on core report filters keep working.

    The Analytics report configs now use searchProps.

diff --git a/packages/js/components/changelog/32254-separate-search-props b/packages/js/components/changelog/32254-separate-search-props
new file mode 100644
index 00000000000..64070c66032
--- /dev/null
+++ b/packages/js/components/changelog/32254-separate-search-props
@@ -0,0 +1,4 @@
+Significance: minor
+Type: add
+
+Add a `searchProps` prop to `CompareFilter` and a `settings.searchProps` option to `FilterPicker` filters, and deprecate the `type`, `autocompleter`, and `labels.placeholder` props they forwarded to `Search`.
diff --git a/packages/js/components/src/compare-filter/README.md b/packages/js/components/src/compare-filter/README.md
index bb3d1372a83..271f9874356 100644
--- a/packages/js/components/src/compare-filter/README.md
+++ b/packages/js/components/src/compare-filter/README.md
@@ -1,5 +1,4 @@
-CompareFilter
-===
+# CompareFilter

 Displays a card + search used to filter results as a comparison between objects.

@@ -10,17 +9,20 @@ const path = ''; // from React Router
 const getLabels = () => Promise.resolve( [] );
 const labels = {
 	helpText: 'Select at least two products to compare',
-	placeholder: 'Search for products to compare',
 	title: 'Compare Products',
 	update: 'Compare',
 };
+const searchProps = {
+	type: 'products',
+	placeholder: 'Search for products to compare',
+};

 <CompareFilter
-	type="products"
 	param="product"
 	path={ path }
 	getLabels={ getLabels }
 	labels={ labels }
+	searchProps={ searchProps }
 />
 ```

@@ -33,4 +35,8 @@ Name | Type | Default | Description
 `param` | String | `null` | (required) The parameter to use in the querystring
 `path` | String | `null` | (required) The `path` parameter supplied by React-Router
 `query` | Object | `{}` | The query string represented in object form
-`type` | String | `null` | (required) Which type of autocompleter should be used in the Search
+`searchProps` | Object | `{}` | Props forwarded to the `Search` component, except `selected` and `onChange`. `searchProps.type` is required. See [Search](../search/README.md) for the full list
+`type` | String | `null` | Deprecated. Use `searchProps.type` instead
+`autocompleter` | Object | `null` | Deprecated. Use `searchProps.autocompleter` instead
+
+The `labels.placeholder` label is deprecated. Use `searchProps.placeholder` instead. When a deprecated prop is set, it takes precedence over the matching `searchProps` value. The deprecated props are scheduled for removal in `@woocommerce/components` 15.0.0.
diff --git a/packages/js/components/src/compare-filter/index.js b/packages/js/components/src/compare-filter/index.js
index 92e0a41025b..51d30ba70c4 100644
--- a/packages/js/components/src/compare-filter/index.js
+++ b/packages/js/components/src/compare-filter/index.js
@@ -10,8 +10,9 @@ import {
 	CardFooter,
 	CardHeader,
 } from '@wordpress/components';
-import { isEqual, isFunction } from 'lodash';
+import { isEqual, isFunction, omitBy, isUndefined } from 'lodash';
 import PropTypes from 'prop-types';
+import deprecated from '@wordpress/deprecated';
 import { getIdsFromQuery, updateQueryString } from '@woocommerce/navigation';

 /**
@@ -90,8 +91,32 @@ export class CompareFilter extends Component {
 		}
 	}

+	getSearchProps() {
+		const { labels, type, autocompleter, searchProps } = this.props;
+		const legacySearchProps = omitBy(
+			{ type, autocompleter, placeholder: labels.placeholder },
+			isUndefined
+		);
+
+		if ( Object.keys( legacySearchProps ).length > 0 ) {
+			deprecated(
+				'Passing `type`, `autocompleter`, or `labels.placeholder` to CompareFilter',
+				{
+					since: '14.2.0',
+					version: '15.0.0',
+					alternative: 'the `searchProps` prop',
+					link: 'https://github.com/woocommerce/woocommerce/blob/trunk/packages/js/components/src/compare-filter/README.md',
+					plugin: '@woocommerce/components',
+				}
+			);
+		}
+
+		// The deprecated props win, so older code that changes them keeps working.
+		return { ...searchProps, ...legacySearchProps };
+	}
+
 	render() {
-		const { labels, type, autocompleter } = this.props;
+		const { labels } = this.props;
 		const { selected } = this.state;
 		return (
 			<Card className="woocommerce-filters__compare">
@@ -107,10 +132,8 @@ export class CompareFilter extends Component {
 				</CardHeader>
 				<CardBody>
 					<Search
-						autocompleter={ autocompleter }
-						type={ type }
+						{ ...this.getSearchProps() }
 						selected={ selected }
-						placeholder={ labels.placeholder }
 						onChange={ ( value ) => {
 							this.setState( { selected: value } );
 						} }
@@ -146,6 +169,8 @@ CompareFilter.propTypes = {
 	labels: PropTypes.shape( {
 		/**
 		 * Label for the search placeholder.
+		 *
+		 * @deprecated Use `searchProps.placeholder` instead.
 		 */
 		placeholder: PropTypes.string,
 		/**
@@ -170,11 +195,32 @@ CompareFilter.propTypes = {
 	 */
 	query: PropTypes.object,
 	/**
-	 * Which type of autocompleter should be used in the Search
+	 * Props forwarded to the `Search` component, except `selected` and `onChange`.
+	 */
+	searchProps: PropTypes.shape( {
+		/**
+		 * Which type of autocompleter should be used in the Search.
+		 */
+		type: PropTypes.string,
+		/**
+		 * The custom autocompleter to use when `type` is `'custom'`.
+		 */
+		autocompleter: PropTypes.object,
+		/**
+		 * Label for the search placeholder.
+		 */
+		placeholder: PropTypes.string,
+	} ),
+	/**
+	 * Which type of autocompleter should be used in the Search.
+	 *
+	 * @deprecated Use `searchProps.type` instead.
 	 */
-	type: PropTypes.string.isRequired,
+	type: PropTypes.string,
 	/**
 	 * The custom autocompleter to be forwarded to the `Search` component.
+	 *
+	 * @deprecated Use `searchProps.autocompleter` instead.
 	 */
 	autocompleter: PropTypes.object,
 };
@@ -182,4 +228,5 @@ CompareFilter.propTypes = {
 CompareFilter.defaultProps = {
 	labels: {},
 	query: {},
+	searchProps: {},
 };
diff --git a/packages/js/components/src/compare-filter/stories/compare-filter.story.js b/packages/js/components/src/compare-filter/stories/compare-filter.story.js
index df798cb7808..05eff0067d2 100644
--- a/packages/js/components/src/compare-filter/stories/compare-filter.story.js
+++ b/packages/js/components/src/compare-filter/stories/compare-filter.story.js
@@ -10,17 +10,19 @@ import { CompareFilter } from '../';

 const query = {};
 const compareFilter = {
-	type: 'products',
 	param: 'product',
 	getLabels() {
 		return Promise.resolve( [] );
 	},
 	labels: {
 		helpText: 'Select at least two products to compare',
-		placeholder: 'Search for products to compare',
 		title: 'Compare Products',
 		update: 'Compare',
 	},
+	searchProps: {
+		type: 'products',
+		placeholder: 'Search for products to compare',
+	},
 };

 export const Basic = ( {
diff --git a/packages/js/components/src/compare-filter/test/compare-filter.js b/packages/js/components/src/compare-filter/test/compare-filter.js
index 18ab9b36057..3ed0d747d0f 100644
--- a/packages/js/components/src/compare-filter/test/compare-filter.js
+++ b/packages/js/components/src/compare-filter/test/compare-filter.js
@@ -3,6 +3,7 @@
  */
 import { render } from '@testing-library/react';
 import { createElement } from '@wordpress/element';
+import { logged } from '@wordpress/deprecated';

 /**
  * Internal dependencies
@@ -18,20 +19,26 @@ Search.mockName( 'Search' );

 describe( 'CompareFilter', () => {
 	let props;
+	let warn;
 	beforeEach( () => {
+		// Reset the deprecation messages, so each test can assert its own warning.
+		Object.keys( logged ).forEach( ( key ) => delete logged[ key ] );
+		warn = jest.spyOn( console, 'warn' ).mockImplementation( () => {} );
 		props = {
 			path: '/foo/bar',
-			type: 'products',
 			param: 'product',
 			getLabels() {
 				return Promise.resolve( [] );
 			},
 			labels: {
 				helpText: 'Select at least two to compare',
-				placeholder: 'Search for things to compare',
 				title: 'Compare Things',
 				update: 'Compare',
 			},
+			searchProps: {
+				type: 'products',
+				placeholder: 'Search for things to compare',
+			},
 		};
 	} );
 	it( 'should render the example from the storybook', () => {
@@ -42,30 +49,84 @@ describe( 'CompareFilter', () => {
 		} ).not.toThrow();
 	} );

-	it( 'should forward the `type` prop the Search component', () => {
+	it( 'should forward `searchProps` to the Search component', () => {
+		props.searchProps = {
+			type: 'custom',
+			autocompleter: productAutocompleter,
+			placeholder: 'Search for things to compare',
+			showClearButton: true,
+		};
+
+		render( <CompareFilter { ...props } /> );
+
+		// Check that Search component received the props, without checking its behavior/internals/implementation details.
+		expect( Search ).toHaveBeenLastCalledWith(
+			expect.objectContaining( {
+				type: 'custom',
+				autocompleter: productAutocompleter,
+				placeholder: 'Search for things to compare',
+				showClearButton: true,
+			} ),
+			expect.anything()
+		);
+		expect( warn ).not.toHaveBeenCalled();
+	} );
+
+	it( 'should keep control of the `selected` and `onChange` Search props', () => {
+		const onChange = jest.fn();
+		props.searchProps = {
+			type: 'products',
+			selected: [ { key: 1, label: 'Foo' } ],
+			onChange,
+		};
+
+		render( <CompareFilter { ...props } /> );
+
+		const [ searchProps ] = Search.mock.calls.slice( -1 )[ 0 ];
+		expect( searchProps.selected ).toEqual( [] );
+		expect( searchProps.onChange ).not.toBe( onChange );
+	} );
+
+	it( 'should still forward the deprecated `type`, `autocompleter`, and `labels.placeholder` props', () => {
+		delete props.searchProps;
 		props.type = 'custom';
+		props.autocompleter = productAutocompleter;
+		props.labels.placeholder = 'Search for things to compare';

 		render( <CompareFilter { ...props } /> );

-		// Check that Search component received the prop, without checking its behavior/internals/implementation details.
 		expect( Search ).toHaveBeenLastCalledWith(
 			expect.objectContaining( {
 				type: 'custom',
+				autocompleter: productAutocompleter,
+				placeholder: 'Search for things to compare',
 			} ),
 			expect.anything()
 		);
+		expect( warn ).toHaveBeenCalledWith(
+			expect.stringContaining( 'to CompareFilter' )
+		);
 	} );
-	it( 'should forward the `autocompleter` prop the Search component', () => {
+
+	it( 'should prefer the deprecated props over `searchProps`', () => {
+		props.type = 'custom';
 		props.autocompleter = productAutocompleter;
+		props.labels.placeholder = 'Changed placeholder';
+		props.searchProps = {
+			type: 'products',
+			placeholder: 'Search for things to compare',
+		};

 		render( <CompareFilter { ...props } /> );

-		// Check that Search component received the prop, without checking its behavior/internals/implementation details.
 		expect( Search ).toHaveBeenLastCalledWith(
 			expect.objectContaining( {
+				type: 'custom',
 				autocompleter: productAutocompleter,
+				placeholder: 'Changed placeholder',
 			} ),
 			expect.anything()
 		);
+		expect( warn ).toHaveBeenCalled();
 	} );
 } );
diff --git a/packages/js/components/src/filter-picker/README.md b/packages/js/components/src/filter-picker/README.md
index 080d7435855..258aa1a013f 100644
--- a/packages/js/components/src/filter-picker/README.md
+++ b/packages/js/components/src/filter-picker/README.md
@@ -1,5 +1,4 @@
-Filter Picker
-===
+# Filter Picker

 Modify a url query parameter via a dropdown selection of configurable options. This component manipulates the `filter` query parameter.

@@ -78,5 +77,38 @@ The `filters` prop is an array of filter objects. Each filter object should have
 - `component`: String - A custom component used instead of a button, might have special handling for filtering. TBD, not yet implemented.
 - `label`: String - The label for this filter. Optional only for custom component filters.
 - `path`: String - An array representing the "path" to this filter, if nested.
+- `settings`: Object - Settings for a filter with a `component`, or for a comparison filter. See the `settings` structure below.
 - `subFilters`: Array - An array of more filter objects that act as "children" to this item. This set of filters is shown if the parent filter is clicked.
 - `value`: String - The value for this filter, used to set the `filter` query param when clicked, if there are no `subFilters`.
+
+### `settings` structure
+
+Two kinds of filter use `settings`. A filter with `component: 'Search'` renders a `Search` component in the dropdown, and `FilterPicker` reads the keys below. A comparison filter (a filter whose `value` starts with `compare`) renders a card instead, and the `Filters` component forwards its `settings` to [CompareFilter](../compare-filter/README.md) as props (`getLabels`, `param`, `labels.title`, `labels.update`, `labels.helpText`, `searchProps`).
+
+A `component: 'Search'` filter has the following `settings` format:
+
+- `param`: String - The url parameter the selected value is stored in.
+- `getLabels`: Function - Function used to fetch labels for the selected values, returns a Promise.
+- `labels.button`: String - Label shown in the dropdown button next to the selected value.
+- `searchProps`: Object - Props forwarded to the `Search` component, except `selected`, `onChange`, `inlineTags`, and `staticResults`. `searchProps.type` is required. See [Search](../search/README.md) for the full list.
+
+```jsx
+{
+	component: 'Search',
+	value: 'single_product',
+	path: [ 'select_product' ],
+	settings: {
+		param: 'products',
+		getLabels: getProductLabels,
+		labels: {
+			button: 'Single product',
+		},
+		searchProps: {
+			type: 'products',
+			placeholder: 'Type to search for a product',
+		},
+	},
+}
+```
+
+The `type`, `autocompleter`, and `labels.placeholder` settings are deprecated. Use `searchProps.type`, `searchProps.autocompleter`, and `searchProps.placeholder` instead. When a deprecated setting is set, it takes precedence over the matching `searchProps` value, so extensions that change it on core filters keep working. The deprecated settings are scheduled for removal in `@woocommerce/components` 15.0.0.
diff --git a/packages/js/components/src/filter-picker/index.js b/packages/js/components/src/filter-picker/index.js
index f962942a3ff..277d208bd51 100644
--- a/packages/js/components/src/filter-picker/index.js
+++ b/packages/js/components/src/filter-picker/index.js
@@ -6,7 +6,16 @@ import { Button, Dropdown } from '@wordpress/components';
 import { focus } from '@wordpress/dom';
 import clsx from 'clsx';
 import { createElement, Component } from '@wordpress/element';
-import { find, partial, last, get, includes } from 'lodash';
+import deprecated from '@wordpress/deprecated';
+import {
+	find,
+	partial,
+	last,
+	get,
+	includes,
+	omitBy,
+	isUndefined,
+} from 'lodash';
 import PropTypes from 'prop-types';
 import { Icon, chevronLeft } from '@wordpress/icons';
 import {
@@ -24,6 +33,37 @@ import Search from '../search';

 export const DEFAULT_FILTER = 'all';

+/**
+ * Get the props to forward to the `Search` component of a filter.
+ * The deprecated `type`, `autocompleter`, and `labels.placeholder` settings take precedence over `settings.searchProps`,
+ * so extensions that change them on core filters keep working.
+ *
+ * @param {Object} settings The `settings` of a filter with a `component`.
+ * @return {Object} Props for the `Search` component.
+ */
+function getSearchProps( settings ) {
+	const { type, autocompleter, labels = {}, searchProps = {} } = settings;
+	const legacySearchProps = omitBy(
+		{ type, autocompleter, placeholder: labels.placeholder },
+		isUndefined
+	);
+
+	if ( Object.keys( legacySearchProps ).length > 0 ) {
+		deprecated(
+			'Passing `type`, `autocompleter`, or `labels.placeholder` in the `settings` of a FilterPicker filter',
+			{
+				since: '14.2.0',
+				version: '15.0.0',
+				alternative: '`settings.searchProps`',
+				link: 'https://github.com/woocommerce/woocommerce/blob/trunk/packages/js/components/src/filter-picker/README.md',
+				plugin: '@woocommerce/components',
+			}
+		);
+	}
+
+	return { ...searchProps, ...legacySearchProps };
+}
+
 /**
  * Modify a url query parameter via a dropdown selection of configurable options.
  * This component manipulates the `filter` query parameter.
@@ -202,7 +242,7 @@ class FilterPicker extends Component {

 	renderButton( filter, onClose, config ) {
 		if ( filter.component ) {
-			const { type, labels, autocompleter } = filter.settings;
+			const searchProps = getSearchProps( filter.settings );
 			const persistedFilter = this.getFilter();
 			const selectedTag =
 				persistedFilter.value === filter.value
@@ -211,10 +251,11 @@ class FilterPicker extends Component {

 			return (
 				<Search
-					autocompleter={ autocompleter }
-					className="woocommerce-filters-filter__search"
-					type={ type }
-					placeholder={ labels.placeholder }
+					{ ...searchProps }
+					className={ clsx(
+						'woocommerce-filters-filter__search',
+						searchProps.className
+					) }
 					selected={ selectedTag ? [ selectedTag ] : [] }
 					onChange={ partial(
 						this.onTagChange,
@@ -411,6 +452,45 @@ FilterPicker.propTypes = {
 				 * An array representing the "path" to this filter, if nested.
 				 */
 				path: PropTypes.string,
+				/**
+				 * Settings for a filter with a `component`, or for a comparison filter.
+				 */
+				settings: PropTypes.shape( {
+					/**
+					 * The url parameter the selected value is stored in.
+					 */
+					param: PropTypes.string,
+					/**
+					 * Function used to fetch labels for the selected values, returns a Promise.
+					 */
+					getLabels: PropTypes.func,
+					/**
+					 * Object of localized labels. `button` is shown in the dropdown button next to the selected value.
+					 */
+					labels: PropTypes.shape( {
+						button: PropTypes.string,
+						/**
+						 * @deprecated Use `searchProps.placeholder` instead.
+						 */
+						placeholder: PropTypes.string,
+					} ),
+					/**
+					 * Props forwarded to the `Search` component, except `selected`, `onChange`, `inlineTags`, and `staticResults`.
+					 */
+					searchProps: PropTypes.shape( {
+						type: PropTypes.string,
+						autocompleter: PropTypes.object,
+						placeholder: PropTypes.string,
+					} ),
+					/**
+					 * @deprecated Use `searchProps.type` instead.
+					 */
+					type: PropTypes.string,
+					/**
+					 * @deprecated Use `searchProps.autocompleter` instead.
+					 */
+					autocompleter: PropTypes.object,
+				} ),
 				/**
 				 * An array of more filter objects that act as "children" to this item.
 				 * This set of filters is shown if the parent filter is clicked.
diff --git a/packages/js/components/src/filter-picker/test/index.js b/packages/js/components/src/filter-picker/test/index.js
index f30395be38f..389fd50f820 100644
--- a/packages/js/components/src/filter-picker/test/index.js
+++ b/packages/js/components/src/filter-picker/test/index.js
@@ -4,6 +4,7 @@
 import { render } from '@testing-library/react';
 import userEvent from '@testing-library/user-event';
 import { createElement } from '@wordpress/element';
+import { logged } from '@wordpress/deprecated';

 /**
  * Internal dependencies
@@ -27,7 +28,18 @@ describe( 'FilterPicker', () => {
 	} );
 	describe( "when a config is given with a filter with `component: 'Search'`", () => {
 		let config;
+		let warn;
+		const openDropdown = ( { queryAllByRole } ) => {
+			// The main dropdown does not have its role defined, so we need to dig deeper into actual internals.
+			userEvent.click( queryAllByRole( 'button' )[ 0 ] );
+		};
+		const getLastSearchProps = () =>
+			Search.mock.calls.slice( -1 )[ 0 ][ 0 ];
+
 		beforeEach( () => {
+			// Reset the deprecation messages, so each test can assert its own warning.
+			Object.keys( logged ).forEach( ( key ) => delete logged[ key ] );
+			warn = jest.spyOn( console, 'warn' ).mockImplementation( () => {} );
 			config = {
 				label: 'Show',
 				staticParams: [],
@@ -41,12 +53,14 @@ describe( 'FilterPicker', () => {
 						chartMode: 'item-comparison',
 						path: 'select_product',
 						settings: {
-							type: 'products',
 							param: 'products',
 							labels: {
-								placeholder: 'Type to search for a product',
 								button: 'Single Product',
 							},
+							searchProps: {
+								type: 'products',
+								placeholder: 'Type to search for a product',
+							},
 						},
 					},
 				],
@@ -84,32 +98,95 @@ describe( 'FilterPicker', () => {
 			// Following will check if it was rendered, not neceserily being visible now.
 			expect( Search ).toHaveBeenCalled();
 		} );
-		it( "for a `'custom'` type should forward autocompleter config the Search component", async () => {
-			const path = '/foo/bar';
+		it( 'should forward `searchProps` to the Search component', async () => {
+			config.filters[ 1 ].settings.searchProps = {
+				type: 'custom',
+				autocompleter: productAutocompleter,
+				placeholder: 'Type to search for a product',
+				showClearButton: true,
+			};

-			const customFilterSettings = config.filters[ 1 ].settings;
-			customFilterSettings.type = 'custom';
-			customFilterSettings.autocompleter = productAutocompleter;
+			openDropdown(
+				render( <FilterPicker path="/foo/bar" config={ config } /> )
+			);

-			const { queryAllByRole } = render(
-				<FilterPicker path={ path } config={ config } />
+			// Check that Search was rendered with the given props, without checking its behavior/internals/implementation details.
+			expect( getLastSearchProps() ).toMatchObject( {
+				type: 'custom',
+				autocompleter: productAutocompleter,
+				placeholder: 'Type to search for a product',
+				showClearButton: true,
+			} );
+			expect( warn ).not.toHaveBeenCalled();
+		} );
+		it( 'should render a filter without `labels`', async () => {
+			delete config.filters[ 1 ].settings.labels;
+
+			openDropdown(
+				render( <FilterPicker path="/foo/bar" config={ config } /> )
 			);

-			// Emulate filter dropdown being opened.
-			// The main dropdown does not have its role defined, so we need to dig deeper into actual internals.
-			userEvent.click( queryAllByRole( 'button' )[ 0 ] );
+			expect( getLastSearchProps() ).toMatchObject( {
+				type: 'products',
+				placeholder: 'Type to search for a product',
+			} );
+			expect( warn ).not.toHaveBeenCalled();
+		} );
+		it( 'should keep its own Search props and merge `className`', async () => {
+			config.filters[ 1 ].settings.searchProps = {
+				type: 'products',
+				className: 'my-search',
+				inlineTags: false,
+				staticResults: false,
+			};

-			// Check that the given component was rendered, without checking its behavior/internals/implementation details.
-			//
-			// In vanilla HTML, we would check
-			// expect( filterPicker.querySelector('woo-search') ).to.have.a.property( 'autocompleter', autocompleter );
-			//
-			// Following will check if it was rendered with given props, not neceserily being visible now.
-			const lastCallArgs = Search.mock.calls.slice( -1 )[ 0 ];
-			expect( lastCallArgs[ 0 ] ).toHaveProperty(
-				'autocompleter',
-				productAutocompleter
+			openDropdown(
+				render( <FilterPicker path="/foo/bar" config={ config } /> )
 			);
+
+			expect( getLastSearchProps() ).toMatchObject( {
+				className: 'woocommerce-filters-filter__search my-search',
+				inlineTags: true,
+				staticResults: true,
+			} );
+		} );
+		it( 'should still forward the deprecated `type`, `autocompleter`, and `labels.placeholder` settings', async () => {
+			const settings = config.filters[ 1 ].settings;
+			delete settings.searchProps;
+			settings.type = 'custom';
+			settings.autocompleter = productAutocompleter;
+			settings.labels.placeholder = 'Type to search for a product';
+
+			openDropdown(
+				render( <FilterPicker path="/foo/bar" config={ config } /> )
+			);
+
+			expect( getLastSearchProps() ).toMatchObject( {
+				type: 'custom',
+				autocompleter: productAutocompleter,
+				placeholder: 'Type to search for a product',
+			} );
+			expect( warn ).toHaveBeenCalledWith(
+				expect.stringContaining( 'FilterPicker filter' )
+			);
+		} );
+		it( 'should prefer the deprecated settings over `searchProps`', async () => {
+			// An extension that changes the settings of a core filter the old way.
+			const settings = config.filters[ 1 ].settings;
+			settings.type = 'custom';
+			settings.autocompleter = productAutocompleter;
+			settings.labels.placeholder = 'Changed placeholder';
+
+			openDropdown(
+				render( <FilterPicker path="/foo/bar" config={ config } /> )
+			);
+
+			expect( getLastSearchProps() ).toMatchObject( {
+				type: 'custom',
+				autocompleter: productAutocompleter,
+				placeholder: 'Changed placeholder',
+			} );
+			expect( warn ).toHaveBeenCalled();
 		} );
 	} );
 	describe( 'getAllFilterParams', () => {
@@ -131,9 +208,9 @@ describe( 'FilterPicker', () => {
 							chartMode: 'item-comparison',
 							path: [ 'select_product' ],
 							settings: {
-								type: 'products',
 								param: 'param_1',
 								getLabels: () => {},
+								searchProps: { type: 'products' },
 							},
 						},
 					],
@@ -143,10 +220,10 @@ describe( 'FilterPicker', () => {
 					value: 'compare-products',
 					chartMode: 'item-comparison',
 					settings: {
-						type: 'products',
 						param: 'param_2',
 						getLabels: () => {},
 						onClick: () => {},
+						searchProps: { type: 'products' },
 					},
 				},
 			],
diff --git a/packages/js/components/src/filters/stories/filters.story.js b/packages/js/components/src/filters/stories/filters.story.js
index 723e72c6229..ae984ad8082 100644
--- a/packages/js/components/src/filters/stories/filters.story.js
+++ b/packages/js/components/src/filters/stories/filters.story.js
@@ -193,17 +193,19 @@ const advancedFilters = {
 };

 const compareFilter = {
-	type: 'products',
 	param: 'product',
 	getLabels() {
 		return Promise.resolve( [] );
 	},
 	labels: {
 		helpText: 'Select at least two products to compare',
-		placeholder: 'Search for products to compare',
 		title: 'Compare Products',
 		update: 'Compare',
 	},
+	searchProps: {
+		type: 'products',
+		placeholder: 'Search for products to compare',
+	},
 };

 export const Examples = () => (
diff --git a/plugins/woocommerce/changelog/32254-separate-search-props b/plugins/woocommerce/changelog/32254-separate-search-props
new file mode 100644
index 00000000000..287c77382da
--- /dev/null
+++ b/plugins/woocommerce/changelog/32254-separate-search-props
@@ -0,0 +1,4 @@
+Significance: minor
+Type: update
+
+Deprecate the `type`, `autocompleter`, and `labels.placeholder` settings of Analytics report filters in favor of `settings.searchProps`, which the core report filters now use.
diff --git a/plugins/woocommerce/client/admin/client/analytics/report/categories/config.js b/plugins/woocommerce/client/admin/client/analytics/report/categories/config.js
index 86fd0c51c23..3e41cda271e 100644
--- a/plugins/woocommerce/client/admin/client/analytics/report/categories/config.js
+++ b/plugins/woocommerce/client/admin/client/analytics/report/categories/config.js
@@ -90,15 +90,17 @@ const filterValues = [
 				chartMode: 'item-comparison',
 				path: [ 'select_category' ],
 				settings: {
-					type: 'categories',
 					param: 'categories',
 					getLabels: getCategoryLabels,
 					labels: {
+						button: __( 'Single Category', 'woocommerce' ),
+					},
+					searchProps: {
+						type: 'categories',
 						placeholder: __(
 							'Type to search for a category',
 							'woocommerce'
 						),
-						button: __( 'Single Category', 'woocommerce' ),
 					},
 				},
 			},
@@ -109,7 +111,6 @@ const filterValues = [
 		value: 'compare-categories',
 		chartMode: 'item-comparison',
 		settings: {
-			type: 'categories',
 			param: 'categories',
 			getLabels: getCategoryLabels,
 			labels: {
@@ -117,12 +118,15 @@ const filterValues = [
 					'Check at least two categories below to compare',
 					'woocommerce'
 				),
+				title: __( 'Compare Categories', 'woocommerce' ),
+				update: __( 'Compare', 'woocommerce' ),
+			},
+			searchProps: {
+				type: 'categories',
 				placeholder: __(
 					'Search for categories to compare',
 					'woocommerce'
 				),
-				title: __( 'Compare Categories', 'woocommerce' ),
-				update: __( 'Compare', 'woocommerce' ),
 			},
 			onClick: addCesSurveyForAnalytics,
 		},
diff --git a/plugins/woocommerce/client/admin/client/analytics/report/coupons/config.js b/plugins/woocommerce/client/admin/client/analytics/report/coupons/config.js
index 544977bba6f..c2299d6a8cb 100644
--- a/plugins/woocommerce/client/admin/client/analytics/report/coupons/config.js
+++ b/plugins/woocommerce/client/admin/client/analytics/report/coupons/config.js
@@ -78,15 +78,17 @@ const filterValues = [
 				chartMode: 'item-comparison',
 				path: [ 'select_coupon' ],
 				settings: {
-					type: 'coupons',
 					param: 'coupons',
 					getLabels: getCouponLabels,
 					labels: {
+						button: __( 'Single Coupon', 'woocommerce' ),
+					},
+					searchProps: {
+						type: 'coupons',
 						placeholder: __(
 							'Type to search for a coupon',
 							'woocommerce'
 						),
-						button: __( 'Single Coupon', 'woocommerce' ),
 					},
 				},
 			},
@@ -96,7 +98,6 @@ const filterValues = [
 		label: __( 'Comparison', 'woocommerce' ),
 		value: 'compare-coupons',
 		settings: {
-			type: 'coupons',
 			param: 'coupons',
 			getLabels: getCouponLabels,
 			labels: {
@@ -107,6 +108,9 @@ const filterValues = [
 					'woocommerce'
 				),
 			},
+			searchProps: {
+				type: 'coupons',
+			},
 			onClick: addCesSurveyForAnalytics,
 		},
 	},
diff --git a/plugins/woocommerce/client/admin/client/analytics/report/customers/config.js b/plugins/woocommerce/client/admin/client/analytics/report/customers/config.js
index ee4b752a17c..79735cae4d4 100644
--- a/plugins/woocommerce/client/admin/client/analytics/report/customers/config.js
+++ b/plugins/woocommerce/client/admin/client/analytics/report/customers/config.js
@@ -49,15 +49,17 @@ export const filters = applyFilters( CUSTOMERS_REPORT_FILTERS_FILTER, [
 						chartMode: 'item-comparison',
 						path: [ 'select_customer' ],
 						settings: {
-							type: 'customerNames',
 							param: 'customers',
 							getLabels: getCustomerLabels,
 							labels: {
+								button: __( 'Single Customer', 'woocommerce' ),
+							},
+							searchProps: {
+								type: 'customerNames',
 								placeholder: __(
 									'Type to search for a customer',
 									'woocommerce'
 								),
-								button: __( 'Single Customer', 'woocommerce' ),
 							},
 						},
 					},
diff --git a/plugins/woocommerce/client/admin/client/analytics/report/products/config.js b/plugins/woocommerce/client/admin/client/analytics/report/products/config.js
index 450e2c6d0f3..f65c6af2439 100644
--- a/plugins/woocommerce/client/admin/client/analytics/report/products/config.js
+++ b/plugins/woocommerce/client/admin/client/analytics/report/products/config.js
@@ -75,15 +75,17 @@ const filterConfig = {
 					chartMode: 'item-comparison',
 					path: [ 'select_product' ],
 					settings: {
-						type: 'products',
 						param: 'products',
 						getLabels: getProductLabels,
 						labels: {
+							button: __( 'Single product', 'woocommerce' ),
+						},
+						searchProps: {
+							type: 'products',
 							placeholder: __(
 								'Type to search for a product',
 								'woocommerce'
 							),
-							button: __( 'Single product', 'woocommerce' ),
 						},
 					},
 				},
@@ -94,7 +96,6 @@ const filterConfig = {
 			value: 'compare-products',
 			chartMode: 'item-comparison',
 			settings: {
-				type: 'products',
 				param: 'products',
 				getLabels: getProductLabels,
 				labels: {
@@ -102,12 +103,15 @@ const filterConfig = {
 						'Check at least two products below to compare',
 						'woocommerce'
 					),
+					title: __( 'Compare Products', 'woocommerce' ),
+					update: __( 'Compare', 'woocommerce' ),
+				},
+				searchProps: {
+					type: 'products',
 					placeholder: __(
 						'Search for products to compare',
 						'woocommerce'
 					),
-					title: __( 'Compare Products', 'woocommerce' ),
-					update: __( 'Compare', 'woocommerce' ),
 				},
 				onClick: addCesSurveyForAnalytics,
 			},
@@ -137,15 +141,17 @@ const variationsConfig = {
 					value: 'single_variation',
 					path: [ 'select_variation' ],
 					settings: {
-						type: 'variations',
 						param: 'variations',
 						getLabels: getVariationLabels,
 						labels: {
+							button: __( 'Single variation', 'woocommerce' ),
+						},
+						searchProps: {
+							type: 'variations',
 							placeholder: __(
 								'Type to search for a variation',
 								'woocommerce'
 							),
-							button: __( 'Single variation', 'woocommerce' ),
 						},
 					},
 				},
@@ -156,7 +162,6 @@ const variationsConfig = {
 			chartMode: 'item-comparison',
 			value: 'compare-variations',
 			settings: {
-				type: 'variations',
 				param: 'variations',
 				getLabels: getVariationLabels,
 				labels: {
@@ -164,12 +169,15 @@ const variationsConfig = {
 						'Check at least two variations below to compare',
 						'woocommerce'
 					),
+					title: __( 'Compare Variations', 'woocommerce' ),
+					update: __( 'Compare', 'woocommerce' ),
+				},
+				searchProps: {
+					type: 'variations',
 					placeholder: __(
 						'Search for variations to compare',
 						'woocommerce'
 					),
-					title: __( 'Compare Variations', 'woocommerce' ),
-					update: __( 'Compare', 'woocommerce' ),
 				},
 			},
 		},
diff --git a/plugins/woocommerce/client/admin/client/analytics/report/taxes/config.js b/plugins/woocommerce/client/admin/client/analytics/report/taxes/config.js
index 4bafcdeec46..3be37f2938b 100644
--- a/plugins/woocommerce/client/admin/client/analytics/report/taxes/config.js
+++ b/plugins/woocommerce/client/admin/client/analytics/report/taxes/config.js
@@ -122,7 +122,6 @@ const filterValues = [
 		value: 'compare-taxes',
 		chartMode: 'item-comparison',
 		settings: {
-			type: 'taxes',
 			param: 'taxes',
 			getLabels: getRequestByIdString(
 				NAMESPACE + '/taxes',
@@ -137,12 +136,15 @@ const filterValues = [
 					'Check at least two tax codes below to compare',
 					'woocommerce'
 				),
+				title: __( 'Compare Tax Codes', 'woocommerce' ),
+				update: __( 'Compare', 'woocommerce' ),
+			},
+			searchProps: {
+				type: 'taxes',
 				placeholder: __(
 					'Search for tax codes to compare',
 					'woocommerce'
 				),
-				title: __( 'Compare Tax Codes', 'woocommerce' ),
-				update: __( 'Compare', 'woocommerce' ),
 			},
 			onClick: addCesSurveyForAnalytics,
 		},
diff --git a/plugins/woocommerce/client/admin/client/analytics/report/variations/config.js b/plugins/woocommerce/client/admin/client/analytics/report/variations/config.js
index 24b8f643248..fb2d68b2bae 100644
--- a/plugins/woocommerce/client/admin/client/analytics/report/variations/config.js
+++ b/plugins/woocommerce/client/admin/client/analytics/report/variations/config.js
@@ -89,15 +89,17 @@ export const filters = applyFilters( VARIATIONS_REPORT_FILTERS_FILTER, [
 						value: 'single_variation',
 						path: [ 'select_variation' ],
 						settings: {
-							type: 'variations',
 							param: 'variations',
 							getLabels: getVariationLabels,
 							labels: {
+								button: __( 'Single variation', 'woocommerce' ),
+							},
+							searchProps: {
+								type: 'variations',
 								placeholder: __(
 									'Type to search for a variation',
 									'woocommerce'
 								),
-								button: __( 'Single variation', 'woocommerce' ),
 							},
 						},
 					},
@@ -108,7 +110,6 @@ export const filters = applyFilters( VARIATIONS_REPORT_FILTERS_FILTER, [
 				chartMode: 'item-comparison',
 				value: 'compare-variations',
 				settings: {
-					type: 'variations',
 					param: 'variations',
 					getLabels: getVariationLabels,
 					labels: {
@@ -116,12 +117,15 @@ export const filters = applyFilters( VARIATIONS_REPORT_FILTERS_FILTER, [
 							'Check at least two variations below to compare',
 							'woocommerce'
 						),
+						title: __( 'Compare Variations', 'woocommerce' ),
+						update: __( 'Compare', 'woocommerce' ),
+					},
+					searchProps: {
+						type: 'variations',
 						placeholder: __(
 							'Search for variations to compare',
 							'woocommerce'
 						),
-						title: __( 'Compare Variations', 'woocommerce' ),
-						update: __( 'Compare', 'woocommerce' ),
 					},
 					onClick: addCesSurveyForAnalytics,
 				},