Analytics

Finde genau das Feld, das dein Lead-Formular killt

Ein Kontaktformular mit 60 % Abbruchrate ist kein Rätsel, das du mit Raten lösen musst. Beaconry erfasst drei Signale pro Formular (form_start, form_abandon, lead) und fasst sie zu einer Drop-off-Tabelle zusammen, die genau das Feld benennt, an dem Leute aufgeben. Es läuft nur über GA4, erreicht nie einen Ad-Kanal und speichert ausschließlich Feldnamen und Zählwerte, niemals einen einzigen Wert, den ein Besucher getippt hat.

Lesezeit: ~7 MinVeröffentlicht: 2026-06-08

Die Frage, die ein Funnel tatsächlich beantwortet

"Mein Formular konvertiert mit 40 %" ist eine Zahl. Sie ist für sich allein nicht handlungsleitend. Die handlungsleitende Version lautet "von den Leuten, die das Formular starten, geben die meisten, die aufgeben, beim Telefonnummer-Feld auf". Dieser Satz sagt dir, was du ändern musst: mach das Telefon-Feld optional, oder verschieb es nach hinten, oder erkläre, warum du es brauchst. Mit einem einzelnen Konversionsprozentsatz kommst du da nicht hin. Du kommst da hin mit einem Funnel, der weiß, an welcher Stelle im Formular Leute stoppen.

Beaconry baut diesen Funnel aus drei Events, alle an denselben Formular-Identifier gekoppelt:

  • form_start feuert beim ersten Fokus eines beliebigen Felds im Formular. Der Besucher hat sich eingelassen.
  • form_abandon feuert beim Verlassen der Seite, wenn ein Formular gestartet wurde, mindestens ein Feld ausgefüllt ist und es nicht abgeschickt wurde. Es trägt den Namen des letzten Felds, das der Besucher berührt hat.
  • Lead-Submit ist das server-seitige Event, das Beaconry ohnehin feuert, wenn das Formular tatsächlich abgeschickt wird (generate_lead, contact, subscribe, was auch immer das Formular abbildet).

Starts minus Submits ergibt den gesamten Drop-off. Das aggregierte last_field jedes Abbruchs ergibt das Wo. Diese zweite Zahl ist die, die ändert, was du als Nächstes baust.

Wie die drei Events zum selben Formular finden

Das Ganze funktioniert nur, wenn Browser-seitiger Start und Abbruch auf demselben Form-Key landen wie der server-seitige Submit. Ein Funnel, der 100 Starts unter einem Key und 40 Leads unter einem anderen Key zählt, ist schlimmer als gar kein Funnel, weil er präzise aussieht und falsch ist.

Beaconry koppelt alle drei an einen Identifier. Auf dem Client gibt detectFormKey() von nl-data entweder fluent_<id> oder kadence_adv_<post_id> aus. Genau diesen String erzeugt der Server mit sanitize_key( plugin . '_' . form_id ), wenn er den Lead feuert. Für Fluent Forms und Kadence Advanced Forms aggregieren Start, Abbruch und Submit also alle auf eine Zeile. Bei Formular-Plugins ohne Browser-seitigen detectFormKey()-Treffer erfasst der Submit weiterhin unter dem Server-Form-Key, während Start und Abbruch unter der DOM-id landen, sodass du für jedes Plugin Lead-Zählwerte bekommst und den vollen Abbruch-Funnel für die zwei, die sauber matchen.

Die Abbruch-Erkennung selbst ist bewusst konservativ. Aus dem tatsächlichen Client-Code:

// form_abandon on page-leave: started, >=1 filled field, not submitted
var filledCount = 0;
for ( var f in st.filled ) {
    if ( Object.prototype.hasOwnProperty.call( st.filled, f ) ) filledCount++;
}
if ( filledCount === 0 ) continue; // started but typed nothing -> not an abandon

sendEvent( 'form_abandon', {
    form_id:       keyFor( formEl ),
    last_field:    st.lastField || 'unknown',
    fields_filled: filledCount,
    fields_total:  st.total || filledCount,
} );

