Skip to content

Add multi-field search search_fields parameter to products REST API #59450

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 16 commits into from
Jul 14, 2025

Conversation

staskus
Copy link
Contributor

@staskus staskus commented Jul 4, 2025

Original PR: #59241
RFC: pfZP8i-cI-p2 -worth checking it first
WOOMOB-713
Documentation PR: woocommerce/woocommerce-rest-api-docs#271

Changes proposed in this Pull Request:

This pull request introduces a new, flexible search mechanism to the WooCommerce Products REST API (GET /wc/v3/products). Instead of expanding search_name_or_sku parameter even further to support global_unique_id, this implementation uses a combination of the existing search parameter and a new search_fields array parameter. This allows clients to specify which fields to search (name, sku, global_unique_id, description, short_description) and supports cross-field matching logic.

The search behavior has been updated to support cross-field matching same way as a search= parameter:

  • Tokenized: The search argument is treated as a collection of space-separated tokens.
  • Cross-Field Matching: All tokens must be present across any of the specified fields for a product to match. A product matches if all tokens are found distributed across the selected fields.
  • Partial Match: A word containing a token is a valid match.

Examples:

  • An argument value of blue shirt with search_fields=name will match a product with the name "Casual Blue Shirt" because both tokens exist in the same field (name), but not "Red Shirt".
  • scarf 987 with search_fields=name,global_unique_id will match a product with name "Premium Wool Scarf" and GTIN "9876543210" because the tokens can be found across the specified fields.
  • quality professionals with search_fields=description,short_description will match a product where "quality" appears in the short description and "professionals" appears in the description.

This new approach takes precedence over all other search-related parameters: search_name_or_sku, search_sku, and sku. If search_fields is present in a request, all others will be ignored for search logic.

This functionality is added to the v3 REST API products endpoint only.

How to test the changes in this Pull Request:

  1. Create products with the following details:

    • Product A:
      • Name: Premium Wool Scarf
      • SKU: SCARF-W-PREM
      • GTIN: 9876543210
      • Description: Essential tool for professionals.
      • Short Description: High quality craftsmanship.
    • Product B:
      • Name: Casual Blue Shirt
      • SKU: SHIRT-B-CASUAL
      • GTIN: 1234567890
      • Description: A premium quality winter scarf made from wool.
      • Short Description: Perfect for summer activities.
    • Product C:
      • Name: Red Cotton Tee
      • SKU: TEE-C-RED
      • GTIN: 0000000000
  2. Create an application password for your WordPress user in your user profile page from the WP admin area.

  3. Test same-field matching: Perform the following request to find "Product B" by searching for tokens that exist in the same field (name).

    curl -u <user>:<application_password> "https://localhost/wp-json/wc/v3/products?_fields=name,sku,global_unique_id&search_fields=name&search=blue+shirt"

    You should get one result: the "Casual Blue Shirt" product.

  4. Test individual field searches: Verify that single tokens work across all fields.

    # Find by name token
    curl -u <user>:<application_password> "https://localhost/wp-json/wc/v3/products?_fields=name,sku,global_unique_id&search_fields=name&search=scarf"
    
    # Find by SKU token  
    curl -u <user>:<application_password> "https://localhost/wp-json/wc/v3/products?_fields=name,sku,global_unique_id&search_fields=sku&search=SCARF"
    
    # Find by GTIN token
    curl -u <user>:<application_password> "https://localhost/wp-json/wc/v3/products?_fields=name,sku,global_unique_id&search_fields=global_unique_id&search=987"
    
    # Find by description token
    curl -u <user>:<application_password> "https://localhost/wp-json/wc/v3/products?_fields=name,sku,global_unique_id&search_fields=description&search=winter"
    
    # Find by short description token
    curl -u <user>:<application_password> "https://localhost/wp-json/wc/v3/products?_fields=name,sku,global_unique_id&search_fields=short_description&search=summer"
  5. Test cross-field matching: Confirm that tokens can match across different specified fields.

    # This should return results (tokens match across different fields)
    curl -u <user>:<application_password> "https://localhost/wp-json/wc/v3/products?_fields=name,sku,global_unique_id&search_fields=name,global_unique_id&search=scarf+987"
    
    # Test cross-field matching with content fields
    curl -u <user>:<application_password> "https://localhost/wp-json/wc/v3/products?_fields=name,sku,global_unique_id&search_fields=description,short_description&search=quality+professionals"
  6. Verify precedence: Confirm that search_fields and search override all other search parameters.

    # Overrides search_name_or_sku
    curl -u <user>:<application_password> "https://localhost/wp-json/wc/v3/products?_fields=name,sku,global_unique_id&search_fields=name&search=blue&search_name_or_sku=scarf"
    
    # Overrides search and search_sku
    curl -u <user>:<application_password> "https://localhost/wp-json/wc/v3/products?_fields=name,sku,global_unique_id&search_fields=name&search=blue&search=red&search_sku=TEE"
  7. Verify regression: Confirm that the older search parameters continue to work as expected when the new one is not present.

    • The following returns the "Casual Blue Shirt" product:

      curl -u <user>:<application_password> "https://localhost/wp-json/wc/v3/products?_fields=name,sku&search_name_or_sku=blue+shirt"
    • The following returns the "Red Cotton Tee" product:

      curl -u <user>:<application_password> "https://localhost/wp-json/wc/v3/products?_fields=name,sku&search=red"
    • The following returns the "Premium Wool Scarf" product:

      curl -u <user>:<application_password> "https://localhost/wp-json/wc/v3/products?_fields=name,sku&search_sku=SCARF"

