How to validate a Google Ads bulk upload CSV before import
- Step 1Export or build the bulk-upload CSV — In Google Ads Editor use Account → Export → 'Export selected' / 'Export whole account' to CSV, or download a bulk sheet from the Campaigns view, then make your edits. Save from Excel with Save As → 'CSV UTF-8 (Comma delimited)' so non-Latin ad copy survives. The validator reads text CSVs only — convert any
.xlsxto CSV first. - Step 2Drop the file onto the validator above — Free tier: 2 MB / 500 rows; Pro: 100 MB / 100,000 rows. A large account export with thousands of keyword rows will exceed the free limit — split it with csv-row-splitter or use Pro. The dropzone accepts
.csv,.tsv, and.txt, and validation runs automatically the moment the file lands. - Step 3Read the four summary tiles — Health score, Rows (lines including the header), Columns (the expected width), and Issues (errors + warnings). For bulk sheets, watch
errors(row_width,encoding) — those misalign or corrupt rows — and thetype_mismatchwarning count on your bid and budget columns. - Step 4Clear errors, then review bid/budget warnings — The Top issues panel lists the first 8 issues. Red
errorbadges (row_width,encoding) block the upload; amberwarningbadges include thetype_mismatchflags onMax CPC/Budgetandduplicate_keyflags on ID columns. Each line names the column and the offending value so you can find it in the sheet. - Step 5Read the Column profile for bid and budget columns — Confirm
Max CPC,Default max. CPC, andBudgetshow asnumber. Astringprofile means most of the column is non-numeric — usually a currency symbol throughout, or text values likeauto. Note: Google Ads bid columns are often legitimately blank for rows where the bid is inherited; those blanks may show asempty_cellonly if the column is otherwise mostly populated. - Step 6Fix in source, download the report, re-validate — Use 'Download report' for a
<filename>.health-report.jsonrecord, then fix in your sheet: strip currency symbols with csv-find-replace, de-dupe accidental repeated rows with csv-deduplicator, and re-save as CSV UTF-8. Re-drop to confirmerrorsis zero, then re-import via Google Ads Editor or the bulk-upload interface — Google's own validation then handles policy and account-level rules.
What the validator checks on a Google Ads CSV
Eight issue types, mapped to bulk-upload failure modes. Error severity misaligns or corrupts a row; warning severity is a quality flag worth reviewing before submit.
| Issue type | Severity | What triggers it | Why bulk sheets hit it |
|---|---|---|---|
| `type_mismatch` | Warning | A cell doesn't match the column's dominant number profile (85% dominance) | auto, --, or £1.50 in Max CPC / Budget; text where Google expects a bare numeric bid |
| `row_width` | Error | A row's column count differs from the header's column count | An unescaped comma inside a headline, description, or Final URL parameter string splits the row |
| `encoding` | Error | Cell contains \uFFFD or a control character below 0x20 | A CP1252 save read as UTF-8 — non-Latin or accented headlines/descriptions come back as garbage |
| `duplicate_key` | Warning | Same value in a column whose name matches the key heuristic (Campaign ID, Ad group ID, names with id) | A duplicated edit row referencing the same entity ID twice |
| `duplicate_row` | Warning | A whole data row matches an earlier one after trimming whitespace and lowercasing | Copy-pasting the same keyword or ad row twice in the sheet |
| `empty_cell` | Warning | An empty cell in a column that's at least 85% populated | A skipped value mid-sheet in a column that's otherwise complete; inherited-bid blanks may exceed 15% and stay quiet |
| `duplicate_header` | Warning | The same header name appears twice in row 1 (case-insensitive) | Two Final URL columns, or merging two Editor exports with overlapping headers |
| `suspicious_value` | Warning | A once-only value substring-similar to a common value, in a 2–12 distinct-value column | Enabled next to Enabled, or Paused next to Paused, in a Status column |
How the validator reads common Google Ads columns
How frequently-used bulk-sheet columns are inferred and what gets flagged. The number classifier strips a leading $ and removes commas before testing.
| Google Ads column | Inferred type | Common flag |
|---|---|---|
Max CPC / Default max. CPC | number | type_mismatch on £1.50, auto, --, or text; $2.50 and 2.50 pass |
Budget / Campaign daily budget | number | type_mismatch on a currency symbol or text; bare decimals pass |
Status / Campaign status | string (categorical) | suspicious_value on Enabled / Paused trailing-space variants |
Campaign ID / Ad group ID / Keyword ID | string (digits) | Checked as a unique key — duplicate_key on repeated IDs |
Final URL | string | Source of row_width errors if a URL parameter string contains an unescaped comma |
Headline 1 ... Description | string | encoding errors on mojibake; row_width if ad copy contains an unescaped comma |
Keyword / Search term | string (high-cardinality) | Usually skips the suspicious-value scan (too many distinct values) |
What the validator catches vs what Google Ads catches
Structural pre-flight here; policy and account-level validation in Google Ads. Both layers matter — clearing this report does not guarantee a clean upload.
| Check | JAD CSV Validator | Google Ads import |
|---|---|---|
| Text or symbol in a bid/budget column | Yes — type_mismatch warning before upload | Yes — rejects the row/file with a terse log entry |
| Row split by a comma in ad copy / Final URL | Yes — row_width error before upload | Yes — misaligned columns, rejected |
| Encoding mojibake in headlines | Yes — encoding error before upload | Yes — character-validation rejection |
Duplicate Campaign ID / Ad group ID | Yes — duplicate_key warning | May create conflicting edits |
| A required column missing (no bid column) | No — profiles only present columns | Yes — import requires the expected columns |
| Ad copy within character limits / policy | No — content/policy out of scope | Yes — Google enforces length and policy |
| Final URL resolves / domain matches | No — URLs not fetched or parsed | Yes — Google validates landing pages |
| File size cap | Free 2 MB / Pro 100 MB | Editor handles large account files locally before upload |
Cookbook
Real Google Ads Editor / bulk-sheet scenarios mapped to the exact validator output. Each code block is a representative excerpt; column names follow common Google Ads Editor export conventions.
Text in a Max CPC bid column
ExampleBid columns expect bare numerics. The classifier accepts $2.50 and 1,000 but not a non-dollar symbol or text like auto, so those cells are flagged against the inferred number profile.
Header:
Campaign,Ad group,Keyword,Max CPC
Rows:
Brand,Core,running shoes,2.50 OK
Brand,Core,trail shoes,$3.00 OK ($ stripped)
Brand,Core,sneakers,auto type_mismatch (text)
Brand,Core,boots,£1.80 type_mismatch (non-$ symbol)
Validator output (2 issues):
Row 4: Expected number based on the column profile, but found "auto".
Row 5: Expected number based on the column profile, but found "£1.80".
Fix: replace text bids with numeric values, and strip non-$ symbols with
csv-find-replace regex [£$€¥] -> (empty) before re-importing.Unescaped comma in ad copy splits the row
ExampleHeadlines and descriptions often contain commas. If the cell isn't quoted, the row gains a column and every field after it shifts. The validator reports a row_width error.
Header (4 cols):
Campaign,Ad group,Headline 1,Final URL
Row (5 cols — comma inside the headline):
Brand,Core,Fast, free shipping,https://example.com
^ this comma split the headline cell
Validator output:
Row 2, Col 1 (row width)
type: row_width severity: error
detail: "Row has 5 columns; header has 4."
Fix: re-export from Google Ads Editor (it quotes ad copy correctly), or
wrap the Headline column in double quotes before saving.Duplicate Campaign ID across rows
ExampleA duplicated edit row referencing the same entity ID can create conflicting changes. Because the column name contains the id token, the repeat is flagged as a unique-key collision.
Header:
Campaign ID,Campaign,Budget
Rows:
101,Brand,50.00
102,Generic,30.00
101,Brand,75.00 <- same Campaign ID, conflicting budget edit
Validator output:
Row 4, Col 1 Campaign ID
type: duplicate_key severity: warning
detail: 'Value "101" also appears on row 2.
This column looks like a unique key.'
Fix: decide which edit wins and de-dupe with csv-deduplicator on the
Campaign ID column, or inspect first with csv-duplicate-finder.Mojibake in a non-Latin headline
ExampleInternational campaigns carry non-Latin headlines. A CP1252 save read back as UTF-8 turns those into the replacement character, flagged as encoding errors that would also trip Google's character validation.
Header:
Campaign,Headline 1
Rows (CP1252 decoded as UTF-8):
DE,Schnelle Lieferung f\uFFFDr alle encoding error (was 'für')
FR,Promotions d'\uFFFDt\uFFFD encoding error (was 'été')
Validator output:
Row 2, Col 2 (Headline 1): replacement/control character — encoding problem.
Row 3, Col 2 (Headline 1): same.
Fix: re-save as 'CSV UTF-8 (Comma delimited)', or re-export from Ads Editor
in UTF-8. csv-cleaner can normalise the text if you only have the
corrupted file.A blank bid in a mostly-filled column
ExampleIf a bid column is at least 85% populated and a row's bid is blank, the validator flags it as an empty_cell warning. Whether that's a problem depends on whether the bid is meant to be inherited — verify before bulk-filling.
Header:
Keyword,Max CPC
Rows (bid column mostly populated):
running shoes,2.50
trail shoes,3.00
sneakers, <- blank bid in a mostly-filled column
boots,1.80
Validator output:
Row 4, Col 2 Max CPC
type: empty_cell severity: warning
detail: "Cell is empty in a mostly populated column."
Fix: if the bid should be set, fill it; if it's intentionally inherited
from the ad group, the warning is informational — verify, don't
bulk-fill blindly.Errors and edge cases
Real errors and silent failures sourced from each platform's own documentation. Match the wording to the row, fix what the row says to fix.
A non-dollar currency symbol in a bid column is flagged
type_mismatch warningThe number classifier strips only a leading $ (with an optional sign) before testing, so $2.50 passes but £1.80, €1.50, and inline codes are flagged as type_mismatch in a numeric bid column. Google Ads bulk sheets expect bid values in the account currency as bare decimals, so strip the symbol in source with csv-find-replace before upload. The flag is correct — a symboled bid would not import as the number you intend.
Unescaped comma in ad copy or a Final URL splits the row
row_width errorHeadlines, descriptions, and URL parameter strings frequently contain commas. In a comma-delimited file an unquoted comma adds a column, so the validator reports row_width with the column-count delta. Re-exporting from Google Ads Editor is the cleanest fix because it quotes text fields correctly; if you're editing by hand, wrap ad-copy and URL columns in double quotes before saving. Don't strip the comma from the copy itself unless you actually want it gone.
Inherited / blank bids may or may not be flagged
Review — empty_cellGoogle Ads bid columns are often legitimately blank when a bid is inherited from a higher level. The empty_cell warning only fires when a bid column is at least 85% populated, so a column that's blank for many rows (over 15% empty) stays quiet, while a mostly-filled column with a few blanks flags them. Treat those warnings as informational — verify each blank is an intentional inheritance, not a forgotten value, before bulk-filling.
The validator can't tell you a required column is missing
Not detectedThis is the most important honesty caveat for Google Ads files: the validator profiles only the columns present and has no knowledge of Google's required-column schema. If your bulk sheet is missing a column Google requires (a bid column, a Campaign column for a new entity), the validator won't flag the absence — it will validate whatever columns exist. Compare your header row against a fresh Google Ads Editor export of the same entity type to confirm you have the right columns before upload.
Encoding errors double as Google character-validation blockers
encoding errorWhen non-Latin or accented ad copy is corrupted by a CP1252 save, the validator flags \uFFFD replacement characters as encoding errors. The same corruption trips Google's own character validation at upload, so clearing these errors here pre-empts a rejection. Fix by re-saving as CSV UTF-8 or re-exporting from Editor in UTF-8; the csv-cleaner tool can normalise text if you only have the corrupted file.
Bid values with thousands commas like 1,000 are read as numbers
SupportedThe classifier removes commas before the numeric test, so 1,000 reads as 1000 and a budget column full of thousands-separated figures is correctly inferred as number with no type_mismatch. Be aware this means an EU comma-decimal like 1,50 also has its comma removed, becoming 150 — a different value — and would still parse as a number, so it won't be flagged. For non-US locales, double-check that decimals use a point, because a silently-misread budget is worse than a flagged one.
Large account export exceeds the free 500-row limit
Blocked on free tierFree tier caps at 2 MB and 500 rows. A whole-account Google Ads Editor export with thousands of keyword and ad rows will be blocked before validation, with the limit message shown. Use Pro (100 MB / 100,000 rows) or split the export with csv-row-splitter — for example by campaign — and validate each segment. Splitting also makes it easier to isolate which part of a large upload has a structural problem.
Policy, character limits, and landing pages are not checked
Out of scopeThe validator is structural: it does not enforce Google's ad-copy character limits, advertising policies, or landing-page validation, and it does not fetch or parse Final URL values. Those are all handled by Google at upload. Use the validator to guarantee the file is well-shaped and the bid/budget columns are clean, then let Google Ads do the policy and content validation — clearing this report reduces, but doesn't eliminate, upload-time rejections.
Only the first 250 issues are reported
By designThe reported issue list is capped at 250 to keep the browser responsive; the Issues tile shows that capped count. A systemic problem — every bid carrying a symbol, or a mis-detected delimiter across a large sheet — can exceed 250 issues. Fix the single root cause and re-validate; the count collapses to a handful. The downloaded JSON report contains the same capped list, so for very large counts the workflow is fix-and-re-run, not scrolling through 250 entries.
Campaign data never leaves the browser
By designThe file is read with the browser's own File.text() and parsed by PapaParse client-side; keyword lists, bids, budgets, and landing-page URLs are never transmitted. The tool is marked 100% client-side, and only an anonymous run counter is recorded for signed-in dashboard stats. Since a full bulk sheet effectively encodes your account strategy, on-device processing keeps that competitive data off any server, including JAD's.
Frequently asked questions
Can it catch every Google Ads upload error?
No — it catches the structural ones that cause most rejections: text or symbols in bid/budget columns (type_mismatch), rows split by an unescaped comma in ad copy or a Final URL (row_width error), encoding corruption in headlines (encoding error), and duplicate entity IDs (duplicate_key). It does not enforce Google's policies, ad-copy character limits, or landing-page validation, and — importantly — it cannot tell you a required column is missing, because it has no knowledge of Google's column schema. Clear this report first, then let Google's own validation handle the rest.
Does it detect a missing required column?
No, and this is the key caveat for Google Ads files. The validator profiles only the columns present in the file; it has no built-in Google Ads schema to compare against, so a missing bid or Campaign column simply isn't reported. To confirm you have the right columns, compare your header row against a fresh Google Ads Editor export of the same entity type. The validator checks the shape and content of the columns you have, not which columns Google expects.
Will it tell me which bids contain text instead of numbers?
Yes. If most of a Max CPC or Budget column parses as numeric, the validator infers it as number and flags every cell that doesn't — auto, --, a non-dollar currency symbol, or any text — with the exact offending value in the detail string. The classifier does strip a leading $ and remove thousands commas, so $2.50 and 1,000 are accepted; it's the genuinely non-numeric values that get flagged.
Does it work with Google Ads Editor exports?
Yes. Export from Ads Editor to CSV (Account → Export), validate the file here, fix what's flagged, then re-import via Editor or the bulk-upload interface. Editor quotes text fields correctly, so a clean Editor export rarely has row_width errors — those tend to appear after hand-editing introduces an unescaped comma. The validator is most useful as a check on a sheet you've edited after export.
Is my campaign data uploaded anywhere?
No. The file is read with the browser's File.text() and parsed entirely client-side by PapaParse — keyword lists, bids, budgets, and landing-page URLs never reach a JAD server. The tool is marked 100% client-side, and only an anonymous usage counter is recorded for signed-in dashboard stats, never the file contents. A full bulk sheet encodes your account strategy, so on-device processing keeps that off any server.
Why is a value like £1.80 flagged but $2.50 isn't?
The number classifier strips only a leading $ (with an optional sign) before testing whether a cell is numeric, so $2.50 becomes 2.50 and passes, while £1.80 keeps its symbol, fails the numeric test, and is flagged as type_mismatch. Google Ads bulk sheets expect bid and budget values as bare decimals in the account currency anyway, so strip non-dollar symbols in source with csv-find-replace before uploading.
My ad copy has commas — will that always cause an error?
Only if the cell isn't properly quoted. In a comma-delimited file, an unquoted comma inside a headline or description adds a column and triggers a row_width error. Google Ads Editor exports quote text fields correctly, so commas in copy from a clean export are fine. The errors typically appear after hand-editing strips the quoting. The fix is to re-export from Editor or wrap ad-copy columns in double quotes — not to remove the commas from your copy.
Can it find duplicate rows in my bulk sheet?
Yes. It flags whole-row duplicates (after trimming whitespace and lowercasing) as duplicate_row, and it flags repeated values in identifier columns — names containing the id token like Campaign ID, Ad group ID, Keyword ID — as duplicate_key. A duplicated edit row referencing the same entity twice can create conflicting changes, so this is worth resolving. Use csv-deduplicator to remove duplicates or csv-duplicate-finder to inspect them first.
What about blank bid cells — are those errors?
They're warnings, and only when the bid column is mostly populated. Google Ads bids are often legitimately blank when inherited from a higher level, so the empty_cell check only fires on columns that are at least 85% filled — a column blank for many rows stays quiet. Treat any empty_cell warning on a bid column as informational: verify each blank is an intentional inheritance rather than a forgotten value before you bulk-fill anything.
How big a bulk sheet can I validate?
Free tier handles up to 2 MB and 500 rows. A whole-account export with thousands of keyword and ad rows will exceed that, so either use Pro (100 MB / 100,000 rows) or split the file with csv-row-splitter — for instance by campaign — and validate each segment. Splitting also helps isolate which part of a large upload has a structural problem when something does get flagged.
Can I download the report?
Yes. 'Download report' saves a <filename>.health-report.json with the full stats, per-column profile, every reported issue (up to 250) with row/column references, the health score, and a 10-row preview. Keep it alongside the bulk sheet as a record of the file's structural state before upload. The issue list in the file matches the capped-at-250 list shown in the UI, so for very large issue counts you fix the root cause and re-run rather than reading every entry.
What does the health score actually tell me?
It's round((1 - reportedIssues / totalCells) * 100) — the percentage of cells that are clean, where total cells is data rows times columns. It's a quick density indicator, and the badge turns green at 90% and amber below, but don't rely on it alone: a high score can still hide one row_width error that misaligns a whole row, or a type_mismatch on every bid. Read the Top issues panel and confirm errors is zero before you upload.
Privacy first
Processing runs locally in your browser with PapaParse. No file is uploaded — only metadata counters are saved for signed-in dashboard stats.