---
url: 'https://www.corbado.com/blog/track-login-form-errors-adobe-analytics'
title: 'Track Login and Form Errors in Adobe Analytics'
description: 'Events, dimensions and link calls for tracking login and form errors in Adobe Analytics, plus the error classes that never reach a report at all.'
lang: 'en'
author: 'Vincent Delitz'
date: '2026-08-13T08:35:14.335Z'
lastModified: '2026-08-13T09:29:32.676Z'
keywords: 'track errors adobe analytics, adobe analytics error tracking, form error tracking adobe, login error tracking, adobe analytics list variable errors'
category: 'Authentication'
---

# Track Login and Form Errors in Adobe Analytics

## 1. Login and Form Errors are the Part nobody instrumented

Most Adobe Analytics implementations track what worked. The form submitted, the page loaded, the order completed. Errors get added later, usually after somebody asks why conversion dropped on the login page and nobody in the room can answer.

## Key Facts

- **Track an error with a counter event, a stable error code and a custom link hit** so it
  is recorded without creating a page view.
- **Keep validation errors, system errors and user decisions separate**, because they
  have different owners and require different responses.
- **Do not send raw error messages as dimension values:** identifiers, timestamps and
  uncontrolled text create privacy and cardinality problems.
- **Browser and operating system prompts need authentication-specific instrumentation**;
  an ignored passkey autofill offer does not generate an event Adobe can report.

Adobe has everything you need for that: a counter event for the error, a dimension holding the error code and a link call to send both without a page view. None of it is switched on by default. The way you set it up decides whether the report stays readable once real traffic hits it.

### 1.1 What this article covers

Sections 2 to 6 build the implementation: which events and dimensions to create, how to fire them without a page view, how to measure form abandonment now that the old plugin is gone and how to keep the error dimension out of Adobe's Low Traffic bucket. Section 7 is the part no tagging plan reaches, because on a login page a whole class of errors never touches your page at all.

## 2. Split Validation Errors, System Errors and User Decisions

Three kinds of failure get lumped together under "error" and each one needs different treatment.

| Class            | Example                                                          | Who fixes it                 |
| ---------------- | ---------------------------------------------------------------- | ---------------------------- |
| Validation error | Email format rejected, password too short, one-time code expired | Product and UX               |
| System error     | Identity provider returns 500, rate limit hit on code requests   | Engineering                  |
| User decision    | Biometric prompt dismissed, passkey offer ignored                | Nobody, until it is measured |

Mixed together they produce a chart that goes up and tells you nothing. Separate them as they arrive, with a dimension that names the class. Doing it later in a report is not possible, because the data no longer says which kind of error it was.

The third class is where authentication differs from a checkout form. A dismissed Face ID sheet is not a bug and it is not a neutral event either. It is a user who wanted to log in and did not.

One common case fits none of the three, and it is worth naming before you build the taxonomy: a one-time code requested and then never used. The message may have failed to arrive, it may have landed in a spam folder, or the user may have given up waiting. From the page all three look identical. Classifying it as a user decision would put a possible delivery failure in the wrong bucket, which is exactly what section 2 is trying to prevent. Keep it as its own drop-off, unclassified until another signal tells you which it was. Section 7 comes back to it.

## 3. Create the Error Event and the Error Dimensions

### 3.1 The Error and Form Events

Under Success events in report suite settings, create counter events:

| Event     | Name          | Fires when                                        |
| --------- | ------------- | ------------------------------------------------- |
| `event10` | Error         | Any tracked error surfaces                        |
| `event11` | Form Start    | The user interacts with the first field of a form |
| `event12` | Form Complete | The form submits successfully                     |

Counter is the default type and simply counts the hits that carried the event. Keep the error event generic and let the dimensions carry the detail. Ten separate error events would cost ten slots in the report suite and tell you nothing a breakdown does not.

### 3.2 The Error Dimensions and their Expiry

| Dimension | Holds                                       | Expiry |
| --------- | ------------------------------------------- | ------ |
| `eVar20`  | Error code, e.g. `auth_invalid_credentials` | Hit    |
| `eVar21`  | Error class: validation, system or user     | Hit    |
| `eVar22`  | Flow step where it happened                 | Hit    |

