AdsApp.search vs report vs Selectors: Which to Use
Selectors are the only layer that can write to an account but cover limited campaign types and cap at 50,000 results. AdsApp.search() is the default read layer: GAQL, all channel types, no entity limits. AdsApp.report() is worth keeping only for exportToSheet().
There are three ways to get data out of an account inside a script, and they are not interchangeable. Choosing wrong is the difference between a report that is complete and a report that quietly omits a third of the account.
The comparison
| Selectors | AdsApp.search() |
AdsApp.report() |
|
|---|---|---|---|
| Language | Method chaining | GAQL | AWQL (legacy) |
| Can write | Yes | No | No |
| Entity limits | 50,000 per iterator | Not subject to them | Not subject to them |
| Performance Max | Only via the dedicated selector | Yes, via FROM campaign |
Yes |
| Speed on large accounts | Slow | Fast | Fast |
| Export to Sheets | Manual | Manual | exportToSheet() built in |
Selectors: the write layer
Selectors return live objects. That is their entire advantage: campaign.pause(), keyword.setMaxCpc(), ad.applyLabel(). Nothing else in the scripting environment can change the account.
Their cost is coverage and speed. AdsApp.campaigns() returns Search and Display campaigns only. Shopping, Video and Performance Max each have their own selector, and Demand Gen has none at all — those campaigns are reachable through reporting only. A loop that iterates AdsApp.campaigns() and calls itself an account-wide check is wrong in most modern accounts.
AdsApp.search(): the default read
This is where new scripts should read from. It speaks GAQL, it matches the field names in the Google Ads API documentation, it covers every campaign type through FROM campaign, and it is exempt from entity limits.
The friction is that you get nested objects in camelCase, everything numeric wants a Number() wrapper, and there is no built-in spreadsheet export.
AdsApp.report(): the one thing it still does better
report() speaks AWQL, the older query language, and it has exportToSheet(). If the entire job is “run a query, put it in a tab of this spreadsheet”, that one method saves twenty lines of cell writing and handles the sizing for you.
For anything else, prefer search(). AWQL field names diverge from the API documentation, which means every question you have will be answered in the wrong vocabulary.
A rule you can apply without thinking
- Need to change something? Selector, fetched narrowly, by ID or exact name.
- Need numbers across the account?
AdsApp.search()withFROM campaign. - Need those numbers in a Sheet and nothing more?
AdsApp.report()withexportToSheet().
Most non-trivial scripts use two of the three. Read wide with a query, decide in JavaScript, then fetch and modify only the handful of entities that need changing. That keeps the write list short enough to log, and a short log is what makes a change reversible.
The failure this pattern prevents is covered in the Performance Max guide.
Questions
- Is AdsApp.report() deprecated?
- It still works and still has exportToSheet(), which AdsApp.search() lacks. For new scripts, search() is the better default because it uses GAQL and matches the API documentation.
- Which layer can pause a campaign?
- Only selectors. AdsApp.search() and AdsApp.report() are read-only.
- Do selectors cover every campaign type?
- No. AdsApp.campaigns() covers Search and Display. Shopping, Video and Performance Max have dedicated selectors, and some newer types such as Demand Gen are reachable through reporting only.