Bulk banners in seconds

Create Banners in Bulk

Easy creation of banners in bulk, for all of your locations

Say no more to manually designing 1000+ ad variations for your PPC campaigns.
 
Manually designing ad variations is:
 
❌ A complete time suck
❌ A branding nightmare
❌ Totally unnecessary
 
So… why are you still doing it?
 
I automated 95% of it using Creatopy + AI, and now I can generate 1000+ ad designs in 5 seconds.
 
Here’s how you can do it too:
 
🔹 Step 1: Create a Google Sheets template
→ List all the businesses/websites you need banners for.
→ Or skip the setup, just copy the Sheets below.
→ Each row = one unique banner variation (custom branding, colors, and logo).
 
🔹 Step 2: Drop in Website URLs
→ Just paste the URLs of the businesses you’re designing for.
→ The script will extract branding elements.
 
🔹 Step 3: Run a simple script (No coding needed!)
→ Go to Extensions → App Scripts in Google Sheets.
→ Copy-paste the provided script (find it below this content section).
→ Save & Run.
 
Boom! The script will now:
✅ Extract the top 10 branding colors of each website.
✅ Grab the logo (no more hunting it down manually).
✅ Store everything in your sheet, ready for banner creation.
 
🔹 Step 4: Open Creatopy & Create a Design
→ Start with 300x250px (Google Ads’ most common banner size).
→ This is your template, which will auto-fill with branding from each website.
 
🔹 Step 5: Link Your CSV Feed
→ Click Feed → Upload CSV in Creatopy.
→ Link the correct fields (logo, colors, text, etc.).
 
Creatopy now mass-generates unique banners for every business in your sheet.
 
🔹 Bonus: AI-Generated Ad Headlines
Want high-converting headlines? Use SheetGPT for Google Sheets with this prompt:
 
=CHATGPT(“What would be a good Headline for a Google Ads ad for this business? Only return 1 headline (the best one) Don’t use quotation marks and max 25 characters”&A6,)
This automatically generates a perfect ad headline directly in your spreadsheet.
 
Need multiple ad variations for the same business? Slightly tweak the CHATGPT prompt to keep refreshing new headlines.
 
The result?
→ 1000+ branded, high-quality banners in seconds
→ Perfectly optimized for Google Ads

→ No more copy-pasting, color-picking, or resizing

Sheets Link: https://docs.google.com/spreadsheets/d/1ELaKIgXc66NMKe1tfuR39-oMo8dKzqYaAOv_uiZvEeA/edit?gid=0#gid=0

Apps Script:

				
					function myFunction() {
  fetchCSS();
}

