Analytics Digital Marketing Google Ads

Why doesn’t my Ad Spend Scale?

Consider this Scenario:

You spend $1,000 on a new Ad Campaign that generates $10,000 in revenue. “That’s a great ROI” you tell yourself, “Let’s double the spend!“.

When you double the budget to $2,000, your Campaign only generates $12,500 in total sales and not the $20,000 you were expecting. Why?

SpendSalesCost of SalesROAS
Ad Campaign #1$1,000$10,00010%1000%
Ad Campaign #2$2,000$12,50016%625%
Increasing spend by $1,000 only resulted in $2,500 in additional revenue: An incremental cost of sales of 40% and an incremental ROAS of 250%

Why doesn’t it scale?

By scale I mean that your ROI should be linear: If the first $1,000 generates $10,000 in sales, then the next $1,000 should also generate $10,000 in sales.

The issue is that rarely is your campaign performance evenly distributed. If your drill down deeper into your initial $1000 Campaign, you might see the spend broken down into something like this:

$1000 Campaign$1,000$10,00010%1000%
Majority of the sales are being generated by a small subset of the overall campaign. A classic 80/20 scenario. The performance of the Branded ad set is subsidizing the cost of the unbranded ad set.

The performance of the Branded subset is subsidizing the cost of the Unbranded subset. 80% of your sales are coming from only 20% of the spend, while 80% of your spend is going towards an underperforming segment.

When we try to double the budget to $2,000, here is how the budget gets allocated:

$2,000 Campaign$2,000$12,50016%625%
When budget is doubled, most of the spend goes towards the underperforming segment, resulting in disappointing incremental sales.

With this new data in mind, we should probably:

  1. Decrease budget on the Unbranded segment
  2. Increase budget on the Branded segment

However, it is probably the case that your performing segment is already receiving 100% reach/impressions. So spending more is usually not possible. (In particular for Google Search Ads targeting your branded term: How much you can spend is a function of how many people are searching for your brand. Once you reach everyone, spending more can’t get you more people).

If you want to increase your spend, the only place to do so is in the underperforming unbranded campaign. But at least you’ll have a better expectation of the results.


  • Avoid making budget decisions on aggregate data. Always try to segment and dig a little deeper.
  • Don’t let underperforming segments ride the coat tails of your top performers. Look for 80/20 campaign and ad group performance and analyze those individually.
  • For Google Search Ads, always separate your Branded search terms and Unbranded search terms into separate campaigns.
Digital Marketing Google Ads Shopify Shopping

Add Microdata to your Shopify Product Pages (with variant support)

If you are using Google Merchant Center to with Shopify then you have likely ran into an issue where Merchant Center will give you a warning that there is insufficient match of micro-data information and that automatic item updates are no longer being performed.

Insufficient micro-data warnings in Google Merchant Center

This is generally due to incorrect or incomplete micro-data on your product page.


All the required edits should be limited to your Product.liquid file. You need to define a Product itemscope which will have properties such as Product Url, Product Image, Product Title, and Product Description.

Nested within the Product will be an Offer itemscope that will contain the product variant’s price, currency, condition, and availability.

What complicates things is that most Shopify Themes will have at least a partial implementation of micro-data, and are perhaps only missing a few items, or perhaps don’t fully support variants.

A good idea would be to first run your product page through Google’s Structured Data Testing Tool to see which tags already exist.

Product ItemScope

1. Open your product.liquid file and add the Product itemscope property to the outer most div. The first line of your product.liquid should look something like this: 

<div itemscope itemtype="">

2. Directly below this line, you should add the product variant’s url and image markup as so:

<meta itemprop="url" content="{{ shop.url }}{{ product.selected_or_first_available_variant.url }}" />
<meta itemprop="image" content="https:{{ product.selected_or_first_available_variant.image.src | product_img_url: 'grande' }}" />

3. Find where your product’s title and description are displayed, and add itemprop=name and itemprop=description attributes as shown below:

