Skip to content

PayloadJson — Submission analytics store

PayloadJson is the canonical store for per-submission data. Each lead form submission writes one row to LeadSubmissions with the full request body stored in the PayloadJson column. This gives you a single place to run submission-level analytics without changing existing Worker behavior.

Workers Analytics Engine: The same submissions are also streamed to Workers Analytics Engine (3‑month retention) for dashboard queries via the SQL API — project, source, referer, CTA, and numeric metrics. Use WAE for time-series and aggregated dashboards; use D1/PayloadJson for long-term storage and full payload detail.

What is stored

  • Table: LeadSubmissions
  • Column: PayloadJson (TEXT) — JSON string of the entire request body at submit time
  • Written by: src/lead.tswriteLeadSubmission()JSON.stringify(body) where body is the parsed request (JSON, form-data, multipart, or text)

So every submission keeps a full snapshot of what was sent: form fields, hidden fields, and any extra keys the form or integrator included.

Known payload keys (for analytics)

These keys are commonly present and can be used for analytics. Custom forms may add more.

KeyDescriptionExample
FIRSTNAMEContact name"Rahul"
EMAILEmail"user@example.com"
PHONEPhone"9876543210"
PROJECTProject/campaign name"Sky Towers"
LOCATIONLocation"pune"
TEAMTeam/source team"internal"
DOMAINDomain or base URL"example.com"
PAGE_URLFull submission page URL"https://example.com/contact"
SOURCELead source"FB", "99", etc.
COUNTRYCODEPhone country code"+91"
KEYWORDSearch keyword
USER_MESSAGEUser message
__visitor_idVisitor ID from trackerUUID
__pg_hpHoneypot (bot detection)
__pg_ttsTime to submit (seconds)"12"
CTA, CTA_NAME, __pg_cta, __pg_cta_nameCTA identifier
utm_source, utm_medium, utm_campaign, utm_term, utm_contentGoogle Ads / UTM (any casing: UTM_SOURCE, utm-source, etc.)Stored as sent; also sent to Analytics Engine (blob10–14).
Any other fieldCustom form fields, gclid, fbclid, etc.Stored as-is in PayloadJson. No code change needed — the full request body is JSON.stringify(body).

Inserting more fields: Add any input to your form (hidden or visible). Whatever the client sends in the request body is stored in PayloadJson. The Worker does not whitelist keys; it persists the entire parsed body. For UTM, use query params or hidden fields with names like utm_source, UTM_SOURCE, or utm-source; the lead handler normalizes these for Analytics Engine, but in D1 the raw keys and values are stored unchanged.

Use the shared type LeadSubmissionPayloadJson in src/types.ts when parsing PayloadJson in Worker or dashboard code. The type lists known keys; the [key: string]: unknown allows any additional fields.

Retention and scope

  • Retention: Same as D1 — no fixed 90-day limit; data stays until you prune or archive.
  • Scope: One JSON object per submission (one row per form submit in LeadSubmissions).
  • Use case: Submission-level analytics (counts by project, by page, by CTA, by date; funnel by PAGE_URL; etc.).

How to query

From SQL (D1)

  • By column: Use LeadSubmissions columns (ProjectName, VisitorId, SubmittedAt, Source, Referer, CtaName, IsPrimaryLead, IsDuplicateWindow, ContactCountryCode) for filters and group-bys.
  • By payload fields: Use SQLite JSON1:
    • json_extract(PayloadJson, '$.FIRSTNAME')
    • json_extract(PayloadJson, '$.PAGE_URL')
    • json_extract(PayloadJson, '$.__pg_tts') (time to submit)
    • json_extract(PayloadJson, '$.utm_campaign'), json_extract(PayloadJson, '$.utm_source') (UTM; key may vary by form)

Example: submissions per project and day:

sql
SELECT ProjectName, date(SubmittedAt) AS day, COUNT(*) AS submissions
FROM LeadSubmissions
WHERE SubmittedAt >= ?
GROUP BY ProjectName, date(SubmittedAt)
ORDER BY day DESC, submissions DESC;

Example: submissions by submission page (from PayloadJson):

sql
SELECT json_extract(PayloadJson, '$.PAGE_URL') AS page_url, COUNT(*) AS n
FROM LeadSubmissions
WHERE PayloadJson IS NOT NULL
GROUP BY page_url;

From application code

  • Worker / API: Parse PayloadJson with JSON.parse(row.PayloadJson) and use LeadSubmissionPayloadJson for typing.
  • Dashboard: Use parseSubmissionPayload() in dashboard/src/lib/lead-utils.ts for display; for analytics you can parse full JSON and read any key.

Using PayloadJson for analytics later

  • No Worker change required: PayloadJson is already written for every submission.
  • Add analytics on top: New dashboards, reports, or API endpoints can read from LeadSubmissions and use PayloadJson for dimensions (e.g. CTA, PAGE_URL, PROJECT, date).
  • Single source of truth: For “what was submitted this time,” use PayloadJson; for “canonical lead record” use Leads and link via LeadId.
  • Lead capture — flow and storage model
  • Analytics API — dashboard analytics endpoints; lead detail includes payload_json per submission
  • Migration: migrations/009_lead_submissions_history.sql (adds LeadSubmissions and PayloadJson)