I am working on a WprdPress/WooCommerce plugin feature where specific Customers are shown only a subset of Products.
The user has a meta data field with an array of post ids corresponding to the allowed Products.
I am trying to hook into the main query:
add_action( 'pre_get_posts', 'customprefix_pre_get_posts' );
function customprefix_pre_get_posts( $query )
{
$user = wp_get_current_user();
if ( $query->is_main_query() && is_product_category() )
{
/** @var array $allowed Post IDs */
$allowed = customprefix_get_allowed_products_per_user( $user );
$query->set( 'post__in', $allowed );
}
}
I have tried many different combinations of how to add the post__in
clause to the query including $query->query_vars['post__in'] = $allowed
and others. I have tried adding $query->parse_query_vars()
as well with no success.
While diving further into the get_posts()
function in WP_Query
it appears that my change to the Query is returned correctly (by reference) from the pre_get_posts
hook. The next line: $q = $this->fill_query_vars( $q );
somehow looses my custom post__in
field.
Most of the documentation for WP_Query
and post__in
revolve around creating a new query, not modifying the main query.
Edit Per request: $allowed = [1234,1235,1236,1237]
Your approach is almost correct, but the issue likely stems from how WooCommerce modifies queries internally. The is_product_category() condition might not be sufficient because WooCommerce uses custom queries, and pre_get_posts can be overridden or modified by WooCommerce itself.
Try adding more specific conditions and adjusting the priority of the hook. WooCommerce uses custom post types (product), so we should also check for the post type explicitly.
Here's a refined version:
add_action( 'pre_get_posts', 'customprefix_pre_get_posts', 20 );
function customprefix_pre_get_posts( $query ) {
// Only modify the main WooCommerce product query on the front end
if ( ! is_admin() && $query->is_main_query() && ( is_product_category() || is_shop() ) ) {
$user = wp_get_current_user();
// Retrieve allowed product IDs
$allowed = customprefix_get_allowed_products_per_user( $user );
// If allowed products exist, set the 'post__in' query var
if ( ! empty( $allowed ) && is_array( $allowed ) ) {
$query->set( 'post__in', $allowed );
}
}
}
What's Changed?
Why we increased to 20: The reason I landed on 20 is based on how WordPress and WooCommerce handle query modifications.
In WordPress, the pre_get_posts hook can have different priorities, which determine the order in which functions attached to it are executed. By default, the priority is 10, but since WooCommerce often hooks into this filter at a later stage (after the main query is set up), increasing the priority helps to ensure that your modifications are applied after WooCommerce’s default behavior.
WooCommerce filters typically hook into the pre_get_posts action at a priority of 10 or 11, so by setting your function to run at priority 20, you ensure it’s applied afterward. This lets you modify the query after WooCommerce has done its filtering but before the query is actually run, preventing conflicts.
WooCommerce also adds additional filters for product-related queries, such as adjusting the product visibility or modifying the product category displays. These filters can be found in files like includes/class-wc-query.php, where WooCommerce hooks into pre_get_posts to control the queries for products, product categories, and shop pages.
By adjusting the priority to 20, you ensure your query filter is applied after WooCommerce's default query modifications but before the page renders. It helps make sure that your query adjustments for the user's allowed products take precedence at the right point in the execution flow.