# Zmienne GTM: wbudowane, zdefiniowane przez użytkownika i zmienne warstwy danych

**Author:** Piotr Litwa - GTM & Analytics Specialist  
**Published:** 2026-08-28  
**URL:** https://piotrlitwa.com/articles/pl/zmienne-gtm.html  
**Language:** pl  
**Keywords:** zmienne gtm, zmienne google tag manager, zmienna warstwy danych gtm, tabela przeglądowa gtm, custom javascript gtm

---

Zmienne GTM to nazwane miejsca, które Google Tag Manager wypełnia wartością w chwili, gdy dzieje się zdarzenie: adres strony, kliknięty link, cena produktu z warstwy danych, plik cookie, stała w rodzaju identyfikatora pomiaru GA4. Zmienne wbudowane są gotowe i wystarczy je włączyć. Zmienne zdefiniowane przez użytkownika tworzysz sam, a najczęściej będziesz używać zmiennej warstwy danych (Data Layer Variable).

Tagi decydują, co wysłać. Reguły decydują, kiedy. Zmienne decydują, co faktycznie jest w środku.

Dlatego zepsuta zmienna rzadko psuje coś, co widać gołym okiem. Tag dalej się uruchamia. Reguła dalej pasuje. A GA4 spokojnie zapisuje `undefined` albo, co gorsze, wartość, która została po wcześniejszym kroku.

> **Najważniejsze wnioski**
> - Zmienne wbudowane (Page URL, Click URL, Video Percent) włączasz w Zmienne > Konfiguruj. Własne tworzysz w Zmienne > Nowa.
> - Zmienne nie są liczone raz na stronę. GTM oblicza je przy każdym zdarzeniu, które ich potrzebuje, więc ta sama zmienna może mieć inną wartość na `gtm.js`, a inną przy kliknięciu.
> - Zmienna warstwy danych czyta scalony model danych GTM, a nie tylko ostatni push. Brakujący klucz zwraca poprzednią wartość, jeśli taka istnieje. To źródło połowy błędów w przychodach, które znajduję.
> - Tabele przeglądowe i tabele RegEx zastępują stosy niemal identycznych tagów. Wartość domyślną ustawiaj zawsze świadomie.
> - Nazywaj zmienne według typu i przeznaczenia (`DLV - ecommerce.value`, `CONST - GA4 ID`). W kontenerze ze 140 zmiennymi nazwy są Twoją jedyną dokumentacją.

## Wbudowane a zdefiniowane przez użytkownika: dwie połowy

**Zmienne wbudowane** przygotował Google. Nie konfigurujesz ich, tylko włączasz w Zmienne > Zmienne wbudowane > Konfiguruj. Świeży kontener internetowy ma włączonych pięć: Page URL, Page Hostname, Page Path, Referrer i Event. Reszta siedzi w grupach, które włączasz w razie potrzeby:

- **Strony**: Page URL, Page Hostname, Page Path, Referrer.
- **Narzędzia**: Event, Container ID, Container Version, Environment Name, Random Number, HTML ID, a do tego nowsze Analytics Client ID i Analytics Session ID.
- **Kliknięcia**: Click Element, Click Classes, Click ID, Click Target, Click URL, Click Text.
- **Formularze**, **Historia**, **Błędy**, **Przewijanie**, **Widoczność** i **Filmy** (Video Percent, Video Title, Video Status i pięć kolejnych).

Większość zmiennych wbudowanych po prostu czyta klucz `gtm.*`, który wypchnęła reguła. Click URL czyta `gtm.elementUrl`. Video Percent czyta `gtm.videoPercent`.

Nie ma reguły kliknięcia, nie ma Click URL. Dlatego Click URL użyty w tagu odsłony strony jest zawsze pusty.

**Zmienne zdefiniowane przez użytkownika** znajdziesz w Zmienne > Zmienne zdefiniowane przez użytkownika > Nowa > Konfiguracja zmiennej. Tu dzieje się prawdziwa robota. Czytasz warstwę danych, mapujesz wartości, rozbierasz adresy URL, piszesz małe funkcje. Google dzieli je na pięć grup: Nawigacja, Zmienne strony, Elementy strony, Narzędzia, Dane kontenera.

## Typy zmiennych GTM w pigułce

