Skip to main content
These hooks allow you to respond to product events and customize how products are displayed.

Actions

Actions fire when products are created, updated, deleted, or when stock levels change.

surecart/product_created

Fires when a new product is created.
Parameters
Action Parameters
add_action( 'surecart/product_created', function( $product, $data ) {
    // Send Slack notification about new product
    wp_remote_post( SLACK_WEBHOOK_URL, [
        'body' => json_encode([
            'text' => sprintf( '🆕 New product created: %s', $product->name )
        ]),
    ]);
}, 10, 2 );

surecart/product_updated

Fires when a product is updated.
Parameters
Action Parameters
add_action( 'surecart/product_updated', function( $product, $data ) {
    // Sync updated product info to Google Sheets
    $sheets_api_url = 'https://sheets.googleapis.com/v4/spreadsheets/' . SHEET_ID . '/values/Products:append';
    
    wp_remote_post( $sheets_api_url, [
        'headers' => [ 'Authorization' => 'Bearer ' . GOOGLE_API_TOKEN ],
        'body'    => json_encode([
            'values' => [[ $product->id, $product->name, $product->sku ?? '' ]]
        ]),
    ]);
}, 10, 2 );

surecart/product_deleted

Fires when a product is deleted.
Parameters
Action Parameters
add_action( 'surecart/product_deleted', function( $product, $data ) {
    // Remove product from external CRM
    wp_remote_request( 'https://api.crm.example.com/products/' . $product->id, [
        'method'  => 'DELETE',
        'headers' => [ 'Authorization' => 'Bearer ' . CRM_API_KEY ],
    ]);
}, 10, 2 );

surecart/product_stock_adjusted

Fires when a product’s stock level changes (e.g., after a purchase or manual adjustment). Products with variants will have stock tracked at both the product and variant level.
Parameters
Action Parameters
Stock Properties:
PropertyDescription
stockTotal on-hand inventory count
held_stockUnits purchased but not yet fulfilled/shipped
available_stockUnits available for purchase (stock - held_stock)
add_action( 'surecart/product_stock_adjusted', function( $product, $data ) {
    // Sync all stock levels to warehouse management system
    wp_remote_post( 'https://wms.example.com/api/inventory', [
        'headers' => [ 'Authorization' => 'Bearer ' . WMS_API_KEY ],
        'body'    => [
            'sku'             => $product->sku ?? $product->id,
            'on_hand'         => $product->stock,
            'held'            => $product->held_stock,
            'available'       => $product->available_stock,
        ],
    ]);
}, 10, 2 );
Low available stock alert:
add_action( 'surecart/product_stock_adjusted', function( $product, $data ) {
    // Alert when available stock (not held) is low
    if ( $product->available_stock <= 5 && $product->available_stock > 0 ) {
        wp_remote_post( SLACK_WEBHOOK_URL, [
            'body' => json_encode([
                'text' => sprintf( 
                    '⚠️ Low stock: %s has %d available (%d on hand, %d held)',
                    $product->name,
                    $product->available_stock,
                    $product->stock,
                    $product->held_stock
                )
            ]),
        ]);
    }
}, 10, 2 );
Sync product and variant stock levels:
add_action( 'surecart/product_stock_adjusted', function( $product, $data ) {
    // Fetch the product with its variants
    $product = \SureCart\Models\Product::with(['variants'])->find( $product->id );
    
    // Sync product-level stock
    $stock_data = [
        [
            'type'            => 'product',
            'id'              => $product->id,
            'sku'             => $product->sku ?? '',
            'stock'           => $product->stock,
            'available_stock' => $product->available_stock,
            'held_stock'      => $product->held_stock,
        ],
    ];
    
    // Include each variant's stock levels
    if ( ! empty( $product->variants->data ) ) {
        foreach ( $product->variants->data as $variant ) {
            $stock_data[] = [
                'type'            => 'variant',
                'id'              => $variant->id,
                'sku'             => $variant->sku ?? '',
                'stock'           => $variant->stock,
                'available_stock' => $variant->available_stock,
                'held_stock'      => $variant->held_stock,
            ];
        }
    }
    
    wp_remote_post( 'https://api.inventory.example.com/stock/bulk', [
        'headers' => [ 'Authorization' => 'Bearer ' . INVENTORY_API_KEY ],
        'body'    => json_encode( $stock_data ),
    ]);
}, 10, 2 );

