An LLM can draft alt text, titles and meta descriptions for hundreds of pages in an afternoon, and it will also happily write "Image of a kurta" for every product photo and the same description for every page. The pipeline that works is draft with a model, check with a script, and have a person read what the script cannot judge. This post gives you the prompts, the rules (with the primary-source wording for each), and a Python checker I ran on a made-up four-page store.
Status: guidance below was read in full on 2026-10-09 from Google Search Central (title links, snippets and meta descriptions, image SEO best practices), the W3C alt decision tree and the WCAG 2.2 Understanding page for success criterion 1.1.1. The store, products and "model outputs" in the demo are invented by me, and the bad outputs are hand-written to trigger the checker, not captured from a model. Prompts are untested templates.
What do WCAG and Google actually say about alt text?
Short version: write for the person who cannot see the image, and the search benefit follows.
- WCAG 2.2, success criterion 1.1.1 (Level A): non-text content that is presented to the user has a text alternative that serves the equivalent purpose, with listed exceptions including decoration. There is no length rule.
- W3C alt decision tree (page updated 13 May 2024): images that contain text get that text in
altif it appears nowhere else; images in links or buttons get the destination or action; images that add meaning get a brief description; graphs and complex images get their information elsewhere on the page; purely decorative images get an emptyalt. - Google image guidance: alt text is "the most important attribute" for image metadata, helps Google understand the image, and should be useful, information-rich and in context. Google's own examples contrast
alt="puppy"(better) withalt="Dalmatian puppy playing fetch"(best), and flags lists of keywords as stuffing that may look like spam.
Those three agree, which makes them a safe base for rules. Note the gap: they do not ban "image of" phrasing or set a character limit. I flag both in the checker as style choices, labelled as such.
What do Google's docs say about titles and descriptions?
Also from the pages as read on 2026-10-09:
- Every page should have a descriptive, concise
titleelement. Avoid vague titles like "Home", keyword stuffing, and repeated or boilerplate text across pages. - Brand concisely, by putting the site name at the start or end separated by a delimiter, rather than a long tagline on every page.
- There is no length limit for titles. Results are truncated as needed, typically to fit the device width.
- Use the same language and writing system as the page: for a Hindi page, write the title in Hindi, not English or transliterated into Latin characters. Otherwise Google may pick a different title. (If you serve Hinglish users, see the Hinglish prompting guide for script routing.)
- Meta descriptions: unique per page, truthful, and Google "sometimes" uses them instead of page content. Programmatic generation "can be appropriate and are encouraged" for large database-driven sites if descriptions are human-readable and diverse. Keyword lists are less likely to be used as snippets.
So you can automate, but only if each output is specific to its page.
How should the pipeline be structured?
Five steps, each with one job.
- Collect facts. Per page: product or article attributes, the visible headline, and for each image its role on the page (hero, product shot, icon, chart) plus a one-line human caption or the product variant. The model must not invent facts, so hand it the facts.
- Classify each image first. Ask the model to pick one of five types from the W3C tree (decorative, functional, informative, text, complex) and only then write alt. Classification catches the biggest mistake, which is describing an icon or divider.
- Generate as JSON so it is checkable. If your provider has schema-enforced output, use it; see structured outputs and JSON mode.
- Run the checker and send failures back with the issue text for one retry.
- Human pass on what code cannot see: is the description true for this image, is the tone right, would a screen-reader user miss anything.
What does the alt text prompt look like?
Untested template. The decision tree is encoded as the first thing the model must do.
You write alt text for a web page. Use only the facts given. Do not guess names, ages, brands or places that are not in the facts.
For each image, first choose "type":
- decorative: purely visual, adds no information. alt must be "".
- functional: inside a link or button. alt names the action or destination.
- text: contains text not otherwise on the page. alt must repeat that text exactly.
- complex: chart, graph or diagram. alt is one short sentence naming what it shows; put the details in "long_description".
- informative: anything else that adds meaning. alt is one short sentence for someone who cannot see it.
Rules for alt: no "image of", "picture of" or file names. No keyword lists. Write for the context of this page. If unsure, set "needs_review": true.
PAGE FACTS:
[PAGE_HEADLINE]
[PRODUCT_OR_ARTICLE_ATTRIBUTES]
IMAGES (src, role on page, human caption):
[IMAGE_LIST]
Return JSON only: [{"src": "...", "type": "...", "alt": "...", "long_description": "", "needs_review": false}]
The needs_review flag matters. A model that is allowed to say "unsure" will say it, and a model that is forced to answer will describe things that are not there. If your model cannot see the images, it is writing from your captions only. If it can, it still does not know which colourway you ship or who the person is, which is why captions go in.
What do the title and description prompts look like?
Write a title (aim under 60 characters) and a meta description (aim 70 to 160 characters) for this page.
Use only the facts below. The title leads with the specific product or topic, then the brand after a pipe.
The description says what the page offers in plain sentences, with at least one concrete detail from the facts (material, size range, price band, shipping time, author, date).
No keyword lists. No ALL CAPS. No claims not in the facts ("best", "cheapest", "#1") unless they are in the facts.
Do not repeat the title in the description. Write in [PAGE_LANGUAGE] using [PAGE_SCRIPT].
FACTS:
[PAGE_FACTS]
The 60 and 70 to 160 ranges are display heuristics. Google says truncation happens by device width, so a title slightly over 60 is not a failure, only a prompt to check that the key words come first.
Can a script check the output?
Yes, for everything mechanical. The checker below takes a JSON file of pages, each with a title, description and image list. It flags missing or non-empty alt on the wrong image types, filename-like alt, "image of" filler, very long alt, keyword stuffing (a word repeated three times or more, or three or more commas), image-of-text alt that does not match the text, title and description length against the house limits, titles or descriptions repeated on several pages, and a description that merely repeats the title. It uses only the standard library.
import json
import re
import sys
from collections import Counter, defaultdict
TITLE_SOFT = 60
DESC_MIN, DESC_MAX = 70, 160
ALT_SOFT = 125
STOP = {"the", "a", "an", "for", "of", "and", "in", "on", "with", "to", "at", "by"}
FILE_LIKE = re.compile(r"(\.(jpe?g|png|webp|gif|svg|avif)$)|(^(img|dsc|image|photo)[_\-]?\d+)", re.I)
PREFIX = re.compile(r"^(an? )?(image|picture|photo|graphic|icon)( of| showing)\b", re.I)
def words(s):
return [w for w in re.findall(r"[a-z0-9']+", s.lower()) if w not in STOP and len(w) > 2]
def stuffed(s):
c = Counter(words(s))
return any(n >= 3 for n in c.values()) or s.count(",") >= 3
def check_image(img, issues):
alt, typ, src = img["alt"].strip(), img["type"], img["src"]
tag = f"IMG {src}"
if typ == "decorative":
if alt:
issues.append(f"{tag}: decorative image should have empty alt")
return
if not alt:
issues.append(f"{tag}: {typ} image has empty alt")
return
if FILE_LIKE.search(alt):
issues.append(f"{tag}: alt looks like a filename")
if PREFIX.search(alt):
issues.append(f"{tag}: alt starts with 'image of' style filler")
if len(alt) > ALT_SOFT:
issues.append(f"{tag}: alt is {len(alt)} chars (house limit {ALT_SOFT})")
if stuffed(alt):
issues.append(f"{tag}: alt looks keyword-stuffed")
if typ == "text" and img.get("text_in_image", "").strip() != alt:
issues.append(f"{tag}: image of text, alt must carry the same text")
def check_title(p, issues):
t = p["title"].strip()
if not t:
issues.append("TITLE missing")
return
if len(t) > TITLE_SOFT:
issues.append(f"TITLE {len(t)} chars (house limit {TITLE_SOFT}; may be truncated)")
if stuffed(t):
issues.append("TITLE looks keyword-stuffed")
if t.isupper():
issues.append("TITLE is all caps")
def check_desc(p, issues):
d = p["description"].strip()
if not d:
issues.append("DESC missing")
return
if not (DESC_MIN <= len(d) <= DESC_MAX):
issues.append(f"DESC {len(d)} chars (house range {DESC_MIN}-{DESC_MAX})")
if stuffed(d):
issues.append("DESC looks keyword-stuffed")
if d.lower().startswith(p["title"].strip().lower()[:25]):
issues.append("DESC repeats the title")
def main(path):
pages = json.load(open(path, encoding="utf-8"))
report = defaultdict(list)
titles, descs = defaultdict(list), defaultdict(list)
for p in pages:
check_title(p, report[p["url"]])
check_desc(p, report[p["url"]])
for img in p["images"]:
check_image(img, report[p["url"]])
titles[p["title"].strip().lower()].append(p["url"])
if p["description"].strip():
descs[p["description"].strip().lower()].append(p["url"])
for kind, groups in (("TITLE", titles), ("DESC", descs)):
for urls in groups.values():
if len(urls) > 1:
for u in urls:
report[u].append(f"{kind} duplicated on {len(urls)} pages")
total = 0
for p in pages:
issues = report[p["url"]]
total += len(issues)
print(f"{p['url']}: {'ok' if not issues else str(len(issues)) + ' issue(s)'}")
for i in issues:
print(" -", i)
print(f"total issues: {total}")
return 1 if total else 0
if __name__ == "__main__":
sys.exit(main(sys.argv[1]))
The demo input, saved as pages.json, is four invented pages for a fictional store called Kala Looms. Page one mostly looks like a good generation (specific title, a description with real details, a good informative alt, an empty alt on a divider) with two planted faults: an alt that is a filename and an icon inside a button with no alt. The other three are deliberately bad: a stuffed title, a boilerplate description repeated across pages, "Image of a kurta", a 223-character alt, a size chart whose alt is "Size chart" instead of its text, and a page with no description.
[
{"url": "/kurtas/indigo-block-print",
"title": "Indigo Block Print Cotton Kurta for Women | Kala Looms",
"description": "Hand block-printed cotton kurta in indigo with a mandarin collar. Sizes S to XL, machine washable, ships from Jaipur in 3 to 5 working days.",
"images": [
{"src": "indigo-kurta-front.webp", "type": "informative", "alt": "Model wearing the indigo block-print cotton kurta with a mandarin collar, front view"},
{"src": "divider.svg", "type": "decorative", "alt": ""},
{"src": "IMG_4471.jpg", "type": "informative", "alt": "IMG_4471.jpg"},
{"src": "cart-icon.svg", "type": "functional", "alt": ""}]},
{"url": "/kurtas/mustard-straight",
"title": "Buy Cheap Kurta Online, Kurta For Women, Women Kurta, Cotton Kurta | Kala Looms",
"description": "Kala Looms - Best Ethnic Wear Online. Shop now for the best kurtas.",
"images": [
{"src": "mustard-kurta.webp", "type": "informative", "alt": "Image of a kurta"},
{"src": "mustard-detail.webp", "type": "informative", "alt": "kurta, cotton kurta, women kurta, cheap kurta online, buy kurta, ethnic wear, block print kurta, indigo kurta"},
{"src": "size-chart.png", "type": "text", "text_in_image": "Size M: bust 38 in, length 42 in", "alt": "Size chart"}]},
{"url": "/kurtas/rust-anarkali",
"title": "Kala Looms - Best Ethnic Wear Online",
"description": "Kala Looms - Best Ethnic Wear Online. Shop now for the best kurtas.",
"images": [
{"src": "rust-anarkali.webp", "type": "informative", "alt": "Rust-coloured cotton Anarkali kurta with a flared hem, shown on a hanger against a white wall, with the dupatta folded over one side so that both the embroidered neckline and the printed border are visible in the same frame"}]},
{"url": "/about",
"title": "Kala Looms - Best Ethnic Wear Online",
"description": "",
"images": [
{"src": "team.webp", "type": "informative", "alt": "Three weavers at wooden looms in a Jaipur workshop"}]}
]
I extracted the Python listing above from this post, ran it on Python 3.13 with python3 -I, and got this real output (exit code 1 because issues were found):
/kurtas/indigo-block-print: 2 issue(s)
- IMG IMG_4471.jpg: alt looks like a filename
- IMG cart-icon.svg: functional image has empty alt
/kurtas/mustard-straight: 7 issue(s)
- TITLE 79 chars (house limit 60; may be truncated)
- TITLE looks keyword-stuffed
- DESC 67 chars (house range 70-160)
- IMG mustard-kurta.webp: alt starts with 'image of' style filler
- IMG mustard-detail.webp: alt looks keyword-stuffed
- IMG size-chart.png: image of text, alt must carry the same text
- DESC duplicated on 2 pages
/kurtas/rust-anarkali: 5 issue(s)
- DESC 67 chars (house range 70-160)
- DESC repeats the title
- IMG rust-anarkali.webp: alt is 223 chars (house limit 125)
- TITLE duplicated on 2 pages
- DESC duplicated on 2 pages
/about: 2 issue(s)
- DESC missing
- TITLE duplicated on 2 pages
total issues: 16
The decorative divider passed, the good alts on pages one and four passed, and the "Jaipur" description passed, which is the point: the checker should be quiet on good output. The "/about" title is flagged only as a duplicate, since a shared boilerplate title is exactly what Google's docs warn about.
What can the script not tell you?
Be clear on this before you trust a green run.
- Truth. "Model wearing the indigo block-print cotton kurta" passes even if the photo shows a green one. Someone has to compare alt text to images, at least for hero and product images and anything legal, medical or financial.
- Usefulness in context. A correct description can still be the wrong one. The same photo needs different alt in a product gallery and in an article about weaving.
- Redundancy with nearby text. If a caption already says everything, the W3C tree points to an empty alt. A script can only guess.
- Whether Google will use your text. Google says it generates title links automatically from several sources and sometimes uses on-page content instead of a meta description. Good metadata raises the odds; it does not guarantee the result.
- Non-English pages. The checker's word lists and regexes are English. The prefix and stuffing rules need rewriting per language, and a native reader should review. For regional-language output, the translation quality checklist has a review rubric.
How do you roll it out without making a mess?
- Pilot on 20 pages. Read every output yourself.
- Fix the prompt for the failure pattern you see most, then rerun the same 20.
- Publish only after zero checker failures and a human pass on images. Keep a log of what changed so a bad batch is reversible.
- For an ecommerce catalogue, pair this with a store audit, which finds missing alt text in bulk, and feed optimization for Merchant Center if titles also feed Google Shopping, where the length rules differ from web titles.
- Re-run the checker on new content automatically (for example in CI) so duplicates do not creep back.
Structured data for the same pages is a separate job with its own validation step: JSON-LD generation with validation.



