# GA4 custom events: how to design and track events beyond the defaults

**Author:** Piotr Litwa - GTM & Analytics Specialist  
**Published:** 2026-09-11  
**URL:** https://piotrlitwa.com/articles/en/ga4-custom-events.html  
**Language:** en  
**Keywords:** ga4 custom events, ga4 event naming, ga4 create event, ga4 key events, ga4 tracking plan

---

GA4 custom events are events you name and define yourself, for actions GA4 doesn't track out of the box and Google has no recommended event for. Before you create one, check two lists: enhanced measurement (already collecting things like `scroll`, `file_download` and `form_submit`) and Google's recommended events (`generate_lead`, `sign_up`, `purchase` and friends). Only when neither fits do you invent a name.

That order matters more than any naming trick. Most of the messy GA4 properties I audit didn't fail on syntax. They failed because someone invented `contact_form_sent` when `generate_lead` already existed, and then a second person invented `lead_submit` six months later.

> **Key Takeaways**
> - Use a recommended event whenever one fits, because GA4 reports and integrations already expect those names and parameters.
> - Event names are case-sensitive, max 40 characters, must start with a letter, and can't use reserved names or the `firebase_`, `google_` and `ga_` prefixes.
> - Up to 25 parameters per event, each visible in reports only once registered.
> - Send events from GTM by default, and keep GA4's "Create event" for small fixes you can't make in the container.

## Custom, recommended or automatic: check before you name anything

GA4 has four kinds of events, and only one of them needs your creativity.

| Event type | Who defines it | Examples | Do you need to build it? |
|---|---|---|---|
| Automatically collected | GA4 | `first_visit`, `session_start`, `user_engagement` | No, they just happen |
| Enhanced measurement | GA4, toggled per data stream | `page_view`, `scroll`, `click` (outbound), `file_download`, `form_start`, `form_submit`, `view_search_results`, `video_start` | No, switch it on in the data stream settings |
| Recommended | Google names it, you send it | `generate_lead`, `sign_up`, `login`, `search`, `purchase`, `add_to_cart` | Yes, but use Google's exact name and parameters |
| Custom | You | `pricing_toggle`, `calculator_complete`, `demo_video_cta` | Yes, and you own the naming |

The recommended list is longer than most people think. There are lead-gen events like `qualify_lead` and `close_convert_lead`, content events like `select_content` and `share`, and the full ecommerce set. If your action is "someone sent a contact form", that's `generate_lead`. Not `contact_form`, not `lead`, not `Form Submit`.

Why bother? Recommended events come with known parameters (`value` and `currency` on `generate_lead`, `method` on `sign_up`) and some reports and integrations expect them. A custom name throws that away for nothing.

