Google Sheets Importer: From Measurement Plan to GTM
Pick one sheet from Drive, choose a template, review the parsed rows, and inject tags, triggers and variables into a GTM workspace. What each step actually does.
Most GTM implementations start life in a spreadsheet. Someone writes a measurement plan: one row per event, the parameters it should carry, the trigger that should fire it. Then a second person opens Google Tag Manager and retypes all of it, tag by tag, trigger by trigger, variable by variable. The retyping is where the naming drifts and where the plan and the container quietly stop agreeing.
The Google Sheets Importer in AI Tag Manager reads that spreadsheet and creates the tags, triggers and variables in a GTM workspace directly. You pick one sheet from your Drive, choose which template layout it follows, review the parsed rows, and inject the ones you want. This post covers what each step actually does, what the sheet has to look like, and where the tool stops being the right one.
It reads one spreadsheet you choose, not your whole Drive
The importer uses the Google Picker with the drive.file scope. That scope grants access only to files you actively select in the picker. It is not the older spreadsheets.readonly scope, which means "see every spreadsheet in this account". AI Tag Manager only ever needs one sheet at a time, so the narrower scope is a more accurate description of the job, and it is a scope Google classes as non-sensitive.
Two consequences follow. First, the Choose from Drive button is the real way to select a sheet. There is still a URL box ("Paste a link, or use Choose from Drive"), and a pasted link pre-navigates the picker to that file, but on its own it grants nothing. Skip the picker and click Load Data, and Google answers 403 or 404 for a file that was never picked; the tool then tells you to choose it through the picker.
Second, the picker mints a short-lived browser token scoped to drive.file alone. It lives in memory for one pick and is never stored or sent to the backend. The server can still read the sheet afterwards because a drive.file grant attaches to the OAuth client and the file, not to the token that did the picking. Accounts that signed in after the scope was added see the picker open silently; older accounts get asked once.
Six templates, and which one you should probably use
Under 2. Select Template you choose a layout. Each template is a different column structure and, behind the scenes, a different edge function that parses it. The dropdown lists:
- Template 1 - GTM Structure (marked Recommended). Ten columns covering the tag, its parameters, and a trigger with a condition. Produces GA4 Event tags.
- Template 2 - Flexible Trigger Setup. Five columns built around a "GA4 Naming Convention" and a trigger definition. Supports Custom Events, link clicks, form submissions, page views and window loaded.
- Template 3 - Google Ads Conversion. Conversion Label per row, one global Conversion ID entered in Configuration.
- Template 4 - Pixel Tracking. Meta, TikTok and similar pixels, with optional Pixel ID injection.
- Template 5 - Microsoft Advertising UET. Track type and custom event action per row, one UET ID in Configuration.
- Template 6 - LinkedIn InsightTag. Conversion ID per row, Partner ID in Configuration.
Each template card links to a reference Google Sheet you can copy. Make a copy, fill in your rows, keep the header names as they are. The rest of this post focuses on Template 1, the general-purpose one. Templates 2 to 6 follow the same five-step flow with their own parsers.
The Template 1 sheet layout, column by column
Template 1 expects these ten headers. Matching is case-insensitive and a few aliases are accepted ("Tag Name" for "GTM Tag Name", for instance), but the names below are the safest.
| Tag Type | GTM Tag Name | GA4 Event Name | Parameters | Parameter Variable | Trigger Name | Trigger Type | Trigger Variable | Trigger Condition | Trigger Value |
|---|---|---|---|---|---|---|---|---|---|
| GA4 Event | GA4 - Lead Form Submit | generate_lead | form_name | {{Form ID}} | Trigger - Lead Form | Form Submission | {{Form ID}} | equals | lead-form |
| page_path | {{Page Path}} | ||||||||
| GA4 Event | GA4 - Call Click | phone_call | link_url | {{Click URL}} | Trigger - Tel Link | Click - Just Links | {{Click URL}} | starts with | tel: |
| GA4 Event | GA4 - Purchase | purchase | value | {{DL - Purchase Value}} | Trigger - Purchase | Custom Event | |||
| currency | {{DL - Currency}} |
Notice the blank rows. A tag with several parameters spans several rows: the first carries the tag and trigger, each following row carries one more Parameters / Parameter Variable pair with everything else empty.
How rows are grouped into tags
The read_sheet action in the edge function fetches the raw cell values, takes the first row as headers, and returns the rest. The page then walks the rows keeping a "current tag" context.
- A row with a Tag Type, a GTM Tag Name and a GA4 Event Name starts a new tag block. Its trigger columns become the trigger for that block.
- A row with only a Trigger Name (while a tag block is open) updates the trigger for the current tag.
- Any row with a Parameters and Parameter Variable value appends that pair to the current tag's parameter list.
- Tags are keyed on event name plus tag name, so two blocks with the same GTM Tag Name and GA4 Event Name merge into one.
Parameter names are normalised to snake_case before injection, so "Form Name" in the Parameters column lands in GTM as form_name. The Parameter Variable value is sent raw and the backend wraps it as a GTM variable reference, which is why {{Form ID}} in the sheet works.
How the trigger columns become a GTM trigger
Trigger Type is mapped to a GTM trigger type with a small lookup. The accepted values are:
| Trigger Type in sheet | GTM trigger created |
|---|---|
| Page View, Pageview | Page View |
| DOM Ready | DOM Ready |
| Window Loaded | Window Loaded |
| Click, Click - All Elements | Click - All Elements |
| Click - Just Links | Click - Just Links |
| Form Submission, Form | Form Submission |
| Scroll, Scroll Depth | Scroll Depth |
| Timer | Timer |
| Custom Event | Custom Event |
| anything else | Custom Event (the fallback) |
The three "Trigger when" columns (Variable, Condition, Value) become the trigger's filter. Condition accepts equals, contains, starts with, ends with and matches (regex); anything else is treated as equals. All three filled gives a "Some ..." trigger; any blank gives the "All ..." version. For Click - Just Links that means All Link Clicks, which is rarely what you want.
Custom Event triggers get one extra step. The tool derives the event name to listen for from the Trigger Name: known phrases first ("Trigger - Purchase" listens for purchase, "add to cart" becomes add_to_cart), otherwise a snake_case conversion with any trailing "trigger" or "event" stripped. Check the result in the preview against what your dataLayer actually pushes; the ecommerce dataLayer blueprint lists the GA4 standard names.
What gets checked before anything touches GTM
Nothing is written to the container until you click Inject to GTM, and that button is disabled until several conditions hold. The checks run in this order:
- A GTM target is selected. The picker at the top of the page ("Pick the account, container and workspace to import into.") must have an account, container and workspace. Without a container, the template and configuration cards do not render at all.
- Your Google connection has the right scopes. On load the page calls google-token-validator and records which scopes the signed-in Google account granted, Sheets and GTM among them. Load Data is disabled without the first; Inject to GTM is disabled without the second, and an alert explains what is missing.
- Template 1 requires a GA4 Measurement ID. The field under Configuration accepts either a G- ID of 9 or 10 characters after the prefix, or a GTM variable reference such as {{GA4 Measurement ID}}. An empty or malformed value stops the import with a toast. The tool also checks separately whether any selected row is a GA4 tag and refuses to proceed if the ID is missing.
- At least one row is selected. Rows arrive unselected. You tick them in the preview, individually or with the select-all button.
The preview under 4. Preview & Select Configurations is the real review gate. It shows every parsed tag with its type, event name, grouped parameters, trigger name and condition. This is where you catch a parameter that landed on the wrong tag, or a Trigger Type that fell through to the Custom Event fallback because of a typo. For checking the tags after they land, see AI tag validation before production.
What injection actually does, per row
Injection is sequential, one selected row at a time, and each row goes through three calls to the gtm-api edge function.
- create_trigger. The trigger config built from the sheet columns is sent first. The trigger ID that comes back is what the tag will fire on.
- ensure_variables. The tool scans every Parameter Variable value for {{...}} references and asks the backend to make sure they exist. Built-in variables such as Click URL, Page Path or Form ID get enabled in the workspace; names it does not recognise as built-ins are reported as ignored, not created. The step retries twice, and if a built-in still cannot be enabled you get a "Built-in Variables Warning" toast saying the tag will be created but the reference may not resolve until you enable it by hand.
- create_or_update_tag. A GA4 Event tag (gaawe) is created with the event name, the Measurement ID override, the event parameters and the trigger attached. If a tag with the same name already exists in the workspace the backend updates it rather than failing, and the result reports whether the row was created or updated.
Each row's status updates live in the preview (pending, processing, injected, failed), and a results dialog lists tag and trigger IDs for successes and the error text for failures. A failure on one row does not stop the others, and the batch is written to the app's GTM write audit log under the source sheets_import. Everything lands in the workspace you picked, unpublished. You still open GTM, preview, and publish.
How to use it, step by step
- Open the Google Sheets Importer. If it says your Google account is missing the Sheets or GTM scope, sign in again.
- In the target picker at the top, choose the account, container and workspace. Use a fresh workspace if the container is shared with other people; the agency GTM workspace workflows post explains why.
- Under 2. Select Template, pick Template 1 - GTM Structure from the "Choose a template structure..." dropdown. Open the reference sheet, make a copy, and fill in your rows.
- Under 3. Configuration, enter your GA4 Measurement ID. The optional prefix and suffix fields add a client code to every tag and trigger name in one go, handy if you follow a scheme like the ones in GTM naming conventions at scale.
- Click Choose from Drive, pick the sheet, then click Load Data. A toast confirms the file name and notes that this import can read that file and no other.
- Under 4. Preview & Select Configurations, read each parsed tag. Tick the rows you want, or use the select-all button.
- Under 5. Inject to GTM, click Inject to GTM. Watch the row statuses, then read the results dialog.
- Open GTM, preview the workspace, and publish when the tags fire as expected.
The Import history button keeps the last 90 days of imports in your browser's local storage (per-browser, not per-account). Clear & Start Over resets the form.
Limits and honest caveats
- Template 1 creates GA4 Event tags only. The Tag Type column is read, but the tag that gets created is always a GA4 Event. For Google Ads, pixels, UET or LinkedIn, use the matching template. For a Custom HTML tag, use the AI Generator instead.
- No user-defined variables are created. If a Parameter Variable references {{DL - Purchase Value}} and that Data Layer variable does not exist, the tag is still created and the reference will not resolve until you add it.
- Same-name tags are updated, not duplicated. Convenient for a re-run, but a row with the same GTM Tag Name as an unrelated existing tag will modify that tag.
- Trigger Type typos become Custom Event triggers. "Form Submit" (not in the list) becomes a Custom Event listening for form_submit, which is not a Form Submission trigger.
- One sheet per import, first tab. There is no tab selector.
- Nothing is published until someone opens GTM.
When to use something else
If you have five events and no spreadsheet yet, the sheet is overhead. Describe them in plain English in the AI Generator and let it build the tags; the post on auto-generating GTM tags from plain English shows what that looks like.
If you want a container export you can review in a pull request or hand to a client to import themselves, the GTM JSON Generator produces a file rather than writing to a live workspace.
If the sheet mixes GA4 events, Ads conversions and a Meta pixel, split it into one sheet per template; each parser rejects the others with a toast naming the columns it expected. And if the container has not been looked at in a while, run the Container Audit first. Forty clean tags next to sixty stale ones does not make the sixty go away.
Where to start
Copy the Template 1 reference sheet, fill in three or four events you already know the dataLayer for, and run them into a scratch workspace. Once you trust what the tool does with four rows, the fortieth costs nothing extra. Open the Google Sheets Importer and pick a sheet.