Ein Besucher, der ein Feld fokussiert und sofort wieder geht, ohne etwas zu tippen, ist kein Abbruch. Das ist ein Bounce, und ihn mitzuzählen würde die Abbruchrate jedes Formulars auf der Site aufblähen. Nur ein gestartetes-und-teilweise-ausgefülltes-und-nicht-abgeschicktes Formular zählt. Die Payload sind vier Keys: die Formular-id, der letzte Feldname und zwei Zählwerte. Schau, was nicht drin ist.

Warum nur Feldnamen und Zählwerte, und sonst nichts

Die mit Abstand wichtigste Eigenschaft dieses Features ist das, was es zu erfassen verweigert. Das Abbruch-Event trägt last_field (den Namen des Felds, etwa phone oder company) und zwei Ganzzahlen. Es trägt nie den Wert, den der Besucher in dieses Feld getippt hat. Die Klasse, die es server-seitig speichert, ist dazu eindeutig:

* PII-safe by construction: only field NAMES and counts are stored, never
* any entered value. The store therefore needs no GDPR special handling;
* the {@see RETENTION_DAYS}-day prune is a courtesy bound, not a
* data-protection requirement.

Der Server härtet das doppelt ab. Der Last-Field-String läuft durch sanitize_text_field(), ist auf 60 Zeichen begrenzt, und empty- oder unknown-Werte werden komplett aus der Top-Fields-Liste herausgehalten. Der Store behält höchstens 40 verschiedene Feldnamen pro Formular pro Tag, nach Häufigkeit sortiert, sodass ein Formular mit Hunderten dynamischer Feldnamen die Option-Zeile nicht aufblähen kann.

Das ist mehr als nur guter Stil. Ein Funnel, der die halb getippte E-Mail-Adresse oder die unvollständige Telefonnummer eines Besuchers aufgezeichnet hätte, wäre ein Haufen personenbezogener Daten, der ohne Rechtsgrundlage und ohne Löschkonzept in wp_options liegt. Indem Beaconrys Funnel nur den Feldnamen speichert, braucht er keinerlei besondere DSGVO-Behandlung. Der 90-Tage-Prune ist da, um die Option-Größe zu begrenzen, nicht um eine Aufbewahrungspflicht zu erfüllen, denn es gibt keine personenbezogenen Daten aufzubewahren. Du lernst, dass Leute am Telefon-Feld hängen bleiben, ohne eine einzige Telefonnummer zu lernen.

Warum es nur GA4 ist und nie einen Ad-Kanal berührt

Das ist der Teil, den die meisten falsch machen, wenn sie es von Hand bauen, und der Teil, den Beaconry als nicht verhandelbar behandelt. form_start und form_abandon sind verhaltensbezogene Analytics-Signale. Sie sind keine Conversions. Meta will kein "jemand hat ein Formular abgebrochen"-Event in seinem Optimierungs-Set, TikTok will es nicht, Google Ads will es nicht. Verhaltens-Rauschen an die Conversion-API einer Ad-Plattform zu schicken, verschmutzt das Optimierungs-Signal, mit dem die Plattform Käufer findet, und auf manchen Plattformen löst es Validierungs-Warnungen aus.

Beaconry erzwingt die Trennung mit zwei unabhängigen Gates, von denen jedes allein den Job erledigen würde:

  1. Das Opt-in-Gate sitzt im Funnel-Observer. Es erfasst den Abbruch immer in den Plugin-internen Store, und dann gibt es für form_abandon null zurück, um den Dispatch komplett abzubrechen, es sei denn, der Kunde hat form_funnel_ga4 explizit eingeschaltet. Standardmäßig berührt ein Abbruch also nur den lokalen Funnel-Store. Es geht überhaupt nichts über die Leitung.
  2. Das zentrale Routing-Gate sitzt im Forwarder. Direkt nach dem Pre-Dispatch-Filter gleicht es den Event-Namen gegen ANALYTICS_ONLY_EVENTS ab. Ein Treffer routet das Event nur an GA4 und stoppt hart, bevor irgendein Ad-Kanal läuft. Das ist die Single Source of Truth für den Split "internes Signal versus Ad-Vendor-Conversion".