Link clicks and YouTube video tracking are their own topic. I wrote up [link click tracking with GTM](https://piotrlitwa.com/articles/en/track-link-clicks-ga4-gtm.html) separately, and video gets its own article later in this series.

## Naming rules GA4 enforces (and the ones it doesn't)

Some rules are hard limits. Break them and the event is dropped or mangled.

- **Up to 40 characters** for the event name.
- **Starts with a letter.** Letters, numbers and underscores only. No spaces, no hyphens.
- **Case-sensitive.** `Sign_Up` and `sign_up` are two different events in your reports, and GA4 will happily count both forever without telling you anything is off.
- **Reserved names** you can't use, including `session_start`, `first_visit`, `user_engagement`, `first_open` and `error`.
- **Reserved prefixes**: `firebase_`, `google_`, `ga_`.

Then there are the rules GA4 doesn't enforce. Those matter more.

Use snake_case, always, because that's what every Google event uses and mixing styles is how duplicates are born. Use an object_action pattern so related events sort together: `pricing_toggle`, `pricing_cta_click`, `calculator_start`, `calculator_complete`. Keep verbs consistent; don't mix `_click`, `_clicked` and `_press`.

And don't put values in the name. `cta_click_header` and `cta_click_footer` should be one event, `cta_click`, with a `cta_location` parameter. Otherwise you get 14 events where one would do, and your Events report turns into a wall.

## The Grzegorz problem: three names, one action

Grzegorz runs marketing for a B2B software company in Poznań. When I opened his property, the Events report listed `Form_Submit` with 312 events, `form_submit_custom` with 118 and `formSubmit` with 47. All three were the same demo request form.

Three agencies over four years. Each one added its own tag. Nobody deleted the old ones, and two were still firing.

So his "demo requests" key event, which was only `Form_Submit`, showed 312 for the quarter. The CRM had 389 demo requests. Close enough that nobody questioned it, wrong enough to skew every channel comparison, because the newer tag only ran on the landing pages paid traffic used.

The fix took an afternoon. One event, `generate_lead`, with a `form_name` parameter set to `demo_request`. One GTM tag.

The two dead tags got paused, the duplicate got removed, and the old `Form_Submit` key event was switched off so nobody would compare it with the new one by accident. Next quarter GA4 showed 402 and the CRM 417, with the gap explained by consent.

The boring lesson: event design is a governance problem first and a technical one second.

## Parameters: what to send and what to register

Each event can carry up to **25 parameters**. Parameter names follow similar rules to event names: up to 40 characters, and values up to 100 characters on standard properties. Don't use the reserved prefixes there either.

A good parameter answers a question you'll actually ask. For a `calculator_complete` event that might be `calculator_type`, `result_band` and `value`. For `generate_lead` it's usually `form_name`, `form_location`, plus `value` and `currency` if leads have a known worth.

Here's the catch that trips most teams. GA4 accepts and stores every parameter, but it only shows a custom parameter in standard reports and Explorations after you register it as a custom dimension. Event-scoped slots are limited to 50 on a standard property, so pick carefully. I covered the setup, scopes and the 50/25/10 limits in [GA4 custom dimensions](https://piotrlitwa.com/articles/en/ga4-custom-dimensions.html).

What I wouldn't send as a parameter:

- Anything personal: emails, phone numbers, names. That's against Google's terms, and under GDPR it turns your analytics property into a data protection problem you have to explain to someone.
- Unique IDs you'll never segment by. They belong in BigQuery, not in a report.
- Free text people type. High cardinality, and it often contains personal data anyway.

## A small tracking plan that fits on one screen

Every property I set up gets a tracking plan before it gets a single tag. It doesn't need to be a 40-tab spreadsheet. For a typical B2B site, this is roughly the whole thing:

| Event name | Type | Fires when | Key parameters | Key event? | Register as custom dimension |
|---|---|---|---|---|---|
| `generate_lead` | Recommended | Contact or demo form succeeds (thank-you state, not button click) | `form_name`, `form_location`, `value`, `currency` | Yes | `form_name`, `form_location` |
| `sign_up` | Recommended | Account created | `method` | Yes | `method` |
| `pricing_toggle` | Custom | User switches monthly/annual on pricing page | `billing_period` | No | `billing_period` |
| `calculator_complete` | Custom | ROI calculator shows a result | `calculator_type`, `result_band` | No | `calculator_type`, `result_band` |
| `cta_click` | Custom | Primary CTA button clicked | `cta_location`, `cta_text` | No | `cta_location` |
| `file_download` | Enhanced measurement | PDF or brochure download | built-in `file_name`, `file_extension` | Maybe | None, built in |

Six events. Two key events. Seven custom dimensions. That's enough to answer most questions a marketing director asks, and small enough that someone will actually keep it updated. The day it stops fitting on one screen is the day to ask which rows nobody uses.

## Sending from GTM vs "Create event" in GA4

There are two ways to get a custom event into GA4, and they're not interchangeable.

**From Google Tag Manager.** You build a GA4 Event tag (tag type **Google Analytics: GA4 Event**), set the event name and parameters, and attach a trigger. Usually that trigger is a Custom Event trigger listening for a `dataLayer.push`, which I walked through in the [GA4 data layer and GTM guide](https://piotrlitwa.com/articles/en/gtm-data-layer-ga4-guide.html):

```js
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
  event: 'calculator_complete',
  calculator_type: 'roi',
  result_band: '10k_50k'
});
```

**From the GA4 interface.** Go to Admin > Data display > Events > Create event. You define a new event name and matching conditions based on an existing event, for example `event_name` equals `page_view` and `page_location` contains `/thank-you`. GA4 then creates the new event from matching incoming data.

A property can hold up to 50 created events and 50 modified ones. Plenty for patches, which is all it should be.

I'll be blunt: I use "Create event" as a patch, not a strategy. It isn't retroactive. It lives outside your GTM container, so anyone reading the container won't know it exists. And it inherits every weakness of the source event. Every one.

Beata learned that the hard way. Her team built `generate_lead` in the GA4 UI from `page_view` on `/thank-you`. GA4 reported 1,180 leads in a quarter. The CRM had 860.

Why? People refreshed the thank-you page, bookmarked it, came back to it from email. Every view was a lead.

I moved the event to a GTM tag fired from a `dataLayer.push` that only happens on a successful form submission. Leads dropped to 874. The CEO wasn't thrilled. The numbers were finally real.

When "Create event" is fine: renaming a legacy event while the GTM fix waits for a release cycle, or splitting one event into a more specific one when you have no container access. Treat it as temporary and write it down.

## Marking it as a key event and testing it in DebugView

An event isn't a conversion until you say so. In GA4 go to **Admin > Data display > Key events > New key event** and type the exact event name, or find the event under **Admin > Data display > Events** and mark it as a key event there. A standard property allows 30 key events. You won't need more than five.

If you also bid on it in Google Ads, the key event has to be imported there as a conversion. That part trips people up constantly, and [the GA4 setup guide](https://piotrlitwa.com/articles/en/ga4-setup-step-by-step.html) covers it.

Then test before you trust anything, because an event that looks right in GTM Preview can still arrive in GA4 with the wrong case, a missing parameter or a duplicate hit. My routine for every new event:

1. Open GTM **Preview**. Tag Assistant connects and your hits get the debug flag automatically.

2. Trigger the action on the site. In Tag Assistant, confirm the GA4 Event tag fired exactly once, not twice, which is the most common failure when an old hardcoded gtag snippet is still sitting in the page template.

3. Open **Admin > Data display > DebugView** in GA4. Find your device and click the event.

4. Check every parameter: right name, right case, a sensible value, nothing truncated.

5. Wait 24 to 48 hours and check the Events report. DebugView is real time; standard reports aren't.

If an event shows in DebugView but never in reports, it's usually filtered by an internal traffic or developer traffic filter, or you're looking at the wrong date. The [DebugView testing guide](https://piotrlitwa.com/articles/en/ga4-debug-mode.html) goes through the failure modes one by one.

## Frequently asked questions

**What is a custom event in GA4?**
A custom event is an event you name and define yourself for an action GA4 doesn't collect automatically and Google has no recommended event for. You usually send it from Google Tag Manager with your own parameters. Check enhanced measurement and the recommended events list first.

**What are the naming rules for GA4 events?**
Event names can be up to 40 characters, must start with a letter, and may contain only letters, numbers and underscores. They're case-sensitive. You can't use reserved names like `session_start` or the `firebase_`, `google_` and `ga_` prefixes.

**How many parameters can a GA4 event have?**
Up to 25 parameters per event. Parameter values are limited to 100 characters on standard properties. Custom parameters only show in reports after you register them as custom dimensions.

**Should I use GTM or "Create event" in GA4?**
Use GTM by default, because the logic lives in one place you can version and audit. "Create event" in the GA4 interface works for quick fixes and renames, but it isn't retroactive and it's easy to forget it exists.

**How do I make a custom event a key event?**
Go to Admin > Data display > Key events, click New key event and enter the exact event name, or mark it from the Events list. A standard property allows up to 30 key events. Import it into Google Ads separately if you want to bid on it.

**Why doesn't my custom event appear in GA4 reports?**
Standard reports usually take 24 to 48 hours to show new events, so check DebugView first. If it shows in DebugView but not in reports, look for data filters (developer traffic filters exclude debug hits by design) or a case mismatch in the name. Not in DebugView at all? Then it's your GTM tag or trigger.

## Next steps

Open Reports > Engagement > Events and sort by event name. Look for near-duplicates: same action, different case, different suffix. Every pair you find is a number someone is reading wrong.

Then write the one-screen tracking plan before you add anything new. Six rows is fine. The plan is what stops the next agency from inventing a fourth name for your demo form.

If you'd like a second pair of eyes on the container itself, the [free GTM audit](https://piotrlitwa.com/checkGTM/) lists every GA4 event tag, the names it sends and the duplicates. And if you want that drift caught before it reaches a board deck, that's what the [GA4 monitoring retainer](https://piotrlitwa.com/services.html#ga4) is for.

---

**Sources and further reading**
- [Recommended events](https://support.google.com/analytics/answer/9267735) (Google Analytics Help)
- [Event collection limits](https://support.google.com/analytics/answer/9267744) (Google Analytics Help)
- [Rename and generate new events](https://support.google.com/analytics/answer/10085872) (Google Analytics Help)
- [Set up events](https://developers.google.com/analytics/devguides/collection/ga4/events?client_type=gtm) (Google for Developers)

---

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