Meta Shops (Facebook/Instagram) can deep‑link shoppers straight into your WooCommerce checkout with pre‑filled cart items (and an optional coupon). On nvdigitialsolutions.com we use a tiny WordPress snippet to parse Meta’s checkout_url, add the right products/quantities to the cart, preserve UTM/analytics params, and then redirect to a clean checkout page. Copy the improved snippet below, drop it in your theme (or a small must‑use plugin), and you’re live.
Why this matters
When shoppers tap “Checkout on website” inside a Facebook or Instagram Shop, you want the fastest possible path to purchase. Every extra click leaks conversions. Meta lets you attach a Checkout URL at the product or catalog level; the trick is mapping that link to a WooCommerce cart the moment the customer lands.
Our approach:
- Listen for a
?products=query string on any page. - Parse the product IDs and quantities (supports both Meta’s
wc_post_id_123:2and plain123:2). - Optionally apply a coupon via
?coupon=CODE. - Preserve analytics parameters (UTM,
fbclid,cart_origin). - Redirect to the canonical WooCommerce checkout URL so the URL stays clean but your tracking survives.
The URL anatomy Meta sends
We standardize on this pattern:
/checkout?products=wc_post_id_123:2,wc_post_id_456:1&coupon=CODE&utm_source=facebook&fbclid=…
- …
products– comma‑separated list ofproduct_id:qtypairs.- Meta may prefix IDs as
wc_post_id_123. We support both with and without the prefix.
- Meta may prefix IDs as
coupon– optional promo code to auto‑apply.- Any other params (UTMs,
fbclid,cart_origin) are carried into the final checkout URL for attribution.
Tip: You can safely point the Checkout URL to any front‑end path on your site; the snippet runs early and redirects to the Woo checkout.
The improved snippet (drop‑in ready)
/**
* Handle Meta (Facebook/Instagram Shops) checkout URLs
* Format: /checkout?products=wc_post_id_123:2,wc_post_id_456:1&coupon=CODE
*/
add_action( 'template_redirect', function() {
if ( ! isset( $_GET['products'] ) ) {
return;
}
// Ensure WooCommerce is loaded and cart is available
if ( ! function_exists( 'WC' ) || ! WC()->cart ) {
return;
}
// ✅ Start with a fresh cart per session entry
WC()->cart->empty_cart();
// Decode products parameter
$products_param = sanitize_text_field( wp_unslash( $_GET['products'] ) );
$items = array_filter( array_map( 'trim', explode( ',', $products_param ) ) );
foreach ( $items as $item ) {
$product_id = 0;
$qty = 0;
// Support both Meta's "wc_post_id_123:2" and Woo's native "123:2"
if ( preg_match( '/wc_post_id_(\d+):(\d+)/', $item, $m ) ) {
$product_id = absint( $m[1] );
$qty = absint( $m[2] );
} elseif ( preg_match( '/(\d+):(\d+)/', $item, $m ) ) {
$product_id = absint( $m[1] );
$qty = absint( $m[2] );
} else {
continue; // Skip malformed entries
}
if ( $product_id > 0 && $qty > 0 ) {
// Optional: guard against crazy quantities
$qty = min( $qty, 99 );
// If you use variations, swap in WC()->cart->add_to_cart( $product_id, $qty, $variation_id, $variation );
WC()->cart->add_to_cart( $product_id, $qty );
}
}
// ✅ Handle optional coupon
if ( isset( $_GET['coupon'] ) ) {
$coupon = sanitize_text_field( wp_unslash( $_GET['coupon'] ) );
if ( $coupon ) {
WC()->cart->apply_coupon( $coupon );
}
}
// Preserve UTM + cart_origin + fbclid for analytics
$extra_params = [];
$whitelist_keys = [ 'cart_origin', 'utm_source', 'utm_medium', 'utm_campaign', 'utm_content', 'fbclid' ];
foreach ( $whitelist_keys as $param ) {
if ( isset( $_GET[ $param ] ) ) {
$extra_params[ $param ] = sanitize_text_field( wp_unslash( $_GET[ $param ] ) );
}
}
// Redirect to clean checkout URL with tracking intact
$redirect_url = add_query_arg( $extra_params, wc_get_checkout_url() );
});
Notable fixes vs. the naive version
- Regex correctness:
\d+(digits) instead ofd+. - Input hardening:
array_filter+trimto ignore empty items; quantity capped. - Commented variation hook: makes it obvious how to support variable products later.
- Parameter whitelist: prevents forwarding unexpected query params into checkout.
How it works (step‑by‑step)
- Hook: We attach to
template_redirect, which fires on front‑end requests after WordPress resolves the queried object but before rendering. It’s early enough to change the cart and redirect cleanly. - Gate by presence: The function bails unless
?products=is present to avoid overhead on normal traffic. - Cart safety: We check
WC()->cartexists (e.g., Woo is active and the session is ready). If not, we do nothing. - Fresh cart:
WC()->cart->empty_cart()ensures the arriving session doesn’t inherit a stale cart from prior browsing. - Parse items: Each pair like
123:2becomesproduct_id=123andqty=2. If Meta sentwc_post_id_123:2, we strip the prefix. - Add to cart: We call
WC()->cart->add_to_cart()for each item. If you sell variations, you’ll passvariation_idand attributes. - Apply coupon: If
?coupon=exists, we apply it now so totals on checkout are correct on first paint. - Preserve attribution: We forward only the analytics parameters we care about (
utm_*,fbclid,cart_origin). - Redirect: We construct a final, canonical checkout URL via
wc_get_checkout_url()and 302 to it. The shopper lands on checkout with the cart and discounts already in place.
Where to put this code
- Best: A tiny must‑use plugin (MU plugin) so deployments and theme switches don’t affect it.
- Also fine: Your child theme’s
functions.phpor a site‑specific plugin.
Remember to clear any object/page cache and exclude the checkout endpoint from caching (server, CDN, and plugin level).
Edge cases & enhancements
- Variable products: If your catalog uses variations, generate URLs with the concrete
variation_idand attributes, then calladd_to_cart( $product_id, $qty, $variation_id, $variation_attrs ). - Stock checks:
add_to_cartrespects stock, but you can fail fast by checkingwc_get_product( $id )->is_in_stock(). - Max quantity: We cap quantities at
99as a sanity guard; adjust for your business rules. - Coupons: Invalid or expired coupons are ignored by Woo; you can surface a friendly notice via
wc_add_notice()if desired. - Security: We whitelist forwarded params and sanitize all inputs. If you need more, sign the URL with a hash and verify server‑side.
- Custom analytics: Extend
$whitelist_keys(e.g.,utm_term,utm_id,ttclid) to suit your ad platforms.
QA checklist (what we validate on nvdigitialsolutions.com)
- Landing on
/checkout?products=123:1empties the prior cart and adds product123qty1. - Landing on
/checkout?products=wc_post_id_123:2,456:1&coupon=SALE10adds both items and applies the coupon. - UTM params survive:
/checkout?...&utm_source=facebook&fbclid=…show up on the checkout URL and in analytics. - Invalid entries like
abcor::are safely ignored. - Hitting the same link twice creates the same deterministic cart.
Frequently asked
What if the Checkout URL goes to a blog post or the home page?
Totally fine. The hook watches any front‑end request for ?products= and then redirects to Woo checkout.
Do I need a dedicated /checkout page slug?
No. We call wc_get_checkout_url(), which respects whatever page WooCommerce is configured to use for checkout.
Will this work with caching/CDNs?
Yes, as long as the first landing path (where ?products= exists) is not aggressively cached, and the checkout endpoint is excluded from caching.
How do I generate these URLs at scale?
In your feed/export, build the products value from your WooCommerce product IDs and append any campaign UTMs.
Final thoughts
This pattern keeps your Meta Shops traffic snappy and attribution‑safe. It’s small, debuggable, and doesn’t fight WooCommerce. If you want us to wire this up or extend it for variations/bundles on nvdigitialsolutions.com, the snippet above is the exact starting point we ship.
Happy conversions!