Das zweite Gate ist das, das sich über die Zeit auszahlt. Weil die Regel zentral auf dem Event-Namen sitzt, erbt sie ein neu hinzugefügter Ad-Kanal automatisch. Es gibt keine Per-Kanal-Skip-Liste, an deren Aktualisierung man denken muss. Wenn Beaconry die nächste Conversion-API hinzufügt, ist form_abandon bereits davon ausgeschlossen, mit null neuem Code, weil das Gate vor dem Fan-out läuft und der Event-Name auf der Analytics-only-Liste steht.

Das war ein echter Bug, bevor es eine Regel war. Bis v0.24.3 gab das Opt-in-Gate das Event an den gesamten Fan-out zurück, sodass ein Kunde, der GA4-Forwarding für Abbrüche einschaltete, unwissentlich ein form_abandon-Custom-Event auch an Meta leakte. Das zentrale Gate hat dieses spezifische Leck behoben und, wichtiger noch, die gesamte Leck-Klasse für jeden künftigen Kanal unmöglich gemacht. Netto-Effekt unabhängig vom Toggle-Zustand: ein Abbruch erreicht GA4 nur, wenn du danach fragst, und erreicht einen Ad-Vendor nie.

Was die Drop-off-Tabelle zeigt

Der Forms-Tab rendert eine Zeile pro Formular mit Aktivität, gezogen aus snapshot(). Jede Zeile ist Starts, Submits, Abbrüche, eine Konversionsrate, eine Abbruchrate und die Top-drei-Abbruchfelder nach Häufigkeit:

$rows[] = [
    'form_key'        => $form_key,
    'label'           => $labels[ $form_key ] ?? $form_key,
    'starts'          => $starts,
    'submits'         => $subs,
    'abandons'        => $abnd,
    'conversion_rate' => $denom > 0 ? (int) round( ( $subs / $denom ) * 100 ) : 0,
    'abandon_rate'    => $starts > 0 ? (int) round( ( $abnd / $starts ) * 100 ) : 0,
    'top_fields'      => array_slice( $fields, 0, 3, true ),
];

Die Zeilen sind nach Traffic sortiert (Starts plus Submits), sodass deine meistbesuchten Formulare oben stehen. Das Label kommt aus dem Formular-Katalog, den Beaconry ohnehin pflegt, sodass du "Contact (Homepage)" liest und nicht kadence_adv_5. Das Zeitfenster ist umschaltbar und auf die 90-Tage-Aufbewahrung begrenzt, sodass du dir Heute, die letzten 7 Tage oder die letzten 30 ansehen kannst. Ein "Clear list"-Button leert den Store, ohne künftiges Tracking zu stoppen, da der Aufbewahrungs-Cron sich beim nächsten Plugin-Bootstrap selbst neu plant.

Die Zahl, auf die du handelst, ist top_fields. Eine Zeile mit "120 Starts, 48 Leads, 60 % Abbruch, Top-Feld: phone" ist ein Arbeitsauftrag. Du gehst hin, machst das Telefon-Feld optional und siehst nächste Woche zu, wie sich die Abbruchrate bewegt.

Warum Commerce-Checkouts bewusst ausgeschlossen sind

Ein WooCommerce-, EDD- oder SureCart-Checkout ist ein Formular im DOM-Sinn, aber kein Lead-Formular, und es hier mitzuzählen würde dich aktiv in die Irre führen. Ein Checkout hat bereits seinen eigenen Funnel (view_item zu begin_checkout zu purchase). Sein Submit passiert über die Store API oder AJAX ohne natives Form-Submit-Event, sodass aus Sicht des Formular-Funnels jeder abgeschlossene Kauf wie ein gestartetes Formular aussieht, das nie abgeschickt wurde, was bei jedem einzelnen Verkauf ein form_abandon feuern würde. Das würde deine bestkonvertierende Seite in dein schlechtest aussehendes Formular verwandeln.

