Most of the work in a traditional tag manager is wiring CSS selectors to event names. The AI Element Picker eliminates that step — you click an element on the customer site and Gemini proposes the selector, event name, and variable definitions.
postMessage.POST /p/:siteKey/preview/ai-element with the captured payload.propose_selector(strategy, value) — preferred selector (data-* > id > unique class combo > nth-child fallback)propose_event_name(name, description) — semantic event name like add_to_cart_clickpropose_variables(name, source, path)[] — variables to extract from the element or its contextreason: 'selector_ambiguous'.Gemini is prompted to prefer selectors in this priority order:
data-* attributes (most stable across site refactors).id attribute (only if it doesn't look auto-generated — UUIDs, hash-suffixed IDs, and obvious build-tool patterns are rejected).tag:nth-of-type(N) as a last resort (fragile — the picker explicitly warns when this is what gets used).The model is also told to look at the surrounding DOM context (5 ancestor levels) so it can suggest selectors that survive sibling reordering.
Only what's needed to identify the element:
outerHTML, truncated at 20 KB. Larger elements return 413 element_html_too_large — pick a more specific sub-element.URL and title.No cookies, localStorage, or other PII leave the customer's browser. The picker runs entirely client-side until the user clicks Apply.
The endpoint is wildcard-CORSed because preview cookies authenticate it, but it's still rate-limited:
PICKER_RATE_LIMIT_PER_IP = 12 in snippetRoutes.js).The limit is intentionally low because each call costs ~5K Gemini input tokens (the element + context + tool declarations).
The endpoint returns 422 { reason } when:
reason |
Meaning |
|---|---|
element_required |
The picked element had neither html nor tag — likely a bug in the picker overlay. |
selector_ambiguous |
Gemini's proposed selector matched multiple elements on the page after post-validation. |
low_confidence |
Gemini wasn't confident enough to commit — the element is too generic (e.g. raw <div>). |
not_actionable |
The element looks like layout, not an interactive control. |
When you see low_confidence or not_actionable, pick a more specific element (e.g. the <button> inside the wrapper <div>).
Every call is logged in the per-tenant llmUsageModel collection with the model name, input tokens, output tokens, and the action that triggered it ('tag-manager-element-picker'). This rolls into the same billing-limits stack as voice / chat agents — see the Billing docs for how the budget is enforced.
The model is configurable via ANALYTICS_AI_MODEL (default gemini-3-flash-preview). Flash is intentionally chosen over Pro because the picker is interactive and latency-sensitive; tag suggestions from the batch path (Event Definitions inbox) use the same model but can afford Pro if you set ANALYTICS_AI_MODEL_BATCH.
The picker is admin-only via the preview cookie, but you should still be aware:
#signup, [data-test="cart"]), it's faster to type them in the Tag Manager UI directly.data-track-id="..." attribute and re-pick.