Filters

Filters allow you to customize how products are displayed, including SEO meta tags, images, and related products.

Content Display

surecart/product/replace_content_with_product_info_part

Control whether product content is replaced with the product info template part. Return false to use custom templates.
add_filter( 'surecart/product/replace_content_with_product_info_part', function( $replace ) {
    // Disable replacement for specific conditions
    if ( is_page_template( 'custom-product-template.php' ) ) {
        return false;
    }
    return $replace;
} );

// Or disable entirely
add_filter( 'surecart/product/replace_content_with_product_info_part', '__return_false' );

sc_product_post_type_link_sc_collection

Filter which collection appears in product URLs when your permalink structure includes the collection slug. When your product permalinks are configured to include the collection (e.g., /products/%sc_collection%/%product%/), a product URL might look like /products/clothing/blue-t-shirt/. But what happens when a product belongs to multiple collections, like both “clothing” and “sale”? This filter lets you control which collection slug is used in the URL. Example scenario:
  • Product “Blue T-Shirt” belongs to collections: clothing, sale, featured
  • Without this filter, WordPress picks one (often unpredictably)
  • With this filter, you can ensure the URL is always /products/clothing/blue-t-shirt/ instead of /products/sale/blue-t-shirt/
Parameters
Filter Parameters
Always use the first assigned collection:
add_filter( 'sc_product_post_type_link_sc_collection', function( $term, $terms, $post ) {
    // Use the first collection assigned to the product
    return $terms[0] ?? $term;
}, 10, 3 );
Prioritize a specific collection when present:
add_filter( 'sc_product_post_type_link_sc_collection', function( $term, $terms, $post ) {
    // If product is in "clothing", always use that in the URL
    foreach ( $terms as $t ) {
        if ( $t->slug === 'clothing' ) {
            return $t;
        }
    }
    return $term;
}, 10, 3 );
Exclude certain collections from URLs:
add_filter( 'sc_product_post_type_link_sc_collection', function( $term, $terms, $post ) {
    // Never use "sale" or "featured" in URLs - these are promotional, not categorical
    $excluded_slugs = [ 'sale', 'featured', 'new-arrivals' ];
    
    // If current term is excluded, find a better one
    if ( in_array( $term->slug, $excluded_slugs, true ) ) {
        foreach ( $terms as $t ) {
            if ( ! in_array( $t->slug, $excluded_slugs, true ) ) {
                return $t;
            }
        }
    }
    
    return $term;
}, 10, 3 );

Images

surecart/product-line-item-image/fallback_src

Filter the fallback image for product line items when no image is set.
add_filter( 'surecart/product-line-item-image/fallback_src', function( $src, $product ) {
    return get_template_directory_uri() . '/images/placeholder.png';
}, 10, 2 );

surecart/product-list/thumbnail-cover-size

Filter the thumbnail size for product list items.
add_filter( 'surecart/product-list/thumbnail-cover-size', function( $size, $post_id ) {
    return 'medium_large';
}, 10, 2 );

// Or use different sizes based on context
add_filter( 'surecart/product-list/thumbnail-cover-size', function( $size, $post_id ) {
    if ( is_archive() ) {
        return 'medium';
    }
    return 'large';
}, 10, 2 );
Filter the limit for related products query.
add_filter( 'surecart_product_related_posts_query_limit', function( $limit ) {
    return 20; // Increase related products limit
} );
Filter the related products SQL query for advanced customization.
add_filter( 'surecart_product_related_posts_query', function( $query, $post_id ) {
    // Modify query parts
    // $query is an array with 'select', 'from', 'where', etc.
    return $query;
}, 10, 2 );

