The one key
analytics sits on a node, beside the action it accompanies.
event is the name that arrives in the analytics tool, and params are the
values sent with it. {{...}} reads the data already in scope at that node, which is the
same binding an action’s values use.
A node with no analytics block reports nothing. Silence is the default.
Moments a click does not cover
A card and its detail are one instance, and the detail opens several ways: the card is tapped, the workflow pushes it open, a suggestion lands on it. Reporting the tap alone misses the rest. So a view event is declared in the manifest, beside the lifecycle hooks, using their grammar. It fires when the state is entered, however it was entered:manifest.yaml
layouts names the states that fire it, exactly as it does for a hook
(Lifecycle hooks). The component already reports entering a
state, so the declaration rides that moment and there is nothing to wire.
A server action takes a manifest entry too, keyed by action instead of phase:
action entry fires when your universe receives that action, rather than on the click,
which an ad-blocker can silence, and rather than on the workflow finishing. The submission
arrived, and that is the fact it reports. A button whose action stays on the page keeps its
analytics key on the node.
The platform already knows what opening something means: an app is done the moment it
loads, and a component with a submit is started when it opens and finished when the
submit arrives. You can name those moments yourself, or override them, with an entry
keyed open:
open entry fires when your universe opens the app for the person, before they have
typed anything. done: true says the open is itself the finish. Without any entry the
defaults apply: loaded for an app, started for a component with a submit, nothing for
a component without one.
Naming events
Use the analytics tool’s own vocabulary wherever it has a word for what happened. GA4 definesview_item, generate_lead, select_item and others, and those names populate
its built-in reports with no setup. Invent a name only when no standard one fits, and
prefix it with unoverse_ so it cannot collide with an event the page already fires.
One event per kind of thing. A course, a product and a service are the same moment:
somebody looked at one thing in detail. Give them one event and separate them with a param.
view_course and view_product fragments the reporting. An analytics
tool breaks one event down by param automatically, and it cannot recombine two event names
into one funnel.
Choosing params
Send what answers a question somebody will ask, and leave out anything that only describes the machinery. Match the field names the customer’s own analytics already uses. Their reports and filters are built on those names, so a matching param joins work they have already done, while a generic name arrives as a column somebody has to map by hand.- No personal data. No name, email, phone or date of birth. Analytics tools prohibit it, and the property receiving it belongs to your customer.
- No authored content. Report which item somebody opened, never what it said.
- No typed text. A person’s question or form entry stays out, because both routinely contain personal detail.
Connecting Google Analytics
You declare what happened, and the host page declares where it goes. That split is deliberate. A visitor’s analytics client id lives in a first-party cookie on the customer’s own domain, which code in the host page can read and a server call cannot. Sending from the server would file your events under a separate, unjoinable visitor: data that looks real and is unusable. Running in the page also inherits the consent state, regional configuration and retention terms the customer has already set, rather than reimplementing all of it per customer. Name the destination onwindow.unoverseConfig, beside the embed:
Which target to name. A bare GA4 snippet is
gtag, because a dataLayer push only
becomes a GA event under Tag Manager. A page running GTM is dataLayer.
The target is never detected. A customer page routinely carries several analytics tools
at once, and picking whichever global happens to exist would eventually push one customer’s
behavioural data into a different vendor’s property. That is a data protection incident
rather than a bug, so the destination is always named, and an absent one means silence,
warned once.
Where events reach
Events fire on all three and only delivery differs, so with no listening host the post is a
silent no-op rather than a broken call.
Expect undercounting. Content blockers stop some of these calls, so never reconcile
these numbers against server-side totals as though the gap were a bug.
Checking your events
Turn on debug for the channel you are testing, and each event prints as it fires.unoverse:analytics to see only these.
When an event misbehaves
Next steps
Validate and ship
What the lint enforces, and what only you can judge.
analytics
The key, the four ready-made events, and turning it on.

