Skip to main content

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

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 conditions field stores both collection conditions (array) and product filter (object)
  • Product filter is identified by its structure: object with rules, combination properties
  • Collection conditions is an array of collection objects
  • The filter is enabled when conditions !== null (no separate enabled field)

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

  1. No enabled field: The filter is enabled if conditions !== null
  2. No IDs in saved data: Rule IDs are only used for UI rendering (React keys), not saved to database
  3. Combination logic: combination field determines how multiple rules are combined (AND/OR)
  4. Legacy support: The code supports old format with logic and conditions fields for backward compatibility
  5. Zero is valid: The value 0 is 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 days
  • more_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 minValue and maxValue are required
  • Validation: minValue cannot be greater than maxValue

Example:

{
"type": "price_range",
"minValue": 10,
"maxValue": 100
}

Description: "Products where at least one variant has a price between $10 and $100"

Notes:

  • Both minValue and maxValue can be 0
  • Both minValue and maxValue are 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

OperatorSymbolAvailable ForDescription
Within the lastwithinnot_sold_daysProducts sold within the last X days
More thanmore_thannot_sold_daysProducts not sold within the last X days
Greater than>inventory_level, total_ordersValue is greater than the specified number
Less than<inventory_level, total_ordersValue is less than the specified number
Greater than or equal>=inventory_level, total_ordersValue is greater than or equal to the specified number
Less than or equal<=inventory_level, total_ordersValue is less than or equal to the specified number
Equals=inventory_level, total_ordersValue 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 touchedConditions to 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:

  1. If conditions === null or undefined: Valid (filter is disabled)
  2. If conditions is an array: Valid (collection conditions, skip product filter validation)
  3. If conditions is an object with rules:
    • If rules.length === 0: Invalid (filter enabled but empty - save button disabled)
    • If rules.length > 0: Each rule must be valid:
      • Valid type field
      • Required fields based on type:
        • price_range: Both minValue AND maxValue must be provided
        • not_sold_days, inventory_level, total_orders: Both operator AND value must be provided
      • All value, minValue, maxValue must be >= 0 (non-negative, 0 is valid)
      • For price_range: If both minValue and maxValue are provided, minValue \<= maxValue

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

  1. Enable/Disable Toggle: Checkbox to enable/disable the filter
  2. 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
  3. Combination Logic: Select AND/OR when multiple conditions exist
  4. Human-Readable Descriptions: Each condition shows a description below it (when valid)
  5. Confirmation Modal: Shows when disabling filter with existing conditions
  6. Auto-Expand: Automatically expands when conditions exist on load
  7. Error Display: Shows validation errors only after user interaction
  8. 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

  1. User checks "Enable product filter" checkbox
  2. Filter section expands
  3. User clicks "Add Condition" button (left-aligned)
  4. Popover shows available condition types with help text
  5. User selects a condition type
  6. Condition card appears with inputs
  7. User fills in the condition values
  8. Errors only appear after user starts typing (touched state)
  9. Human-readable description appears below the condition (when valid and no errors)
  10. User can add more conditions or change combination logic
  11. 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 use Math.max(0, ...)
  • Empty Values: Empty inputs are saved as null, not 0
  • Zero Values: Zero (0) is a valid value and is accepted, displayed, and saved
  • Display: Empty fields show empty string, 0 shows 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., operator and value are removed when changing to price_range)
  • Default Values:
    • not_sold_days: operator: "within", value: undefined
    • price_range: minValue: undefined, maxValue: undefined
    • inventory_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

  1. Filter Disabled (conditions === null): ✅ Valid
  2. Collection Conditions (conditions is array): ✅ Valid (skip validation)
  3. Filter Enabled with 0 Rules: ❌ Invalid (save button disabled)
  4. 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: Both minValue AND maxValue (both required)
    • Others: Both operator AND value
  • Value Constraints:
    • All values must be >= 0 (0 is valid)
    • price_range: minValue <= maxValue (both required)

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