Performance Max text limits: 30, 90, 90 and 25 characters, counted
Performance Max headlines run 30 characters, long headlines 90, descriptions 90, business name 25. A short script counts generated copy before upload.

Performance Max asset groups accept headlines of up to 30 characters (3 to 15 of them), long headlines of up to 90 (1 to 5), descriptions of up to 90 (2 to 5) and a business name of up to 25. Count generated copy against those limits in code before you submit it, because a slot over its limit fails the format.
The text slots
The Google Ads API page on Performance Max asset requirements lists these slots. Values were read on 2026-10-03.
| Slot | Max characters | How many |
|---|---|---|
| Headline | 30 | 3 to 15 |
| Long headline | 90 | 1 to 5 |
| Description | 90 | 2 to 5 |
| Business name | 25 | 1 |
Pair copy with visuals as a unit
A creative review goes faster when the copy slot and the visual that sits next to it are produced together. A batch that renders ten visuals and a separate sheet of copy invites mismatches, such as a headline that names a product color the image does not show.
One approach is to generate the visual from a structured brief and write the headline from the same brief. With Sume, a Format run item can bind an output_schema, so the run returns copy fields in a fixed shape. Bulk runs queue 1 to 100 such items with a concurrency window of 1 to 16.
A counting script
The script below checks a list of candidates against each slot and prints what to cut. It counts Python characters, so confirm how Google counts any non-Latin script or emoji in your account before relying on it for those.
LIMITS = {
"headline": (30, 3, 15),
"long_headline": (90, 1, 5),
"description": (90, 2, 5),
"business_name": (25, 1, 1),
}
def check(slot, items):
max_len, min_n, max_n = LIMITS[slot]
problems = []
if not (min_n <= len(items) <= max_n):
problems.append(f"{slot}: need {min_n}-{max_n} items, have {len(items)}")
for text in items:
if len(text) > max_len:
problems.append(f"{slot}: {len(text)}/{max_len} -> {text!r}")
return problems
copy = {
"headline": ["Trail shoes built to grip", "Light on the foot", "Free returns for 30 days"],
"long_headline": ["Lightweight trail running shoes with a grippy sole for wet ground"],
"description": [
"Cushioned, light and built for wet trails.",
"Order today and try them for 30 days.",
],
"business_name": ["Northline Outfitters"],
}
for slot, items in copy.items():
for line in check(slot, items):
print(line)
print("done")Where generated copy goes wrong
Language models overshoot character limits often, and asking for a limit in the prompt does not guarantee it. Keep the check as a gate in the pipeline, not as a hope. When a line is over, rewrite it and recheck rather than truncating mid-word.
Keep claims in the copy consistent with the product the visual shows. Anything about price, discounts or shipping must match your landing page, which is a policy matter on the ad platform and not something a character counter can verify.
Sources
Related posts
More in Developers
- Phone clip with rotation metadata: vertical? Read video inspect
A phone MP4 can store 1920x1080 pixels plus a rotation tag and play as vertical. Read width, height and rotation from video inspect before you judge a Short.
- Phone-quality TTS: 8 kHz mu-law on Sume and where MAI Flash fits
Sume's TTS output_format accepts 8000 Hz and pcm_mulaw in wav or raw. What that gives a phone system, what it does not, and how MAI Flash is positioned.
- Pick seedance-2 or seedance-2.5 in code by clip length, with price
A small Python helper that sends clips up to 15 s to seedance-2 and longer ones to seedance-2.5, with the 720p price from Sume token math. Runs as written.
- Pick a subtitle translation model with a 20-cue pilot
Vendor benchmark scores do not tell you how a model handles your captions. Run the same 20 cues through each candidate, burn them, and compare on screen.
Written by Sume