In May 2025 I spoke at the Whitepress SEO Vibes Summit in Zakopane about ‘How to Grow Your eCommerce with AI & Automation’. Find my speaker slides, my Google Apps Scripts and my video about how I optimize product descriptions and automate it with AI. In this practical talk, you’ll learn why you need to optimize product descriptions, how you can create and automate new product descriptions and then optimize them further with Search Console data.
Get my slides here: https://speakerdeck.com/katarinadahlin/seovibes25

How to optimize product descriptions wth ChatGPT and Make
Here is one version of how I optimize products with ChatGPT and automate it with Make. I made this video in September but I use the same method still.
If you are new to Make.com, you can try it out and get your 1 month of the Pro plan for FREE!.
Try Make.com here and Get your 1 month of the Pro plan for FREE!.
Google App scripts in re-optimizing old content
I use three Google apps script when re-optimizing old content with Search Console data.
- Check if a keyword is found in the page content or not (2-part script. Data from Search Console)
- Grouping keywords in a sheet to the urls they are ranking on using Search Console data
- After running an automation where keywords were added to the content I run this Google Apps Script that highlights the changes in column C (current text content) and D (Optimized text content) for easier quality check and read through.
Check if a keyword is found in the page content or not with Google Apps Script
First you need to get your Search Console data into a Google Sheet where you combine Search Query with Landing page. After that you can check if a keyword ranking well, between 5-30, is found in the content or not. If it is relevant enough to add to the existing content, you can do it. This is a low hanging fruit and the keywords usually starts to rank better.
Here is a video how I check if keywords are found on page or not with Google Apps scripts:
This script is also two folded. The first script that checking if the keyword is found on the page or not. This script continues from the next row where it left off. The other script is for resetting from which row to start with, and running the script makes it start checking from row 2 again.
Run this script to check if the keyword in column B is found or not found on the page in column A. The results will be added back to column G as Found or Not found. It checks 100 rows at one run. It continues from the next row when running the script again.
Google Apps Script for checking if the keyword is found on the page or not part 1:
function checkKeywordInPage() {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
const lastRow = sheet.getLastRow();
const maxRows = 8000; // Limit to 8000 rows
const startRow = Number(PropertiesService.getScriptProperties().getProperty('lastProcessedRow')) || 2; // Start at saved row or row 2
const chunkSize = 100; // Process 100 rows per run
Logger.log(`Starting from row: ${startRow}`);
for (let i = startRow; i <= lastRow && i < startRow + chunkSize && i <= maxRows; i++) {
const url = sheet.getRange(i, 1).getValue();
const keyword = sheet.getRange(i, 2).getValue().toLowerCase(); // Convert keyword to lowercase
try {
// Use muteHttpExceptions to handle non-200 responses without throwing an error
const response = UrlFetchApp.fetch(url, {muteHttpExceptions: true});
const responseCode = response.getResponseCode();
// If URL returns a 404 error, mark it and skip keyword checking.
if (responseCode === 404) {
sheet.getRange(i, 7).setValue("404");
continue;
}
const content = response.getContentText().toLowerCase(); // Convert page content to lowercase
// Replace spaces in the keyword with optional hyphen regex pattern
const modifiedKeyword = keyword.replace(/\s+/g, '[-\\s]*');
const keywordRegex = new RegExp(modifiedKeyword, 'i'); // Case-insensitive regex for the modified keyword
if (keywordRegex.test(content)) {
sheet.getRange(i, 7).setValue("Found");
} else {
sheet.getRange(i, 7).setValue("Not Found");
}
} catch (e) {
sheet.getRange(i, 7).setValue("Error: " + e.message);
}
}
// Save progress
const nextRow = startRow + chunkSize;
if (nextRow <= lastRow && nextRow <= maxRows) {
PropertiesService.getScriptProperties().setProperty('lastProcessedRow', nextRow);
Logger.log(`Saved progress. Next start row: ${nextRow}`);
} else {
PropertiesService.getScriptProperties().deleteProperty('lastProcessedRow'); // Reset when done
Logger.log('Processing complete or reached row limit!');
}
}
function resetProgress() {
// Manually reset the progress
PropertiesService.getScriptProperties().deleteProperty('lastProcessedRow');
Logger.log('Progress has been reset.');
}
Google Apps Script for checking if the keyword is found on the page or not part 2:
This script resets the first script to start again from row 2.
// Reset the progress to start the script from row 2
function resetProgress() {
PropertiesService.getScriptProperties().deleteProperty('lastProcessedRow');
Logger.log('Progress has been reset. The script will start from row 2.');
}
// Check the saved progress to see where the script left off
function checkProgress() {
const progress =
After you got the data if keywords are found on the page content or not, you can first filter out the keywords that are ranking on position 5-30 for example and that has more than xxx amount of impressions. Delete everything else from the sheet. Then run another Google Apps Script that groups them in your sheet so that all the keywords that is ranking on a url in the sheet will be added in a column, so that you can use this data for your automations later to get the keywords that you want to add to your content.
Google Apps Script for grouping keywords to the urls they are ranking on
This Google Apps Script is designed to group search performance metrics (query, clicks, impressions, position) by landing page URL in a Google Sheet — and then write these grouped values back into the sheet, one entry per line, in columns F through I.

function groupKeywordsClicksImpressionsPositionByURL() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var data = sheet.getDataRange().getValues();
var lastProcessedRow = 1; // Default to row 2 if no previous data exists
for (var i = data.length - 1; i >= 1; i--) {
if (data[i][5] && data[i][6] && data[i][7] && data[i][8]) { // Check if all grouped columns have data
lastProcessedRow = i + 1;
break;
}
}
var urlToQueries = {};
var urlToClicks = {};
var urlToImpressions = {};
var urlToPosition = {};
// Loop through data from last processed row
for (var i = lastProcessedRow; i < data.length; i++) {
var url = data[i][0]; // Column A (Landing page)
var query = data[i][1]; // Column B (Search query)
var clicks = data[i][2]; // Column C (Clicks)
var impressions = data[i][3]; // Column D (Impressions)
var position = data[i][4]; // Column E (Average position)
if (url) {
if (!urlToQueries[url]) {
urlToQueries[url] = [];
urlToClicks[url] = [];
urlToImpressions[url] = [];
urlToPosition[url] = [];
}
if (query || query === 0) {
urlToQueries[url].push(query);
}
if (clicks || clicks === 0) {
urlToClicks[url].push(clicks);
}
if (impressions || impressions === 0) {
urlToImpressions[url].push(impressions);
}
if (position || position === 0) {
urlToPosition[url].push(position);
}
}
}
// Write back the grouped data to Columns F, G, H, and I with each entry on a new line
for (var i = lastProcessedRow; i < data.length; i++) {
var url = data[i][0];
if (urlToQueries[url]) {
sheet.getRange(i + 1, 6).setValue(urlToQueries[url].join("\n"));
sheet.getRange(i + 1, 7).setValue(urlToClicks[url].join("\n"));
sheet.getRange(i + 1, 8).setValue(urlToImpressions[url].join("\n"));
sheet.getRange(i + 1, 9).setValue(urlToPosition[url].join("\n"));
delete urlToQueries[url]; // Prevent duplicate writing
delete urlToClicks[url];
delete urlToImpressions[url];
delete urlToPosition[url];
}
}
}
Google Apps Script that highlights the changes in column C (current text content) and D (Optimized text content) for easier quality check and read through:
This Google Apps Script compares the text in Column C and Column D of a Google Sheet row by row, and highlights in red bold any words in Column D that are different from those in Column C. It’s useful for visually spotting changes between original and edited text versions.

