How Plane's Work Item Filter and Query System Works in the Utils Package
Plane's work item filter and query system utilizes a composable "rich-filter" architecture that converts UI selections into backend queries through configurable filter definitions and a store adapter that manages serialization between the MobX state, URL parameters, and API requests.
Plane implements this system within the @plane/utils package of the makeplane/plane repository to provide a type-safe, extensible filtering mechanism for work items. The architecture separates filter definitions—which specify available operators, labels, and UI behaviors—from the state management logic that translates user selections into query objects. This design allows developers to register new filters by creating configuration files without modifying core state management code.
Filter Configuration Architecture
Filter definitions reside in packages/utils/src/work-item-filters/configs/filters/ and describe the metadata and behavior for each available filter. Each definition specifies a unique filter key (id) used in query objects, a human-readable label, an optional icon (filterIcon), a set of supported operators (e.g., IN, EQ), and a UI configuration dictating whether the filter renders as single-select or multi-select with search capabilities.
The core utility createFilterConfig, located in packages/utils/src/filter.ts, generates these definitions. This helper returns a higher-order function that accepts concrete parameters—such as available filter values—and produces a fully-typed filter configuration object. This approach ensures type safety across the filter key generics while maintaining flexibility for filter-specific options.
Multi-Select UI Configuration
For filters requiring selection from predefined lists—such as State, State Group, or Assignee—the utils package exposes specialized helpers like getStateMultiSelectConfig and getStateGroupMultiSelectConfig. These functions construct a MultiSelectConfig object that defines how to render options, extract IDs and labels, and display icons.
The multi-select configuration integrates with the filter definition through the supportedOperatorConfigsMap, connecting collection operators to their corresponding UI components. This separation allows the same underlying data structures to power both the visual dropdown interface and the query generation logic.
Operator Mapping and Constraints
Each filter configuration maps supported operators to specific UI behaviors via the supportedOperatorConfigsMap property. For example, the State filter exclusively supports the IN collection operator, which internally utilizes the EQUALITY_OPERATOR.EXACT single-value operator when processing multi-select values.
This mapping ensures that UI components only present valid operator choices while the adapter correctly translates complex collection operations into the primitive equality checks required by the backend API. The operator definitions are imported from @plane/types and provide the type constraints that govern filter interactions throughout the system.
Store Adapter and Query Serialization
The shared-state package contains the workItemFiltersAdapter defined in packages/shared-state/src/utils/work-item-filters.helper.ts. This adapter serves as the bridge between the filter definitions and the MobX store, performing three critical functions:
- Registration: Imports and registers all filter configs exported from
@plane/utilsinto the centralized store. - Serialization: Converts active filter selections from the store into a query object format expected by the backend API (e.g.,
{ state: ["open", "in-progress"] }). - Deserialization: Parses query parameters from the URL and hydrates the store's filter state, maintaining bidirectional synchronization between the UI and browser navigation.
When components need to access or modify filters, they invoke the useWorkItemFilters() hook located in apps/web/core/hooks/store/work-item-filters/use-work-item-filters.ts. This hook retrieves current selections from the store, processes them through the adapter, and returns a filterQuery object suitable for API client consumption.
Implementing a Custom Filter
Extending the system requires creating a filter definition and registering it with the adapter. The following example demonstrates implementing a "Tag" filter:
// packages/utils/src/work-item-filters/configs/filters/tag.ts
import { createFilterConfig, getMultiSelectConfig } from "@plane/utils";
import { COLLECTION_OPERATOR, EQUALITY_OPERATOR } from "@plane/types";
export type TCreateTagFilterParams = {
tags: { id: string; name: string }[];
filterIcon: JSX.Element;
};
export const getTagMultiSelectConfig = (params, singleValueOperator) =>
getMultiSelectConfig(
{
items: params.tags,
getId: t => t.id,
getLabel: t => t.name,
getValue: t => t.id,
getIconData: () => null,
},
{ singleValueOperator, ...params },
params
);
export const getTagFilterConfig = <P extends string>(key: P) =>
(params: TCreateTagFilterParams) =>
createFilterConfig<P>({
id: key,
label: "Tag",
...params,
icon: params.filterIcon,
supportedOperatorConfigsMap: new Map([
[COLLECTION_OPERATOR.IN, (p) => getTagMultiSelectConfig(p, EQUALITY_OPERATOR.EXACT)],
]),
});
After defining the configuration, register the filter in the shared-state adapter:
// Registration within the adapter
import { getTagFilterConfig } from "@plane/utils/src/work-item-filters/configs/filters/tag";
workItemFiltersAdapter.register({
key: "tag",
configFactory: getTagFilterConfig("tag"),
});
Components consume the filter through the dedicated hook:
import { useWorkItemFilters } from "@/hooks/store/work-item-filters/use-work-item-filters";
function WorkItemList() {
const { filterQuery, setFilter } = useWorkItemFilters();
// filterQuery contains { tag: ["frontend", "bug"] }
const { data } = useQuery(["work-items", filterQuery], fetchWorkItems);
return (
<FilterDropdown
filterKey="tag"
onChange={value => setFilter("tag", value)}
/>
);
}
Key Implementation Files
The work item filter and query system spans several critical files within the repository:
packages/utils/src/work-item-filters/index.ts– Re-exports the complete filter configuration bundle.packages/utils/src/work-item-filters/configs/filters/state.ts– Contains full filter definitions for State and State Group filters, including UI configurations and operator mappings.packages/utils/src/filter.ts– Houses core utilities includingcreateFilterConfigandgetMultiSelectConfig.packages/shared-state/src/utils/work-item-filters.helper.ts– Implements theworkItemFiltersAdapterfor store registration and query serialization.apps/web/core/hooks/store/work-item-filters/use-work-item-filters.ts– Provides theuseWorkItemFiltershook for component-level filter access.
Summary
- Filter definitions in
packages/utils/src/work-item-filters/configs/specify metadata, operators, and UI behavior using thecreateFilterConfighelper. - Multi-select helpers like
getStateMultiSelectConfiggenerate the configuration objects that power filter dropdown interfaces. - Operator mapping through
supportedOperatorConfigsMapconnects collection operators (e.g.,IN) to specific UI renderers and equality checks. - Store adapter (
workItemFiltersAdapter) registers filters, serializes state into API query objects, and deserializes URL parameters. - Hook interface (
useWorkItemFilters) exposes the currentfilterQueryand mutation methods to React components. - The architecture is fully type-safe and extensible, requiring only a new config file and adapter registration to add filters.
Frequently Asked Questions
How do I add a new work item filter to Plane?
Create a new filter definition file in packages/utils/src/work-item-filters/configs/filters/ using the createFilterConfig helper to specify the filter key, label, supported operators, and UI configuration. Export a configuration factory function, then register it with the workItemFiltersAdapter in packages/shared-state/src/utils/work-item-filters.helper.ts using the register() method.
What operators are supported in Plane's filter system?
The system supports collection operators such as IN (for multi-select scenarios) and equality operators like EXACT through the COLLECTION_OPERATOR and EQUALITY_OPERATOR enums from @plane/types. Each filter definition explicitly declares its supported operators via the supportedOperatorConfigsMap, which maps operators to their corresponding UI configuration functions.
How does the filter state synchronize with the URL?
The workItemFiltersAdapter handles bidirectional synchronization between the MobX store and URL query parameters. When filter selections change in the store, the adapter serializes them into query string format and updates the browser URL. Conversely, when the page loads with query parameters, the adapter deserializes these values and hydrates the store state, ensuring the UI reflects the URL state.
Where is the filter query object generated for API calls?
The query object is generated within the useWorkItemFilters hook located at apps/web/core/hooks/store/work-item-filters/use-work-item-filters.ts. This hook retrieves the current filter state from the MobX store, processes it through the workItemFiltersAdapter serialization logic, and returns a filterQuery object (e.g., { state: ["open"], priority: ["high"] }) that API clients pass directly to backend endpoints.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →