Product Filter Feature Documentation
Overview
The Product Filter feature allows users to add an extra layer of filtering on top of their product selection. This feature enables schedule-level filtering that applies to all products selected for a schedule, helping merchants refine which products from their selection will be processed by scheduled actions.
Table of Contents
- Storage
- Data Structure
- Available Filter Types
- Operators
- State Management
- Validation
- User Interface
- Usage Examples
- Component Structure
Storage
Database Schema
The product filter is stored in the Schedule table in the conditions field as a JSON object:
model Schedule {
id Int @id @default(autoincrement())
title String
description String?
selectionType SelectionType
targetSchedule SelectionType @default(PRODUCTS)
selectionIds Json
filter String? // Store the rule-based filter string for ADVANCED selection
conditions Json? // Store product filter as JSON object OR collection conditions as JSON array
actions ScheduleAction[]
// ... other fields
}
Important Notes:
- The
conditionsfield stores both collection conditions (array) and product filter (object) - Product filter is identified by its structure: object with
rules,combinationproperties - Collection conditions is an array of collection objects
- The filter is enabled when
conditions !== null(no separateenabledfield)
Storage Path
- State Path:
global_settings.conditions - Database Field:
Schedule.conditions(JSON)
Data Structure
Saved Format
The product filter is saved to the database in the following format:
{
combination: "AND" | "OR", // How to combine multiple rules
rules: [ // Array of filter conditions (NO IDs in saved data)
{
type: "not_sold_days" | "price_range" | "inventory_level" | "total_orders",
operator?: string, // Operator for comparison (not for price_range)
value?: number, // Single value (not for price_range, accepts 0)
minValue?: number, // Minimum value (only for price_range, accepts 0)
maxValue?: number // Maximum value (only for price_range, accepts 0)
}
]
}
Key Points
- No
enabledfield: The filter is enabled ifconditions !== null - No IDs in saved data: Rule IDs are only used for UI rendering (React keys), not saved to database
- Combination logic:
combinationfield determines how multiple rules are combined (AND/OR) - Legacy support: The code supports old format with
logicandconditionsfields for backward compatibility - Zero is valid: The value
0is accepted and displayed for all numeric inputs
Example Saved Data
{
"combination": "AND",
"rules": [
{
"type": "not_sold_days",
"operator": "within",
"value": 30
},
{
"type": "price_range",
"minValue": 10,
"maxValue": 100
},
{
"type": "inventory_level",
"operator": ">=",
"value": 5
},
{
"type": "total_orders",
"operator": "=",
"value": 0
}
]
}
Available Filter Types
1. Sold in X Days (not_sold_days)
Filters products based on when they were last sold.
Operators:
within- Products sold within the last X daysmore_than- Products not sold within the last X days (sold more than X days ago)
Accepts:
operator:"within"|"more_than"(required)value: Number (number of days, minimum: 0, 0 is valid)
Example:
{
"type": "not_sold_days",
"operator": "within",
"value": 30
}
Description: "Products sold within the last 30 days"
Note: The value 0 is accepted and will display as "Products sold within the last 0 days"
2. Price Range (price_range)
Filters products within a specific price range.
Operators:
- None (uses min/max values directly)
Accepts:
minValue: Number (minimum price, can be 0, minimum: 0, 0 is valid, required)maxValue: Number (maximum price, can be 0, minimum: 0, 0 is valid, required)- Both
minValueandmaxValueare required - Validation:
minValuecannot be greater thanmaxValue
Example:
{
"type": "price_range",
"minValue": 10,
"maxValue": 100
}
Description: "Products where at least one variant has a price between $10 and $100"
Notes:
- Both
minValueandmaxValuecan be0 - Both
minValueandmaxValueare required (cannot be empty) - If
minValue > maxValue, validation will fail and show error on both fields - The filter applies if at least one variant of the product matches the price criteria
3. Inventory Level (inventory_level)
Filters products based on their inventory quantity.
Operators:
>- Greater than<- Less than>=- Greater than or equal<=- Less than or equal=- Equals
Accepts:
operator:">"|"<"|">="|"<="|"="(required)value: Number (quantity, minimum: 0, 0 is valid)
Example:
{
"type": "inventory_level",
"operator": ">=",
"value": 10
}
Description: "Products with inventory greater than or equal 10"
Example with 0:
{
"type": "inventory_level",
"operator": "=",
"value": 0
}
Description: "Products with inventory equals 0"
4. Orders Count (total_orders)
Filters products based on their total order count.
Operators:
>- Greater than<- Less than>=- Greater than or equal<=- Less than or equal=- Equals
Accepts:
operator:">"|"<"|">="|"<="|"="(required)value: Number (order count, minimum: 0, 0 is valid)
Example:
{
"type": "total_orders",
"operator": ">=",
"value": 5
}
Description: "Products with orders count greater than or equal 5"
Example with 0:
{
"type": "total_orders",
"operator": "=",
"value": 0
}
Description: "Products with orders count equals 0"
Operators
Operator Reference
| Operator | Symbol | Available For | Description |
|---|---|---|---|
| Within the last | within | not_sold_days | Products sold within the last X days |
| More than | more_than | not_sold_days | Products not sold within the last X days |
| Greater than | > | inventory_level, total_orders | Value is greater than the specified number |
| Less than | < | inventory_level, total_orders | Value is less than the specified number |
| Greater than or equal | >= | inventory_level, total_orders | Value is greater than or equal to the specified number |
| Less than or equal | <= | inventory_level, total_orders | Value is less than or equal to the specified number |
| Equals | = | inventory_level, total_orders | Value equals the specified number |
Default Operators
not_sold_days:"within"(default)inventory_level:">="(default)total_orders:">="(default)price_range: No operators (uses min/max values)
State Management
Global State Path
The product filter is stored in global state at:
global_settings.conditions
State Structure
// When product filter is enabled
global_settings.conditions = {
combination: "AND" | "OR",
rules: [
{
type: string,
operator?: string,
value?: number, // Can be 0
minValue?: number, // Can be 0
maxValue?: number // Can be 0
}
]
}
// When product filter is disabled
global_settings.conditions = null
Component State
The ScheduleProductFilter component:
- Reads from
global_settings.conditions - Detects product filter structure (object with
rules/combination) - Adds temporary IDs to rules for UI rendering (not saved)
- Tracks
touchedConditionsto control error display timing - Updates state directly via
updateState("global_settings.conditions", ...)
Validation
SaveBar Validation
The product filter is validated in SaveBarValidationAction.ts:
Validation Rules:
- If
conditions === nullorundefined: Valid (filter is disabled) - If
conditionsis an array: Valid (collection conditions, skip product filter validation) - If
conditionsis an object withrules:- If
rules.length === 0: Invalid (filter enabled but empty - save button disabled) - If
rules.length > 0: Each rule must be valid:- Valid
typefield - Required fields based on type:
price_range: BothminValueANDmaxValuemust be providednot_sold_days,inventory_level,total_orders: BothoperatorANDvaluemust be provided
- All
value,minValue,maxValuemust be>= 0(non-negative, 0 is valid) - For
price_range: If bothminValueandmaxValueare provided,minValue \<= maxValue
- Valid
- If
UI Validation
The component includes real-time validation with error display:
Error Display Rules:
- Errors only show after user interaction (touched state)
- Price range errors show on TextField components when
minValue > maxValue - Other condition errors show in a Banner below the inputs
- Condition descriptions are hidden when errors exist
Validation Functions:
validateCondition(): Validates a single condition and returns error message- Located in:
app/components/ScheduledItems/productFilter/utils/validation.ts
Validation Examples
Valid:
{
"combination": "AND",
"rules": [
{ "type": "price_range", "minValue": 10, "maxValue": 100 },
{ "type": "inventory_level", "operator": ">=", "value": 0 }
]
}
Invalid (enabled but empty):
{
"combination": "AND",
"rules": []
}
Invalid (missing required fields):
{
"combination": "AND",
"rules": [
{ "type": "inventory_level", "operator": ">=" }
// Missing "value"
]
}
Invalid (price range min > max):
{
"combination": "AND",
"rules": [
{ "type": "price_range", "minValue": 100, "maxValue": 50 }
// minValue > maxValue - validation error
]
}
User Interface
Features
- Enable/Disable Toggle: Checkbox to enable/disable the filter
- Condition Management:
- Add conditions via popover with condition type selection and help text
- Remove conditions (with delete button, only shown when multiple conditions exist)
- Edit condition values and operators
- Change condition type via dropdown
- Combination Logic: Select AND/OR when multiple conditions exist
- Human-Readable Descriptions: Each condition shows a description below it (when valid)
- Confirmation Modal: Shows when disabling filter with existing conditions
- Auto-Expand: Automatically expands when conditions exist on load
- Error Display: Shows validation errors only after user interaction
- Support Contact: Message at bottom for requesting additional condition types
UI Components
- ConditionCard: Individual condition card with type selector, inputs, and error display
- ConditionInput: Renders appropriate input fields based on condition type
- AddConditionButton: Button with popover for adding new conditions
- DisableModal: Confirmation modal when disabling filter with conditions
UI Flow
- User checks "Enable product filter" checkbox
- Filter section expands
- User clicks "Add Condition" button (left-aligned)
- Popover shows available condition types with help text
- User selects a condition type
- Condition card appears with inputs
- User fills in the condition values
- Errors only appear after user starts typing (touched state)
- Human-readable description appears below the condition (when valid and no errors)
- User can add more conditions or change combination logic
- On save, conditions are saved to
global_settings.conditions
Error Display Behavior
- Initial State: No errors shown (even if invalid)
- After Interaction: Errors appear when user:
- Types in a field
- Changes an operator
- Changes condition type (values are reset to defaults for the new type)
- Price Range: Errors show on both min and max TextFields when
minValue > maxValue - Other Conditions: Errors show in a red Banner below inputs
Usage Examples
Example 1: Filter by Price Range
{
"combination": "AND",
"rules": [
{
"type": "price_range",
"minValue": 20,
"maxValue": 50
}
]
}
Result: Products where at least one variant has a price between $20 and $50
Example 2: Multiple Conditions (AND)
{
"combination": "AND",
"rules": [
{
"type": "not_sold_days",
"operator": "within",
"value": 30
},
{
"type": "inventory_level",
"operator": ">=",
"value": 10
},
{
"type": "price_range",
"minValue": 15,
"maxValue": 100
}
]
}
Result: Products that:
- Were sold within the last 30 days AND
- Have inventory >= 10 AND
- Have at least one variant with price between $15 and $100
Example 3: Multiple Conditions (OR)
{
"combination": "OR",
"rules": [
{
"type": "inventory_level",
"operator": "<=",
"value": 5
},
{
"type": "total_orders",
"operator": "=",
"value": 0
}
]
}
Result: Products that:
- Have inventory <= 5 OR
- Have 0 orders
Example 4: Products Not Sold Recently
{
"combination": "AND",
"rules": [
{
"type": "not_sold_days",
"operator": "more_than",
"value": 90
}
]
}
Result: Products not sold within the last 90 days
Example 5: Zero Values
{
"combination": "AND",
"rules": [
{
"type": "price_range",
"minValue": 0,
"maxValue": 10
},
{
"type": "inventory_level",
"operator": "=",
"value": 0
}
]
}
Result: Products that:
- Have at least one variant with price between $0 and $10 AND
- Have inventory equals 0
Technical Notes
ID Handling
- UI IDs: Generated temporarily for React keys and condition updates
- Saved Data: IDs are stripped before saving using
removeIdsFromConditions() - Loading: IDs are regenerated when loading from database using
useMemo
Value Validation
- Minimum Value: All numeric inputs enforce
min={0}and useMath.max(0, ...) - Empty Values: Empty inputs are saved as
null, not0 - Zero Values: Zero (
0) is a valid value and is accepted, displayed, and saved - Display: Empty fields show empty string,
0shows as"0"in input
Error Display Timing
- Touched State: Tracks which conditions have been interacted with
- Error Display: Errors only show after condition is marked as "touched"
- Touched Triggers:
- User changes any field value
- User changes condition type (values are reset to defaults for the new type)
- Touched Cleanup: Removed from touched set when condition is deleted
Condition Type Changes
- Value Reset: When changing condition type, all values are reset to defaults for the new type
- Field Cleanup: Fields that don't belong to the new type are removed (e.g.,
operatorandvalueare removed when changing toprice_range) - Default Values:
not_sold_days:operator: "within",value: undefinedprice_range:minValue: undefined,maxValue: undefinedinventory_level,total_orders:operator: ">=",value: undefined
- Validation: Only fields relevant to the current type are validated (prevents errors from leftover fields)
Legacy Format Support
The component supports both old and new formats:
Old Format:
{
"enabled": true,
"logic": "AND",
"conditions": [...]
}
New Format:
{
"combination": "AND",
"rules": [...]
}
The component automatically detects and converts between formats.
Validation Rules Summary
SaveBar Validation
- Filter Disabled (
conditions === null): ✅ Valid - Collection Conditions (
conditionsis array): ✅ Valid (skip validation) - Filter Enabled with 0 Rules: ❌ Invalid (save button disabled)
- Filter Enabled with Rules: ✅ Valid if all rules are valid
Rule Validation
Each rule must have:
- Valid
type: One of the 4 supported types - Required Fields:
price_range: BothminValueANDmaxValue(both required)- Others: Both
operatorANDvalue
- Value Constraints:
- All values must be
>= 0(0 is valid) price_range:minValue <= maxValue(both required)
- All values must be
UI Validation
- Errors only show after user interaction (touched)
- Price range: Errors on TextFields when
minValue > maxValue - Other conditions: Errors in Banner below inputs
- Descriptions hidden when errors exist
Future Enhancements
- GraphQL query building for actual product filtering
- Support for more condition types (product type, vendor, tags, etc.)
- Preview matching products count
- Export/import filter configurations
- Bulk condition operations
- Condition templates/presets