Files
greeneclipse 8580c996c3 Initial commit of Descrybe v2 without local scratch artifacts.
Drop one-shot tmp/axe scripts and agent i18n scratch so the Gitea tree is deployable.
2026-08-09 22:47:43 +02:00

173 lines
8.3 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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`](./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:
```json
"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
1. Create a **GA4** property and note its Measurement ID (`G-…`).
2. Create a **GTM Web** container (or use an existing empty workspace). Note the container public ID (`GTM-…`).
3. App loads GTM only when `PUBLIC_GTM_ID` is a valid `GTM-…` id (see root `.env.example` and `apps/web/src/lib/analytics.ts`).
## Import steps (re-import)
1. Open [Google Tag Manager](https://tagmanager.google.com/) → your **Web** container.
2. **Admin****Import Container**.
3. Choose `docs/gtm/descrybe-web-container.json`.
4. Choose a **workspace** (new or existing).
5. 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.
6. After import, open **Variables****Constant - GA4 Measurement ID** and set the value to your real Measurement ID (replace `G-XXXXXXXX`).
7. **Consent (verify):** each GA4 tag imports with **Additional Consent Checks** requiring **`analytics_storage`** (`consentStatus` = `NEEDED`, list item type `TEMPLATE`). If Preview shows tags firing without analytics consent, open each GA4 tag → **Advanced settings****Consent Settings** → require `analytics_storage`. Add `ad_storage` / `ad_user_data` / `ad_personalization` only if you later add ads tags.
8. **Preview** with Tag Assistant against a local/staging site that has analytics consent granted.
9. **Submit****Publish**.
10. Set **`PUBLIC_GTM_ID`** in the web env to the containers public ID (`GTM-…`) so it matches the published container. The placeholder `GTM-XXXXXXX` in 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`):
```js
{
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
```text
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):
1. Use a **new workspace** (or **Overwrite** on an empty one) so you do not keep a half-imported state.
2. **Admin****Import Container** → choose `docs/gtm/descrybe-web-container.json`.
3. Confirm all five GA4 tags show Consent → require `analytics_storage`.
4. 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.
- **`items` as 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 an `ecommerce: { … }` 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.