function fetchCSS() {
  var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheets()[0];
  var urls = sheet.getRange('A:A').getValues().flat().filter(String);

  if (!urls.length) {
    console.log('Missing URLs');
    return;
  }

  urls.forEach((url, index) => {
    try {
      var response = UrlFetchApp.fetch(url);
      var html = response.getContentText();
      var cssLinks = html.match(/<link[^>]+?rel=["']stylesheet["'][^>]*?>/gi);
      var logoUrl = extractLogoUrl(html) || extractLogoFromHeader(html) || extractSvgLogo(html) || extractFavicon(html);

      console.log("CSS Links Found: " + (cssLinks ? cssLinks.length : "None") + " for: " + url);

      var colorCount = {};

      cssLinks && cssLinks.forEach(function (link) {
        var hrefMatch = link.match(/href=["']([^"']+)["']/);
        if (!hrefMatch) {
          return;
        }

        var cssUrl = decodeHtmlEntities(hrefMatch[1]);
        handleCssUrl(cssUrl, url, colorCount, logoUrl);
      });

      outputColorsToSheet(sheet, colorCount, url, index + 1, logoUrl);
    } catch (e) {
      console.log('Error: ' + e.toString());
    }
  });
}

function handleCssUrl(cssUrl, baseUrl, colorCount, logoUrl) {
  if (!cssUrl.startsWith('http')) {
    if (cssUrl.startsWith('//')) {
      cssUrl = 'https:' + cssUrl;
    } else {
      try {
        cssUrl = new URL(cssUrl, baseUrl).href;
      } catch (e) {
        cssUrl = null;
      }
    }
  }

  if (!cssUrl) {
    return;
  }

  try {
    var cssResponse = UrlFetchApp.fetch(cssUrl);
    var cssText = cssResponse.getContentText();
    extractColors(cssText, colorCount);
    // Check for background-image logos if no other logo was found
    if (!logoUrl) {
      logoUrl = fetchBackgroundLogo(cssText, baseUrl);
    }
  } catch (e) {
    console.log('Error fetching CSS from URL: ' + cssUrl + " | Error: " + e.toString());
  }
}

function extractLogoUrl(html) {
  var patterns = [
    /<img[^>]+src=["']([^"']+)["'][^>]*(id=["'][^"']*logo[^"']*["']|class=["'][^"']*logo[^"']*["'])/i,
    /<img[^>]+class=["'][^"']*logo[^"']*["'][^>]*src=["']([^"']+)["']/i,
    /<img[^>]+id=["'][^"']*logo[^"']*["'][^>]*src=["']([^"']+)["']/i
  ];

  for (let pattern of patterns) {
    let match = html.match(pattern);
    if (match) {
      return decodeHtmlEntities(match[1]);
    }
  }
  return null;
}

function fetchBackgroundLogo(cssText, baseUrl) {
  var urlMatch = cssText.match(/\.logo[^{]*{[^}]*background-image:\s*url\(["']?([^"'\)]+)["']?\);/i);
  if (urlMatch) {
    let imageUrl = urlMatch[1];
    if (!imageUrl.startsWith('http')) {
      imageUrl = new URL(imageUrl, baseUrl).toString();
    }
    return imageUrl;
  }
  return null;
}

function extractSvgLogo(html) {
  var svgMatch = html.match(/<svg[^>]*class=["'][^"']*logo[^"']*["'][^>]*>(.*?)<\/svg>/i);
  if (svgMatch) {
    return svgMatch[0];
  }
  return null;
}

function extractLogoFromHeader(html) {
  var headerContent = html.match(/<header[^>]*>([\s\S]*?)<\/header>/i);
  if (headerContent) {
    return extractLogoUrl(headerContent[1]);
  }
  return null;
}

function extractFavicon(html) {
  var faviconMatch = html.match(/<link[^>]*rel=["']icon["'][^>]*href=["']([^"']+)["']/i);
  if (faviconMatch) {
    return decodeHtmlEntities(faviconMatch[1]);
  }
  return null;
}

function extractColors(cssText, colorCount) {
  var colors = cssText.match(/#([0-9a-f]{3,6})\b/gi);
  if (colors) {
    colors.forEach(function (color) {
      color = color.toLowerCase();
      colorCount[color] = (colorCount[color] || 0) + 1;
    });
  }
}

function outputColorsToSheet(sheet, colorCount, url, index, logoUrl) {
  var colorData = [];
  if (colorCount) {
    var sortedColors = Object.keys(colorCount).sort(function (a, b) {
      return colorCount[b] - colorCount[a];
    });

    colorData = sortedColors.slice(0, 10).map(function (color) {
      return color;
    });
  }

  if (colorData.length === 0) {
    console.log("No color data to write for: " + url);
  } else {
    // Write the top 10 colors starting from column B for the row corresponding to the URL
    sheet.getRange(index, 2, 1, colorData.length).setValues([colorData]);
  }

  // Write the logo URL to column M for the row corresponding to the URL
  if (logoUrl) {
    sheet.getRange(index, 13).setValue(logoUrl);
  }
}

function decodeHtmlEntities(text) {
  return text.replace(/&#(\d+);/g, function (match, dec) {
    return String.fromCharCode(dec);
  }).replace(/&#[xX]([0-9a-fA-F]+);/g, function (match, hex) {
    return String.fromCharCode(parseInt(hex, 16));
  }).replace(/&(\w+);/g, function (match, name) {
    const entities = {
      quot: '"',
      amp: '&',
      lt: '<',
      gt: '>',
      nbsp: '\u00A0',
      hellip: '…',
      ndash: '–',
      mdash: '—'
    };
    return entities[name] || match;
  });
}

				
			

productivity & automation hacks straight to your e-mail

Get Timehacker for free in your inbox right now

Join over 1.000+ savvy marketeers who benefit from this FREE inspirational newsletter!