Changelog entry

  • Automatically create a changelog entry from the details below.
Changelog Entry Details

Significance

  • Patch
  • Minor
  • Major

Type

  • Fix - Fixes an existing bug
  • Add - Adds functionality
  • Update - Update existing functionality
  • Dev - Development related task
  • Tweak - A minor adjustment to the codebase
  • Performance - Address performance issues
  • Enhancement - Improvement to existing functionality

Message

Add support for dynamic multi-field product search via search_fields and search parameters in the Products REST API endpoint with cross-field matching capability.

staskus added 2 commits July 4, 2025 17:59
… parameter

Introduces a flexible search mechanism for the v3 products endpoint by adding a `search_fields` parameter.

Previously, searching was limited to predefined parameter combinations like `search_name_or_sku`. This change allows clients to specify exactly which fields to search—`name`, `sku`, or `global_unique_id`—using the `search_fields` parameter in combination with the `search` parameter.

Key features:
- All search tokens must match within the same field (e.g., searching for "blue shirt" will not match a product named "blue" with a SKU of "shirt").
- The `search_fields` parameter takes precedence over all other search-related parameters to ensure predictable results.
- Full backward compatibility is maintained for existing search parameters.

Includes a comprehensive suite of PHPUnit tests to validate the new functionality, including parameter precedence, same-field matching logic, and security against invalid field names.
Refactored the build_dynamic_search_clauses method in the products REST API controller to use a mapping array for resolving API field names to database columns, replacing the previous switch statement.

This change makes it easier to extend the search functionality in the future and reduces code repetition.
@staskus staskus added the plugin: woocommerce Issues related to the WooCommerce Core plugin. label Jul 4, 2025
@staskus staskus marked this pull request as ready for review July 4, 2025 15:27
Copy link
Contributor

coderabbitai bot commented Jul 4, 2025

📝 Walkthrough

Walkthrough

The changes introduce a new search_fields parameter to the WooCommerce Products REST API, enabling dynamic multi-field product searches. The controller logic is updated to handle this parameter, prioritize it over legacy search options, and generate SQL queries accordingly. Comprehensive tests are added to validate the new search functionality and its precedence.

Changes

