Drop one-shot tmp/axe scripts and agent i18n scratch so the Gitea tree is deployable.
8.3 KiB
Descrybe web — GTM container import
Importable Google Tag Manager workspace for apps/web analytics ($lib/analytics, Consent Mode v2, SPA page_view).
| File | Purpose |
|---|---|
descrybe-web-container.json |
GTM export format version 2 — tags, triggers, variables |
No real secrets. Placeholders only: G-XXXXXXXX, GTM-XXXXXXX.
Import fix notes
Invalid Parameter.type enum (STRING)
GTM import failed with:
File format is invalid. Error deserializing enum type [Type]. Unrecognized value [STRING].
Root cause: a previous edit set consentSettings.consentType list items to "type": "STRING", matching outdated API prose (“list item is of type STRING”). That value is not in the real Parameter type enum.
Official GTM API Parameter.type values (camelCase in the API; SNAKE_CASE in container import/export JSON):
| API | Import/export JSON |
|---|---|
template |
TEMPLATE |
integer |
INTEGER |
boolean |
BOOLEAN |
list |
LIST |
map |
MAP |
triggerReference |
TRIGGER_REFERENCE |
tagReference |
TAG_REFERENCE |
There is no string / STRING. Text values (including consent type names like analytics_storage) use TEMPLATE.
This file now uses:
"consentSettings": {
"consentStatus": "NEEDED",
"consentType": {
"type": "LIST",
"list": [{ "type": "TEMPLATE", "value": "analytics_storage" }]
}
}
Invalid { / } in entity names
GTM rejects { / } in tag / trigger / variable names. An earlier draft named a tag GA4 Event - Custom Event ({{Event}}), which failed import with “The name contains invalid character: {”.
That tag is now GA4 Event - Catch-all Custom Events. The GA4 event name parameter still uses {{Event}} (built-in Event variable) — macro syntax is valid in parameter values, not in entity names.
Prerequisites
- Create a GA4 property and note its Measurement ID (
G-…). - Create a GTM Web container (or use an existing empty workspace). Note the container public ID (
GTM-…). - App loads GTM only when
PUBLIC_GTM_IDis a validGTM-…id (see root.env.exampleandapps/web/src/lib/analytics.ts).
Import steps (re-import)
- Open Google Tag Manager → your Web container.
- Admin → Import Container.
- Choose
docs/gtm/descrybe-web-container.json. - Choose a workspace (new or existing).
- Import option:
- Prefer Merge into an empty/new workspace, or
- Overwrite only if you intend to replace the workspace contents.
- If you previously failed mid-import or have a partial copy, use a fresh workspace (or Overwrite) so you do not keep the old invalid-named tag.
- After import, open Variables → Constant - GA4 Measurement ID and set the value to your real Measurement ID (replace
G-XXXXXXXX). - Consent (verify): each GA4 tag imports with Additional Consent Checks requiring
analytics_storage(consentStatus=NEEDED, list item typeTEMPLATE). If Preview shows tags firing without analytics consent, open each GA4 tag → Advanced settings → Consent Settings → requireanalytics_storage. Addad_storage/ad_user_data/ad_personalizationonly if you later add ads tags. - Preview with Tag Assistant against a local/staging site that has analytics consent granted.
- Submit → Publish.
- Set
PUBLIC_GTM_IDin the web env to the container’s public ID (GTM-…) so it matches the published container. The placeholderGTM-XXXXXXXin the JSON is not a real container id — GTM assigns the public ID when you create the container; import merges into that container.
What the container includes
Variables
| Name | Type | Notes |
|---|---|---|
Constant - GA4 Measurement ID |
Constant | Replace G-XXXXXXXX |
DL - value / currency / items / transaction_id |
Data Layer | Ecommerce fields (version 2) |
DL - page_path / page_title / page_location |
Data Layer | SPA page_view |
DL - billing_term / plan / pack_id |
Data Layer | Flat extras on checkout/purchase |
Built-ins enabled: Event, Page URL, Page Path, Page Hostname, Referrer.
Triggers (Custom Event only)
| Trigger | Event name |
|---|---|
CE - page_view |
page_view |
CE - begin_checkout |
begin_checkout |
CE - purchase |
purchase |
CE - checkout_canceled |
checkout_canceled |
CE - sign_up |
sign_up |
CE - login |
login |
CE - generate_lead |
generate_lead |
CE - processing_job_started |
processing_job_started |
CE - consent_update |
consent_update |
CE - Other Custom Events (catch-all) |
Regex excluding the above + gtm.* |
Do not add History Change or GA4 enhanced-measurement automatic page_view — the app already pushes page_view from trackPageview / AnalyticsHost.
Tags
| Tag | Fires on | Behavior |
|---|---|---|
GA4 Configuration |
Initialization (built-in 2147479573) |
sendPageView=false; consent: analytics_storage |
GA4 Event - page_view |
CE - page_view |
Sends page_path, page_title, page_location |
GA4 Event - begin_checkout |
CE - begin_checkout |
Maps currency, value, items, plus billing_term / plan / pack_id |
GA4 Event - purchase |
CE - purchase |
Same ecommerce fields + transaction_id + flat extras |
GA4 Event - Catch-all Custom Events |
Named core events + catch-all | Event name parameter = {{Event}} (dataLayer event name) |
Ecommerce mapping (begin_checkout / purchase)
Expected dataLayer shape (flat, same object as event):
{
event: "begin_checkout", // or "purchase"
currency: "USD",
value: 99,
items: [
{
item_id: "pro",
item_name: "Pro",
item_category: "subscription", // or "credit_pack"
price: 99,
quantity: 1
}
],
// purchase only:
transaction_id: "cs_test_…",
// optional flat extras:
billing_term: "monthly",
plan: "pro",
pack_id: "pack_…"
}
Dedicated tags map those DL variables into GA4 event parameters. List / marketing prices may be approximates (e.g. plan card pricing before Stripe tax/discounts); use Stripe / server data for finance-grade reporting.
Consent
The app sets Consent Mode v2 defaults to denied, then updates via the CMP (updateConsentMode + consent_update dataLayer event). GTM tags are gated with Additional Consent Required → analytics_storage. Client-side trackEvent / trackPageview also no-op until analytics is granted.
After publish — env checklist
PUBLIC_GTM_ID=GTM-XXXXXXXX # must match the published container public ID
Do not put the GA4 Measurement ID in the web app env for primary loading — measurement ID lives in the GTM variable; the app loads GTM only.
Re-import after this fix
If a previous import failed on STRING (or left a partial workspace):
- Use a new workspace (or Overwrite on an empty one) so you do not keep a half-imported state.
- Admin → Import Container → choose
docs/gtm/descrybe-web-container.json. - Confirm all five GA4 tags show Consent → require
analytics_storage. - Set Constant - GA4 Measurement ID, Preview, then Publish.
Limitations / manual fix-ups
- Placeholder account/container IDs (
0000000000/GTM-XXXXXXX) are rewritten by GTM on import into your container. If import rejects metadata, create an empty Web container first, then Merge this file. - Fingerprints are synthetic; GTM regenerates them after import.
- Catch-all does not forward arbitrary event parameters to GA4 (only the event name). Dedicated ecommerce tags carry
value/currency/items/transaction_id. Add event-parameter tables for other events if needed. - No ads tags in this export — no
ad_*consent requirements yet. itemsas a GA4 event parameter relies on GTM/GA4 accepting the DL array via the event settings table. If Preview shows empty items, switch the ecommerce tags to “Send ecommerce data” and push anecommerce: { … }object from the app, or verify in Tag Assistant that{{DL - items}}resolves.- Re-export from GTM after your first successful import if you want a fingerprint-perfect baseline in git.