“We need analytics” often becomes an excuse for invasive user tracking. But you can measure what matters without knowing who your users are.
The Problem with Traditional Form Analytics
Most form analytics tools track:
- Individual user identities
- Browsing history across sites
- Device fingerprints
- IP addresses and locations
- Session recordings
- Mouse movements and clicks
This data is then:
- Stored indefinitely
- Shared with third parties
- Used for ad targeting
- Vulnerable to breaches
All to answer questions like “how many people completed my form?”
There’s a better way.
What You Actually Need to Know
Be honest: what decisions do your form analytics inform?
- Is my form working? (completion rate)
- Where do people abandon? (drop-off points)
- Are there errors? (error rates by field)
- Is performance acceptable? (load times, submission times)
- How are submissions trending? (volume over time)
None of these require knowing who specific users are.
Privacy-First Analytics Approach
Aggregate, Don’t Track
Instead of: “User #12345 abandoned at email field” Use: “15% of users abandon at email field”
The insight is the same. The privacy impact is vastly different.
Event-Based, Not User-Based
Track events, not people:
// Bad: User-based tracking
analytics.identify('user_123');
analytics.track('form_started', { userId: 'user_123' });
// Good: Event-based tracking
analytics.increment('form_started_count');
analytics.recordTiming('form_load_time', 234);
Session Isolation
If you need session-level data, use ephemeral session IDs:
// Generate session ID that expires with the tab
const sessionId = crypto.randomUUID();
// No cookies, no persistence, no cross-site tracking
Metrics You Can Measure Privately
Form Views
Count page views without identifying viewers:
// Increment counter, nothing more
await fetch('/api/analytics', {
method: 'POST',
body: JSON.stringify({
event: 'form_view',
formId: 'contact'
})
});
Completion Rate
Completion Rate = Submissions / Views × 100
Track both as counters, calculate the ratio.
Field-Level Drop-off
Track which field was last interacted with before abandonment:
let lastField = null;
form.querySelectorAll('input, textarea, select').forEach(field => {
field.addEventListener('focus', () => {
lastField = field.name;
});
});
window.addEventListener('beforeunload', () => {
if (lastField && !formSubmitted) {
navigator.sendBeacon('/api/analytics', JSON.stringify({
event: 'form_abandon',
formId: 'contact',
lastField: lastField
}));
}
});
Result: “30% of abandonments happen after the phone field” without knowing who.
Error Rates
Track validation errors by field:
function trackError(fieldName, errorType) {
fetch('/api/analytics', {
method: 'POST',
body: JSON.stringify({
event: 'validation_error',
formId: 'contact',
field: fieldName,
errorType: errorType // e.g., 'required', 'format', 'length'
})
});
}
Result: “Email format errors occur on 12% of submissions”
Time to Complete
Measure how long forms take:
const startTime = Date.now();
form.addEventListener('submit', () => {
const duration = Date.now() - startTime;
navigator.sendBeacon('/api/analytics', JSON.stringify({
event: 'form_complete',
formId: 'contact',
durationMs: duration
}));
});
Result: “Median completion time is 45 seconds”
Submission Volume
Track submissions over time:
// Server-side: increment daily counter
await redis.incr(`submissions:contact:${today}`);
Result: Time-series data showing trends without user details.
VeilForms Analytics
VeilForms includes privacy-first analytics:
What We Track
- Form views (count only)
- Submissions (count only)
- Error rates (aggregate)
- Performance metrics (aggregate)
What We Don’t Track
- User identities
- IP addresses
- Device fingerprints
- Cross-site behavior
- Individual session recordings
Dashboard Metrics
Contact Form (Last 30 Days)
├── Views: 1,234
├── Submissions: 456
├── Completion Rate: 37%
├── Avg. Time to Complete: 42s
└── Error Rate: 8%
Field Performance
├── Name: 2% errors
├── Email: 12% errors (format)
├── Phone: 15% drop-off
└── Message: 1% errors
All aggregate. No individual user data.
Implementing Your Own Privacy Analytics
If you’re building custom analytics, follow these principles:
1. Minimize Collection
Only collect what you’ll actually use. “Nice to have” data becomes a liability.
2. Aggregate Early
Don’t store individual events if you only need totals:
// Instead of storing every event
INSERT INTO events (formId, field, timestamp) VALUES (...)
// Store aggregates directly
UPDATE form_stats SET view_count = view_count + 1 WHERE form_id = ?
3. Set Retention Limits
// Delete detailed data after 30 days
DELETE FROM form_analytics WHERE created_at < NOW() - INTERVAL 30 DAY;
// Keep only aggregates long-term
4. No Third-Party Scripts
Every analytics script you add can track your users. Self-host or use privacy-focused alternatives.
5. Document What You Collect
Be transparent. If you collect it, disclose it in your privacy policy.
Tools for Privacy-First Analytics
Self-Hosted Options
- Plausible - Privacy-focused, GDPR compliant
- Umami - Simple, fast, no cookies
- Fathom - Privacy-first with EU hosting
Custom Implementation
Build exactly what you need:
// Minimal analytics endpoint
app.post('/api/analytics', (req, res) => {
const { event, formId } = req.body;
// Store only aggregates
switch (event) {
case 'form_view':
incrementCounter(`views:${formId}`);
break;
case 'form_submit':
incrementCounter(`submissions:${formId}`);
break;
case 'form_error':
incrementCounter(`errors:${formId}:${req.body.field}`);
break;
}
res.status(204).end();
});
The Bottom Line
You don’t need to spy on users to understand your forms. Aggregate metrics tell you what’s working and what isn’t—without the privacy cost.
Questions to ask any analytics tool:
- Can I get the same insights with less data?
- Do I need user-level detail, or are aggregates sufficient?
- What happens to this data long-term?
- Who else has access to it?
Choose tools that respect your users. They’ll thank you for it (statistically speaking).
Want form analytics without the tracking? VeilForms includes privacy-first metrics out of the box.