Guide
Track button clicks and form submissions with lytics
Count how many visitors click your signup link and how many submit your waitlist form. You install the lytics snippet once, give the link and the form names that will not change, and add two tags from the dashboard. After that, new tags reach the site without another deploy.
How tags work
The lytics snippet loads a tracking script from lytics on every page. That script counts pageviews, and it also carries the app's tags: rules of the form when this happens, send this event. A tag is a CSS selector or a path and an event name — there is no way to add code to your page from lytics.
- Installing the snippet is a deploy. It has to be in your site's HTML, so the first time takes whatever a change to your site usually takes.
- Tags after that are not. Adding, changing or removing a tag changes the script lytics serves, not your site. Browsers keep the script for up to five minutes, so a change reaches every page within that.
- The elements must be findable. A tag can only match what is in your HTML. Giving the elements you care about a stable
idordata-attribute is a one-time change, best made in the same deploy as the snippet (step 3).
1. Create an account and an app
Create an account, or sign in. You arrive on Your apps. Under Add an app:
| Field | What to enter |
|---|---|
| Name | Anything you will recognise, e.g. Example landing page. |
| Domains | Every hostname the site is served from, comma-separated — for the sample, example.com, www.example.com. www.example.com and example.com are different hosts; *.example.com covers both and every subdomain. Events from any other host are dropped. Testing on your machine? Add localhost while you do. |
Press Add. lytics opens the new app's page with Set up this app at the top. Domains can be changed later under Settings on that page.
Create an account and add an app
2. Install the snippet once
Copy the snippet from the app's setup — or its Install section, which shows the same one — into the <head> your pages share: the layout or template every page uses. It looks like this, with your app's own key where this one says lyt_REPLACE_WITH_YOUR_SITE_KEY:
<script>window.lytics=window.lytics||function(){(lytics.q=lytics.q||[]).push(arguments)};</script>
<script defer src="https://lytics.lp-data.cloud/api/t/lyt_REPLACE_WITH_YOUR_SITE_KEY.js"></script>Copy it from your app rather than from this page: the key is what sends the events to that app. Deploy, open the site, and press Check for pageview in the setup. Carry on once it confirms a pageview — if pageviews do not arrive, tag events will not either, and the setup lists what to check. On an Astro site, see Add analytics to an Astro site first.
3. Give the link and the form stable names
The sample is the landing page of a fictional product: a signup link in the header, another in the page, and a waitlist form. The snippet is in its head, and the elements to track carry names made for tracking:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Example — plan your week</title>
<!-- The lytics snippet, from your app's Install section. Once, in the
shared <head> every page uses. Tags added later need no new deploy. -->
<script>window.lytics=window.lytics||function(){(lytics.q=lytics.q||[]).push(arguments)};</script>
<script defer src="https://lytics.lp-data.cloud/api/t/lyt_REPLACE_WITH_YOUR_SITE_KEY.js"></script>
</head>
<body>
<header>
<a href="/">Example</a>
<a href="/signup" data-analytics="signup-link">Start free</a>
</header>
<main>
<h1>Plan your week in five minutes</h1>
<p>A fictional product, for this guide.</p>
<a href="/signup" data-analytics="signup-link">
<span>Create your free account</span>
</a>
<h2>Not ready yet?</h2>
<form id="waitlist-form" action="/waitlist" method="post">
<label for="waitlist-email">Email</label>
<input id="waitlist-email" name="email" type="email" required />
<button type="submit">Join the waitlist</button>
</form>
</main>
</body>
</html>- Both signup links carry
data-analytics="signup-link", so one tag counts both. A data attribute does not get renamed when somebody restyles the page, as a class likebtn-primarymight, and it says what it is for. - The form has
id="waitlist-form". Anidis unique on the page, which suits a single form. The name of the data attribute is yours to choose; lytics only reads the selector you give it. - No values. The tag will match the form, not read it: the email field is there for your visitors, and nothing typed in it is sent to lytics.
4. Add the signup-click tag
On the app's page, go to Tags and fill in the form:
| Field | Value |
|---|---|
| When | An element is clicked |
| Matching | [data-analytics="signup-link"] |
| On paths starting | empty — every page |
| Send the event | signup_clicked |
Press Add tag; it appears in the list as Click on [data-analytics="signup-link"], sends signup_clicked. A click counts when it lands on a matching element or anything inside it, so a click on the <span> inside the second link counts too. Enter on a focused link is a click as well.
5. Add the form-submit tag
Add a second tag:
| Field | Value |
|---|---|
| When | A form is submitted |
| Matching | #waitlist-form |
| On paths starting | empty — every page |
| Send the event | waitlist_submitted |
A submit tag matches the <form> element, not its button: #waitlist-form, not #waitlist-form button. The event is sent when the browser submits the form — by the button or the Enter key — after the browser's own checks such as required pass, and before your server answers. So it counts submissions, not successful signups; for those, see Troubleshooting.
6. Check the events
Wait up to five minutes, or open the site in a new private window so it fetches the script with the new tags. Then click and submit. With the two tags, the sample sends:
| The visitor | Events sent |
|---|---|
| Opens the home page, / | pageview |
| Clicks “Start free” in the header — or the words inside “Create your free account” | signup_clicked |
| Fills in an email and presses “Join the waitlist” | waitlist_submitted |
| Presses “Join the waitlist” with the email field empty | none — the browser stops the form |
| Opens /pricing and clicks “Start free” there | pageview, signup_clicked, signup_clicked_pricing |
The last row assumes the optional /pricing tag from Path prefixes; without it, only pageview and signup_clicked.
Where to look on the app's page:
- Live lists each event as it arrives, with the path it came from — the quickest check while you test.
- Events, under the visits chart, counts each event name over the chosen range:
signup_clickedandwaitlist_submittedappear there as rows. - The Other events tile adds up every event that is not a pageview.
Each event is stored with its name, the page's path (never its query string), the time, the kind of device and, if your app identifies signed-in users, their id. Not the text of the link, not the form's fields.
The optional path prefix
On paths starting limits a tag to pages whose path begins with what you type. Empty means every page, which is what both tags above use. Say the fictional site also has a pricing page with the same link:
<!-- /pricing — same <head>, same snippet --> <a href="/signup" data-analytics="signup-link">Start free</a>
To know how many signup clicks come from pricing, add a third tag:
| Field | Value |
|---|---|
| When | An element is clicked |
| Matching | [data-analytics="signup-link"] |
| On paths starting | /pricing |
| Send the event | signup_clicked_pricing |
Every tag that matches a click sends its event, so a click on pricing sends both signup_clicked and signup_clicked_pricing: the first stays the site-wide total, the second is the pricing share. The prefix is compared as plain text from the start of the path — case matters, and hostnames and query strings play no part:
| Prefix | Matches | Does not match |
|---|---|---|
/pricing | /pricing, /pricing/teams, /pricing-2025 | /, /en/pricing |
/pricing/ | /pricing/teams | /pricing |
/ | every page — the same as leaving it empty | nothing |
On a single-page app, the path is checked when the click happens, so a tag follows client-side navigation.
Choosing event names
- Name what happened, in one style:
signup_clicked,waitlist_submitted. The name is the only thing the event carries, and it is what the Events table shows. - Letters, digits,
_ . : - $and spaces, up to 64 characters.pageviewis refused. Avoidlogintoo: lytics counts everyloginevent as a sign-in. - A tag sends the same name every time, so keep values out of it — no plan names typed by visitors, no ids. To tell two buttons apart, give them different selectors and names.
- An app can have up to 50 tags. Removing a tag stops new events; the ones already sent stay in the counts.
Troubleshooting
| Symptom | What to check |
|---|---|
| Pageviews arrive, but a new tag sends nothing. | Tags travel inside the tracking script, which browsers keep for up to five minutes. Wait, or open the page in a new private window, which fetches the script again. |
| Nothing arrives at all, pageviews included. | The snippet is not installed on that page, or the page's host is not one of the app's domains. Go back to the app's setup until it confirms a pageview — tags cannot work before that does. |
| The submit tag never fires, though the click tag does. | A submit tag's selector has to match the <form> element itself (or something around it),
not its button. #waitlist-form button matches nothing on submit; #waitlist-form does. |
| The form is sent, but no submit event arrives. | The form is being sent by a script calling form.submit(), which browsers send without a submit
event. form.requestSubmit(), a submit button or the Enter key all fire one. If the form is not a
<form> at all, tag a click on its button instead. |
| More submit events than real signups. | The event is sent as the browser submits the form — after its own checks, such as required, but
before the server or the page's script has accepted it. To count signups that succeeded, tag a view of the
thank-you page (A page is viewed), or send the event from the app with lytics('event', …). |
| The dashboard says a selector or a path is not allowed. | A selector can be up to 200 characters, without <, >, { or
}. A path has to start with / and contain no spaces. An event name is letters, digits,
_ . : - $ and spaces, up to 64; pageview is taken. |
Events from automated and headless browsers are dropped as bots, so test in an ordinary browser window. An ad or tracking blocker can stop the script or its requests as well.
Next
- Create an account and add your first app.
- Not ready yet? Explore a sample dashboard with illustrative data.
- The tags reference and the calls for events your app sends itself.