Content A/B testing with the visual editor
A content campaign changes elements on your existing pages, such as headlines, images, buttons, sections and their order, without touching your theme code. You make the changes in Personizely's visual editor, then run them as an A/B test against the original page or as a personalization for a specific audience.
Available on: all platforms (Shopify, Shopify Hydrogen, Wix, WordPress and WooCommerce, Webflow, Square, and any site with the script tag). Plan: content campaigns must be included in your plan. Available as: A/B test · Personalization
Test or personalize. You can run a content campaign as an A/B test, where visitors are split at random and you measure which version wins, or as a personalization, where each variation is shown to a specific audience, with an optional control group to measure the lift. The setup is the same; Personalizations covers what's different.
How it works
- You choose the pages. A campaign runs on one or more pages: a single URL, a set of URLs matched by rules or a regular expression, or every page.
- You edit each variation in the visual editor. The editor loads your live site. You select elements and change them. Each change is saved as a region: a CSS selector plus the changes to apply to the element it matches. Each variation stores its changes per page.
- The visitor is assigned. When a visitor opens a matching page, Personizely checks the campaign's target and traffic allocation, then assigns them to the control or a variation. The visitor keeps that assignment on later pages and visits. See How visitors are assigned.
- The changes are applied in the browser. The snippet finds each region's element and applies the changes. Elements that are about to change stay hidden until the change is in place, so visitors don't see the original content flash. If an element can't be changed quickly (for example, it never appears on the page), it's shown unchanged rather than left hidden.
- The view is recorded. A campaign view is counted once per page load for the visitor's variation, or for the control. Conversions are then attributed to the variation.
Set up a content campaign
- Go to Campaigns → New campaign → Content, and choose Experiment or Personalization.
- Under Pages, pick an existing page or click Create a page. See Campaign pages and URL matching.
- Open a variation in the editor and make your changes. Use Find element to locate an element by CSS selector, and Edit code to add CSS or JavaScript.
- If the campaign runs on more than one page, switch pages in the editor and make each page's changes.
- Set the target, goals and (for experiments) the traffic split.
- Preview each variation with Preview on the live site, then start the campaign.
Campaign options
Option | What it does | Values / default |
|---|---|---|
Pages | Where the campaign runs | One or more pages |
Target | Who can enter the campaign | All visitors, or a specific target |
Targeting mode | Live re-checks the target on every page; Permanent keeps showing the visitor's variation once they've been targeted | Live (default), Permanent |
Traffic allocation | Share of eligible visitors who enter the campaign | 100% |
Control group | Share of entering visitors who see the original page | 50% for experiments, 25% for personalizations |
Trigger | When the campaign runs | Immediate (runs as the page loads) or Manual (runs only when started from the JavaScript API or, for personalizations, a widget automation) |
Goals | What counts as a conversion | At least one goal is required to start |
The Trigger option exists only for content campaigns. A Manual campaign runs when you call ply.runCampaign(campaignId) from the JavaScript API, or, for personalizations, when a widget's Run campaign automation fires. Once a visitor has been assigned to a variation, a manual campaign keeps applying it automatically on their later page loads.
What you can change
Change | What it does |
|---|---|
Text and HTML | Replaces the element's content. Supports dynamic text such as {visitor.firstName} or {geo.city}. |
Styles | Sets inline styles (colors, fonts, spacing, visibility, background image) |
Classes | Adds or removes CSS classes |
Image or video source | Replaces the src. On Shopify, responsive image sizes from the Shopify CDN are kept, and lazy-loaded images are updated too. |
Attributes | Sets any attribute, for example a link's href |
Insert element | Inserts new HTML after an existing element |
Rearrange | Reorders the children of an element, for example sections or list items |
Watching an element for changes
Some themes rewrite elements after the page loads, for example a product title re-rendered when the variant changes. By default a change is applied once. Click the eye icon on a region to watch it: the change is applied again whenever the site rewrites the element, and again if the element is removed and re-added. Don't watch elements whose content legitimately changes on its own, such as cart counts, sliders or live prices.
Desktop and mobile
To show an element only on one device size, select it in the visual editor and use the device visibility button to show it on desktop only, on mobile only (screens 480 px wide and below), or hide it. This is useful for inserted elements.
Custom CSS and JavaScript
Edit code opens the CSS and JavaScript editors, each with two tabs:
- Campaign: code that runs for every variation (not the control), on each page where the variation has changes.
- Variation: code that runs for this variation on the current page only.
Variation JavaScript has variationId and pageId in scope. Both campaign and variation JavaScript get a helpers object:
Helper | What it does |
|---|---|
helpers.waitForElement(selector, callback, timeout) | Calls callback(element) once the first element matching selector exists. Optional timeout in ms. |
helpers.liveQuerySelectorAll(selector, callback, timeout) | Calls callback(element) for every current and future element matching selector, until the optional timeout |
helpers.swap(parent, a, b) | Moves the child at index b of parent before the child at index a |
helpers.readCookie(name) | Reads a cookie |
helpers.createCookie(name, value, seconds) | Writes a cookie; value of null deletes it |
// Variation JavaScript: add a badge to every product card, including ones loaded later
helpers.liveQuerySelectorAll('.product-card', (card) => {
card.insertAdjacentHTML('afterbegin', '<span class="badge">Bestseller</span>')
})Errors in your code don't break the page. They're logged to the console when debug mode (?ply_debug=1) is on.
Behavior details and limitations
- One campaign per element. If two running campaigns change the same element, the first one to apply its changes wins and the other's changes to that element are skipped. Changes from different campaigns are never merged. Use campaign groups to keep overlapping tests apart.
- Pages are matched on the path. The domain is ignored, so the same page definition matches on every domain your snippet runs on. See Campaign pages.
- A page with no changes still counts. A visitor assigned to a variation that has no changes on the current page still records a view for that variation.
- Dynamic text waits for data. If a change uses {visitor.*}, {geo.*} or {company.*} placeholders, the campaign waits for that data before running, and the affected elements stay hidden meanwhile.
- Targets that need server data. Targets using filters such as location or order history are checked once that data has loaded. Affected elements stay hidden until then.
- Snippet loading affects flicker. With the snippet loaded asynchronously, the page can paint before the snippet runs, and the original content can briefly show. When you first start a campaign, Personizely offers to switch your site to synchronous loading. See Performance and flicker.
- Single-page apps. On sites that change pages without a full reload, campaigns re-evaluate on each route change and re-apply their changes.
- Selectors must be stable. Changes are tied to CSS selectors. If you edit your theme and a selector stops matching, the change silently stops applying. The editor flags regions it can't find on the page.
- Deploying. When a content experiment ends, you can deploy the winning variation. It's then shown to every visitor who matches the target, with no further tracking, until you undeploy it. Personalizations can't be deployed.
Running it as a personalization
- One target per variation, first match wins. Each variation has its own target, and a visitor gets the first variation in the list whose target they match. Put narrow audiences above broad ones. A visitor who matches no variation sees the original page.
- Control group. 25% of matched visitors by default see the original page and are counted as control, so the report can show the lift per variation.
- Live or Permanent. With Live (the default), targets are checked again on every page, so a visitor can switch variations, for example after logging in. Use Permanent to keep the first variation for the rest of the visit when the audience depends on something that changes, such as cart value.
- Run it from a widget. Only content personalizations can use the Manual trigger with a widget's Run campaign automation, for example to change the page after a visitor answers a quiz.
- No deploy. Personalizations can't be deployed, so the report has no Deploy. A personalization keeps running until you stop it, and you can edit its variations and targets as needed. To stop measuring and show it to every matching visitor, set the control group to 0%.
- Example audiences: ad-matched headlines, with one variation per utm_campaign value using the UTM parameters filter; or a different hero for visitors from one country with the Location filter.
Examples
- Headline test on the homepage: a Single page for /, two variations with different hero headlines and button text, primary goal Purchase with revenue per visitor.
- Trust badges on all product pages: a Multiple pages page with the rule Include pages starting with /products/, and a variation that inserts a badge row after the add-to-cart button.
- Returning-customer messaging: a personalization on the cart page whose variation targets returning customers and changes the free-shipping banner text, with a 25% control group.
Troubleshooting
- The original content flashes before the change → the snippet loads asynchronously. Switch to synchronous loading or move the snippet higher in the page. See Performance and flicker.
- A change applies, then reverts → the theme rewrites the element after load. Turn on watching (eye icon) for that region.
- A change never applies → the selector no longer matches, or another campaign already changed the element. Open the page with ?ply_debug=1 and check the console.
- The campaign doesn't run on a page → the page definition doesn't match the URL (check Include query), the visitor isn't in the target, or they're outside the traffic allocation.