Beaconry blockt das auf zwei Wegen. Erstens deaktiviert ein render-agnostisches formFunnelExclude-Config-Flag, server-seitig über die Plattform-APIs gesetzt (is_checkout(), is_cart(), edd_is_checkout()), den gesamten Funnel auf einer Commerce-Checkout- oder Warenkorb-Seite, inklusive EDDs eigenem Markup-Block-Checkout, den kein CSS-Selektor zuverlässig matchen würde. Zweitens fungiert ein isCommerceForm()-Check in nl-data als CSS-Klassen-Backstop für die Standard-Checkout- und Warenkorb-Komponenten-Klassen von WooCommerce, EDD und SureCart. Checkout-Schritt-Abbruch ist Aufgabe des Commerce-Funnels, ein begin_checkout ohne purchase, und das bleibt auch dort.

Gegenprobe: der von Hand gebaute GA4-Funnel

Du kannst das direkt in GA4 annähern. Leute tun das. Hier ist, was es tatsächlich erfordert, und warum das Ergebnis schlechter ist.

Zuerst müssen die Events überhaupt existieren. GA4 hat kein eingebautes "Formular bei Feld X abgebrochen"-Event, also verdrahtest du Google-Tag-Manager-Trigger auf Formularfeld-Fokus und auf Seitenverlassen, schreibst Custom-JavaScript, um zu tracken, welches Feld zuletzt berührt wurde und ob das Formular abgeschickt wurde, pushst das in den Data Layer und registrierst form_id und last_field als Custom Dimensions oder Event-Parameter im GA4-Admin. Dieser GTM-Container lädt von googletagmanager.com, was genau die Drittanbieter-Domain ist, die ein Adblocker verwirft, sodass deine Funnel-Daten bereits zugunsten der Besucher verzerrt sind, die am wenigsten wahrscheinlich diejenigen sind, die frustriert abbrechen. Dann baust du einen Explorations-Funnel in GA4, hoffst, dass die Custom Dimensions sich füllen, und wartest die Verarbeitungs-Verzögerung ab, bevor die Daten nutzbar sind.

Jetzt der Teil, der später zubeißt. Wenn dein GTM-Tag für das Abbruch-Event im selben Container sitzt, der Conversions an Meta und TikTok weiterleitet, ist es wirklich leicht, dieses verhaltensbezogene Event versehentlich in das Signal einer Ad-Plattform fließen zu lassen, dasselbe Leck, gegen das Beaconrys zentrales Gate existiert, außer dass es in GTM kein einzelnes Gate gibt, nur Per-Tag-Trigger-Bedingungen, die du richtig hinkriegen und richtig halten musst. Und der Data-Layer-Push für "zuletzt berührtes Feld" trägt am Ende häufig den Feldwert neben dem Namen mit, es sei denn, du bist vorsichtig, was GA4 still mit PII durchsetzt.

Beaconry kollabiert all das zu einem Toggle. Die Events sind First-Party und Same-Origin, also ist der Adblock-Bias weg. Die PII-Grenze wird im Code erzwungen, nicht in deiner GTM-Disziplin. Das GA4-only-Routing ist ein zentrales Gate, kein Satz Per-Tag-Bedingungen. Du baust keinen Funnel, du liest einen.

Fazit

Eine Konversionsrate sagt dir, dass ein Formular leckt. Ein Funnel sagt dir, wo. Beaconrys Per-Formular-Funnel erfasst form_start, form_abandon und lead, koppelt alle drei an dasselbe Formular, damit die Rechnung ehrlich ist, und legt das genaue Feld offen, an dem Leute aufgeben. Es bleibt nur GA4 hinter zwei unabhängigen Gates, sodass das Verhaltens-Rauschen nie einen Ad-Kanal verschmutzt, und es speichert ausschließlich Feldnamen und Zählwerte, sodass es keine PII zu schützen und kein DSGVO-Konzept zu schreiben gibt. Du bekommst die eine Tatsache, die ändert, was du als Nächstes baust, welches Feld du fixen musst, ohne den Adblock-Bias, das Leck-Risiko oder die PII-Exposition eines von Hand gebauten GA4-Funnels zu erben.