Oto ściąga, którą mam otwartą podczas audytów. Nie każdy typ, ale każdy, który widuję w prawdziwych kontenerach.

| Typ zmiennej | Co czyta | Typowe użycie | Na co uważać |
|---|---|---|---|
| Zmienna warstwy danych | Klucz w modelu danych GTM | `ecommerce.value`, `transaction_id`, `user_type` | Stare wartości z wcześniejszych pushy, zła wersja. |
| Stała | Stały tekst | Identyfikator pomiaru GA4, identyfikator konwersji Ads | Wpisywanie tego samego ID ręcznie w 12 tagach. |
| Tabela przeglądowa | Dokładne dopasowanie innej zmiennej | Host na identyfikator pomiaru, ścieżka na typ strony | Brak wartości domyślnej, więc niedopasowane wejście daje `undefined`. |
| Tabela RegEx | Dopasowanie wzorca, od góry do dołu | Grupowanie adresów w sekcje | Wygrywa pierwsze dopasowanie, kolejność ma znaczenie. |
| URL | Część adresu URL | Parametr zapytania, host z Click URL | Źródło domyślnie ustawione na Page URL. |
| Plik cookie własny | `document.cookie` po nazwie | Cookie zgód, grupa testu A/B | Nie odczyta ciasteczek HttpOnly. |
| Element DOM | Tekst lub atrybut elementu | Nazwa produktu w `h1`, atrybuty `data-` | Elementu jeszcze nie ma w DOM przy Container Load. |
| Zmienna JavaScript | Globalna zmienna JS | `navigator.language`, `document.title` | Zmiana nazwy zmiennej globalnej w nowej wersji strony. |
| Własny kod JavaScript | Wynik funkcji | Drobne przekształcenia, wyszukiwanie przez `closest()` | Efekty uboczne, wiele uruchomień na stronę. |
| Tag Google: ustawienia konfiguracji | Parametry konfiguracji | Wspólne ustawienia wszystkich tagów Google | Zmiana zmienia każdy tag, który jej używa. |
| Tag Google: ustawienia zdarzenia | Parametry zdarzenia | `page_type`, `logged_in` w każdym zdarzeniu GA4 | Parametry, o których dodawaniu zapomniałeś. |

## Zmienna warstwy danych i dlaczego jej wersja ma znaczenie

Zmiennych warstwy danych utworzysz więcej niż jakichkolwiek innych. Zmienne > Nowa > Zmienna warstwy danych, wpisujesz nazwę klucza, gotowe. Poza jednym polem, którego większość ludzi nigdy nie rusza: **Wersja warstwy danych**.

**Wersja 2** (domyślna) traktuje kropki jako zagnieżdżenie. `ecommerce.value` oznacza "klucz `value` wewnątrz obiektu `ecommerce`". Sięgniesz też do tablic: `ecommerce.items.0.item_id` daje ID pierwszego produktu.

**Wersja 1** traktuje kropki jako część nazwy klucza. Push `{"a.b.c": "value"}` zostaje jednym kluczem o nazwie dosłownie `a.b.c`. Przez ostatnie pięć lat widziałem dokładnie jeden sensowny powód, żeby jej użyć: starą stronę, której developerzy wypychali klucze z kropkami w nazwach. Jeśli w kontenerze masz zmienne w wersji 1, których nie wybrałeś świadomie, prawdopodobnie nic nie czytają.

Zachowanie, które gryzie ludzi, to sposób, w jaki wersja 2 pamięta. GTM nie czyta "ostatniego pusha". Trzyma scalony model danych: każdy push jest dołączany do tego, co już tam jest. Jeśli klucza nie ma w bieżącym pushu, zmienna zwraca to, co zostawił któryś z wcześniejszych.

Damian prowadzi sklep internetowy z suplementami, około 2600 zamówień miesięcznie. Jego zdarzenie `purchase` korzystało z `DLV - ecommerce.value`.

Po przebudowie checkoutu developerzy przenieśli sumę zamówienia do nowego klucza, `ecommerce.order_total`, i przestali wysyłać `value`. Nic nie zgłosiło błędu. Zmienna dalej zwracała wartość z ostatniego pusha `add_to_cart` na stronie, czyli cenę jednego produktu, gdzieś między 60 a 90 zł.

