Same creative, many ads: de-duplicating by creative_id
The connector hands you the field you need in order to group by creative. It is also the one field in the report you cannot sort or filter on.
Group by creative_id, and know that nothing upstream will do it for
you. In Meta's official ads connector that field exists at ad level only, and it is one of
the few in the whole catalog you can neither filter on nor sort by. So there is no "creatives"
report to ask for. You pull one row per ad with creative_id attached, collapse the rows
yourself, and check the collapse. Every creative-level number you will ever read sits downstream of
that one step.
You ask which ads are working. Back comes a tidy table, sorted, five rows at the top. Two of those rows are the same picture running against different audiences, one is that picture again with a swapped headline, and you are now looking at a list of three things dressed as five.
The fix gets repeated everywhere, including on this site: group by the creative, not the ad. Nobody says how you actually get that grouping out of Meta's connector, and it is more awkward than the advice implies. An ad and a creative are two objects, they live behind two tools, and the field that joins them is inert.
Why nothing groups it for you
The connector ships a tool that returns metadata about its own reporting fields, which means you
can interrogate the vocabulary instead of guessing at it. Asked about creative_id on
23 August 2026, it returns: "The unique identifier of the creative used in the ad", type
integer, supported levels ["ad"] — a list with one entry — filterable: false,
sortable: false, and an empty list of filter operators.
Read that against a normal metric. Amount spent is filterable and sortable at every level, with a full set of comparison operators. Spend is something the platform will reorganise your account around. The creative identifier it will hand you, and nothing more.
The consequence arrives late. There is no creative level to request — "creatives ranked by spend" is not a question the reporting surface can take, the way campaigns ranked by spend is. The collapse happens after the rows land, in a spreadsheet or in the assistant's working memory, which is exactly the arithmetic worth checking against Ads Manager.
Verified against the connector's own field-metadata tool on 23 August 2026: creative_id
comes back with filterable: false, sortable: false and no operators, at ad
level only. Meta's public documentation covers none of this — the tool's schema is the source, as it
is for most of what the connector does.
Two surfaces, one join key
Performance and creative attributes come from different tools. The reporting tool knows what an
ad spent and produced, plus the creative's identifier and nothing else about it. The creatives tool
knows the body text, the headline, the image hash and the underlying post — and nothing about
performance. creative_id is all they share.
There is a second trap, and the creatives tool warns about it itself: a plain listing returns only the identifier, name, account and status. Everything else is omitted, and the schema says in as many words "do NOT assume a field is empty just because it was missing from a previous listing response." Ask for the image hash or the underlying post by name, in a second call. An assistant that lists creatives once and then discusses their content is reading an empty table.
It matters because two ads can be the same picture and still carry different identifiers:
duplicating an ad reuses the creative object, re-uploading the same file makes a new one. Motion,
whose creative-reporting product exists to solve this, tells its users the same thing in its
help
documentation on grouping by creative — duplicate rather than re-upload, or the report splits one
creative into several. Vendor doc, not Meta's. It matches what the field list implies:
image_hash and object_story_id sit below the identifier, and they settle
the question.
The order that survives contact with an account
-
Pull at ad level, with the field named START HERE
Ask for a window, level ad, and name
creative_idin the field list. It is not in the default set and it will not appear just because the question was about creatives. The trade: ad is the only level carrying it, so a busy account returns a lot of rows and the tool caps how many come back per call. -
Collapse, then check the collapse
Sum spend and results across rows sharing an identifier, then take the biggest resulting creative and add its rows up by hand. Nothing verified that arithmetic, and you are about to spend against it. The trade: a minute — the one that makes the rest of the table usable.
-
Ask whether two identifiers are one asset
Where two creatives look identical, pull
image_hashandobject_story_idfor both. Same hash, same picture. Same story identifier, same underlying post, which also means the likes and comments pool in one place. The trade: an extra call each, so run it on the top of the list, not the account. -
Only now rank
One row per creative, totals summed, and ranking is the easy part — by volume and by share of spend against share of results, not by the ratio everyone sorts on.
An ad is a placement decision. A creative is the thing you made. Reports count the first and you make more of the second.
Getting the unit right is the first thing the weekly routine does, before it looks at a single number. See how the week runs →
Asking for it in one go
Field names matter here, because the obvious guesses are wrong. There is no field called
purchases; the catalog resolves it to omni_purchase, which displays as
"purchases". Spend is amount_spent, with spend accepted as an alias. Both
checked on 23 August 2026.
For ad account <ID>, window <YYYY-MM-DD> to <YYYY-MM-DD>:
level = ad. Fields: id, name, creative_id, amount_spent,
omni_purchase.
Echo the window back, then group the rows by creative_id and sum
amount_spent and omni_purchase within each group.
Show me the row count, the number of distinct creative_id values,
and the grouped table sorted by omni_purchase.
Do not sort or filter on creative_id — the API does not support it.
The two counts are the point. If the row count is much larger than the number of distinct identifiers, every per-creative figure you have read so far was split across duplicates.
- Grouping by ad name. Names are typed by humans under time pressure. The identifier is assigned by Meta and cannot be typed wrong.
- Assuming the assistant grouped anything. Ask it to state the row count and the distinct count. If it cannot, it summarised rather than grouped.
- Treating identical pictures as one creative without checking. Re-uploaded assets get their own identifier, and no amount of prompting will merge them for you.
- Doing this at campaign level. The field is not there. You will get an answer anyway, which is the problem.
Try this tonight Run the prompt above over your last thirty days and read only the two counts. If rows outnumber distinct creatives, take the identifier with the most rows and look at which ad sets they sit in. That is one creative competing with itself, and deciding whether that was deliberate is tonight's real decision.
This is question two of the four-question audit, expanded. If nothing is connected yet, start here.
Frequently asked questions
Can I just ask for a report grouped by creative?
You can ask, and you will get something back. But the reporting surface has no creative level, so whatever you receive was grouped after the fact by the model, not by Meta. Ask it to show the row count and the distinct count so you can see whether the grouping happened.
Why is creative_id not filterable?
Meta does not say. The field metadata reports it as neither filterable nor sortable with no operators listed, and none of the public documentation covers the reporting field catalog at all. Treat it as a fact about the surface rather than a decision you can appeal.
Do two ads with the same image always share a creative_id?
No. Duplicating an ad reuses the creative object; uploading the same file again produces a new one. Compare image_hash on the creative objects to tell those apart.
Does this apply to third-party Meta ads MCP servers too?
They read the same Marketing API underneath, so the underlying constraint is the same. What differs is how much joining the server does before handing you rows — worth checking, and worth reading alongside what you hand over to a third party.
The unit is the creative, not the ad
The course is the operating routine behind pages like this one — what to ask for, in what order, and which answers to check before you spend against them.
See what's inside — $67 founding pricePre-sale: the modules are still being recorded and founding members get the checkout link first. No charge today. Public price $97.