Skip to content

Customizing Filters

Filter Sphere allows you to extend the default filtering capabilities with your own specific logic. This feature is particularly useful when you need to implement complex or domain-specific filtering that isn’t covered by the built-in filters.

To add custom filters to Filter Sphere, you can use the defineTypedFn function. This allows you to create type-safe, custom filter functions.

Use defineTypedFn to create a custom filter:

const customFilter = defineTypedFn({
name: "your filter name",
define: z.function({
input: [schemaToFilter, optionalUserInputSchema],
output: z.boolean(),
}),
implement: (value, userInput) => {
// Your filter logic here
return value === userInput;
},
});
  • name: A string describing your filter. Ensure it is unique and doesn’t duplicate any built-in filter names.
    • By default, the name will be used as the filter label in the UI. You can override this by overriding the mapFilterName property in the useFilterSphere hook.
  • define: A Zod schema defining the function signature.
    • First argument: The schema of the data being filtered.
    • Second argument (optional): The schema for user input, if required.
  • implement: The actual filter logic.

Add your custom filters to the filterFnList when using the useFilterSphere hook:

import { useFilterSphere, presetFilter } from "@fn-sphere/filter";
const { predicate, context } = useFilterSphere({
schema: yourSchema,
filterFnList: [
customFilter,
// Other custom filters...
...presetFilter, // Include preset filters if needed
],
});

This approach allows you to extend Filter Sphere’s functionality with your own custom filtering logic while maintaining type safety and integration with the existing system.

Here’s an example of using a custom filter function to filter the average of an array of numbers:

scoresaverage
[1]1
[1,10]5.5
[1,2,3,4,5]3
[6,7,8,9,10]8
import { z } from "zod";
import { Table } from "~/components/table";
import {
FilterSphereProvider,
FilterBuilder,
useFilterSphere,
presetFilter,
defineTypedFn,
type FnSchema,
} from "@fn-sphere/filter";
const data = [
{ scores: [1] },
{ scores: [1, 10] },
{ scores: [1, 2, 3, 4, 5] },
{ scores: [6, 7, 8, 9, 10] },
].map((item, index) => ({
...item,
average: item.scores.reduce((acc, curr) => acc + curr) / item.scores.length,
}));
const schema = z.object({
scores: z.array(z.number()),
});
const dataFilters: FnSchema[] = [
// Define a custom filter function
defineTypedFn({
name: "Average score is greater than",
define: z.function({
// The first argument is the schema that needs filtering
// and the second argument is the user input
input: [z.array(z.number()), z.number()],
output: z.boolean(),
}),
implement: (value, input) => {
// Implement the filter logic
const sum = value.reduce((acc, cur) => acc + cur);
return sum / value.length > input;
},
}),
// Preset filters contains all the built-in filters
...presetFilter,
];
export default function AdvancedFilter() {
const { predicate, context } = useFilterSphere({
schema,
// Pass the custom filters to the filterFnList
filterFnList: dataFilters,
});
return (
<FilterSphereProvider context={context}>
<FilterBuilder />
<Table data={data.filter(predicate)} />
</FilterSphereProvider>
);
}

Generic filters work across multiple data types, unlike custom filters that are bound to specific schemas. For example, the built-in equals filter works with strings, numbers, booleans, and union types without needing separate implementations.

Use defineGenericFn to create a generic filter:

nameageactive
Alice25true
Bob30false
alice35true
ALICE40false
import { z } from "zod";
import { Table } from "~/components/table";
import {
FilterSphereProvider,
FilterBuilder,
useFilterSphere,
defineGenericFn,
type FnSchema,
} from "@fn-sphere/filter";
import type {
$ZodBoolean,
$ZodString,
$ZodNumber,
$ZodUnion,
$ZodLiteral,
} from "zod/v4/core";
const data = [
{ name: "Alice", age: 25, active: true },
{ name: "Bob", age: 30, active: false },
{ name: "alice", age: 35, active: true },
{ name: "ALICE", age: 40, active: false },
];
const schema = z.object({
name: z.string(),
age: z.number(),
active: z.boolean(),
});
const genericFilter = defineGenericFn({
name: "generic equals",
// Define which types this filter can work with
genericLimit: (genericT): genericT is $ZodBoolean | $ZodString | $ZodNumber =>
genericT._zod.def.type === "boolean" ||
genericT._zod.def.type === "string" ||
genericT._zod.def.type === "number",
// Define the function signature based on the type
define: (genericT) =>
z.function({
input: [genericT, genericT],
output: z.boolean(),
}),
// Implement logic that handles all types
implement: (
value: boolean | string | number,
target: boolean | string | number,
) => {
if (typeof value === "string" && typeof target === "string")
return value.toLowerCase() === target.toLowerCase();
return value === target;
},
});
export default function GenericFilterExample() {
const { predicate, context } = useFilterSphere({
schema,
filterFnList: [genericFilter],
});
return (
<FilterSphereProvider context={context}>
<FilterBuilder />
<Table data={data.filter(predicate)} />
</FilterSphereProvider>
);
}

Key properties:

  • name: A unique string identifying your filter.
  • genericLimit: A type guard that determines which Zod types this filter applies to.
  • define: A function that receives the matched type and returns a Zod function schema.
  • implement: The filter logic that handles all specified types.