<!-- Product Title -->
<h1 itemprop="name">{{ product.title }}</h1>
<!-- Product description -->
<div class="product-description" itemprop="description">
{{ product.description }}

Offer ItemScope

4. Now you need to find the place in your product.liquid file where you display your price. Find a wrapping div tag and add the Offers itemscope attributes to the tag. The Offers itemscope MUST nested within the Product itemscope div tag.

<div itemprop="offers" itemscope itemtype="">

5. Immediately after this line you can add the product variant’s price, currency, condition, and availability microdata as so:

<meta itemprop="priceCurrency" content="{{ shop.currency }}" />
<meta itemprop="price" content="{{ product.selected_or_first_available_variant.price | money_without_currency | remove: ',' }}" />
<meta itemprop="itemCondition" itemtype="" content=""/>
{% if product.selected_or_first_available_variant.available %}
  <link itemprop="availability" href="" />
{% else %}
  <link itemprop="availability" href="" />
{% endif %}

Putting it all together

Once done, your product.liquid should have roughly the following structure.

<!-- BEGIN Product itemscope -->
<div itemscope itemtype="">
  <meta itemprop="url" content="{{ shop.url }}{{ product.selected_or_first_available_variant.url }}" />
  <meta itemprop="image" content="https:{{ product.selected_or_first_available_variant.image.src | product_img_url: 'grande' }}" />
  <!-- Product Title & Description -->
  <h1 itemprop="name">{{ product.title }}</h1>
  <div class="product-description" itemprop="description">
  {{ product.description }}</div>
  <!-- BEGIN Offer itemscope -->
  <div itemprop="offers" itemscope itemtype="">
    <meta itemprop="priceCurrency" content="{{ shop.currency }}" />
    <meta itemprop="price" content="{{ product.selected_or_first_available_variant.price | money_without_currency | remove: ',' }}" />
    <meta itemprop="itemCondition" itemtype="" content=""/>
    {% if product.selected_or_first_available_variant.available %}
      <link itemprop="availability" href="" />
    {% else %}
      <link itemprop="availability" href="" />
    {% endif %}

Final Steps

  1. Save and test your new product page in Google’s Structured Data Testing Tool, and fix all errors if any.
  2. Wait and check back periodically in Google Merchant center to ensure no new errors were introduced. It will take a few weeks before the “unable to update” warning disappears.

Related Reading

eCommerce Google Ads Shopify Shopping

DIY Shopify xml Product Feed for Google Shopping

WARNING: This post is OUTDATED and the code is no longer maintained. Although the code should still work, please use at your own risk. I personally now use the Google Sales Channel App in combination with Merchant Center feed rules to manage my Shopping Campaigns.

The custom XML Product Feed Code is still available here: Shopify XML Product Feed Template

Here is how to build your own Shopify XML product feed to submit to Google Shopping or other platforms.

  1. Install and upload your feed using the Shopify Google Shopping App (This will setup some meta fields that are used in the script)
  2. Create a custom collection template that outputs xml
  3. Assign the products you want to publish to your new xml collection template.
  4. Submit your new xml collection url to Google Merchant Center.

Step 1: Install Shopify’s Google Shopping App

Shopify’s app is an excellent tool for managing your Google Shopping product attributes, including custom labels, product categories, etc… The custom feed we are about to build will enhance Shopify’s base feed with extra variant information and customized titles. But the base app is still needed to make use of the app’s metafields.

Step 2: Create the XML Collection Template

This step involves getting your hands dirty with code. If you are not a developer and have a low appetite for risk, consider asking a coder to do this step for you.

What we are doing here is creating a template, that when assigned to a collection, will cause that collection to be displayed as XML instead of HTML.

In your Shopify admin, go to:

  • Online Store > Themes > Edit HTML / CSS
  • Under Templates, choose Add a new Template
  • Choose collection from the drop down and name your template xml-product-feed