File(s) Change Summary
plugins/woocommerce/changelog/59450-woomob-713-add-global_unique_id-to-searched-fields-2 Added changelog entry describing support for dynamic multi-field product search via the search_fields parameter.
plugins/woocommerce/includes/rest-api/Controllers/Version3/class-wc-rest-products-controller.php Implemented search_fields parameter support in product search: added logic, SQL clause builder, and API docs.
plugins/woocommerce/tests/php/includes/rest-api/Controllers/Version3/class-wc-rest-products-controller-tests.php Added tests for search_fields covering single/multiple fields, precedence, validation, and backward compatibility.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant REST_API
    participant ProductsController
    participant Database

    Client->>REST_API: GET /wc/v3/products?search=foo&search_fields=name,sku
    REST_API->>ProductsController: prepare_objects_query(request)
    ProductsController->>ProductsController: Validate and tokenize search_fields
    ProductsController->>ProductsController: Build dynamic SQL WHERE clause
    ProductsController->>Database: Query products with dynamic search criteria
    Database-->>ProductsController: Return matching products
    ProductsController-->>REST_API: Return filtered products
    REST_API-->>Client: Respond with product data
Loading
sequenceDiagram
    participant Client
    participant REST_API
    participant ProductsController

    Client->>REST_API: GET /wc/v3/products?search=foo&search_fields=invalid
    REST_API->>ProductsController: prepare_objects_query(request)
    ProductsController->>ProductsController: Validate search_fields
    ProductsController-->>REST_API: Return 400 error (invalid field)
    REST_API-->>Client: Respond with error
Loading

📜 Recent review details

Configuration used: .coderabbit.yml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4125e51 and 364f9a0.

