PMax Bulk Builder
Sheet-driven control centre for Performance Max: download campaigns, asset groups, assets, sitelinks, listing groups and page feeds of the selected accounts into a Google Sheet, push UPDATE rows back, bulk-create campaigns and asset groups with all their assets from blank rows, schedule asset groups, texts, images and videos for a time window with automatic revert, and see impressions, clicks, cost, conversions, ROAS and CPA per campaign, asset group and asset for a chosen period — atomically, MCC-aware, hourly and preview-safe by default.
by Google (Mad PMax concept), Dmytro Tonkikh, chiliad (Ads Scripts rewrite)·github.com/google-marketing-solutions/madpmax
Overview
A single-script port of Google's open-source Mad PMax tool, which normally needs Cloud Functions and Terraform to run. Make a copy of the PMax Bulk Builder template sheet (https://docs.google.com/spreadsheets/d/1h4mRrhBzrkXfdhGgp9ufGKj_uVJEPTrr4UFB_q8pgDo/copy), paste the script at account or MCC level, tick the accounts to manage in the Customer list, and every run syncs the sheet with Google Ads. Build and edit: campaigns, asset groups, assets, sitelinks, listing groups and page feeds download into their tabs as DOWNLOADED rows; add rows with a blank Status to create campaigns or asset groups (all assets of a group go up in one atomic request), set UPDATE to push edits back, DELETE to unlink assets, sitelinks or feed URLs — each processed row gets UPLOADED / UPDATED / REMOVED or ERROR with the reason written next to it. Schedule: the Schedule tab runs things for a time window and puts them back afterwards — an ASSET_GROUP row enables the group at Start and restores its previous status at End; a TEXT, IMAGE or VIDEO row links a new headline, description, image or YouTube video at Start, optionally taking an existing asset of the same type out for the window, and reverses the swap at End. Rows move SCHEDULED → ACTIVE → DONE, times are read in the account timezone and resolved on every hourly run, and the tab is created with EXAMPLE rows to copy. Performance: the Performance tab is rewritten every run with impressions, clicks, CTR, cost, conversions, conversion value, ROAS and CPA per campaign, asset group and asset (plus each asset's serving status) for PERFORMANCE_PERIOD — presets from TODAY to LAST_MONTH or a custom date range. Works in a single account or at MCC level (child accounts run one after another); paste an account's ocid into the Customer list and downloaded names become deep links into Google Ads. The sheet's companion Apps Script adds a live Asset Check and a Change log of manual edits.
Setup
- 1Make a copy of the PMax Bulk Builder template sheet — https://docs.google.com/spreadsheets/d/1h4mRrhBzrkXfdhGgp9ufGKj_uVJEPTrr4UFB_q8pgDo/copy — (it ships with the companion Apps Script for the Asset Check and Change log; Google's stock Mad PMax sheet works too) and put the URL of your copy into SPREADSHEET_URL.
- 2Open Google Ads → Tools → Bulk Actions → Scripts, create a new script, paste the code and authorise Google Ads, Sheets, URL fetch and Mail when asked. Works in a single account or pasted at MCC level.
- 3Run once with PREVIEW_MODE = true: the Customer list fills with every enabled account — tick Selected for the ones to manage and optionally paste their ocid (from the Google Ads URL) into the OCID column for clickable names.
- 4Run again: campaigns, asset groups, assets, sitelinks, listing groups and page feeds download into their tabs as DOWNLOADED rows; the Listing groups and Page feeds tabs are created automatically.
- 5Add rows with a blank Status to create (campaigns need name, budget and bidding strategy; asset groups need at least 3 headlines, a long headline, 2 descriptions, a business name and marketing, square and logo image URLs), set UPDATE on edited rows, DELETE to unlink assets, sitelinks or feed URLs.
- 6To run something for a period, add a row on the Schedule tab: Scope ASSET_GROUP (enable the group in the window) or TEXT / IMAGE / VIDEO with Field Type, New Value (text, image URL or YouTube URL) and optionally Replaces (an existing asset to take out), plus Start and End as yyyy-MM-dd HH:mm in the account timezone. Set PERFORMANCE_PERIOD to the period the Performance tab should cover.
Configuration Variables
| Variable | Default |
|---|---|
SPREADSHEET_URL | (empty) |
PREVIEW_MODE | true |
DOWNLOAD_ENTITIES | true |
REFRESH_LISTS | true |
Expected Output
══════ PMax Bulk Builder (LIVE) ══════ MCC mode (My Agency (123-456-7890)): 42 enabled account(s); 1 selected in CustomerList. Refreshed "CustomerList" with all 42 enabled child account(s). ── Account: My Store (956-205-6666) — currency EUR, timezone Europe/Berlin ── Download scope: 2 campaign(s) listed on the Campaigns tab. Downloaded: 2 campaign(s), 4 asset group(s), 53 asset link(s), 0 sitelink(s). Downloaded 115 page feed URL(s) and 2 attachment(s). Performance (LAST_30_DAYS): 2 campaign(s), 4 asset group(s), 53 asset(s) written to the Performance tab. Refreshed the Account / Campaign / Asset Group name dropdowns on the input tabs. ─── Campaigns: 1 row(s) to process ─── ─── Asset Groups: 1 row(s) to process ─── ─── Assets: 2 row(s) to process ─── ─── Page feeds: 1 row(s) to process ─── ─── Schedule: 2 row(s) to evaluate (now 2026-09-01 09:03) ─── ─── Summary (LIVE) ─── Created: 5 Updated: 0 Removed: 0 Synced to existing: 0 Valid in preview: 0 Skipped: 0 Failed: 0 Schedule: 0 armed, 1 activated, 1 reverted Performance rows: 59 Campaigns r21 — PMax | Spring Launch: UPLOADED Asset Groups r308 — PMax | Spring Launch > Running Shoes: UPLOADED Assets r610 — PMax | Spring Launch > Running Shoes: UPLOADED Page feeds r117 — Page feed | example.com > https://example.com/spring: UPLOADED Schedule r6 — PMax | Spring Launch > Running Shoes [HEADLINE: Labor Day Sale -30%]: ACTIVE — until 2026-09-08 00:00 Schedule r7 — PMax | Spring Launch > Weekend Deals [asset group]: DONE
Notes
Campaigns and asset groups are always created PAUSED. Budget delivery must be STANDARD — Google rejects ACCELERATED on Performance Max budgets, so the script validates it before calling the API. Deleting campaigns or asset groups from the sheet is intentionally not supported (do it in Google Ads); text and image assets cannot be edited in place — add a new row and set DELETE on the old one. Listing groups only apply to retail campaigns (Merchant Center ID set on the campaign). New PMax campaigns auto-enable Brand Guidelines; the script opts out at creation so asset groups can be added in a later step. The ocid used for deep links is not available through the API — copy it per account from the Google Ads URL. The sheet's companion Apps Script shows a live Asset Check while you fill in asset-group rows and appends every manual edit (who, when, cell, old → new value) to a Change log tab. Schedule windows are resolved on each run, so run the script hourly: a Start in the past applies at the next run, a window that closed while the script was not running is marked DONE without being applied, and an asset swap links a new text / image / video and unlinks the old one (assets are never edited in place). Asset-level metrics come from the asset_group_asset report; Google's BEST/GOOD/LOW performance label is no longer exposed by the API, so the Performance tab shows the asset's serving status (ELIGIBLE / LIMITED / NOT_ELIGIBLE) instead.