Paste the following code into your new template and click Save. (It is probably safer to copy and paste this code from github, as that will always be the most up to date version: Shopify xml Variant Shopping Feed)

IMPORTANT: Do not add any extra spaces before or after this code. Your very first line should be start with {% layout none %} and your very last line should be {% endpaginate %}

{% layout none %}<?xml version="1.0"?> <rss xmlns:g="" version="2.0"> {% paginate collection.products by 1000 %} {%- assign useSEOtitle = false -%} {%- assign useSEOdescription = false -%} {%- assign CountryCode = 'US' -%} {%- if shop.currency == 'CAD' -%}{%- assign CountryCode = 'CA' -%}{%- endif -%} {%- assign Color = "" -%} {%- assign Size = "" -%} <channel> <title>{{ }} {{ collection.title | strip_html | strip_newlines | replace: '&', '&amp;' }}</title> <link>{{ shop.url }}</link> <description>{{ collection.description | strip_html | strip_newlines | replace: '&', '&amp;' }}</description> {% for product in collection.products %}  {%- assign GoogleProductCategory = -%} {%- assign Gender = -%} {%- assign AgeGroup = -%}          {% for variant in product.variants %}     {%- assign Color = "" -%}     {%- assign Size = "" -%}     {%- for option in product.options -%}    {%- if option == 'Color' -%}{%- capture Color -%}{{ variant.options[forloop.index0] }}{%- endcapture -%}    {%- elsif option == 'Size' -%}{%- capture Size -%}{{ variant.options[forloop.index0]  }}{%- endcapture -%}    {%- endif -%}    {%- endfor -%}          {%- capture productTitle -%}{{ product.vendor }} {{ product.title }}{%- endcapture -%}     {%- unless productTitle contains Color -%}{%- capture productTitle -%}{{ productTitle }} {{ Color }}{%- endcapture -%}{%- endunless -%}     {%- if useSEOtitle and > 0 -%}{%- assign productTitle = -%}{%- endif -%}     {%- assign productDescription = product.description -%}     {%- if useSEOdescription and > 0 -%}{%- assign productDescription = -%}{%- endif -%}     {%- assign OnSale = false -%}     {%- assign Price = variant.price -%}     {%- if variant.compare_at_price > variant.price -%}       {%- assign OnSale = true -%}       {%- assign Price = variant.compare_at_price -%}       {%- assign SalePrice = variant.price -%}     {%- endif -%} <item> <title>{{ productTitle | strip_html | replace: '&', '&amp;' }}</title> <link>{{ shop.url }}{{ variant.url }}</link> <description>{{ productDescription | strip_html | strip_newlines | replace: '&', '&amp;' }}</description> <g:google_product_category>{{ GoogleProductCategory | replace: '&', '&amp;'  }}</g:google_product_category> <g:item_group_id>shopify_{{ CountryCode }}_{{ }}</g:item_group_id> <g:id>shopify_{{ CountryCode }}_{{ }}_{{ }}</g:id> <g:condition>new</g:condition> <g:price>{{ Price | money_without_currency }} {{ shop.currency }}</g:price> {%- if OnSale -%} <g:sale_price>{{ SalePrice | money_without_currency }} {{ shop.currency }}</g:sale_price> {%- endif -%} <g:availability>{% if variant.available %}in stock{% else %}out of stock{% endif %}</g:availability> <g:image_link>http:{% if variant.image.src %}{{ variant.image.src | product_img_url: 'grande' }}{% else %}{{ product.featured_image.src | product_img_url: 'grande' }}{% endif %}</g:image_link> <g:gtin>{{ variant.barcode }}</g:gtin> <g:brand>{{ product.vendor }}</g:brand> <g:mpn>{{ variant.sku }}</g:mpn> <g:product_type>{{ product.type }}</g:product_type> <g:age_group>{{ AgeGroup }}</g:age_group> {% unless Color == "" %}<g:color>{{ Color | strip_html | strip_newlines | replace: '&', '&amp;' }}</g:color>{% endunless %} {%- unless Size == "" -%} <g:size>{{ Size | strip_html | strip_newlines | replace: '&', '&amp;' }}</g:size> <g:size_system>US</g:size_system> {%- endunless -%} <g:gender>{{ Gender }}</g:gender> <g:custom_label_0>{{ }}</g:custom_label_0> <g:custom_label_1>{{ }}</g:custom_label_1> <g:custom_label_2>{{ }}</g:custom_label_2> <g:custom_label_3>{{ }}</g:custom_label_3> <g:custom_label_4>{{ }}</g:custom_label_4> <g:shipping_weight>{{ variant.weight | weight_with_unit }}</g:shipping_weight> </item> {% endfor %} {% endfor %} </channel> </rss> {% endpaginate %}

