
🔄 WooCommerce recently viewed products shortcode: ready-made code and setup
A visitor enters the store, browses five products, closes the tab, and a minute later forgets about the site. This is a familiar picture for any WooCommerce owner. A "recently viewed" block solves this problem: one click on a familiar card returns the visitor back into the funnel.
WooCommerce out of the box writes the IDs of viewed products to the woocommerce_recently_viewed cookie. This data already sits on your store's server, no separate tracking plugin needed. All that remains is to read the cookie and display the products. Below is a ready-made shortcode that does this in 30 lines of modern PHP.
💡 Quick overview:
- Understand how WooCommerce stores views in a cookie, one short paragraph of theory
- Copy the
[recently_viewed_products]shortcode into thefunctions.phpof a child theme or Code Snippets - Place the block on the cart page, in the product sidebar, or after an empty search, and bring visitors back
How WooCommerce stores view history
When a visitor opens a product page, WooCommerce silently appends that product's ID to the woocommerce_recently_viewed cookie. Format: a string with | separator, for example 123|456|789. The oldest entry is on the left, the newest on the right. The cookie is session-based: it lives until the browser is closed.
We don't need to write our own view tracking. WooCommerce already collects the data. The shortcode's task: read the string, split it into IDs, filter only existing and published products, and pass them to the built-in [products] mechanism.
This approach gives native WooCommerce markup: responsive grid, stock and price handling, "Add to Cart" button, everything the standard [products] shortcode can do, without reimplementing the display template.
Ready-made code: [recently_viewed_products] shortcode
Add this code to the functions.php of a child theme or via the Code Snippets plugin. It registers the [recently_viewed_products] shortcode with two parameters: per_page and columns.
1 /** 2 * Shortcode: [recently_viewed_products per_page="5" columns="4"] 3 * 4 * Displays products recently viewed via the native WooCommerce cookie. 5 * Uses the built-in [products] shortcode for rendering — no custom WP_Query. 6 */ 7 add_shortcode( 'recently_viewed_products', 'sd_recently_viewed_products' ); 8 9 function sd_recently_viewed_products( $atts ) { 10 $atts = shortcode_atts( array( 11 'per_page' => 5, 12 'columns' => 4, 13 ), $atts ); 14 15 // Read the WooCommerce cookie 16 $cookie = ! empty( $_COOKIE['woocommerce_recently_viewed'] ) 17 ? wp_unslash( $_COOKIE['woocommerce_recently_viewed'] ) 18 : ''; 19 20 if ( empty( $cookie ) ) { 21 return '<p>' . esc_html__( 'You have not viewed any products yet.', 'textdomain' ) . '</p>'; 22 } 23 24 // Parse and sanitize product IDs 25 $viewed_ids = array_filter( array_map( 'absint', explode( '|', $cookie ) ) ); 26 27 if ( empty( $viewed_ids ) ) { 28 return ''; 29 } 30 31 // Keep only the N most recent items (oldest first in cookie, so slice from start) 32 $viewed_ids = array_slice( $viewed_ids, 0, (int) $atts['per_page'] ); 33 34 $ids_string = implode( ',', $viewed_ids ); 35 36 return '<h3>' . esc_html__( 'Recently Viewed Products', 'textdomain' ) . '</h3>' 37 . do_shortcode( 38 "[products ids=\"$ids_string\" columns=\"{$atts['columns']}\" orderby=\"post__in\"]" 39 ); 40 }
Where to insert the code
**Child theme **functions.php, best option. The code survives parent theme updates and is stored in version control along with the rest of the site customization.
Code Snippets plugin, if there's no child theme. Install Code Snippets from WordPress.org from the directory, create a new snippet, paste the code, and activate. Works with any theme.
Don't insert the code into the parent theme's functions.php: you'll lose it on the first update.
Line-by-line breakdown
shortcode_atts()merges user parameters with defaults, standard WordPress function for processing shortcode attributes. Unlike the deprecatedextract(), it doesn't create variables from an array and doesn't pollute the scope.wp_unslash()removes escaping slashes from the cookie value, standard practice when reading superglobals in WordPress.explode('|', ...)splits the string123|456|789into an array of string IDs.array_map('absint', ...)turns each ID into a non-negative integer, protection against garbage in the cookie.array_filter()throws out zeros and empty values: products that no longer exist in the database are excluded at the ID level.array_slice()leaves only the N most recent views. The cookie writes old IDs first, fresh ones last, so we trim from the beginning of the array.implode(',', ...)assembles the string123,456,789for theidsparameter of the built-in shortcode.do_shortcode('[products ids="..."]')launches the native WooCommerce mechanism: thumbnails, titles, prices, "Add to Cart" button, all in the standard theme markup. Out of stock products are automatically not displayed.
No need to manually assemble <ul><li><a>.... The built-in [products] already knows how to do all this and is maintained by the WooCommerce team.
How to use the shortcode: parameters and examples
Place [recently_viewed_products] anywhere WordPress processes shortcodes: page, post, text widget, Shortcode block in the editor.
Parameter | Default | Purpose |
|---|---|---|
|
| How many products to show |
|
| Number of columns in the grid |
Examples:
[recently_viewed_products per_page="3" columns="3"], compact row of three products for the sidebar[recently_viewed_products per_page="8" columns="4"], two rows of four products for the cart page
If the visitor hasn't viewed any products yet, the shortcode displays a neutral message "You have not viewed any products yet", which can be replaced with your own text in the line with esc_html__().
In practice, setup takes three minutes. In the video above, a step-by-step breakdown of working with WooCommerce shortcodes and placing them on the site.
Where to place the shortcode: what works

Cart page, the strongest placement. The visitor has already collected products, but before checkout sees a reminder of what they viewed earlier. For stores with 50+ products, this returns a noticeable portion of abandoned views back to the cart.
Product page sidebar. The buyer is comparing several models, the "you viewed" block keeps alternatives in front of their eyes. Works especially well in electronics and clothing stores, where comparison is part of the selection process.
After an empty search. Best fallback: "Didn't find anything, but here's what you checked earlier." Instead of an empty page, the visitor sees familiar products and is more likely to stay on the site.
Add the shortcode to a sidebar text widget or via the Shortcode block in the Gutenberg editor. In theme hooks (woocommerce_after_cart, woocommerce_before_shop_loop), only if you're confident editing templates.
⁉️🤔 Frequently asked questions
Does the shortcode work with caching?
The shortcode depends on the
woocommerce_recently_viewedPHP cookie, which is read server-side. If the page is cached at the HTTP level (via Varnish, NGINX FastCGI Cache, or a plugin like WP Rocket), the shortcode content will also be cached, and one visitor may see "someone else's" viewed products. Solution: exclude pages with the shortcode from cache or enable WooCommerce-compatible cache mode (ESI or fragment caching).
Can I display viewed products without a shortcode, via a hook?
Yes. The
sd_recently_viewed_products()function returns a string, it can be called directly in any theme hook:echo sd_recently_viewed_products( array( 'per_page' => 4 ) );. Suitable hooks arewoocommerce_after_cart,woocommerce_after_single_product, andwoocommerce_before_shop_loop. Consider caching if the page is cached entirely.
How many products are stored in the cookie?
By default, WooCommerce stores the IDs of the last 10 viewed products. This is set in the WooCommerce code and is not configurable via the interface. The shortcode determines via the
per_pageparameter how many of those 10 to display.
Why is this approach better than a separate plugin?
Code of 30 lines adds no extra database queries, uses the same cookie that WooCommerce already wrote. A separate plugin often pulls its own tracking mechanism (additional cookies, JS events, database tables). For a typical store, the shortcode covers the scenario completely, without bloating the admin.
Why use [products], not your own WP_Query?
The built-in
[products]gives the theme's responsive grid, prices, "Add to Cart" button, stock handling and out of stock status, for free. With manualWP_Query, you'd have to reimplement all of this and maintain it through WooCommerce updates.
Shortcode in 30 lines: is it worth it
The woocommerce_recently_viewed cookie is data your store already collects. A 30-line shortcode turns it into a working "recently viewed" block without plugins and extra database queries.
- If you have a store with 50+ products, place the shortcode on the cart page and try an A/B test with a control group without the block.
- If you use aggressive caching, exclude the page with the shortcode or enable fragment caching.
- If the visitor hasn't viewed products, the shortcode quietly displays a neutral message and doesn't break the layout.
Add [recently_viewed_products] to your site and check in an incognito browser window: open a couple of products, then go to the page with the shortcode. If the block doesn't show what you viewed, check your caching settings, most likely that's the cause. What method of displaying viewed products do you use, shortcode, plugin, or theme hook? Write in the comments.



