Managing hundreds of title tags and meta descriptions manually is inefficient and inconsistent. A simple automated system using Google Sheets, Apps Script, and the OpenAI API removes the work entirely. This tutorial walks through every step required to build a repeatable, scalable metadata generator.
1. Prepare the Google Sheet
Create a new Google Sheet with three columns:
- Column A: URL
- Column B: Title Tag
- Column C: Meta Description
Paste your full list of URLs starting from A2.
Example:
A1: URL
B1: Title Tag
C1: Meta Description
A2: https://www.example.com/page-1/
A3: https://www.example.com/page-2/
…
This sheet will become the input and output for the automation.
2. Open Apps Script
Inside your Google Sheet:
- Go to Extensions → Apps Script
- Delete any existing code in Code.gs
You’ll be writing a script that loops through all URLs, sends them to OpenAI, and writes back optimised metadata.
3. Add the OpenAI API Script
Paste this entire script into Code.gs.
Replace ‘sk-xxxx’ with your real OpenAI API key.
const OPENAI_API_KEY = ‘sk-xxxx’; // Replace with your new regenerated API key
function generateMetaForUrl(url) {
if (!url) return { title: ”, description: ” };const prompt = `
You are an SEO specialist.Task:
Given this URL, generate:
1) An SEO title tag (~50–60 characters).
2) A meta description (~130–150 characters).Rules:
– Australian English
– Be concise
– Avoid keyword stuffing
– Return ONLY valid JSON:
{“title”: “…”, “description”: “…”}URL: ${url}
`;const payload = {
model: “gpt-4.1-mini”,
messages: [
{
role: “system”,
content:
“You write SEO titles and meta descriptions that are short, clear and follow character limits.”
},
{ role: “user”, content: prompt }
],
temperature: 0.3
};const options = {
method: “post”,
contentType: “application/json”,
headers: {
Authorization: “Bearer ” + OPENAI_API_KEY
},
payload: JSON.stringify(payload),
muteHttpExceptions: true
};const response = UrlFetchApp.fetch(
“https://api.openai.com/v1/chat/completions”,
options
);const status = response.getResponseCode();
const text = response.getContentText();if (status !== 200) {
Logger.log(“HTTP error: ” + status);
Logger.log(text);
return { title: “”, description: “” };
}let data;
try {
data = JSON.parse(text);
} catch (e) {
Logger.log(“Failed to parse JSON response”);
Logger.log(text);
return { title: “”, description: “” };
}if (!data.choices || !data.choices.length) {
Logger.log(“No choices in response”);
Logger.log(JSON.stringify(data));
return { title: “”, description: “” };
}const raw = (data.choices[0].message.content || “”).trim();
try {
const parsed = JSON.parse(raw);
return {
title: parsed.title || “”,
description: parsed.description || “”
};
} catch (e) {
Logger.log(“JSON parse error: ” + e);
Logger.log(“Raw content: ” + raw);
return { title: “”, description: “” };
}
}function generateAllMeta() {
const sheet = SpreadsheetApp.getActiveSheet();
const lastRow = sheet.getLastRow();
if (lastRow < 2) return;const urlRange = sheet.getRange(2, 1, lastRow – 1, 1);
const urlValues = urlRange.getValues();const titleRange = sheet.getRange(2, 2, lastRow – 1, 1);
const descRange = sheet.getRange(2, 3, lastRow – 1, 1);const titles = [];
const descriptions = [];for (let i = 0; i < urlValues.length; i++) {
const url = urlValues[i][0];if (!url) {
titles.push([“”]);
descriptions.push([“”]);
continue;
}const result = generateMetaForUrl(url);
titles.push([result.title]);
descriptions.push([result.description]);Utilities.sleep(1200); // Protect against rate limits
}titleRange.setValues(titles);
descRange.setValues(descriptions);
}function onOpen() {
const ui = SpreadsheetApp.getUi();
ui.createMenu(“SEO Tools”)
.addItem(“Generate Meta for All URLs”, “generateAllMeta”)
.addToUi();
}function testSingle() {
const sheet = SpreadsheetApp.getActiveSheet();
const url = sheet.getRange(2, 1).getValue();
const result = generateMetaForUrl(url);
sheet.getRange(2, 2).setValue(result.title);
sheet.getRange(2, 3).setValue(result.description);
}
4. Add Permissions
When you run the script the first time:
- Click Run → testSingle
- Google will ask for permissions
- Approve them
This ensures Apps Script is allowed to access external APIs.
5. Test the Script on One URL
Use the built-in helper:
- Go to Run → testSingle
This will generate the title and meta description for the URL in A2.
Check columns B2 and C2.
If the metadata appears correctly, the system is working.
6. Run the Full Automation
Once tested:
- In Sheets, click SEO Tools → Generate Meta for All URLs
The script will loop through every URL from A2 downward and fill in metadata automatically.
A large batch will take time because each request includes a delay to avoid rate limits.
7. Updating or Re-Running
You only modify one place:
The prompt text inside generateMetaForUrl.
Everything else stays the same.
You can:
- Change style
- Add branding
- Adjust character limits
- Add or remove categories
- Add keyword rules
- Enforce templates per folder structure
Then re-run generateAllMeta and overwrite the old metadata.
8. Benefits of This System
- Works entirely inside Google Sheets
- Requires no external apps
- Uses the OpenAI API directly
- Produces deterministic, consistent metadata
- Scales to thousands of URLs
- Easy to reuse for any client or website
- Fully automated once set up
This is a permanent workflow you can use for every SEO project.