This column setup gives a good overview of your ROI focused e-commerce campaigns and ad sets.
Order
Column Name
1
Object Names & Ids > Campaign Name
2
Object Names & Ids > Ad Set Name
3
Performance > Delivery
4
Ad Relevance Diagnostics > Quality Ranking
5
Performance > Attribution Setting
6
Performance > Impressions
7
Performance > Reach
8
Performance > Clicks (All)
9
Clicks > Outbound Clicks
10
Performance > CTR (All)
11
Clicks > Outbound CTR
12
Conversions > Adds to Cart: Total Deselect all sub-categories
13
Conversions > Purchases: Total Deselect all sub-categories
14
Conversions > Purchases: Value Deselect all sub-categories
15
Conversions > Purchases: Cost Deselect all sub-categories
16
Performance > Amount Spent
17
Goal, Budget & Schedule > Budget
18
Performance > Frequency
19
Conversions > Website Purchase ROAS (Return on Ad Spend)
Select “Save as Preset” and name your column set “ROAS”
Click the column icon and select “Set as Default”
Optional: Custom Columns
Optionally, create the following custom columns
% Cost of Sales
This indicates what percentage of your sales are going towards Facebook ads cost (ad spend as a cost of sales). Where ROAS is advertiser centrice, Cost of Sales is more business centric.
Conversion rates can vary a great deal across devices, and so it makes sense to bid differently for Mobile vs Desktop vs Tablet. Here two methods to calculate your bid adjustment, as well as an automated bidding script that will do all the work for you!
Calculating based on Conversion Rates
The simplest way is to calculate bid adjustments based on the individual device conversion rates relative to the Campaign average
Calculating based on Click Value
I believe a more correct way is to calculate bid adjustments based on the relative per click values of each device (the Conv. Value / Click column)
Example Using Real Data
Sample Campaign
Device
Conv. rate
Conv. value / click
Adjustment (Conv. Rate)
Adjustment (Click Value)
Mobile
1.15%
$6.90
-40%
-43%
Desktop
3.01%
$20.20
+57%
+68%
Tablet
0.89%
$1.85
-54%
-85%
Campaign
1.91%
$12.03
Automating Bid Adjustments
Below is a Google Ads Script that will automatically make these adjustments for you (based on relative conversion rate). You can download the latest version of this script on GitHub here: Google Ads Device Bid Adjustment Script
// Version: 1.13.1 Muppet
// Latest Source: https://github.com/Czarto/Adwords-Scripts/blob/master/device-bid-adjustments.js
//
// This Google Ads Script will incrementally change device bid adjustments
// based on conversion rates using the Campaign's average conversion rate
// as a baseline.
//
/***********
MIT License
Copyright (c) 2016-2021 Alex Czartoryski
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
**********/
var LABEL_PROCESSING_DESKTOP = "_processing_desktop";
var LABEL_PROCESSING_MOBILE = "_processing_mobile";
var LABEL_PROCESSING_TABLET = "_processing_tablet";
var BID_INCREMENT = 0.05; // Value by which to adjust bids
var MIN_CONVERSIONS = 10; // Minimum conversions needed to adjust bids.
var MAX_BID_ADJUSTMENT = 1.90; // Do not increase adjustments above this value
function main() {
initLabels(); // Create Labels
/*****
Device performance *should* theoretically not vary over time
(unless a site redesign has been performed) and so it makes
most sense to use a relatively long time period (1 year)
on which to base adjustments.
Shorter time periods included for reference, but commented out
*****/
//setDeviceBidModifier("LAST_7_DAYS");
//setDeviceBidModifier("LAST_14_DAYS");
//setDeviceBidModifier("LAST_30_DAYS");
//setDeviceBidModifier(LAST_90_DAYS(), TODAY());
setDeviceBidModifier(LAST_YEAR(), TODAY());
cleanup(); // Remove Labels
}
//
// Set the Processing label
// This keeps track of which bid adjustments have already been processed
// in the case where multiple time-lookback windows are being used
//
function initLabels() {
checkLabelExists();
cleanup();
var itemsToLabel = [AdWordsApp.campaigns(), AdWordsApp.shoppingCampaigns()];
for (i = 0; i < itemsToLabel.length; i++) {
var iterator = itemsToLabel[i].get();
while (iterator.hasNext()) {
campaign = iterator.next();
campaign.applyLabel(LABEL_PROCESSING_DESKTOP);
campaign.applyLabel(LABEL_PROCESSING_MOBILE);
campaign.applyLabel(LABEL_PROCESSING_TABLET);
}
}
}
//
// Create the processing label if it does not exist
//
function checkLabelExists() {
var labels = [LABEL_PROCESSING_DESKTOP, LABEL_PROCESSING_MOBILE, LABEL_PROCESSING_TABLET];
for (i = 0; i < labels.length; i++) {
var labelIterator = AdWordsApp.labels().withCondition("Name = '" + labels[i] + "'").get();
if (!labelIterator.hasNext()) {
AdWordsApp.createLabel(labels[i], "AdWords Scripts label used to process device bid adjustments");
}
}
}
//
// Remove Processing label
//
function cleanup() {
var cleanupList = [AdWordsApp.campaigns(), AdWordsApp.shoppingCampaigns()];
for (i = 0; i < cleanupList.length; i++) {
var iterator = cleanupList[i].get();
while (iterator.hasNext()) {
campaign = iterator.next();
campaign.removeLabel(LABEL_PROCESSING_DESKTOP);
campaign.removeLabel(LABEL_PROCESSING_MOBILE);
campaign.removeLabel(LABEL_PROCESSING_TABLET);
}
}
}
//
// Set Device Bids
//
function setDeviceBidModifier(dateRange, dateRangeEnd) {
var STANDARD = 0;
var SHOPPING = 1;
for (i = 0; i < 2; i++) {
//Logger.log('--- ' + (i==STANDARD ? 'Standard Campaigns' : 'Shopping Campaigns'));
var labels = [LABEL_PROCESSING_DESKTOP, LABEL_PROCESSING_MOBILE, LABEL_PROCESSING_TABLET];
for (l = 0; l < labels.length; l++) {
//Logger.log(' ' + labels[l]);
var campaigns = (i==STANDARD ? AdWordsApp.campaigns() : AdWordsApp.shoppingCampaigns());
var campaignIterator = campaigns.forDateRange(dateRange, dateRangeEnd)
.withCondition("Status = ENABLED")
.withCondition("Conversions >= " + MIN_CONVERSIONS)
.withCondition("LabelNames CONTAINS_ANY ['" + labels[l] + "']")
.get();
while (campaignIterator.hasNext()) {
var campaign = campaignIterator.next();
var baseConversionRate = campaign.getStatsFor(dateRange, dateRangeEnd).getConversionRate();
var platforms = [campaign.targeting().platforms().desktop(),
campaign.targeting().platforms().mobile(),
campaign.targeting().platforms().tablet()
];
//Logger.log(' CAMPAIGN: ' + campaign.getName());
var targetIterator = platforms[l].get();
if (targetIterator.hasNext()) {
var target = targetIterator.next();
var stats = target.getStatsFor(dateRange, dateRangeEnd);
var conversions = stats.getConversions();
var conversionRate = stats.getConversionRate();
var targetModifier = (conversionRate / baseConversionRate);
var currentModifier = target.getBidModifier();
//Logger.log(' Conversions: ' + conversions);
if (conversions >= MIN_CONVERSIONS) {
if (Math.abs(currentModifier - targetModifier) >= BID_INCREMENT) {
if (targetModifier > currentModifier) {
target.setBidModifier(Math.min(currentModifier + BID_INCREMENT, MAX_BID_ADJUSTMENT));
} else {
target.setBidModifier(Math.max(currentModifier - BID_INCREMENT, 0.1));
}
}
campaign.removeLabel(labels[l]);
//Logger.log(' Remove Label: ' + labels[l]);
}
}
}
}
}
}
//
// Date range helper function
// Returns today's date
//
function TODAY() {
var today = new Date();
var dd = today.getDate();
var mm = today.getMonth() + 1; //January is 0!
var yyyy = today.getFullYear();
return { year: yyyy, month: mm, day: dd };
}
//
// Date range helper functions
// Returns date 90 days ago
//
function LAST_90_DAYS() {
var date = new Date();
date.setDate(date.getDate() - 90);
var dd = date.getDate();
var mm = date.getMonth()+1; //January is 0!
var yyyy = date.getFullYear();
return {year: yyyy, month: mm, day: dd};
}
//
// Date range helper functions
// Returns date 1 year ago
//
function LAST_YEAR() {
var today = TODAY();
today.year = today.year - 1;
return today;
}
Every week, the script will look at the past 365 days of data for each of your Search and Shopping campaigns, and make device bid adjustments based on conversion rates.
The script will raise and lower your device bid adjustments by up to 5% each week (you can change this value if you prefer bigger adjustments). You can also adjust the date range for which you want the script to run, and what the minimum number of conversions are required to make a change.
You have been running a campaign for 7 days with a daily budget of $50. Over those 7 days you have spent $350 with an ROAS of 1500%. You are using a “Maximize ROAS” bid strategy, and want to hit an ROAS of 2000%.
What should your daily budget be?
Here is the formula:
Formula for adjusting a budget towards a target ROAS
This formula calculates what your daily spend should have been to hit your target ROAS. Although there are no guarantees that future performance will be the same as past performance, it does provide a baseline from which to work.
If you are running a smart bidding campaign in either Google Ads or Facebook Ads, and are aiming for a target Return on Ad Spend (ROAS), this is a great formula to use for adjusting your budgets.
Here are some incredibly useful custom columns that I add to every Google Ads account I manage:
Column Name
Formula
Type
ROAS
Conv. Value ÷ Cost
%
Cost of Sales (COS)
Cost ÷ Conv. Value
%
% COS CPC
Conv. Value ÷ Clicks x %
$
Optimistic % COS CPC
(Conv. Value + (Conv. Value ÷ Conversions)) ÷ Clicks x %
$
Below is each column described in more detail:
ROAS
ROAS stands for Return On Ad Spend
Name
ROAS
Description
Return on Ad Spend
Formula
Conv. Value ÷ Cost
Data Format
Percent (%)
This metric tells you the percentage of revenue generated for each dollar in ad spend.
100% ROAS means that there is a 1:1 relationship between your revenue and ad spend. For every $1 spent, you generated $1 in revenue.
200% ROAS means that is a 2:1 relationship between your revenue and ad spend. For every $1 spent, you generated $2 in revenue.
Many of the automated bidding strategies in Google Ads let you specify a target ROAS goal (Return on Ad Spend), so adding this custom column allows you quickly see in a glance if your campaigns, ad groups, and keywords are meeting your ROAS targets.
COS (Cost of Sales)
COS stands for Cost Of Sales
Name
COS
Description
Cost of Sales
Formula
Cost ÷ Conv. Value
Data Format
Percent (%)
This metric is the inverse of ROAS. It tells us what percentage of revenue was spent on ads.
This metric is usually more aligned with how most businesses look at Ad Spend (as a cost). I find looking at things from a Cost of Sales standpoint makes more sense than ROAS for most businesses. (ROAS is more aligned with how ad agencies think about ad spend).
20% Cost of Sales means that for every $1 in revenue, we spent $0.20 on ads.
If you know your profit margins, then it’s easy to see when your Cost of Sales exceeds those margins, and when you start to lose money on every sale.
To convert between ROAS and COS use these formulas:
Cost of Sales (COS) = 1 / ROAS
Return on Ad Spend (ROAS) = 1 / COS
% COS CPC
“What is the max Cost per Click bid that will keep me below the specified % Cost of Sales target.“
Name
5% COS CPC
Description
Maximum CPC to stay below 5% Cost of Sales
Formula
Conv. Value ÷ Clicks x 0.05
Data Format
Money ($)
If you use Manual CPC bidding for a campaign, creating a few of these columns for various profit margins will give you access to quick calculation for what your max CPC bids.
I usually create a few of these columns. Eg:
5% COS
Max CPC bid to hit 5% cost of sales
35% COS
Max CPC bid to hit 35% cost of sales
55% COS
Max CPC bid to hit 55% cost of sales
The number of columns you create and the values you use will depends on your business and profit margins. I usually create one column that targets a very low cost of sales (eg: 5%, that is used for Branded search bids), and then another column that targets my maximum profit margin (eg: 55%, that is used for Unbranded/Prospecting search bids).
Optimistic % COS CPC
“What is the max Cost per Click bid that will keep me below the specified % Cost of Sales target, under the assumption that the next click will result in a sale“
Name
Optimistic 5% COS CPC
Description
Maximum CPC to stay below 5% Cost of Sales, assuming the next click will result in a sale
Formula
(Conv. Value + (Conv. Value ÷ Conversions)) ÷ Clicks x 0.05
or use a harde-coded order value: (Conv. Value + avg_order_value) ÷ Clicks x 0.05
Data Format
Money ($)
This custom column is similar to the % COS CPC column above, except that it is optimistic in assuming that the “next click” will result in a sale, and includes the next sale’s revenue in the Max cost per click calculations.
You can either hard-code your average conversion value, or you can calculate it based on past conversion data.
This column is useful when you are starting a new campaign where you don’t have much conversion data yet, and you want to be optimistic with your bidding.
Additional Images by Variant: More control over which product images get associated with which variant.
Exclude Out of Stock items from Google Programs. If you are paying to advertise, make sure you are only paying to display products you can sell.
Exclude Low Stock variants from Google Programs. Exclude color ways that have low size options. Show other color options or products instead.
GTIN: Optionally set GTIN to be your barcode value
SALE labels and price markdown annotations usually only appear if you provide Google Shopping with both your regular price (compare_at_price) and your sale price.
To add the above functionality, you must create and upload a supplemental data feed to Google Merchant Center.
Step 1 Create a Supplemental Data Feed in Shopify
The first step is to create a data feed in Shopify. We can accomplish this by creating a custom Shopify Collection Template that will output XML data instead of HTML:
1. Create a new Collection Template called collection.google-update.liquid with the following code:
{% layout none %}<?xml version="1.0"?>
<rss xmlns:g="http://base.google.com/ns/1.0" version="2.0">
{% comment %}
Google Shopping / Merchant Center + Shopify Product Update Feed by Alex Czartoryski
https://business.czarto.com/2020/10/14/enhance-shopify-google-shopping/
This version: Aug 30, 2022
The latest version of this script available here:
https://github.com/Czarto/ShopifyScripts/blob/master/Templates/collection.google-feed-update.liquid
TODO: Test & handle products without variants
TODO: Test & hanlde products without color
TODO: Specify Sizes to never exclude. eg: Women's 7,8
{% endcomment %}
{% comment %} Settings {% endcomment %}
{%- assign exclude_unavailable_variants = true -%}
{%- assign exclude_variant_colors_with_limited_availability = false -%}
{%- assign ignore_x_smallest_sizes = 1 -%}
{%- assign ignore_x_largest_sizes = 1 -%}
{%- assign minimum_percentage_availability = 50 -%}
{%- assign filter_variantImages_byColor = false -%}
{%- assign use_barcode_as_gtin = false -%}
{%- comment -%}
TODO: Move this into a snippet and use capture to assign the variable
{%- endcomment -%}}
{%- case shop.currency -%}
{%- when 'USD' -%}{%- assign CountryCode = 'US' -%}
{%- when 'CAD' -%}{%- assign CountryCode = 'CA' -%}
{%- when 'GBP' -%}{%- assign CountryCode = 'GB' -%}
{%- when 'AUD' -%}{%- assign CountryCode = 'AU' -%}
{%- else -%}{%- assign CountryCode = 'US' -%}
{%- endcase -%}
<channel>
<title>{{ shop.name }} {{ collection.title | strip_html | strip_newlines | replace: '&', '&' }}</title>
<link>{{ shop.url }}</link>
<description>{{ collection.description | strip_html | strip_newlines | replace: '&', '&' }}</description>
{%- paginate collection.products by 1000 -%}
{%- for product in collection.products -%}
{%- comment -%} Get color option {%- endcomment -%}
{%- for option in product.options -%}
{%- if option == 'Color' -%}{% capture option_color %}option{{ forloop.index }}{% endcapture %}{%- endif -%}
{%- endfor -%}
{%- comment -%} Make a list of Colors to exclude {%- endcomment -%}
{%- assign colors_to_exclude = "" -%}
{%- if exclude_variant_colors_with_limited_availability -%}
{%- for color in product.options_by_name['Color'].values -%}
{%- assign variants = product.variants | where: option_color, color -%}
{%- assign variants_to_process_count = variants.size | minus:ignore_x_smallest_sizes | minus:ignore_x_largest_sizes -%}
{%- assign available_count = 0 -%}
{%- assign total_processed_count = 0 -%}
{%- for variant in variants offset:ignore_x_smallest_sizes limit:variants_to_process_count -%}
{%- assign total_processed_count = total_processed_count | plus:1 -%}
{%- if variant.available -%}{%- assign available_count = available_count | plus:1 -%}{%- endif -%}
{%- endfor -%}
{%- if total_processed_count == 0 -%}
{%- continue -%}
{%- endif -%}
{%- assign percentage_availability = available_count | times: 100.0 | divided_by: total_processed_count | round -%}
{%- if percentage_availability < minimum_percentage_availability -%}
{% capture colors_to_exclude %}{{colors_to_exclude}}#{{ color }}{%endcapture%}
{%- endif -%}
{%- endfor -%}
{%- assign colors_to_exclude = colors_to_exclude | split: "#" -%}
{%- endif -%}
{%- for variant in product.variants -%}
<item>
<g:item_group_id>shopify_{{ CountryCode }}_{{ product.id }}</g:item_group_id>
<g:id>shopify_{{ CountryCode }}_{{ product.id }}_{{ variant.id }}</g:id>
<g:mpn>{{ variant.sku }}</g:mpn>
<g:barcode>{{ variant.barcode }}</g:barcode>
{% if use_barcode_as_gtin %}<g:gtin>{{ variant.barcode }}</g:gtin>{% endif %}
{%- comment -%} Additional Images by Color {%- endcomment -%}
{%- assign additional_images = product.images -%}
{%- for option in product.options -%}
{%- if option == 'Color' -%}{% capture variant_color %}{{ variant.options[forloop.index0] }}{% endcapture %}{%- endif -%}
{%- endfor -%}
{% if filter_variantImages_byColor %}{% assign additional_images = product.images | where: "alt", variant_color | sort: 'attached_to_variant' | reverse%}{% endif %}
{% if additional_images.size > 1 %}{%- for image in additional_images offset:1 limit:10 -%}
<g:additional_image_link>https:{{ image.src | product_img_url: 'master' }}</g:additional_image_link>
{% endfor %}{% endif %}
{%- comment -%} Exclude Out of Stock Variants {%- endcomment -%}
{% if exclude_unavailable_variants and variant.available == false %}
<g:pause>ads</g:pause>
{% elsif exclude_variant_colors_with_limited_availability and colors_to_exclude contains variant_color %}
<g:pause>ads</g:pause>
{% endif %}
</item>
{% endfor %}
{% endfor %}
{% endpaginate %}
</channel>
</rss>
2. Create a new collection called “google-update” and choose google-update as your collection template.
3. Preview the collection and copy the url. Your url should look something like this: yourstoredomain.com/collections/google-update
Step 2 Configure your Feed
There are a few options you can configure in your feed, all located towards the top of your template file in the “configuration” section.
Exclude Out of Stock Variants exclude_unavailable_variants
true
Exclude Limited Stock Color Variants exclude_variant_colors_with_limited_availability ignore_x_smallest_sizes ignore_x_largest_sizes minimum_percentage_availability
false 1 1 50
Filter Variant Images by Color + Alt Text Matching filter_variantImages_byColor
false
Set GTIN to Variant’s Barcode Value use_barcode_as_gtin
false
Exclude Out of Stock Variants
exclude_unavailable_variants = true
By default, any variants that are out of stock will be excluded from Google Shopping, Google Shopping Actions, and Dynamic Remarketing. Change the value of exclude_unavailable_variants = false if you want to disable this behaviour.
This setting is intended for apparel products, where you may have many colors of a product, but limited sizes available in a specific product. Setting this value to true will cause the script to attempt to exclude the colors with low availability (so that alternative colors or products can show instead).
There are a few additional configuration items for this setting that you can change:
minimum_percentage_availability default = 50
Minimum % of sizes available before all variants of this color are excluded.
ignore_x_smallest_sizes default = 1
Ignore the x smallest sizes in the % available calculation
ignore_x_largest_sizes default = 1
Ignore the x largest sizes in the % available calculation
Filter Variant Images by Color and Alt Text Matching
filter_variantImages_byColor = true
Setting this value to true will assign additional images to the current variant where the image’s Alt Text matches the variant’s color. Note that the primary variant’s image will always be included in the feed regardless of Alt text.
Set GTIN to Variant’s Barcode Value
use_barcode_as_gtin = true
Setting this value to true will assign your barcode value to GTIN. If you would like the option to set a different value than Barcode, it should be pretty straightforward to edit the code.
Step 3 Add a Supplemental Data Feed in Google Merchant Center
1.Open Merchant Center and go to Products > Feeds > Supplemental Feeds > Add Supplemental Feed
Name
Feed Update Script
Feed Type
Scheduled Fetch
File Name
google-update
File Url
yourstoredomain.com/collections/google-update
Leave everything else as default values and click Continue
2. Make sure there’s a checkmark beside Content API and click Create Feed
3. You should now see your newly created feed in the Supplemental Feeds section. Click on your feed’s name and then click on Fetch Now to update your product data now.
Testing
It may take up to 30 minutes for your main feed to be updated. It is a good idea to review your products and feed to ensure that everything is coming through as expected, and tweak as required.
If everything looks good, your new sale pricing, variant images, and program availability should now be updated once per day.
Connect your Google Account (must be an account that has access to both Google Merchant Center and Google Ads)
Connect your Google Ads Account in the “Smart Shopping campaign” section
STEP 1 Remove Old Conversion Tracking Code
By connecting Shopify to Google Ads via the Google Shopping Channel, Shopify will begin sending conversion data to your Google Ads account. If you were already tracking conversions in Google Ads, then you need to make sure you are not duplicating your conversion data:
If you previously had a Google Ads conversion tracking script installed in your checkout, then remove that code.
If you were importing conversions from Google Analytics, stop importing those conversions.
STEP 2 Fix Conversion Categories
Log into Google Ads and then Go to Tools & Settings > Measurement > Conversions
You should see a bunch of new conversion actions created by Shopify. You will also see a warning that your conversion categories are out of date, and that you should update 4 of your conversions actions.
Click Update Now and update the settings to the following:
STEP 3 Conversion Windows and Attribution Models
Although Shopify created multiple conversion actions, you only need to worry about the Google Shopping App Purchase conversion event.
Click on the Google Shopping App Purchase conversion action and update the following settings:
Old Value
New Value
Count
Every Conversion
One
View-through Conversion Window
30 days
1 day
Attribution Model
Last click
Linear
One Conversion per click
If you count “Every Conversion” you increases the risk of double attribution across your various channels.
For example: A user clicks on your Google ad and makes a purchase. It makes sense to count and attribute this conversion to Google Ads. If this same user later receives your newsletter, and purchases again, then you probably want to attribute that sale to the Newsletter and NOT to Google. Setting conversion count to “One” instead of “Every” ensures that only the first sale gets attributed to google, and not the second.
Counting “Every” conversion increases the risk of double attribution
Click-Through Conversions
Arguably, you should set your Click-Through Conversion window to 30 days. 90 days seems extremely long, and could increases your chances of double attribution across multiple sales channels.
However, if you are using Smart Campaigns or Smart Bidding, the bidding algorithm can only take into account conversions that have occurred within the specified conversion window. So theoretically, the longer your conversion window, the more data for Smart Bidding to optimize with. So the 90 day click-conversion window can probably stay.
View-Through Conversions
Set the View-Through Conversion window to only 1 day. A longer View-Through conversion window is dangerous, and will lead to over attributing sales to Display Remarketing, YouTube, and Smart Shopping. This will invariably cause you to overspend on those campaigns.
A View-Through conversion window greater than 1 day is dangerous, and will lead to over attributing sales to undeserving channels.
Which Attribution Model to use?
Linear, Time Decay, Position Based, or Data Driven are all better than First or Last click, as they allow you to attribute sales across a wider range of ad interactions, which allows you to spend more evenly across your entire funnel.
I personally usually choose Linear or Position Based, as I like pushing more attribution (and therefore spend) into the top part of the funnel.
STEP 4 Enhanced Conversion Tracking
This option is still in beta, and so you may or may not have access to this in your account. At the bottom of your Purchase conversion action, you should see a section called “Enhanced Conversions”
Expand this section and enter the following settings:
Turn on Enhanced Conversions
Enter your site URL (ideally your order receipt / thank you page)
Choose the Global Site Tag
Select “Enter Javascript or CSS Selectors” (note that all of the below are case sensitive. So the first “S” of Shopify needs to be capitalized, and the remaining characters need to be lower case)
DEPRECATED: As of at least Aug 30, 2022, the defautlt Google sales channel properly sends the product’s sale price to Google. This post and code are no longer being maintained, and are here only for reference.
SALE labels and price markdown annotations usually only appear if you provide Google Shopping with both your regular price (compare_at_price) and your sale price.
Create a Price Feed in Shopify
The first step is to create a data feed in Shopify containing your products sale and regular prices. We can accomplish this by creating a custom Shopify Collection Template that will output XML data instead of HTML:
2. Create a new collection called “google-feed-sale-price” based on your Collection Template
IMPORTANT Choose xml-pricing-feed as your collection template
Add products to the collection (or you can create an automatic collection with Compare At Price is Greater than 1)
3. Preview the collection and copy the url.
Your url should look something like this: yourstoredomain.com/collections/google-feed-sale-price
When you preview your page, it should look like a bunch of unformatted text on your page. If you see images, then you probably skipped the first bullet point in Step 2.
Add a Supplemental Data Feed in Google Merchant Center
4.Open Merchant Center and go to Products > Feeds > Supplemental Feeds > Add Supplemental Feed
Leave everything else as default values and click Continue
5. Make sure there’s a checkmark beside Content API and click Create Feed
6. You should now see your newly created feed in the Supplemental Feeds section. Click on your feed’s name and then click on Fetch Now to update pricing data immediately.
Done
It may take up to 30 minutes for your main feed to be updated. Any new sale pricing will now be uploaded once per day.
Evergreen Campaigns add some structure and sanity to your Facebook Ad management. They prevent the typical disorganized Campaign sprawl that plagues most Facebook Ads Accounts. Evergreen Campaigns encourage continuous improvement and optimization, facilitates data analysis, and lowers maintenance costs.
An Evergreen Campaign Structure doesn’t change much year over year: The Campaigns and Ad Sets are continually in use, and only the ad creative is changed.
Campaign Structure
The campaign structure is relatively simple: 3-4 primary campaigns targeting 3 main segments: Past Purchasers, Remarketing, and Prospecting.
Past Purchasers
Remarketing
Prospecting
People who have previously purchased from you.
People who have interacted with your brand via your website, social media, or otherwise, but have not purchased from you yet.
People who have never purchased from you, and who have not interacted with your brand in the past 180 days.
CPA $
CPA $$
CPA $$$
Budget $$
Budget $$$
Budget $
Facebook Evergreen Campaign Structure
Audiences
Before you can create your campaigns, you will need to define some base audiences to capture purchasers and website visitors. A full description of the essential Facebook audiences that should be created is available here.
Building the Campaigns
Campaign #1: Past Purchasers
This campaign targets people who have previously purchased from you. Generally your cost per acquisition will be low, and your budget will be a function of how many customers you have.
Campaign Name
Buying Type
Objective
Past Purchasers
Auction
Conversions
Ad Sets for Past Purchasers Campaign
At it’s most basic, the campaign contains a single Ad Set containing all your past customers:
Ad Set Name
Audiences
Past Purchasers All Time
Purchase 10d Purchase 30d Purchase 180d Purchaser All Time
For high volume businesses that have large amount of customers, multiple Ad Sets could be create, one for each audience to have more granular control based on past purchase date. However, a single campaign targetting all past purchasers is also quite effective, trusting Facebook’s algorithm to take purchase recency into account.
Campaign #2: Remarketing
This campaign targets website visitors and people who have previously engaged with your brand on facebook, instagram, or otherwise, but who have NOT previously purchased from your business.
Your Cost per Acquisition (CPA) will be relatively low, and therefore you should manage to have a relatively large budget while maintaining a profitable CPA.
This campaign actually needs to be setup as two separate campaigns
Campaign Name
Buying Type
Objective
Remarketing
Auction
Conversions
Dynamic Remarketing
Auction
Catalog Sales
The Remarketing campaign needs to be split into two: one for regular ads, and another for dynamic product ads.
Ad Sets for Remarketing Campaigns
Ad it’s most basic, the Remarketing Campaign contains only two Ad Sets targeting visitors who engaged with your brand up to 30 days ago, and another for people who engaged with your brand up to 180 days ago (the Facebook maximum)
Ad Set Name
Audiences
Exclude
Remarketing 30d
Add to Cart 30d Visitors Top 25% 30d FB Engagement 30d IG Engagement 30d
Purchase 30d
Remarketing 180d
Add to Cart 180d Visitors Top 25% 180d FB Engagement 180d IG Engagement 180d
Purchase 180d Add to Cart 30d Visitors Top 25% 30d FB Engagement 30d IG Engagement 30d
For higher volume sites, you can consider adding more granular Ad Sets for 10, 60, 90 day etc… remarketing audiences.
Ad Sets for Dynamic Remarketing
Ad Set Name
Audiences
Product View 30d
Retarget Ads: Viewed or Added to Cart but not Purchased: 30d
Product View 180d
Retarget Ads: Viewed or Added to Cart but not Purchased: 180d
Cart Abandon 30d
Cart Abandon 30d
Cart Abandon 180d
Cart Abandon 180d
For high-volume sites, or for periods of high sales such as Black Friday, you can also consider creating 10, 4, or even 1 day Ad Sets.
Campaign #3: Prospecting
This campaign will target “brand unaware” customers. People that have never purchased from you, and that have not interacted with your brand or site in at least 180 days.
This campaign will feed your Remarketing campaigns. You can expect your Cost per Acquisition (CPA) to be relatively high and your budgets will need to be relatively low to remain profitable. But the more you can manage to spend here, the more you will be able to spend on Remarketing.
Campaign Name
Buying Type
Objective
Prospectiv=ng
Auction
Conversions
Ad Sets for Prospecting Campaign
At it’s most basic, the prospecting Campaign should target a 1% look-a-like audience (based on the Purchase pixel event).
More advanced campaigns can also target 2%-10% look-a-like audiences, or custom interest based audiences. But it is important to always exclude your Past Purchasers and Remarketing audiences so that there is no overlap with your campaigns.
Ad Set Name
Audiences
Exclude
1% Look-a-like Purchase
1% Look-a-like Purchase
Purchase All Time Purchase 180d Add to Cart 180d Site Visitors Top 25% 180d FB Engagement 180d IG Engagement 180d
The Big Picture
Once all the above is setup, your Campaign and Ad Sets should look something like this:
Facebook Ads Evergreen Campaign Structure
All that remains now is to create ads or duplicate your posts into these campaigns. But that is a topic for another day.
Below is a list of the Essential Facebook Audiences the every e-commerce site should create in their Facebook Ads Account. Even if you don’t plan on using them right away, creating them now will ensure that you can serve ads to these users in the future.
Past Purchasers
Past Purchasers audiences will be used to target users that have previously purchased from you. There are 4 audiences with varying time periods, to allow you to target your past purchasers by how recently they purchased from you.
To create these go to Audiences > Create Audience > Custom Audience
Audience Name
Source
Pixel Criteria
Purchase: 10 day
Website
Purchase: 10 days
Purchase: 30 day
Website
Purchase: 30 days
Purchase: 180 day
Website
Purchase: 180 days
Purchase: All Time
Customer List
Customer list upload*
* The “Purchase: All Time” audience will need to be updated every 180 days.
Visitors and Social Fans
These audiences include website visitors, cart abandoners, and people who have engaged with your content on Facebook and Instagram. These are all people that are “brand aware” and are some of the lowest hanging fruit in terms of generating sales.
To create these audiences go to Audiences > Create Audience > Custom Audience
Audience Name
Source
Pixel Criteria
Add to Cart: 1 day
Website
AddToCart: 1 day
Add to Cart: 4 day
Website
AddToCart: 4 days
Add to Cart: 30 day
Website
AddToCart: 30 days
Add to Cart: 180 day
Website
AddToCart: 180 days
Site Visitors: Top 25%: 10 day
Website
Visitors by Time spent: Top 25%: 10 days
Site Visitors: Top 25%: 30 day
Website
Visitors by Time spent: Top 25%: 30 days
Site Visitors: Top 25%: 180 day
Website
Visitors by Time spent: Top 25%: 180 days
FB Engagement: 10 day
Facebook
Everyone who engaged with your page: 10d
FB Engagement: 30 day
Facebook
Everyone who engaged with your page: 30d
FB Engagement: 180 day
Facebook
Everyone who engaged with your page: 180d
IG Engagement: 10 day
Instagram
Everyone who engaged with your business: 10d
IG Engagement: 30 day
Instagram
Everyone who engaged with your business: 30d
IG Engagement: 180 day
Instagram
Everyone who engaged with your business: 180d
Lookalike Audiences
Lookalike audiences are a good base to use for new customer prospecting. If you target multiple countries, you will need to create a separate set of these for each country.
To create these audiences go to Audiences > Create Audience > Lookalike Audience
Lookalike Source
Event
Number of Audiences
Audience Markers
Facebook Pixel
Purchase
4
0%, 1%, 2%, 5%, 10%
Choose 4 audiences, and set markers at 0%, 1%, 2%, 5%, and 10%
Next: Campaigns
Once you’ve created your audiences, the next step is to create a Facebook Evergreen Campaign Structure properly segmented into Past Purchasers, Remarketing, and New Customer Prospecting.
If you use the Facebook Channel on your Shopify store, you will notice that some product attributes are missing in the product catalog:
Google Product Categoryfixed!
SEO Description
Gender
Material
Additional Images
Below is the solution to add these missing details. This will give you finer control over your Facebook Product Sets and will let you create richer dynamic ads with multiple product images.
Pre-requisites
Before we start, make sure all of the following are done:
Install the Google Sales Channel The script leverages some of the metafields that are created on by the Google Sales Channel
Install the Facebook Sales Channel
Setup Empty Field Rules This is not actually a pre-requisite, but it’s a good backup in case things break down with your feed. This sets some default values for fields such as condition and availability.
Go to Facebook Business Manager and then Commerce Manager > Catalog > Data Sources
Click on your data feed and go to Settings
Scroll down to Data Feed Rules and click on Add Rules > Set Default Values
Create default values for age_group, gender, availability, condition, material, and any other fields that make sense for your business.
Step 1 Create the Facebook Product Feed Template
To create the Facebook product update feed, we will use a “hack” to transform a standard Shopify Collection page into and XML data feed:
In the Shopify admin, go to Online Store > Themes > Action > Edit Code
Under Templates, choose Add a new Template
Choose collection from the drop down and name your template facebook-feed-template
Paste the code below into your newly created template file and click Save.
{% layout none %}<?xml version="1.0"?>
<rss xmlns:g="http://base.google.com/ns/1.0" version="2.0">
{%- paginate collection.products by 1000 -%}
{%- assign useSEOdescription = true -%}
{%- assign additionalImagesForVariants = false -%}
{%- assign includeOutOfStock = false -%}
{%- assign filterVariantImagesByColor = false -%}
{% comment %}
This template is used to add additional information to the Facebook product catalog
Documentation: https://business.czarto.com/2019/12/11/update-your-shopify-facebook-product-feed-with-missing-attributes/
<comment:title>{{ product.title}} {{variant.title}}</comment:title>
{% endcomment %}
<channel>
<title>{{ shop.name }} {{ collection.title | replace: '&', '&' }}</title>
<link>{{ shop.url }}</link>
<description>{{ collection.description | strip_html }}</description>
{% for product in collection.products %}
{%- assign Gender = product.metafields.mm-google-shopping.gender -%}
{%- assign AgeGroup = product.metafields.mm-google-shopping.age_group -%}
{%- assign Material = product.metafields.mm-google-shopping.material -%}
{%- assign Color = "" -%}
{%- if product.variants.size > 0 -%}
{%- for variant in product.variants -%}
{%- if includeOutOfStock or variant.available -%}
{%- for option in product.options -%}
{%- if option == 'Color' -%}{% capture Color %}{{ variant.options[forloop.index0] }}{% endcapture %}{%- endif -%}
{%- endfor -%}
{% assign additional_images = product.images %}
{% if filterVariantImagesByColor %}{% assign additional_images = product.images | where: "alt", Color | sort: 'attached_to_variant' | reverse%}{% endif %}
<item>
<g:id>{{ variant.id }}</g:id>
<g:brand>{{ product.vendor }}</g:brand>
{% if useSEOdescription and product.metafields.global.description_tag.size > 0 %}<g:description>{{ product.metafields.global.description_tag | strip_html | strip_newlines | replace: '&', '&' }}</g:description>{% endif %}
<g:product_type>{{ product.type | replace: '&', '&' }}</g:product_type>
<g:mpn>{{ variant.sku }}</g:mpn>
<g:item_group_id>{{ product.id }}</g:item_group_id>
<g:content_id>{{ variant.id }}</g:content_id>
<g:material>{{ Material }}</g:material>
<g:gender>{{ Gender }}</g:gender>
<g:age_group>{{ AgeGroup }}</g:age_group>
{% if additionalImagesForVariants %}
{% if additional_images.size > 1 %}{%- for image in additional_images offset:1 limit:10 -%}
<g:additional_image_link>https:{{ image.src | product_img_url: 'master' }}</g:additional_image_link>
{% endfor %}{% endif %}
{% endif %}
</item>
{%- endif -%}
{% endfor %}
{%- else -%}
<item>
<g:id>{{ product.id }}</g:id>
<g:brand>{{ product.vendor }}</g:brand>
{% if useSEOdescription and product.metafields.global.description_tag.size > 0 %}<description>{{ product.metafields.global.description_tag | strip_html | strip_newlines | replace: '&', '&' }}</description>{% endif %}
<g:product_type>{{ product.type | replace: '&', '&' }}</g:product_type>
<g:item_group_id>{{ product.id }}</g:item_group_id>
<g:material>{{ Material }}</g:material>
<g:gender>{{ Gender }}</g:gender>
<g:age_group>{{ AgeGroup }}</g:age_group>
{% if product.images.size > 1 %}{%- for image in product.images offset:1 limit:10 -%}
<g:additional_image_link>https:{{ image.src | product_img_url: 'master' }}</g:additional_image_link>
{% endfor %}{% endif %}
</item>
{% endif %}
{% endfor %}
</channel>
</rss>
{% endpaginate %}
Although the script above will work as is, there are three items that you can configure:
useSEOdescription = true Setting this to true will use your product’s SEO description. Setting to false will not upload any description (default Shopify feed will be used)
additionalImagesForVariants = false Setting this to true will upload all your product’s additional images. If you use variants, all your variants will have the identical additional images (their primary image will be as-configured in Shopify)
filterVariantImagesByColor = false Setting this to true will only upload additional images for a variant IF the ALT text of the images exactly match that variant’s color attribute.
Step 2 Select the Products to Send to Facebook
Now select which products will be included in your feed.
In Shopify Admin, go to Products > Collections > Create Collection
Enter a Title: “Facebook”
Add Products to the collection (either manually or using conditions). If you want to include all your products, then add a rule similar to “Inventory Stock is greater than 0”.
IMPORTANT! Assign the Facebook Feed Template to this collection. In the bottom right column of the page, you should see a section called Theme templates. Choose collection.facebook-feed-templateotherwise none of this will work.
At the bottom of the page, click on Edit Website SEO and enter “facebook” as your collection url handle. (optional – but helps with remembering your feed url)
Save and Preview the collection. You should see unformatted text on the screen. This is your Facebook feed. Do a “View Source” in your browser to preview the XML data.
Go to your Facebook Business Manager and go to Commerce Manager
Expand Catalogs in the left hand menu and select Data Sources
In the Supplementary Feeds section, click on Add supplementary feed
Click Upload Feed and choose Scheduled Feed
Paste in the feed url generated in Step 2 and click Next (leave Login Details blank)
Schedule your updates to occur daily or hourly. Turn off automatic updates (or try leaving this on — I’ve never tried it)
Select the data feed to update
Name your supplementary feed and click Upload
Wait for Facebook to finish fetching your feed
Done
Your products should now have the missing information added. You will probably want to repeat STEP 3 anytime you modify or add new products to your shop.
It’s just a matter of time before Shopify starts uploading the full data specs to Facebook, but until that time, this is the workaround!