📒 Files selected for processing (1)
  • plugins/woocommerce/includes/rest-api/Controllers/Version3/class-wc-rest-products-controller.php (8 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • plugins/woocommerce/includes/rest-api/Controllers/Version3/class-wc-rest-products-controller.php
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (26)
  • GitHub Check: Core e2e tests 1/6 - @woocommerce/plugin-woocommerce [e2e]
  • GitHub Check: Blocks e2e tests 6/10 - @woocommerce/plugin-woocommerce [e2e]
  • GitHub Check: Blocks e2e tests 10/10 - @woocommerce/plugin-woocommerce [e2e]
  • GitHub Check: Blocks e2e tests 7/10 - @woocommerce/plugin-woocommerce [e2e]
  • GitHub Check: Blocks e2e tests 8/10 - @woocommerce/plugin-woocommerce [e2e]
  • GitHub Check: Blocks e2e tests 9/10 - @woocommerce/plugin-woocommerce [e2e]
  • GitHub Check: Blocks e2e tests 5/10 - @woocommerce/plugin-woocommerce [e2e]
  • GitHub Check: Core API tests - @woocommerce/plugin-woocommerce [api]
  • GitHub Check: Blocks e2e tests 1/10 - @woocommerce/plugin-woocommerce [e2e]
  • GitHub Check: Blocks e2e tests 3/10 - @woocommerce/plugin-woocommerce [e2e]
  • GitHub Check: Blocks e2e tests 4/10 - @woocommerce/plugin-woocommerce [e2e]
  • GitHub Check: Core e2e tests 3/6 - @woocommerce/plugin-woocommerce [e2e]
  • GitHub Check: Blocks e2e tests 2/10 - @woocommerce/plugin-woocommerce [e2e]
  • GitHub Check: Metrics - @woocommerce/plugin-woocommerce [performance]
  • GitHub Check: Core e2e tests 4/6 - @woocommerce/plugin-woocommerce [e2e]
  • GitHub Check: Core e2e tests 5/6 - @woocommerce/plugin-woocommerce [e2e]
  • GitHub Check: Core e2e tests 6/6 - @woocommerce/plugin-woocommerce [e2e]
  • GitHub Check: PHP: 7.4 WP: prerelease [WP 6.8.2-RC1] 1/2 - @woocommerce/plugin-woocommerce [unit:php]
  • GitHub Check: Core e2e tests 2/6 - @woocommerce/plugin-woocommerce [e2e]
  • GitHub Check: PHP: 7.4 WP: latest - 1 [WP 6.7.2] 2/2 - @woocommerce/plugin-woocommerce [unit:php]
  • GitHub Check: PHP: 7.4 WP: prerelease [WP 6.8.2-RC1] 2/2 - @woocommerce/plugin-woocommerce [unit:php]
  • GitHub Check: PHP: 8.4 WP: latest [WP latest] 1/2 - @woocommerce/plugin-woocommerce [unit:php]
  • GitHub Check: PHP: 7.4 WP: latest - 1 [WP 6.7.2] 1/2 - @woocommerce/plugin-woocommerce [unit:php]
  • GitHub Check: PHP: 8.4 WP: latest [WP latest] 2/2 - @woocommerce/plugin-woocommerce [unit:php]
  • GitHub Check: Lint - @woocommerce/plugin-woocommerce
  • GitHub Check: build
✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

github-actions bot commented Jul 4, 2025

Test using WordPress Playground

The changes in this pull request can be previewed and tested using a WordPress Playground instance.
WordPress Playground is an experimental project that creates a full WordPress instance entirely within the browser.

Test this pull request with WordPress Playground.

Note that this URL is valid for 30 days from when this comment was last updated. You can update it by closing/reopening the PR or pushing a new commit.

@staskus staskus requested a review from jorgeatorres July 4, 2025 15:44
Copy link
Contributor

github-actions bot commented Jul 4, 2025

Testing Guidelines

Hi @jorgeatorres ,

Apart from reviewing the code changes, please make sure to review the testing instructions (Guide) and verify that relevant tests (E2E, Unit, Integration, etc.) have been added or updated as needed.

Reminder: PR reviewers are required to document testing performed. This includes:

  • 🖼️ Screenshots or screen recordings.
  • 📝 List of functionality tested / steps followed.
  • 🌐 Site details (environment attributes such as hosting type, plugins, theme, store size, store age, and relevant settings).
  • 🔍 Any analysis performed, such as assessing potential impacts on environment attributes and other plugins, conducting performance profiling, or using LLM/AI-based analysis.

⚠️ Within the testing details you provide, please ensure that no sensitive information (such as API keys, passwords, user data, etc.) is included in this public issue.

Copy link
Member

@jorgeatorres jorgeatorres left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @staskus!

This is looking really good and tests as described. I left some feedback but nothing too serious.
I'm wondering whether search_fields is the best name for the argument, not because it is incorrect, but because it could give the impression that it can be used to search by any product field which is not the case. Even if that were possible, the search heuristics for these specific name/SKU/GTIN searches are not necessarily what others might expect for a generic search field. Not sure if something like sku_search_fields could work (even if SKU is not the only field involved).

If we can't find a different arg name, that's probably fine, though. Just curious if you have other ideas.

In any case, I think this implementation is way better than adding new args for every possible combination of fields. Let me know what you think!

staskus added 2 commits July 8, 2025 11:03
The REST API framework already handles validation of search_fields against
the allowed enum values, making the manual array_intersect validation
redundant. This simplifies the code by removing the duplicate validation
logic and relying on the framework's built-in validation.
Only include 'sku' in the search_fields enum when wc_product_sku_enabled()
returns true. This ensures consistency with other SKU-related functionality
and prevents SKU searches when the SKU feature is disabled.
@staskus staskus force-pushed the woomob-713-add-global_unique_id-to-searched-fields-2 branch from 5160c66 to 3e458c7 Compare July 8, 2025 12:29
@staskus
Copy link
Contributor Author

staskus commented Jul 8, 2025

@jorgeatorres, thanks for the review! I made improvements based on your comments.

Regarding the naming, it's complicated.

I agree that search_fields can sound broad, and its matching logic serves a specific search purpose, although I find it hard to come up with a good alternative. If we put a specific parameter (sku, name, global_id) into this parameter naming, we would come back to the problems of the previous implementation, making search_fields inflexible and closed for easy expansion.

  • search_by is a possible alternative, although it's similar to search_fields in its broadness.
  • search_by_identifier could communicate that we limit options by what we consider to be an identifier rather than every field.

Even if that were possible, the search heuristics for these specific name/SKU/GTIN searches are not necessarily what others might expect for a generic search field

Yes, that's an extra level of complexity. It's quite hard to tell whether the search tokens should or shouldn't be split between the SKU/Global Identifier/Name. We assume that you're looking for one of those and not aiming to look for something among multiple fields. Probably search= is the one designed for the generic behavior, and search_fields could be the more strict one. Could we cover the clarity with an API documentation? 🤔

@staskus
Copy link
Contributor Author

staskus commented Jul 8, 2025

@jorgeatorres Another idea proposed by the team is to keep search_fields= but expand it to support the same fields that search= does, which are name, description, and short_description. Then, search_fields would support [name, description, short_description, sku, global_unique_id].

As I understood, search_fields=name, description, short_description may not behave the same way as search= without passing search_fields since it may use a different matching logic across all the fields. However, we could be flexible on that for search_fields, especially if changing AND to OR doesn't induce a high performance penalty.

What would be your take on this idea?

@jorgeatorres
Copy link
Member

jorgeatorres commented Jul 9, 2025

Thanks for making the changes and for giving this a bit more thought @staskus! Things are looking good to me.

Another idea proposed by the team is to keep search_fields= but expand it to support the same fields that search= does, which are name, description, and short_description. Then, search_fields would support [name, description, short_description, sku, global_unique_id].

I think this makes sense. We don't have to change the search logic now. Maybe in the future we can offer different "matching" heuristics, but as long as we document that by using search_fields things will work in a given way, I'm good. Would you like to make this change, then?

If it's too much trouble, we can go with what we have and iterate later on. Just let me know what you'd like to do, before I do one final round of testing on the PR.

staskus and others added 3 commits July 9, 2025 11:12
…-field matching

- Add description and short_description fields to search_fields parameter
- Implement cross-field matching behavior (tokens match across any specified field)
- Update schema enum to include new content fields
- Add comprehensive test coverage for new functionality
- Maintain WordPress native search compatibility
- Fix code formatting and alignment issues
staskus and others added 2 commits July 9, 2025 14:49
Remove array_map('esc_sql', ...) calls in prepare_objects_query method that                                                                        were causing double-escaping of search tokens before passing to $wpdb->prepare().                                                                  This was preventing searches with special characters like apostrophes from                                                                        working correctly.
@staskus
Copy link
Contributor Author

staskus commented Jul 9, 2025

@jorgeatorres, thanks again for all the help.

@staskus staskus requested a review from jorgeatorres July 9, 2025 12:01
Copy link
Member

@jorgeatorres jorgeatorres left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @staskus!

Thanks for making the changes and for all your work on this and for submitting an update to the REST docs as well.
This looks good to me and I'm pre-approving with just one tiny (optional) feedback.

As discussed, this does search the way matching works. Previously searching for, say, scarf+987 using search_name_or_sku wouldn't have returned a match for a product with name "Scarf" and SKU "SKU987" but now it does. As long as this behavior is not problematic for you on the POS side, I'm good with it.

Let me know what you think.

}

