How bulk CSV editing works for Google map listings
The 16-column export, the field-level diff preview, and the per-location apply step that make bulk CSV editing of Google map listings safe at scale.
Bulk CSV editing for Google map listings is a three-step loop: export every location to a spreadsheet, edit the cells you want to change, then import the file and review a field-level diff before anything is written back. The diff step is what separates a safe bulk edit from a mass-overwrite you cannot undo. This post explains how that loop works in PlaceOptimizer, column by column and stage by stage, using the actual engine code rather than a feature-grid summary.
Google itself supports bulk location management through a spreadsheet upload in Business Profile Manager, and documents both the upload flow and the checks it runs (Google's bulk location management overview, accessed August 2026). What Google's own flow does not give you is a preview of exactly which fields will change on which store before you commit. That gap is the reason a diff-and-apply layer exists at all.
What is bulk CSV diff-and-apply?
Bulk CSV diff-and-apply is a workflow where an edited spreadsheet is compared field by field against the current state of every listing, and the resulting change set is shown to a human for approval before any write is sent to the provider. The three words each carry weight. Bulk means one file covers the whole estate. Diff means the system computes what differs rather than blindly re-sending every cell. Apply means the write is a separate, explicitly confirmed action, scoped to the locations you tick.
The alternative most tools ship is bulk import: you upload a file and it overwrites. That works fine until a spreadsheet formula mangles a phone column, or someone re-sorts rows and the store codes drift, and you discover the damage across two hundred listings after Google has already accepted it. A diff preview turns that class of accident into a screen you can read and reject.
The 16-column export format
The export is one row per location with sixteen columns, in a fixed order that is designed to be readable and editable in Excel or Google Sheets rather than optimised for machines. The header row is exactly: Store Code, Title, Phone, Address Line, Locality, Admin Area, Postal Code, Website, Primary Category, then the seven weekday columns from Monday through Sunday.
Nine of those columns are listing attributes and seven are trading hours, one per day. Splitting hours across seven named columns rather than packing them into a single field is a deliberate readability trade: an operations person standardising Sunday hours across an estate can sort or filter one column, which is impossible if all seven days share a cell.
Store Code is the join key and the only truly mandatory column. On import, each row is matched to an existing location by its store code, compared case-insensitively after trimming whitespace. A row whose store code matches nothing is not an error that aborts the batch; it is reported separately as an unknown row, so a file containing three stores you have not connected yet still processes the rest. Title is the only other column the validator requires to be non-empty, because a listing with no name is not a listing.
Each weekday cell holds either a range in HH:MM-HH:MM form or the literal word closed. An empty cell is read as closed, and both closed and CLOSED canonicalise to the same value. A close time of 24:00 is accepted as a legitimate end-of-day marker; 24:30 and 25:00 are rejected, because they are almost always a typo rather than an intent.
Why the parser follows RFC 4180 strictly
The export and import both implement the RFC 4180 CSV specification published by the IETF, which is the closest thing the format has to a standard. In practice that matters for four shapes real spreadsheets produce, and each of them will silently corrupt a naive split(',') parser.
A byte-order mark at the start of a file, which Excel adds when saving UTF-8, is stripped rather than being read as part of the first header name. Both CRLF and bare LF row endings are accepted, because the file may have been round-tripped through Windows, macOS, and a web upload. Quoted fields may contain embedded commas and even embedded newlines, so an address written Shop 4, Ground Floor, MG Road survives as one cell. And a doubled quote inside a quoted field is unescaped to a single quote, which is how a business name containing an inch mark or a quoted nickname stays intact.
Serialization is symmetric: fields containing a comma, a quote, or a newline get quoted and their internal quotes doubled, and rows are joined with CRLF. Export then re-import of an unmodified file therefore produces an empty diff, which is the property that makes the whole workflow trustworthy. If a round trip through a spreadsheet produced spurious changes, every real change would be buried in noise.
What the diff actually compares
The diff is computed between two canonical rows, not between raw text. The current state of each location is rendered into the same 16-column row shape the export produces, the uploaded row is canonicalised the same way, and the two are compared column by column. A difference produces a field change carrying three things: the dotted path of the field in the domain model, the before value, and the after value.
Canonicalising both sides before comparison is what stops cosmetic differences from reading as edits. 10:00-21:00 and 10:00-21:00 differ as strings and are identical as trading hours. An empty Sunday cell and the word closed mean the same thing. Without canonicalisation, a spreadsheet that reformatted a column would present as hundreds of changes, and an operator faced with hundreds of meaningless changes stops reading the diff, which defeats its purpose entirely.
The preview that comes back is a summary plus the detail. The summary counts rows seen, rows matched to a location, locations actually changed, locations unchanged, unknown store codes, parse errors, and the total number of individual field changes. Those seven numbers are the honest headline: "42 rows, 40 matched, 12 changed, 31 field changes, 2 unknown" tells an operator in one line whether the file is what they expected before they read a single row of detail.
The apply step, and why failures are isolated
Applying is a separate action against only the locations you select. Each selected location's change set is sent to the provider independently, and the batch is settled rather than chained, so one location failing does not abort the rest. The result splits into applied locations, each with its field-change count, and failures, each carrying the location reference and the provider's error message.
That isolation is a deliberate design choice with a real operational consequence. If store 17 has been suspended and rejects a write, stores 1 through 16 and 18 through 40 still land, and you get a two-line failure list to work rather than an ambiguous partial state you have to reconstruct by hand.
The full loop, step by step
This is the whole procedure, and it is worth running once against two or three locations before you point it at an estate.
- Export the current state. Download the CSV from the bulk page. This is your before-state and your backup; keep the untouched copy, because a clean re-import of it is your fastest path back if an edit goes wrong.
- Edit only the cells you intend to change. Leave Store Code alone in every row, because it is the join key. Do not re-order or delete the header row. Do not let a spreadsheet auto-format the Phone or Postal Code columns into numbers, which strips leading zeros and plus signs.
- Import the edited file. The parser validates every row against the schema and reports problems by physical row number, counting the header as row one, so an error message points at the line you can actually see in your editor.
- Read the summary, then the diff. Check that the changed-locations count matches what you intended. If you edited Sunday hours for six stores and the summary says 40 locations changed, stop and find out why before going further.
- Select and apply. Tick the locations you want to write, confirm, then read the applied and failed lists. Re-export afterwards to confirm the new state is what you expect.
Honest status
Bulk CSV diff-and-apply is built and running in the PlaceOptimizer operator console at /locations/bulk. The parse, diff, and apply engine lives in the shared core package and is covered by unit tests — every import is previewed as a field-level diff, per location, before a single value is written.
The practical implication is worth stating plainly. You can see the export format, the diff preview, and the apply flow end to end — and the diff is the point: nothing is written until you have read, line by line, exactly which field on which location changes.
Frequently asked questions
What happens if I change a store code in the CSV?
The row stops matching its location and is reported as an unknown row rather than creating a new listing or renaming the old one. This is intentional: store code is the identity column, so treating a changed value as a rename would let a typo silently reassign edits to the wrong store. If you genuinely need to change a store code, do it in the console rather than through the bulk file.
Can I import a file with only some of the columns?
No. The header must match all sixteen columns in order, compared case-insensitively and ignoring internal whitespace. A file with a missing or extra column is rejected as a whole with a message naming the expected header, rather than being partially processed. The reason is that column position determines meaning, so a shifted file would map Locality values onto Admin Area and produce a diff that looks plausible and is entirely wrong.
Does an empty cell clear the field or leave it alone?
An empty cell is a value, not an absence, so it produces a change when the current value is non-empty. The one nuance is trading hours, where an empty weekday cell canonicalises to closed and therefore means the store is shut that day. If you want a field left untouched, leave the exported value in place rather than blanking it.
How is this different from Google's own bulk upload?
Google's bulk location management accepts a spreadsheet and processes it, and it is the right tool when you are onboarding locations in volume (Google's bulk location management overview, accessed August 2026). The difference here is the review gate: you see the computed field-level change set against current state, per location, and choose which locations to write. That is a safety layer on top of the same underlying capability, not a replacement for Google's tooling.
If you want to see what state your listings are in before you start editing them in bulk, the free audit scores every listing Google returns for your brand across seven check categories in about a minute, and the resulting issue list is a reasonable input to your first bulk edit.
Put this playbook on autopilot.
The free audit runs every check in this post across all your locations — no card required.