Przez sześć tygodni przychód w GA4 był o mniej więcej 41 000 zł niższy niż w panelu sklepu. Tag strzelał przy każdym zamówieniu. DebugView pokazywał `value` przy każdym zakupie. Po prostu nie tę liczbę.

Dwie poprawki, obie tanie:

1. Wypychaj `{ ecommerce: null }` przed każdym zdarzeniem ecommerce, tak jak w przykładach Google dla GA4. To czyści stary obiekt z modelu danych.
2. Co tydzień porównuj przychód w GA4 z backendem. To jedyny test, który łapie wiarygodnie wyglądającą, błędną liczbę.

[Poradnik o warstwie danych GA4 i GTM](https://piotrlitwa.com/articles/pl/data-layer-ga4-gtm-poradnik.html) opisuje szczegółowo stronę pushy.

## Tabele przeglądowe i RegEx: mniej tagów, jedna decyzja

Tabela przeglądowa bierze zmienną wejściową i mapuje dokładne wartości na wyniki. Klasyka: ruch ze stagingu kierowany do testowej usługi.

| Gdy `{{Page Hostname}}` równa się | Ustaw `{{LT - GA4 measurement ID}}` na |
|---|---|
| `sklep.example.com` | `G-PROD12345` |
| `staging.example.com` | `G-TEST67890` |
| (wartość domyślna) | `G-TEST67890` |

Ten ostatni wiersz to cały sens.

Sklep meblowy Justyny w Łodzi miał dokładnie taką tabelę, tylko wartość domyślną ustawiono na produkcyjne ID, "żeby nic nie zginęło".

Potem agencja developerska postawiła `preview-482.example.com` pod redesign i puściła na nim automatyczne testy checkoutu. W marcu do produkcyjnego GA4 wpadło 3400 dodatkowych sesji i 212 testowych zakupów. Tabela zrobiła dokładnie to, co jej kazano.

Powiem wprost: **wartość domyślna tabeli przeglądowej ma być wartością bezpieczną, nigdy produkcyjną.** Nieznane wejście znaczy, że nie wiesz, skąd przychodzi ruch. Traktuj go jak dane testowe.

**Tabela RegEx** robi to samo z wyrażeniami regularnymi. Wzorce są sprawdzane od góry do dołu i wygrywa pierwsze dopasowanie. Domyślnie wzorzec musi pasować do całego wejścia i ignoruje wielkość liter.

W Ustawieniach zaawansowanych możesz wyłączyć **Tylko pełne dopasowania**. Możesz też włączyć **Grupy przechwytywania i zamiana** i używać `$1` w wyniku.

Dokumentacja Google ostrzega, że grupy przechwytywania przy wyłączonym pełnym dopasowaniu potrafią zwracać dziwne wyniki. Ma rację. Zostawiam pełne dopasowanie włączone i dopisuję `.*` tam, gdzie trzeba.

## Custom JavaScript, DOM, cookie i cała reszta

**Własny kod JavaScript** (Custom JavaScript) musi być anonimową funkcją, która zwraca wartość:

```js
function() {
  var path = {{Page Path}} || '';
  if (path.indexOf('/blog/') === 0) return 'blog';
  if (path.indexOf('/produkt/') === 0) return 'product';
  return 'other';
}
```

Dwie zasady. Po pierwsze, zero efektów ubocznych: żadnego `dataLayer.push()`, żadnych zmian w DOM, żadnego zapisywania cookies. GTM może obliczyć tę samą zmienną wiele razy na jednej stronie, raz przy każdym zdarzeniu, które jej używa, więc push w środku zmiennej robi pętlę dodatkowych zdarzeń.

Po drugie, krótko. Każda linijka wlicza się w limit rozmiaru kontenera, czyli 200 KB, a 40-linijkowa funkcja do typu strony to tabela RegEx w przebraniu.

**Element DOM** przyjmuje metodę wyboru (ID albo selektor CSS) i opcjonalną nazwę atrybutu. Bez atrybutu zwraca tekst elementu. DOM czyta w chwili zdarzenia, więc przy Container Load (`gtm.js`) elementu może jeszcze nie być. Używaj go od DOM Ready w górę.

**Plik cookie własny** (1st-Party Cookie) czyta ciasteczko po nazwie, z opcją dekodowania URI. Jeśli dwa ciasteczka mają tę samą nazwę, zwraca pierwsze. Nie widzi ciasteczek HttpOnly, bo nie widzi ich żaden JavaScript.

**Zmienna JavaScript** czyta zmienną globalną po ścieżce, np. `navigator.language`. To nie kod, tylko ścieżka. W porządku dla wartości, które platforma wystawia na stronie. Krucha, gdy aktualizacja motywu zmieni nazwę zmiennej globalnej.

**Stała** to najbardziej niedoceniany typ. Jedna `CONST - GA4 measurement ID` używana wszędzie oznacza jedno miejsce do zmiany przy migracji usługi.

**Tag Google: ustawienia konfiguracji** i **Tag Google: ustawienia zdarzenia** trzymają grupy parametrów do wielokrotnego użytku. Pierwsza trafia do tagów Google, druga do tagów zdarzeń GA4. Świetnie sprawdzają się dla `page_type` czy `logged_in` w każdym zdarzeniu. Tylko je dokumentuj, bo parametr dodany po cichu przez zmienną ustawień jest niewidoczny, kiedy otwierasz pojedynczy tag.

## Kiedy zmienne są obliczane (i co naprawdę znaczy undefined)

Tego prawie nikt nie tłumaczy. GTM nie oblicza zmiennych przy ładowaniu strony i nie trzyma ich w zapasie. Oblicza zmienną wtedy, gdy coś jej potrzebuje: reguła sprawdza warunek albo uruchamiany tag czyta pole. Każde zdarzenie w podglądzie (Container Loaded, DOM Ready, Link Click, Twoje własne zdarzenia) ma własną zakładkę Zmienne z wartościami z tej chwili.

Czyli `{{Click Text}}` jest pusty na Container Loaded i wypełniony na Link Click. `{{DLV - user_type}}` może mieć `undefined` na `gtm.js`, a `"logged_in"` trzy zdarzenia później, gdy strona go wypchnie. Jeśli Twój tag strzela na Wszystkich stronach, czyta tę wczesną wartość.

`undefined` zwykle znaczy jedną z trzech rzeczy:

- Klucz nigdy nie został wypchnięty albo został wypchnięty po zdarzeniu, na którym strzela tag.
- Nazwa klucza się nie zgadza. `transactionId` i `transaction_id` to różne klucze. Wielkość liter ma znaczenie.
- Zmienna czyta złe źródło, np. zmienna URL dalej wskazuje na Page URL zamiast Click URL.

Pomagają dwa ustawienia. **Ustaw wartość domyślną** w zmiennych warstwy danych i w tabelach daje wartość zapasową. **Formatuj wartość** (w ustawieniach każdej zmiennej) zmienia wielkość liter i podmienia `undefined`, `null`, `true` albo `false` na Twój tekst. Używam "Konwertuj undefined na" `not_set` przy parametrach, które chcę widzieć w raportach jako dziury, a nie jako ciche braki.

## Konwencja nazw, która przetrwa 140 zmiennych

Kontenery, które audytuję, mają od 60 do 150 zmiennych zdefiniowanych przez użytkownika. Bez konwencji dostajesz `price`, `Price 2`, `dl price NEW` i `price (nie używać)`. Wszystkie cztery aktywne.

Mój format: **TYP - co przechowuje**.

- `DLV - ecommerce.value` (ścieżka klucza, dokładnie).
- `CONST - GA4 measurement ID`.
- `LT - hostname to GA4 ID`.
- `RT - page path to page type`.
- `URL - Click Hostname`, `URL - query utm_campaign`.
- `CJS - link location`.
- `COOKIE - cmp consent`.
- `DOM - product h1`.
- `GT EVENT - common params`.

Sortuje się według typu. Mówi, skąd pochodzi wartość. A junior przeczyta tag bez otwierania sześciu zmiennych.

Nieużywane zmienne wylatują. Moja [lista kontrolna audytu GTM](https://piotrlitwa.com/articles/pl/audyt-gtm.html) ma kroki sprzątania. Stare zmienne zasilające żywe tagi to jeden z [5 błędów GTM, które po cichu kosztują konwersje](https://piotrlitwa.com/articles/pl/5-bledow-gtm-ktore-kosztuja-konwersje.html).

Chcesz zobaczyć to w prawdziwym wdrożeniu? Tekst o [śledzeniu kliknięć w linki w GA4 przez GTM](https://piotrlitwa.com/articles/pl/sledzenie-klikniec-linkow-ga4.html) krok po kroku używa Click URL, zmiennej URL dla klikniętego hosta i małej zmiennej Custom JavaScript.

## Najczęstsze pytania

**Czym się różnią zmienne wbudowane od zdefiniowanych przez użytkownika w GTM?**
Wbudowane pochodzą od Google. Tylko je włączasz w Zmienne > Konfiguruj. Własne budujesz sam, np. zmienną warstwy danych dla `transaction_id` albo stałą z identyfikatorem pomiaru.

**Dlaczego moja zmienna warstwy danych zwraca undefined?**
Klucz nigdy nie został wypchnięty, został wypchnięty po zdarzeniu, na którym strzela tag, albo jego nazwa nie zgadza się co do znaku, łącznie z wielkością liter. Otwórz podgląd, wybierz zdarzenie, na którym strzela tag, i sprawdź w zakładce Warstwa danych, czy klucz jest tam w tej chwili.

**Wersja 1 czy 2 warstwy danych?**
Wersja 2, domyślna, praktycznie zawsze. Czyta kropki jako zagnieżdżenie, więc `ecommerce.value` działa. Wersja 1 traktuje kropki jako zwykłe znaki w nazwie klucza i pasuje tylko do starych wdrożeń, które wypychają klucze w stylu `"a.b.c"`.

**Kiedy tabela przeglądowa, a kiedy tabela RegEx?**
Tabela przeglądowa, gdy wejścia to dokładne, znane wartości, np. hosty. Tabela RegEx, gdy dopasowujesz wzorce, np. grupujesz tysiące adresów w typy stron. Tabela RegEx zatrzymuje się na pierwszym dopasowaniu, więc tam liczy się kolejność. Obie potrzebują świadomie ustawionej wartości domyślnej.

**Czy zmienna Custom JavaScript może wypychać dane do warstwy danych?**
Może, ale nie powinna. GTM może ją uruchomić przy każdym zdarzeniu, które jej używa, więc push w środku tworzy dodatkowe zdarzenia. Trzymaj zmienne Custom JavaScript w czystości: odczyt, przekształcenie, zwrot.

**Ile zmiennych może mieć kontener GTM?**
Nie ma sztywnego limitu liczby, pod który warto planować. Prawdziwą granicą jest rozmiar kontenera, 200 KB, i Twoja własna zdolność do zrozumienia całości. Przy każdym audycie usuwaj nieużywane.

## Co dalej

Otwórz w kontenerze ekran Zmienne i posortuj listę zmiennych użytkownika po nazwie. Jeśli o połowie z nich nie umiesz powiedzieć, co trzymają, bez otwierania każdej, to Twoje pierwsze zadanie: zmień nazwy na TYP - przeznaczenie i usuń to, do czego nic się nie odwołuje.

Potem weź trzy najważniejsze tagi (zakup, lead, rejestracja) i sprawdź w podglądzie każdą zmienną, której używają, na dokładnie tym zdarzeniu, na którym strzelają. Nie na Container Loaded. Na prawdziwym zdarzeniu. Tam chowają się `undefined` i stare wartości.

Nie chcesz robić tego ręcznie? [Darmowy audyt GTM](https://piotrlitwa.com/checkGTM/) wskaże zepsute i nieużywane zmienne, które widzi w Twoim kontenerze. Przy kontenerach, które zmieniają się co sprint, [abonament na monitoring GTM](https://piotrlitwa.com/services.html#assurance) pilnuje ich na bieżąco, żeby przemianowany klucz w warstwie danych nie kosztował Cię sześciu tygodni danych o przychodach.

---

**Źródła i dalsza lektura**
- [User-defined variable types for web](https://support.google.com/tagmanager/answer/7683362) (Pomoc Menedżera tagów)
- [Built-in variables for web containers](https://support.google.com/tagmanager/answer/7182738) (Pomoc Menedżera tagów)
- [The data layer](https://developers.google.com/tag-platform/tag-manager/datalayer) (Google for Developers)

---

*Written by [Piotr Litwa](https://piotrlitwa.com/about.html) - independent GTM & Analytics specialist.*