or Download from Github: Shopify xml Product Feed

Step 3: Assign products to your xml Collection.

You should now have a new XML Template created. This is just the template that defines how your collection will be displayed on your site. We now need to assign products to this template so we can submit the feed to google.

This involves creating a new product collection, and choosing the xml-product-feed as the template to use:

  • Go to Products > Collections
  • Click the Add Collection button
  • Enter a name for your new collection. I recommend XML Google Shopping Feed – All if you want a single feed for all your products or XML Google Shopping Feed – Shoes if you want this feed to contain your shoe products (or other product type).
  • Add products you want published as part of this collection feed. (either manually or via dynamic rules)
  • Click Save.

Preview the collection to make sure it works. You should just see a bunch of unformatted text on the screen. If you do a “view source” in your browser, you should now see the formatted XML.

Make sure you copy the url of this collection, as you will need to submit this url to Google Merchant center.

Step 4: Submit your feed to Google Merchant

Make sure you have your xml collection url open from step 3 above.

  • Log into Google Merchant Center or create an account if you do not have one.
  • Go to Feeds and click the +Feed button to create a new feed.
  • Mode: Choose Standard or Test (recommended that you choose Test until you are sure the feed is correct)
  • Feed type: Products
  • Select your target country & language. If you have multiple target countries, you need to submit a separate feed for each.
  • Enter a descriptive name for your feed. eg: Shopify XML Feed – Shoes
  • Choose Scheduled Fetches as your input method (recommend daily)
  • Enter your collection url in the File URL field, and the collection’s filename in the name field (eg: name=google-shopping-feed-shoes and url=
  • Save and click the Fetch Now button to download the feed. Wait a few minutes for the feed to complete.
  • Fix errors: If there are errors, Google Merchant Center will tell you in a few minutes. Go back and fix any errors, re-fetch, and keep doing so until the file is error free.

If you have more than 1000 variants, you will need to submit multiple feeds with the ?page=x querystring appended as so:


If you receive any errors, the best way to troubleshoot them is to open the the collection / feed url in your browser and do a view source. Then navigate to the line & character of the error to investigate further

Common Errors

  • Improperly formatted XML Error on Line 1: This is usually because your template has a blank line as it’s first line. Delete the blank line and make sure your template begins with {% layout none %} … exactly as in the code.
  • Improperly formatted XML Error on Line 1: The other cause of this error is that you forgot to choose the XML template for your Shopping Feed Url, and you are actually outputting HTML. Go to your collection in the Shopify Admin console and set the template correctly (at the bottom of the right hand column) 
  • Errors at Lines XXX: The xml file does not support & and > characters. These must be replaced by encoded versions &amp ; &gt ; or the feed will report errors.

(optional) Step 5: Exclude your feed collection from showing up on your store pages

If your store is setup to programatically display all your collections, then you will want to make sure your xml feed doesn’t show up. The exact way to do this will change depending on your theme, but generally you will want to have an “unless” statement within the loop that displays your collections:

{% unless collection.title contains "xml" %}
... your collection code ...
{% endunless %}


If you have complex rules or customization that you want to apply to your feed, you have several options:

  • Use the Google Merchant Center “Feed Rules” tab to transform various values based on various conditions. This is very powerful!
  • Modify your collection template and add conditional statements based on product titles, tags, metafields, etc…
  • Create multiple collection templates with hard coded values
  • Create custom Feed Rules in Google Merchant Center
  • A combination of the above.

If you have more than 1000 variants, you will need to submit multiple feeds with the ?page=x querystring appended as so:

Related Posts & Resources

Attribution Digital Marketing Facebook Ads Google Ads

The Value of View Through Conversions

My personal experience with View Through Conversions (VTCs) is that they provide very little real-world lift in sales, and are more likely just poaching attribution from other channels. So I almost always give VTCs a value of zero and ignore them.

The truth probably lies somewhere in-between. For lower funnel remarketing campaigns VTCs probably provide close to zero value, while for truly brand-unaware audiences, the value is somewhere between 0 and 100%.

If you are one of those who wants to believe in the value of VTCs, you should calculate the true value of VTCs by running a placebo A/B test. Below is just such a test that we ran on the AdRoll network back in 2013:

Placebo A/B Tests to Measure View Through Conversions

Display Network A/B Test

How to Setup a Placebo A/B Test

You will likely need assistance from your ad platform or ad agency to properly run this sort of test. It is usually difficult to have a blank or psa ad approved.

  1. Create 2 separate (but equal) non-overlapping campaigns
  2. Campaign A will serve your “normal” ad
    Campaign B will serve a Public Service Announcement ad
  3. Run both campaigns for a few weeks
  4. Calculate VTC “lift” as follows:
Valid VTC Formula

Real Life Example

Campaign A
(Real Ad)
Campaign B
(PSA Ad)
Data from a real world A/B test performed in 2013 on the AdRoll network

VTC “Lift” = (329 – 261) / 329 = 20%

In this real life example, the irrelevant PSA Ads still managed to generate 80% of the View Through Conversion volume that the real ad generated. To be more clear, 261 people saw an ad to adopt a cat, and later went to to purchase a boating license.

So 80% of VTCs can be given a value of zero. Of the remaining 20%, more analysis is needed to figure out exactly how they influenced sales. The Real Ad did seem to generate more VTCs than the PSA ad, but the real question is if those VTCs resulted in extra incremental sales or were they simply tracking sales generated by another channel such as e-mail?

Real Life Example #2: Facebook

A few years later we ran a similar Placebo A/B test on Facebook with the help of SocialCode. Unfortunately I no longer have the data for this test, but the end results was that there was ZERO lift from VTCs, and the PSA ad actually outperformed the real ads from a VTC standpoint! (People seeing cats were buying more boating licenses than the people seeing ads for a boating license)

So how should you value View Through Conversions?

Here are my recommendations:

  • Give View Through Conversions a value of ZERO. Unless you can prove otherwise via an A/B test. This is especially true for re-marketing campaigns where the visitors have previously visited your site, and may be actively engaged in checkout when the ad is shown.
  • Run your own Placebo A/B test. If someone insists on using VTCs in performance metrics, then you should insist on running an A/B test to calculate the true value. You should run at least two tests: One for remarketing audiences and another for brand-unaware audiences.
  • Be cautious and skeptical of anyone pushing the value of VTCs especially if they are an ad agency or ad platform that will benefit from including the extra VTCs in their performance metrics.

When can View Through Conversions be valuable?

  • Brand Unaware Audiences on Trusted Networks: If you are marketing to a 100% brand-unaware audience, and a trustworthy network, then there might be a valid argument to give VTCs some credit.
  • To measure which sites your customers frequently visit: Looking at VTCs on a per-placement level, *should* theoretically be a good indication of which sites your customers spend time on. You can then consider having targeted prospecting campaigns focused explicitly on those sites.