← Blog

TutorialDevelopers

Feature analytics with one HTML attribute

You've been asked to "add analytics" to an internal app. Here's the version that takes minutes, needs no tracking plan, no event pipeline, and no meeting: one attribute on the elements worth counting.

July 21, 2026 · 4 min read

Illustration of dashboard charts and a star rating

Step 0: the snippet (you probably have it already)

One script tag in the app's footer or layout template:

<script async src="https://YOUR-HOST/widget.js" data-feedback-key="fbk_..."></script>

That alone captures pageviews — including SPA route changes — and adds the feedback icon. No configuration, no cookie banner (visitors are anonymous by design), no build step.

Step 1: tag the elements worth counting

Function-level tracking is declarative. Put a data-feedback-action attribute on any clickable element:

<button data-feedback-action="export-csv">Export CSV</button>
<a href="/reports/new" data-feedback-action="create-report">New report</a>
<button data-feedback-action="approve-expense">Approve</button>

That's it. Zero JavaScript. The widget listens for clicks on tagged elements and each one shows up in the dashboard as a named function with its own usage trend. Works identically in server-rendered templates, React, Vue, or a jQuery app from 2013 — it's just an attribute.

Two naming rules borrowed from teams who've done this at scale: use stable kebab-case verbs (export-csv, not btnExport2), and name the intent, not the widget — if the button becomes a menu item next quarter, the metric should survive the redesign.

Step 2 (optional): track from code

For things that aren't clicks — a wizard completing, a background import finishing — call the API:

MorveroFeedback.track('import-completed');
MorveroFeedback.open();   // opens the feedback panel programmatically

One gotcha worth knowing, because it costs people an afternoon: the widget loads async, so calls made at page-load time need the queue stub — and API calls must live in their own script tag (a tag with src ignores its inline content):

<script>
  window.MorveroFeedback = window.MorveroFeedback || {
    q: [],
    track: function (n) { this.q.push(['track', n]); },
    open: function () { this.q.push(['open']); }
  };
  MorveroFeedback.track('uptime-page-viewed');  // queued, replayed on load
</script>
<script async src="https://YOUR-HOST/widget.js" data-feedback-key="fbk_..."></script>

Calls made later — click handlers, route changes — don't need the stub; by then MorveroFeedback is the real API.

Step 3: read the answers

Within a day, the dashboard answers questions that used to be guesses: which functions carry the app, which shipped features nobody found, whether the redesigned flow actually gets completed. The classic first discovery is the 80/20 inversion — most of the features, a sliver of the use. That's not bad news; that's your simplification roadmap, written by your users' clicks.

Or let your agent do steps 0–3

If your team uses Claude or Cursor, add morvero.com/mcp as a connector and the agent can create the product, paste the real snippet, tag the buttons worth counting, and — via verify_install — confirm events are actually arriving before it reports done. (The full story: what agents do with our MCP server.)

Get the snippet: create a free workspace, add a product, and you'll have a key in about a minute. Full tracking docs, including the domain allowlist and exclusions, at docs → tracking. Framework examples: github.com/Morvero/examples.