function highlightDifferencesInColumnD() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var lastRow = sheet.getLastRow(); // Get the last row with data
for (var row = 2; row <= lastRow; row++) { // Start from row 2 and go down
var cellC = sheet.getRange(row, 3); // Column C
var cellD = sheet.getRange(row, 4); // Column D
var textC = cellC.getValue().toString().trim();
var textD = cellD.getValue().toString().trim();
if (textC === "" || textD === "") {
continue; // Skip rows where one or both cells are empty
}
var formattedD = highlightDifferences(textC, textD);
cellD.setRichTextValue(formattedD);
}
}
function highlightDifferences(textC, textD) {
var richText = SpreadsheetApp.newRichTextValue().setText(textD);
var diffRanges = findWordDifferences(textC, textD);
var highlightStyle = SpreadsheetApp.newTextStyle()
.setBold(true)
.setForegroundColor("red") // Set the text color to red
.build();
diffRanges.forEach(range => {
richText.setTextStyle(range[0], range[1], highlightStyle);
});
return richText.build();
}
function findWordDifferences(textC, textD) {
var wordsC = textC.split(/\s+/);
var wordsD = textD.split(/\s+/);
var commonWords = findLCS(wordsC, wordsD);
var indexD = 0, posD = 0;
var diffRanges = [];
wordsD.forEach(word => {
if (indexD >= commonWords.length || word !== commonWords[indexD]) {
var start = posD;
posD += word.length;
diffRanges.push([start, posD]);
} else {
indexD++;
posD += word.length;
}
posD++; // account for spaces
});
return diffRanges;
}
function findLCS(wordsC, wordsD) {
var m = wordsC.length, n = wordsD.length;
var dp = Array(m + 1).fill(null).map(() => Array(n + 1).fill(0));
for (var i = 1; i <= m; i++) {
for (var j = 1; j <= n; j++) {
if (wordsC[i - 1] === wordsD[j - 1]) {
dp[i][j] = dp[i - 1][j - 1] + 1;
} else {
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
}
}
}
var i = m, j = n, commonWords = [];
while (i > 0 && j > 0) {
if (wordsC[i - 1] === wordsD[j - 1]) {
commonWords.unshift(wordsC[i - 1]);
i--;
j--;
} else if (dp[i - 1][j] > dp[i][j - 1]) {
i--;
} else {
j--;
}
}
return commonWords;
}
Import in bulk to your site
Use All Import WP-plugin for WordPress to upload everything in bulk to your site.
All import Instructions for WordPress:
https://katarinadahlin.com/all-import-product-descriptions-wordpress/