Product Page

surecart_product_page_query_args

Filter the WP_Query arguments used to fetch the product on a product page. By default, this queries a single published product by its post ID. Default query args:
[
    'post_type'      => 'sc_product',
    'posts_per_page' => 1,
    'post__in'       => [ $product_post_id ],
    'post_status'    => [ 'publish' ],
]
Allow previewing draft products:
add_filter( 'surecart_product_page_query_args', function( $args ) {
    // Allow admins to preview unpublished products
    if ( current_user_can( 'manage_options' ) ) {
        $args['post_status'] = [ 'publish', 'draft', 'private' ];
    }
    return $args;
} );
Remove post restriction for page builders:
add_filter( 'surecart_product_page_query_args', function( $args ) {
    // In editor context, allow any product to be displayed for previewing
    if ( is_admin() || defined( 'REST_REQUEST' ) ) {
        unset( $args['post__in'] );
    }
    return $args;
} );

Use Cases

Sync Product Metadata to External System

add_action( 'surecart/product_created', 'sync_product_to_erp', 10, 2 );
add_action( 'surecart/product_updated', 'sync_product_to_erp', 10, 2 );

function sync_product_to_erp( $product, $data ) {
    wp_remote_post( 'https://erp.example.com/api/products', [
        'body' => [
            'external_id' => $product->id,
            'name'        => $product->name,
            'sku'         => $product->sku ?? '',
            'description' => $product->description ?? '',
        ],
        'headers' => [
            'Authorization' => 'Bearer ' . ERP_API_KEY,
        ],
    ]);
}

// Sync stock levels separately when they change
add_action( 'surecart/product_stock_adjusted', function( $product, $data ) {
    wp_remote_patch( 'https://erp.example.com/api/products/' . $product->id, [
        'body'    => [
            'stock'           => $product->stock ?? 0,
            'available_stock' => $product->available_stock ?? 0,
            'held_stock'      => $product->held_stock ?? 0,
        ],
        'headers' => [ 'Authorization' => 'Bearer ' . ERP_API_KEY ],
    ]);
}, 10, 2 );

Low Stock Notifications

add_action( 'surecart/product_stock_adjusted', function( $product, $data ) {
    $low_stock_threshold = get_option( 'sc_low_stock_threshold', 10 );
    
    // Use available_stock to check what's actually purchasable
    if ( $product->available_stock <= $low_stock_threshold && $product->available_stock > 0 ) {
        // Send Slack notification
        wp_remote_post( SLACK_WEBHOOK_URL, [
            'body' => json_encode([
                'text' => sprintf( 
                    '⚠️ Low stock: %s has %d available (%d on hand, %d held)',
                    $product->name,
                    $product->available_stock,
                    $product->stock,
                    $product->held_stock
                )
            ]),
        ]);
    }
    
    if ( $product->available_stock === 0 ) {
        // Send out of stock alert (no units available for purchase)
        wp_remote_post( SLACK_WEBHOOK_URL, [
            'body' => json_encode([
                'text' => sprintf( '🚨 Out of stock: %s (held: %d)', $product->name, $product->held_stock )
            ]),
        ]);
    }
}, 10, 2 );

Use Custom Placeholder Image

add_filter( 'surecart/product-line-item-image/fallback_src', function( $src, $product ) {
    // Check if product has a category
    $terms = get_the_terms( $product->post, 'sc_collection' );
    
    if ( $terms && ! is_wp_error( $terms ) ) {
        $category_slug = $terms[0]->slug;
        $custom_placeholder = get_template_directory_uri() . '/images/placeholders/' . $category_slug . '.png';
        
        if ( file_exists( get_template_directory() . '/images/placeholders/' . $category_slug . '.png' ) ) {
            return $custom_placeholder;
        }
    }
    
    return get_template_directory_uri() . '/images/default-product.png';
}, 10, 2 );