Writing a Custom Product-Type Adapter
The plugin ships adapters for WooCommerce Product Bundles and Composite Products that enrich the AJAX stock payload with per-child / per-component data. You can add the same kind of support for any custom or third-party product type using a single filter — no core changes.
The extension point
ssap_stock_data_for_product filters the per-product stock payload before it’s returned in the AJAX response. It only runs when Refresh stock via AJAX is enabled.
- Params:
( array $data, WC_Product $product ) - Return: the (augmented)
$dataarray.
Default keys in $data:
| Key | Type | Meaning |
|---|---|---|
availability_html | string | WooCommerce’s rendered stock HTML |
is_in_stock | bool | $product->is_in_stock() |
is_purchasable | bool | $product->is_purchasable() |
add_to_cart_html | string | Rendered loop Add-to-Cart markup |
Minimal example
add_filter( 'ssap_stock_data_for_product', function ( $data, $product ) {
$data['my_field'] = (bool) get_post_meta( $product->get_id(), '_my_flag', true );
return $data;
}, 10, 2 );
A proper adapter (mirroring the built-ins)
Self-registering, and a no-op unless the target product type/plugin is present:
class My_SSAP_Adapter {
public static function maybe_register() {
// Bail if the target plugin/type isn't loaded — keeps this harmless everywhere else.
if ( ! class_exists( 'WC_Product_MyType' ) ) {
return;
}
add_filter( 'ssap_stock_data_for_product', array( __CLASS__, 'add_data' ), 10, 2 );
}
public static function add_data( $data, $product ) {
if ( ! is_object( $product ) || ! method_exists( $product, 'is_type' ) || ! $product->is_type( 'mytype' ) ) {
return $data;
}
// Guard every type-specific call with method_exists for cross-version safety.
$children = array();
if ( method_exists( $product, 'get_my_children' ) ) {
foreach ( $product->get_my_children() as $child ) {
$children[ (int) $child->get_id() ] = array(
'is_in_stock' => (bool) $child->is_in_stock(),
'is_purchasable' => (bool) $child->is_purchasable(),
);
}
}
$data['mytype_children'] = $children;
return $data;
}
}
add_action( 'init', array( 'My_SSAP_Adapter', 'maybe_register' ) );
Registering the filter without a class_exists guard, or skipping the is_type() check inside the callback. Without the guard your adapter does work (or fatals) when the target plugin is inactive; without the type check it runs for every product on the page, not just the type you meant to enrich.
Patterns to copy from the built-in adapters (includes/adapters/)
- Guard registration with
class_existsand each call withmethod_exists/is_type— so the adapter tolerates the plugin being absent or a different version. - Return only JSON-safe values — cast to
int/bool, key child data by ID. - Keep it cheap — this runs for every product on the page (subject to the
ssap_max_products_per_requestcap). Avoid heavy per-product queries; cache if you must.
This filter fires once per product in the batch, up to ssap_max_products_per_request (default 200). A get_post_meta() or database call inside it is multiplied by the number of products on the page, so read from the WC_Product object you already have, or cache the lookup, rather than querying per product.
Consuming it on the front end
The whole stock map is delivered to JavaScript via the ssap_stock_updated event:
jQuery( document.body ).on( 'ssap_stock_updated', function ( e, stock ) {
// stock[ productId ].mytype_children …
} );
Notes
- Data only appears in the response when Refresh stock via AJAX is enabled.
- The payload is per-page and capped — don’t bloat it.
- For the request/response shape, see Developer Reference.

Comments