Hit expiry is the right default here, because an error belongs to the moment it happened. Let the dimension persist and the failure gets attached to every later hit in the visit, which inflates every breakdown built on it.

The third row is the one people skip. A custom link call carries no page name, so the page report cannot tell you where the error happened. Put the step of the flow into its own dimension instead.

### 3.3 Track several Errors on one Hit with a List Variable

A form that fails validation on four fields produces four errors at once. An eVar holds one value, so four eVar hits would misreport both the error count and the form count.

Use a [list variable](https://experienceleague.adobe.com/en/docs/analytics/implementation/vars/page-vars/list) instead. It holds several values in one variable, separated by a delimiter you set in the report suite settings. Adobe gives you three of them, each value can be up to 255 bytes, there is no limit on the variable as a whole and it takes up to 250 unique values per visitor.

A list prop is the cheaper alternative, but the whole variable is capped at 100 bytes including the delimiters. That ceiling arrives faster than it sounds. Four codes the length of `auth_invalid_credentials`, 24 bytes each, already come to 99 bytes with the delimiters counted, so the fifth error on that hit is lost.

## 4. Fire the Error Event without a Page View

An error usually does not navigate, so a page view call is the wrong vehicle. Use a custom link call.

### 4.1 Send Errors with AppMeasurement

The [`s.tl()` method](https://experienceleague.adobe.com/en/docs/analytics/implementation/vars/functions/tl-method) takes three arguments: the element that was clicked, the type of link and a name for the report. Type `o` stands for a custom link, `d` for a download and `e` for an exit link. Adobe never tracks custom links on its own, so every error call has to be written by hand.

```javascript
s.events = "event10";
s.eVar20 = "auth_invalid_credentials";
s.eVar21 = "system";
s.eVar22 = "login_submit";
s.linkTrackVars = "events,eVar20,eVar21,eVar22";
s.linkTrackEvents = "event10";
s.tl(true, "o", "Login Error");
```

Three things have to line up. The event has to be in `s.events`, `events` has to be listed in [`linkTrackVars`](https://experienceleague.adobe.com/en/docs/analytics/implementation/vars/config-vars/linktrackvars) and the specific event has to be listed in [`linkTrackEvents`](https://experienceleague.adobe.com/en/docs/analytics/implementation/vars/config-vars/linktrackevents).

Those two lists are filters, not requirements. Adobe documents that an empty or undefined `linkTrackVars` sends all variables and an undefined `linkTrackEvents` sends all events. The moment you set them, as the snippet above does and as every tag manager does, they become the complete list of what is allowed through. Anything not named gets dropped.

That is the trap. Forget one entry and the call still goes out, the event is just missing from it and nothing anywhere warns you. It is the most common reason a correctly written error tracking implementation reports nothing at all.

Write `eVar20` in `linkTrackVars`, not `s.eVar20`. The object identifier belongs in the assignment, not in the list.

### 4.2 Send Errors with the Web SDK

With the Web SDK the same information travels in a `sendEvent` call:

```javascript
alloy("sendEvent", {
    data: {
        __adobe: {
            analytics: {
                events: "event10",
                eVar20: "auth_invalid_credentials",
                eVar21: "system",
                eVar22: "login_submit",
            },
        },
    },
});
```

There are no allow-lists to forget here, so the trap from the previous section disappears with them.

## 5. Measure Form Abandonment without the Form Analysis Plugin

Adobe once shipped a Form Analysis plugin that flagged which field a user last touched before leaving a form. It did not survive the move to AppMeasurement.js and Adobe's [plug-ins overview](https://experienceleague.adobe.com/en/docs/analytics/implementation/vars/plugins/impl-plugins) no longer carries it. Implementations that still reference it are usually running an archived copy.

Three pieces do the same job today:

1. **A form start event** fired on the first meaningful interaction, typically the first field that receives focus. Do not fire it on page load, because that counts impressions as starts.
2. **A form complete event** fired on successful submission, confirmed by the server, not by the click.
3. **A calculated metric** that subtracts complete from start for abandonment or divides one by the other for a completion rate.

For field-level detail, instrument focus and blur yourself and write the last field touched into a dimension on the abandonment call. That is more work than the old plugin and it is the version that survives a framework migration.

One caveat applied to the plugin and applies to your own version too: every abandonment sends an extra link call and Adobe [counts each one as a server call](https://experienceleague.adobe.com/en/docs/analytics/admin/admin-tools/server-call-usage/overage-overview). On a high-traffic login page, that is a conversation about your licence rather than a code review.

## 6. Send Error Codes to keep the Dimension out of Low Traffic

Error tracking breaks on cardinality more often than on bugs.

Adobe buckets dimension items under [Low Traffic](https://experienceleague.adobe.com/en/docs/analytics/technotes/low-traffic) once a dimension exceeds its unique value threshold, by default 2,000,000 unique values per dimension, per report suite, per calendar month. Bucketed values are aggregated together instead of being reported individually.

The mechanism is easier to see than to describe. Three failures of the same kind collapse into one dimension item or into three:

Three rules keep the dimension under the threshold:

- **Send codes, not messages.** `auth_invalid_credentials` is a dimension value. "Sorry Jane, the password for jane@example.com is incorrect (ref 8f2c1a)" is a new value for every user and every attempt.
- **Never send identifiers.** Session IDs, request IDs and timestamps inside the error string are the fastest route into Low Traffic.
- **Use classifications for the human text.** Upload a lookup that maps the code to a readable label. The report stays readable, the dimension stays small.

## 7. Login Errors that never reach the Report

Everything above assumes the error surfaces somewhere your code can see it. In authentication, a large class of failures does not.

- **A cancelled biometric prompt.** The user dismisses Face ID or Windows Hello. Nothing renders, nothing navigates.
- **A login cancelled by a lost window focus.** The Web Authentication API cancels a running passkey login as soon as the browser window loses focus, so switching to a password manager app ends the login in silence.
- **An ignored passkey offer.** The browser offers the passkey in its own autofill dropdown, drawn outside your page. There is no element, no click handler and nothing submitted that you could instrument.
- **A login finished on a phone.** The user scans a QR code on the desktop and confirms on the phone. The phone loads no page and fires no tag.
- **A one-time code that never arrives or never gets typed.** The user requests an SMS or email code and leaves the page to fetch it. Whether the message was delivered, whether it was read and whether the user gave up in their inbox all happen outside the browser. Your page sees a code request followed by silence, which is indistinguishable from a user who simply left.

Those five fail in different ways, so it helps to sort them before planning any work.

Three of the first four are within reach of a wrapper around the `navigator.credentials` call. A dismissed prompt and a cancellation through lost focus both make that call fail, so the wrapper catches them. The phone case is the third: the call completes back on the desktop, because the phone only stands in as the authenticator, so the outcome is measurable even though the middle of the journey is not.

The ignored autofill offer is the one case with nothing to catch. The call never finishes, it just sits there until something else ends it.

The one-time code case needs a different instrument again, because nothing throws and nothing fails. What makes it readable is the shape of the step: how many code requests were followed by a submitted code, how long the gap between the two was, and how often a second code was requested before the first one was used. None of that is an error your code catches. All of it is a drop-off you can count, and it applies to every SMS, email OTP and step-up MFA flow you run today, with or without passkeys in the picture.

So a wrapper buys you the attempt and the outcome. It does not buy you the reason. When the call does fail, the browser returns `NotAllowedError` whether the user cancelled, the login timed out or no passkey was on the device. The W3C [declined to split that into separate codes](https://github.com/w3c/webauthn/issues/2062), because telling the cases apart would let any site test whether a visitor has a passkey.

That single code is a container, not a reason. No vendor including us can open it. What separates the cases is what surrounded the call: how long it ran, whether the browser could offer a passkey at all, what the user did next. Those signals have to be collected while the login is running, because no report can reconstruct them later. [WebAuthn errors](https://www.corbado.com/blog/webauthn-errors) covers the full taxonomy.

## 8. How Corbado can help

Everything up to section 6 makes the errors your page can see reportable. The section above is the remainder. In a login it is the part that matters most. [Corbado Observe](https://www.corbado.com/observe) is the authentication observability layer that captures it, by measuring the passkey login itself instead of the page around it. It runs client-side next to your existing login, works with any identity provider and sends UUID-only telemetry with no PII.

[Video: Passkey error](https://www.corbado.com/videos/features/passkey-error.mp4)

- **Errors split by what they mean:** user decisions, system errors and platform issues separated as they arrive, which is the three-way split from section 2 [applied to authentication](https://www.corbado.com/observe/passkey-errors) instead of rebuilt by hand.
- **A container error turned into a reason:** the same `NotAllowedError` broken down by call duration, by client capability and by what the user did next.
- **Regressions visible as patterns:** an error spike after a browser or OS update shows up as a shape in [the funnel](https://www.corbado.com/observe/login-funnel) instead of noise in a support queue.
- **One user, end to end:** when a ticket says "I cannot log in", [replay that exact session](https://www.corbado.com/observe/user-debugging) with events, errors and device context.
- **The OTP step as a measured step:** code requested, code submitted, code accepted, with the time between them and the repeat requests in view, so the silent version of section 7 becomes a drop-off you can act on.

None of this waits for a passkey rollout. The same instrumentation reads your existing password, SMS, email OTP and MFA flows, which is where most teams find their first surprise. Your Adobe error tracking keeps doing what it is good at. The login itself gets measured where it happens.

## 9. Conclusion: instrument the Error, then check what is missing

Error tracking in Adobe Analytics is a solved problem for anything your code can catch. Create one generic error event, carry the detail in Hit-scoped dimensions, fire it through `s.tl()` with `linkTrackVars` and `linkTrackEvents` set correctly, use a list variable when a hit carries several errors and send codes instead of messages.

Then compare what your error report says against your identity provider and your support queue. The gap between them is made up of the failures from section 7, the ones that never reach a tag. That is usually the part to fix first. The [authentication error rate](https://www.corbado.com/kpi/authentication-error-rate) definition and the [authentication analytics playbook](https://www.corbado.com/blog/authentication-analytics-playbook) are the next two stops.

## Frequently Asked Questions

### How do I track error messages in Adobe Analytics?

Create a counter event for the error, a dimension for the error type and fire both through a custom link call instead of a page view. With AppMeasurement the event has to be in `s.events`, `events` has to be listed in `linkTrackVars` and the specific event has to be listed in `linkTrackEvents`. Set the error dimension to expire on Hit, because an error belongs to the moment it happened and not to the rest of the visit.

### Should I put the raw error message into an eVar?

No. An eVar holds 255 bytes and Adobe buckets a dimension under Low Traffic once it exceeds 2,000,000 unique values per report suite per calendar month, so raw messages with identifiers or timestamps in them destroy the breakdown they were supposed to provide. Send a stable error code as the dimension value and keep the human-readable text in your own logs or in a classification.

### How do I track more than one error on the same hit?

Use a list variable. Adobe provides three of them, each value can be up to 255 bytes, the variable itself has no total byte limit and it supports up to 250 unique values per visitor. A form that fails validation on four fields at once is exactly the case list variables exist for, because four separate eVar hits would misreport both the error count and the form count.

### Can Adobe Analytics track form abandonment?

Not on its own. The legacy Form Analysis plugin that flagged the last field a user touched did not survive the move to AppMeasurement.js and has no official replacement. What works today is a form start event, a form complete event and a calculated metric that subtracts one from the other, plus a field-level dimension if you instrument focus and blur yourself.

### Why do login errors not show up in Adobe Analytics at all?

Because a large class of authentication failures never produces a page event. A cancelled biometric prompt, a passkey ceremony that times out, a WebAuthn call cancelled by focus loss and an ignored passkey autofill offer render nothing, navigate nowhere and click nothing. A wrapper around the WebAuthn call catches most of it: a dismissed prompt and a focus-loss cancellation reject the call, while a completed cross-device flow resolves it on the desktop. An ignored autofill offer produces nothing to catch, because the call stays pending. Either way this is an instrumentation question, not a reporting one.
