DEV Community

Roman | Ad Intel Lab
Roman | Ad Intel Lab

Posted on

Temu's Meta ads: 149 of 150 are catalog ads, one shows a raw template tag

Many brand pages in the Meta Ad Library read like a copywriter's folder. Temu's reads like a database export. In the 150 active ads I pulled on November 6, 2026, 149 are labelled DPA, Meta's dynamic product ads that fill a template from a product catalog. And one of the ad texts still starts with the catalog variable itself: {{product.custom_label_0}}.

Below: those ads as records, a short script, and where the source stops you.

Sample frame

Source Snapshot date Filters Ads collected Texts analysed
Meta Ad Library 2026-11-06 page facebook.com/temu, activeStatus: active, cap 150, no country or date filter 150, all active first 30 bodies in library order, cut at 300 characters; 24 distinct
Google Ads Transparency Center 2026-11-06 domain temu.com, cap 150, no country or date filter 150 headline decoded for 13 ads; copy not analysed

Both runs hit the cap of 150, so each is likely a slice of a larger set of unknown size. Meta shows catalog shopping ads in several languages; Google shows mostly text ads served for the domain. Almost everything below is about the Meta set.

What a catalog ad looks like as data

Each Meta record carries a title, a body, a ctaText, placements and a start date. On a DPA, the title is the product name from the feed, often a long string of keywords:

"Shading Tarpaulin For Camping, Protective Items, Tent Decoration, Outdoor Canopy Tent Cloth, Rain Shade Cloth, For Wilderness Camping Rain And Wind Protection, Courtyard Garden Backyard,"

The body is the template text around it. In this record it is in Portuguese, under an English product name:

"⚡️Ofertas relâmpago⚡️ Descontos incríveis em tudo! Não perca! Baixe agora o app da Temu! 📱"

So for a developer the useful questions are what the template layer looks like and whether it ever breaks.

The script

I ran this over the collector's JSON. Language is guessed with marker words, checked in a fixed order, so it is crude but auditable.

import collections
import json
import re

with open("promo/data/2026-11-06-temu.json", encoding="utf-8") as f:
    d = json.load(f)

fb = d["facebook"]
bodies = fb["bodies"]

# Language by marker words, checked in this order; anything else counts as English.
LANG = [
    ("pt", re.compile(r"\b(Comece|Compre|Descontos|frete|Baixe|aproveite)\b|relâmpago")),
    ("es", re.compile(r"¡|Ahorra|\b(Descuentos|Envío|Solo|increíbles|Descarga)\b")),
    ("tl", re.compile(r"\b(sa|para|Wag|marami)\b")),
]


def lang(text):
    for code, rx in LANG:
        if rx.search(text):
            return code
    return "en"


RULES = {
    "unfilled {{...}} placeholder": re.compile(r"\{\{.*?\}\}"),
    "mentions the app or downloading": re.compile(r"download|descarga|baixe|\bapp\b", re.I),
    "'new users' condition": re.compile(r"new (app )?users|nuevos usuarios", re.I),
    "hashtag": re.compile(r"#\w"),
}

print(f"Meta ads: {fb['ads']}, active: {fb['active']}")
print("Meta formats:", fb["formats"])
print(f"Bodies kept: {len(bodies)}, distinct: {len(set(bodies))}")
print("Languages:", dict(collections.Counter(lang(b) for b in bodies).most_common()))
for name, rx in RULES.items():
    print(f"  {name}: {sum(1 for b in bodies if rx.search(b))}")
top = collections.Counter(bodies).most_common(1)[0]
print(f"Most repeated body ({top[1]}x): {top[0][:40]!r}...")

print("\nLongest-running Meta ads (body language, product title):")
for a in fb["longestRunning"]:
    print(f"  {a['daysActive']:>3}d  {a['startDate']}  body={lang(a['body'])}  {a['title'][:34]}...")

