For developers
Everything below is stable, public surface — the filter contract a theme implements, the helpers a child theme or another extension can call, and the option keys behind the settings page.
The theme contract
The extension detects whether the active theme is WooCommerce-aware and wires its settings to one of
two sets of filters. The detection happens at after_setup_theme (priority 99), after the theme has
had its say.
A WooCommerce-aware theme
If the theme declares WooCommerce support itself, the extension steps aside and feeds these filters instead — the theme owns the markup and decides what to do with the values:
| Filter | Default | Purpose |
|---|---|---|
unysonplus_woocommerce_loop_columns | 3 | Shop grid columns |
unysonplus_woocommerce_products_per_page | 12 | Products per page |
unysonplus_woocommerce_sidebar | none | Shop sidebar (none | left | right) |
unysonplus_woocommerce_thumbnail_columns | 4 | Gallery thumbnail columns |
unysonplus_woocommerce_related_count | 3 | Related-products count |
A theme can also override them directly, which wins over the saved setting:
// Force a 4-column shop grid regardless of what is saved in the settings.
add_filter( 'unysonplus_woocommerce_loop_columns', function () {
return 4;
} );
Any other theme
When no theme integration is present, the same values are applied through WooCommerce's own filters
— loop_shop_columns, loop_shop_per_page, woocommerce_product_thumbnails_columns and
woocommerce_output_related_products_args — plus add_theme_support( 'woocommerce' ) and the
gallery features, and a small baseline stylesheet.
You do not choose between the two paths; the extension picks the right one.
Helper functions
All of these are defined in the extension's helpers.php, are safe to call whether or not
WooCommerce is active, and are guarded with function_exists() so they can be safely called from a
theme that might run without the extension.
upwc_wc_catalog_mode()
if ( function_exists( 'upwc_wc_catalog_mode' ) && upwc_wc_catalog_mode() ) {
// The store is a lookbook — do not print a price or a cart button.
}
True when Catalog Mode is on, whether or not Disable Purchasing is on with it.
Catalog Mode works by unhooking WooCommerce's price / add-to-cart templates. Anything that renders a price or cart button without going through those hooks — a custom card, a widget, a theme partial — must check this helper, or it will keep selling from a shop that is meant to be a catalog.
upwc_wc_catalog_locked()
if ( function_exists( 'upwc_wc_catalog_locked' ) && upwc_wc_catalog_locked() ) {
return; // Nothing can be bought — this element has nothing to do.
}
True when Catalog Mode and Disable Purchasing are both on. Use it to hide anything that only makes sense in a shop that sells: a cart link, a mini-cart, a checkout button. The extension's own shop-only elements gate on exactly this.
upwc_wc_enquiry_html( $product )
echo upwc_wc_enquiry_html( $product ); // '' when the enquiry button is off or unconfigured
Returns the enquiry button markup for one product, or an empty string. Already escaped. Use it if you are rendering your own product card and want the same button the archives get.
Wishlist
upwc_wishlist_enabled(); // bool — feature on (and not catalog-locked)
upwc_wishlist_ids(); // int[] — this visitor's list, newest first
upwc_wishlist_has( $product_id ); // bool
upwc_wishlist_toggle( $product_id ); // [ 'ids' => int[], 'active' => bool ]
upwc_wishlist_save( $ids ); // int[] as stored
upwc_wishlist_button_html( $id ); // string — the heart, or '' when off
Signed-in visitors are stored in the upwc_wishlist user meta; guests in the upwc_wishlist
cookie. upwc_wishlist_ids() reads whichever applies, so you rarely need to know which.
add_action( 'upwc_wishlist_saved', function ( $ids ) {
// Fires on every add, remove and login-merge.
} );
// Cap the list (default 200).
add_filter( 'upwc_wishlist_max_items', fn() => 50 );
Compare
upwc_compare_enabled(); // bool
upwc_compare_ids(); // int[]
upwc_compare_max(); // int (2–6)
upwc_compare_button_html( $id ); // string
upwc_compare_table_html( $ids ); // string — the side-by-side table
Back in stock
upwc_bis_enabled(); // bool
upwc_bis_emails( $product_id ); // string[] — who is waiting
upwc_bis_subscribe( $product_id, $email ); // bool
// Rewrite the restock email.
add_filter( 'upwc_bis_notification', function ( $mail, $product, $emails ) {
$mail['message'] = my_template( $product );
$mail['headers'] = [ 'Content-Type: text/html; charset=UTF-8' ];
return $mail;
}, 10, 3 );
// Cap sign-ups per product (default 2000).
add_filter( 'upwc_bis_max_subscribers', fn() => 500 );
Swatches
upwc_swatches_enabled(); // bool
upwc_swatches_on_cards(); // bool
upwc_swatches_card_html( $product ); // string — the card swatches
upwc_swatch_term_visual( $term ); // [ 'type' => color|image|label, 'value' => … ]
// Where a term's colour / image is read from — add your own plugin's keys.
add_filter( 'upwc_swatch_color_meta_keys', fn( $keys ) => [ ...$keys, 'my_swatch_hex' ] );
add_filter( 'upwc_swatch_image_meta_keys', fn( $keys ) => [ ...$keys, 'my_swatch_img' ] );
// Above this many options an attribute keeps its dropdown (default 15).
add_filter( 'upwc_swatches_max_options', fn() => 24 );
Sticky add-to-cart and size guide
upwc_sticky_atc_enabled(); // bool
upwc_size_guide_enabled(); // bool
upwc_size_guide_content( $product_id ); // string — product's own, else the store default
The per-product size guide is the _upwc_size_guide post meta; back-in-stock sign-ups are
_upwc_bis_emails.
upwc_wc_truthy( $value )
Normalizes a stored switch value ('yes', '1', true, …) to a boolean. Settings are stored as
strings, so compare with this rather than == true.
Reading a setting
Every value on the settings page is stored in the extension's settings option:
$columns = fw_get_db_ext_settings_option( 'woocommerce', 'shop_columns' );
$mode = upwc_wc_truthy( fw_get_db_ext_settings_option( 'woocommerce', 'catalog_mode' ) );
Option keys
| Key | Type | Default |
|---|---|---|
shop_columns | select 2–6 | 3 |
products_per_page | text (number) | 12 |
shop_sidebar | none | left | right | none |
gallery_thumbnail_columns | select 2–6 | 4 |
related_count | text (number) | 3 |
gallery_zoom | yes | no | yes |
gallery_lightbox | yes | no | yes |
gallery_slider | yes | no | yes |
catalog_mode | yes | no | no |
catalog_lock_purchasing | yes | no | no |
catalog_closed_notice | textarea | (empty) |
catalog_enquiry | yes | no | no |
catalog_enquiry_label | text | Request a Quote |
catalog_enquiry_url | text | (empty) |
sale_badge_style | text | percent | text |
sticky_atc | yes | no | no |
sticky_atc_position | bottom | top | bottom |
sticky_atc_image | yes | no | yes |
wishlist | yes | no | no |
wishlist_page | text (URL) | (empty) |
compare | yes | no | no |
compare_page | text (URL) | (empty) |
compare_max | text (number) | 4 |
back_in_stock | yes | no | no |
back_in_stock_label | text | Email me when this is back |
back_in_stock_subject | text | {product} is back in stock |
swatches | yes | no | no |
swatches_cards | yes | no | no |
swatches_shape | circle | square | circle |
size_guide | yes | no | no |
size_guide_label | text | Size guide |
size_guide_content | wp-editor | (empty) |
ajax_add_to_cart | yes | no | yes |
show_breadcrumb | yes | no | yes |
Reacting to a save
add_action( 'fw_extension_settings_form_saved:woocommerce', function ( $before ) {
// $before = the values as they were prior to this save.
// Flush a cache, regenerate a stylesheet, …
} );
This fires whether the settings were saved from the Unyson+ → WooCommerce page or from the Extensions manager, so a listener does not need to know which screen was used.
What Disable Purchasing hooks
Useful to know if you are debugging why something will not add to the cart. When Catalog Mode and Disable Purchasing are both on, the extension registers:
| Hook | Value |
|---|---|
woocommerce_is_purchasable | false (priority 99) |
woocommerce_variation_is_purchasable | false (priority 99) |
woocommerce_get_price_html | '' (priority 99) |
woocommerce_loop_add_to_cart_link | '' (priority 99) |
woocommerce_add_to_cart_validation | false (priority 99) |
wp_loaded @5 | removes WC_Form_Handler::add_to_cart_action and scrubs the add-to-cart request parameter |
template_redirect | redirects Cart / Checkout to the shop, or renders the closed-shop message |
These are registered during after_setup_theme rather than on wp, because WooCommerce's
add-to-cart form handler runs on wp_loaded — waiting for the query would be too late to stop it.
To exempt something, run at a later priority than 99, or turn the setting off and implement your own policy.
AJAX endpoints
All of them are admin-ajax.php actions, nonce upwc_wc_storefront (localized to the front end as
upwcStorefront.nonce), available to signed-in visitors and guests alike.
| Action | Body | Returns |
|---|---|---|
upwc_wc_wishlist_toggle | product_id | ids, active, count |
upwc_wc_wishlist_get | — | ids, count |
upwc_wc_compare_toggle | product_id | ids, items, active, full, max, message |
upwc_wc_compare_get | — | ids, items, max |
upwc_wc_compare_clear | — | empty ids / items |
upwc_wc_bis_subscribe | product_id, email | message |
All are rate-limited (fw_rate_limit_ajax); the back-in-stock one hardest, since it writes an
address someone else will be emailed at.
The repaint event
Wishlist and compare controls are rendered "off" and switched on in the browser, so anything that injects product cards after load must say so:
document.dispatchEvent( new CustomEvent( 'upwc:products:updated' ) );
Load More, Quick View and AJAX filtering already do. Dispatch it from your own code if you inject cards another way.
The settings page
The settings page is FW_Woocommerce_Settings_Page and registers at
admin.php?page=fw-woocommerce-settings, under the fw-extensions parent menu, on admin_menu
priority 20. It only registers when the WooCommerce plugin is active.
It renders the extension's own settings-options.php schema through fw()->backend->render_options()
and saves on the page's load- hook, merging over stored values rather than writing wholesale. The
Extensions-manager card's Settings link is filtered (fw_ext_manager_settings_url) to point here,
so there is a single settings screen rather than two that can disagree.
Element rendering
The product-card markup for [wc_products], [wc_product] and the Load More / Quick View AJAX
endpoints is shared in includes/products-render.php, so the initial render, the appended pages and
the modal produce identical output. The mini-cart markup is shared between the wc_mini_cart
shortcode and the header/footer element by upwc_render_mini_cart() in
includes/mini-cart-render.php.
If you are adding an element that prints a price or a cart button, gate it on
upwc_wc_catalog_mode() / upwc_wc_catalog_locked() — that is the whole reason those helpers are
public.