PMax Bulk Builder: Run Performance Max from a Google Sheet
Performance Max has a strange gap in its tooling. Google built an open-source project for managing it from a spreadsheet — Mad PMax — and the spreadsheet part is genuinely good: one tab per entity type, fill in the rows, the tool creates campaigns and asset groups with all their assets. The problem is everything behind the sheet. Mad PMax needs a Google Cloud project, Cloud Functions, Pub/Sub, a service account and a Terraform deployment. I have watched capable PPC managers give up on page three of the setup guide.
So I ported it. PMax Bulk Builder is the Mad PMax workflow as a single Google Ads script: paste it once, point it at your copy of the sheet, run it hourly. No cloud project, no Terraform, nothing to deploy. And because it runs inside Google Ads, I could add the two things I always wanted from the original — scheduling and performance data — without another service.
The Sheet Is the Interface
Every run does the same loop: download what exists, apply what you changed, write the result next to the row.
The rows you type are the whole API:
- Blank Status on a row means create it. A campaign row carries budget, bidding strategy with target CPA or ROAS, start and end dates, countries, languages and the EU political-ads declaration. An asset-group row carries the final URL, paths, three headlines, a long headline, two descriptions, business name and the marketing, square and logo image URLs — everything Google requires to accept a new asset group in one request, because that is exactly how the script sends it.
- UPDATE pushes your edits back: campaign name, status, dates, budget, targets; asset-group name, status, URLs and paths; sitelink text and descriptions.
- DELETE unlinks an asset, a sitelink or a page-feed URL. Deleting campaigns and asset groups is deliberately not supported from the sheet — that is one click you should make in Google Ads, with your eyes open.
Each processed row gets UPLOADED, UPDATED, REMOVED or ERROR written next to it, with the reason in plain words: “need 3 headlines (found 2)”, “HEADLINE over 30 chars”, “campaign not found in this account”. The sheet’s companion Apps Script checks asset-group rows as you type, so most mistakes are visible before the script ever runs.
Downloads go the other way. Campaigns, asset groups, assets, sitelinks, listing groups and page feeds of the selected accounts arrive as DOWNLOADED rows, with their IDs and resource names, so every existing entity is one status change away from an update. At MCC level the script walks the accounts you tick in the Customer list, one after another.
Scheduling: Promotions That Put Themselves Back
This is the feature the original never had and the one I use most. PMax assets are immutable — you cannot edit a headline, only add a new one and remove the old — which makes a weekend sale a four-step manual job: add the promo text, remove the everyday text, and reverse both on Monday morning before anyone notices the discount is still live.
The Schedule tab turns that into one row:
- ASSET_GROUP — enable an asset group at Start, restore its previous status at End. A seasonal group that is paused 50 weeks a year and runs itself for the other two.
- TEXT, IMAGE or VIDEO — link a new headline, description, image or YouTube video at Start, optionally taking an existing asset of the same type out for the window, and reverse the swap at End. The script remembers what it removed and puts exactly that back.
Start and End are wall-clock times in the account’s timezone. Rows move SCHEDULED to ACTIVE to DONE, with the activation and revert timestamps written beside them; DELETE cancels a window and reverts immediately. Leave End blank and the change is applied once and kept. Because the script resolves windows on every run, hourly is the schedule to use — a window opens within the hour, and a window that closed while the script was not running is skipped rather than applied late.
Performance, Where You Are Already Working
The Performance tab is rewritten every run with impressions, clicks, CTR, cost, conversions, conversion value, ROAS and CPA for each campaign, asset group and individual asset in scope, plus the asset’s serving status. The period is a setting: any preset from today to last month, or a custom date range.
Asset-level numbers are the point. A spreadsheet that shows you fifteen headlines with their costs and conversions next to a Schedule tab that can swap them is a testing loop PMax does not give you anywhere else. Google stopped exposing its own BEST / GOOD / LOW asset labels through the API this year; the serving status and the raw metrics are what remain, and they are more useful anyway.
Setting It Up
- Make a copy of the template sheet. It ships with the companion Apps Script, so the live Asset Check and the Change log tab work immediately.
- Install PMax Bulk Builder from the marketplace and paste the loader into Google Ads — at account level, or at MCC level if you manage several accounts. Put your copy’s URL into SPREADSHEET_URL.
- Run it once with PREVIEW_MODE on. The Customer list fills with your accounts; tick the ones to manage.
- Run again. Everything downloads. Add rows, set statuses, switch PREVIEW_MODE off, and schedule the script hourly.
Preview mode is the safety rail: it validates every row and logs what it would do while still syncing the sheet, so you can see the whole plan before a single change reaches Google Ads.
What I Learned Testing It
I ran the script through a full QA cycle on a real account before publishing — every create, update, delete and schedule path, with every result checked against the Google Ads API rather than the sheet. Three things are worth knowing.
Google now rejects accelerated budget delivery on Performance Max. The script validates it before calling the API instead of handing you a cryptic error. Image assets are deduplicated by URL across asset groups, so one set of creative URLs feeds as many groups as you like without uploading the same file ten times. And a strict dropdown in a spreadsheet can reject the script’s own status write after the change has already been made in Google Ads — the script now recovers from that on its own, which is the kind of bug you only find by actually running the thing.
It is in the marketplace now, alongside 130+ other scripts. If you manage Performance Max for more than one client, I think it will save you an afternoon a week.