g = d["google"]
print(f"\nGoogle ads: {g['ads']}, decoded headlines: {len(g['headlines'])}")
print("Google advertisers:", g["advertisers"])
Enter fullscreen mode Exit fullscreen mode

Output, pasted as-is:

Meta ads: 150, active: 150
Meta formats: [['DPA', 149], ['IMAGE', 1]]
Bodies kept: 30, distinct: 24
Languages: {'en': 23, 'es': 4, 'tl': 2, 'pt': 1}
  unfilled {{...}} placeholder: 1
  mentions the app or downloading: 8
  'new users' condition: 2
  hashtag: 4
Most repeated body (4x): '🛍️ Grab Hot Deals 🎉\n🛒 Shop the Best\n🔥 Ho'...

Longest-running Meta ads (body language, product title):
  366d  2025-11-06  body=es  Camisetas Sin Deportivas Para Homb...
  366d  2025-11-05  body=pt  Shading Tarpaulin For Camping, Pro...
  366d  2025-11-05  body=en  Stylish Letter Print Men's Casual ...
  366d  2025-11-05  body=es  Máquina de Troquelado y Repujado M...
  361d  2025-11-10  body=en  Women's Retro Flat Loafers, Comfor...
  356d  2025-11-15  body=es  Camiseta informal de manga larga c...
  326d  2025-12-15  body=en  1 Pair Elegant Floral Print Women'...
  314d  2025-12-28  body=en  Elegant A-line Maxi Dress With Puf...

Google ads: 150, decoded headlines: 13
Google advertisers: [['ELEMENTARY INNOVATION PTE. LTD.', 137], ['Whaleco Inc.', 12], ['Dagen He', 1]]
Enter fullscreen mode Exit fullscreen mode

The template layer

One Meta page, four languages in 30 texts. The most repeated body appears 4 times and names no product or offer:

"🛍️ Grab Hot Deals 🎉
🛒 Shop the Best
🔥 Hot Deals Inside 🎁"

Two texts carry a condition instead, such as "Only for new app users with qualifying orders". Across all 150 ads, "Shop now" is the button on 138.

The leaked variable

This is the body as the library stores it:

"{{product.custom_label_0}}🎉 Unbelievable na DISCOUNTS sa Fashion, Home, at marami pang iba! Wag palampasin! I-Download na ang Temu! 💕"

custom_label_0 is a catalog field advertisers use for their own tags. The library shows it unresolved; I can't tell whether people saw the braces, nothing, or a filled-in value, since the library may store the template rather than the rendered ad. Still, \{\{.*?\}\} is a cheap check for any feed pipeline.

A note on Google

Searching Google's library for an advertiser called Temu would miss most of it: 137 of the 150 ads for temu.com run under ELEMENTARY INNOVATION PTE. LTD., 12 under Whaleco Inc. Query by domain, then group by advertiser.

What this data can't tell you

  • A capped slice. Both sources stopped at 150, and text counts come from 30 Meta bodies cut at 300 characters.
  • Labels and dates come from the source. 149 of 150 marked DPA may partly reflect how the library labels shopping ads. Four ads show 366 days and started November 5–6, 2025, exactly a year before the snapshot, so I don't read that order as a ranking.
  • No spend, reach or results. Nothing says which template sells or how Temu splits budget. Google left headline and description empty for 137 of 150 ads, so I skip Google copy.

Running it on another store

I built these scrapers. The Facebook Ads Library Scraper returns title, body, CTA, display format, placements and start date per ad, and the Google Ads Transparency Scraper returns format, advertiser and dates for a domain, as JSON or CSV. The Meta input for this post was {"queries": ["https://www.facebook.com/temu"], "maxAdsPerQuery": 150, "activeStatus": "active"}. Both are free to try on the Apify free plan, and a run of a few hundred ads costs well under a dollar.

Not affiliated with Temu; all data is from public ad libraries.

If you were writing a lint step for a catalog ad feed, which check would you ship first: unresolved {{...}} tokens, a body language that doesn't match the title, or an offer line with no product in it?

Top comments (0)