$db_column = $column_map[ $field ];
$field_token_clauses[] = '(' . $db_column . ' LIKE ' . $wpdb->prepare( '%s', $like_search ) . ')';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe something along these lines would be a tiny bit easier to grasp?

Suggested change
$field_token_clauses[] = '(' . $db_column . ' LIKE ' . $wpdb->prepare( '%s', $like_search ) . ')';
$field_token_clauses[] = $wpdb->prepare( "({$db_column} LIKE %s)", $like_search );

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, definitely reads better 👍

@staskus
Copy link
Contributor Author

staskus commented Jul 11, 2025

@jorgeatorres thank you for the review! Yes, the change in the search behavior is not problematic.

Should I know anything about deployment or merging is enough?

@jorgeatorres
Copy link
Member

Should I know anything about deployment or merging is enough?

Merging should be enough after the approval.

staskus and others added 4 commits July 14, 2025 09:52
Refactor SQL query construction in build_dynamic_search_clauses() to use more common WordPress coding patterns. Replace string concatenation approach with wpdb::prepare() for the entire SQL clause.
@staskus staskus merged commit 4702ce2 into trunk Jul 14, 2025
39 checks passed
@staskus staskus deleted the woomob-713-add-global_unique_id-to-searched-fields-2 branch July 14, 2025 08:50
Copy link
Contributor

⚠️ API Documentation Reminder

Hi @staskus! Your PR contains REST API changes. Please consider updating the REST API documentation if your changes affect the public API.

Changed API files:

plugins/woocommerce/includes/rest-api/Controllers/Version3/class-wc-rest-products-controller.php

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
plugin: woocommerce Issues related to the WooCommerce Core plugin.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy