Tutorial: See Page Visits by Page in Near Real Time
- 1 Overview
- 1.1 What You'll Need
- 1.2 When to Use Each Method
- 1.3 Step 1: Open the Reporting Area
- 1.4 Step 2: Use AI Report Builder to Create a Page Visits Report
- 1.5 Step 3: Use the Streaming Analytics Query
- 1.5.1 Parameters
- 1.5.2 What the report returns
- 1.6 Step 4: Save and Organize the Report
- 1.7 Step 5: Handle Permission Issues
- 1.7.1 What to do
- 1.8 Step 6: Add a Live "Visitors Active Now" Indicator
- 1.8.1 Choosing the Right Option
- 1.8.2 Option A: Firebase Realtime Database (Recommended — No Backend Required)
- 1.8.2.1 When to Use
- 1.8.2.2 Limitations
- 1.8.2.3 Setup: Create a Firebase Project
- 1.8.2.4 Security Rules
- 1.8.2.5 Storefront Script (No Backend Required)
- 1.8.3 Option B: Cloudflare Workers + KV (Serverless)
- 1.8.3.1 When to Use
- 1.8.3.2 Limitations
- 1.8.3.3 Setup: Deploy the Worker
- 1.8.3.4 wrangler.toml Configuration
- 1.8.3.5 Worker Script (src/index.js)
- 1.8.3.6 Frontend Script for Cloudflare Option
- 1.9 Step 7: Decide Where the Live Indicator Should Appear
- 1.10 Troubleshooting
- 1.11 Best Practices
- 1.12 Expected Outcome
- 1.13 Conclusion
This tutorial shows UltraCart merchants how to monitor page activity by page URL using two approaches:
Near-real-time reporting with the
uc_analytics_session_streamingtableA lightweight live visitor indicator script for approximate current page activity
The reporting approach is the best fit for saved reports, analysis, and dashboards. The live script approach is best when you want an on-page or admin-facing indicator that shows how many visitors are currently active on a page.
UltraCart’s reporting tools include AI Reports and custom reporting workflows under Operations → Reporting, and the AI Report Builder can generate SQL, parameters, sample data, and visualizations from natural-language prompts.
Overview
Merchants often ask a version of this question:
How do I see page visits by page in real time?
There is not a single prepared report described in the supplied source material that acts as a literal second-by-second “who is on this page right now” monitor. The best UltraCart-native path is to build a custom report using the streaming analytics dataset. The AI Report Builder is designed for exactly this kind of need: you can ask a question in plain English, review the generated SQL, inspect parameters like @start_date and @end_date, preview sample data, and save the report for later reuse. ucdoc-AI-Powered Report Builder…
For merchants who need a more immediate “current visitors on this page” approximation, you can also add a small custom heartbeat script to the storefront. That script is separate from UltraCart’s saved reporting flow and should be treated as a custom live-presence feature rather than authoritative analytics.
What You'll Need
Prerequisite: You must have access to your UltraCart account and the appropriate reporting permissions. The AI Report Builder documentation states that access to Data Warehouse data requires the account owner or granted access, and that the email used for access must be a Google account. ucdoc-AI-Powered Report Builder…
Before you begin, make sure you have:
Access to Operations → Reporting
Permission to use AI Reports / Build Custom Reports
Access to the
ultracart_dw_streamingdataset if you want to queryuc_analytics_session_streamingA developer resource, webhook endpoint, or small backend service if you want to implement the live visitor indicator script
A clear understanding that the live script is approximate and not a replacement for reporting-grade analytics
When to Use Each Method
Use the streaming analytics report when you need:
Page views by full URL
Unique visitors by page
New versus returning visitor breakdowns
Saved reports and reusable analysis
Dashboards and AI-powered summaries
Historical and near-real-time trend reporting
Use the live script when you need:
An approximate “visitors active now” count
A badge on the storefront or a private admin panel
A lightweight live-presence indicator
A custom real-time merchandising or monitoring aid
Note: The reporting query is the better source of truth for analytics. The live script is a convenience feature for presence monitoring.
Step 1: Open the Reporting Area
Log in to your UltraCart account.
Go to Operations → Reporting.
Review the available reporting options, including Available Reports, AI Reports, Reports Dashboards, Schedule Reports, and Report Pickup. The Reporting documentation describes these tools and explains that AI Reports are used to build custom reports and charts, while Report Pickup is used for asynchronously generated reports.
Step 2: Use AI Report Builder to Create a Page Visits Report
The AI Report Builder documentation describes the workflow as:
Navigate to Operations → Reporting
Click Build Custom Reports
Click Create New Report
Type a natural-language question
Review the SQL, parameters, sample data, and visualization
Save the report with a name and optional group for reuse ucdoc-AI-Powered Report Builder…
Follow these steps:
In Operations → Reporting, click Build Custom Reports.
Click Create New Report.
In the prompt box, ask for the report in plain English.
Example prompt:
Show total page views and unique visitors by page URL using the streaming analytics sessions table for the selected date range. Include average views per visitor, new visitor views, returning visitor views, first visit date, and last visit date. Sort by total page views descending.Review the generated SQL.
Review the parameters panel.
Check the sample data preview.
Review or request a visualization.
Save the report.
Tip: The AI Report Builder shows the raw SQL it generated and supports reuse in other tools like BigQuery. It also supports chart requests such as bar, line, pie, map, and other common visualizations. ucdoc-AI-Powered Report Builder…
Step 3: Use the Streaming Analytics Query
If the merchant has access to the streaming analytics dataset, use the following query.
-- ===========================================
-- STOREFRONT PAGE VISITS ANALYTICS REPORT
-- ===========================================
-- Purpose: Analyze visitor traffic by page URL to understand which pages are most popular
-- and track visitor engagement patterns across the storefront
SELECT
-- PAGE IDENTIFICATION
hit.page_view.url AS page_url,
-- VISIT METRICS
COUNT(*) AS total_page_views,
COUNT(DISTINCT s.client_id) AS unique_visitors,
-- ENGAGEMENT QUALITY METRICS
ROUND(COUNT(*) * 1.0 / COUNT(DISTINCT s.client_id), 2) AS avg_views_per_visitor,
-- VISITOR TYPE BREAKDOWN
COUNTIF(s.new_visitor = TRUE) AS new_visitor_views,
COUNTIF(s.new_visitor = FALSE) AS returning_visitor_views,
-- TIME ANALYSIS
DATE(DATETIME(TIMESTAMP(MIN(hit.ts)), 'America/New_York')) AS first_visit_date,
DATE(DATETIME(TIMESTAMP(MAX(hit.ts)), 'America/New_York')) AS last_visit_date
FROM `ultracart-dw-demo.ultracart_dw_streaming.uc_analytics_session_streaming` s,
UNNEST(hits) AS hit
WHERE
session_dts BETWEEN @start_date AND @end_date
AND partition_date >= DATE_TRUNC(DATE_SUB(@start_date, INTERVAL 1 MONTH), WEEK(SUNDAY))
AND partition_date <= DATE_TRUNC(DATE_ADD(@end_date, INTERVAL 1 MONTH), WEEK(SUNDAY))
AND hit.page_view.url IS NOT NULL
GROUP BY page_url
ORDER BY total_page_views DESCParameters
Use these parameters when running the report:
Name | Type | Required | Description |
|---|---|---|---|
| DATETIME | Yes | Beginning of the reporting window |
| DATETIME | Yes | End of the reporting window |
Example values:
{
"start_date": "2026-03-01 00:00:00",
"end_date": "2026-03-17 23:59:59"
}What the report returns
Field | Description |
|---|---|
| Full URL of the page viewed |
| Total number of page-view events |
| Distinct visitor count by |
| Average repeat-view rate |
| Page views from new visitors |
| Page views from returning visitors |
| First date that page was viewed in the selected period |
| Most recent date that page was viewed in the selected period |
Step 4: Save and Organize the Report
Once the query looks correct:
Click Save.
Enter a descriptive Report Name.
Optionally enter a Group Name to organize related reports.
Reopen the report later in the Report Viewer for refreshed analysis.
The AI Report Builder documentation recommends consistent naming conventions and explains that saved reports can be revisited for updated visualizations, SQL review, and AI analysis. ucdoc-AI-Powered Report Builder…
Suggested names:
Storefront Page Visits by URLTop Landing Pages - StreamingPage Traffic by URL - Near Real Time
Step 5: Handle Permission Issues
The merchant’s stated access issue is:
The streaming analytics dataset requires additional permissions.
The needed resolution is:
Request access to the
ultracart_dw_streamingdataset for analytics reporting.
What to do
Confirm the user has access to Operations → Reporting.
Confirm the user can access AI Reports or custom reporting.
If the query fails due to dataset permissions, request access to the streaming dataset from the UltraCart account owner or administrator.
Make sure the approved email account used for BigQuery access is a Google account, as noted in the AI Report Builder documentation. ucdoc-AI-Powered Report Builder…
Warning: Without the required dataset permission, the report may build successfully in concept but fail when run against the streaming analytics table.
Step 6: Add a Live "Visitors Active Now" Indicator
If the merchant wants a near real-time indicator showing how many visitors are currently active on a page, there are two recommended no-server options that are free-tier eligible and require no infrastructure setup.
Note: This feature provides an approximate live visitor count and is not a replacement for analytics reporting. For reporting, continue using the streaming analytics report.
Choosing the Right Option
Option | Setup Complexity | Persistence | Best For |
|---|---|---|---|
Firebase Realtime DB | Very Low | Yes | No-server, quick deploy |
Cloudflare Workers | Low (serverless) | Yes | Global edge, free tier |
In-Memory Node.js | Medium (server req.) | No | Testing / internal use only |
SQLite Node.js | Medium (server req.) | Yes | Single-server deployment |
Firebase and Cloudflare are the recommended paths for most UltraCart merchants. The Node.js options are listed for reference only and are not covered in this tutorial.
Option A: Firebase Realtime Database (Recommended — No Backend Required)
Firebase Realtime Database is a cloud-hosted NoSQL database from Google. It has built-in presence APIs, a generous free tier (the Spark plan), and requires no backend server whatsoever — the storefront script communicates directly with Firebase.
When to Use
No developer or server infrastructure available
Quick deployment needed
Small to medium storefronts
Already using Google services
Limitations
Requires a free Firebase (Google) account
Visitor counts are visible in the Firebase console, not UltraCart
Free tier limits apply (generous for this use case: 1 GB storage, 10 GB/month transfer)
Setup: Create a Firebase Project
Go to console.firebase.google.com and sign in with your Google account.
Click Add project and follow the prompts (no need to enable Google Analytics).
In the left sidebar, click Build → Realtime Database.
Click Create Database, choose a region, and start in test mode.
Copy your database URL (looks like:
https://your-project-default-rtdb.firebaseio.com).In Project Settings → General, scroll to Your apps, click the web icon (
</>), register your app, and copy thefirebaseConfigobject.
Security Rules
In the Firebase console under Realtime Database → Rules, set the following to allow the presence script to read and write:
json
{
"rules": {
"presence": {
".read": true,
".write": true
}
}
}Note: Test mode rules expire after 30 days. Replace them with the rules above before launch to maintain access.
Storefront Script (No Backend Required)
Add the following to your UltraCart storefront. Replace the firebaseConfig values with your own from the Firebase console.
html
<div id="uc-live-visitors"></div>
<script type="module">
import { initializeApp } from
"https://www.gstatic.com/firebasejs/10.12.0/firebase-app.js";
import { getDatabase, ref, set,
onDisconnect, onValue }
from "https://www.gstatic.com/firebasejs/10.12.0/firebase-database.js";
// Replace with your Firebase project config
const firebaseConfig = {
apiKey: "YOUR_API_KEY",
authDomain: "YOUR_PROJECT.firebaseapp.com",
databaseURL: "https://YOUR_PROJECT-default-rtdb.firebaseio.com",
projectId: "YOUR_PROJECT_ID",
storageBucket: "YOUR_PROJECT.appspot.com",
messagingSenderId: "YOUR_SENDER_ID",
appId: "YOUR_APP_ID"
};
const app = initializeApp(firebaseConfig);
const db = getDatabase(app);
// Stable session ID stored in localStorage
const STORAGE_KEY = "uc_live_presence_id";
let sessionId = localStorage.getItem(STORAGE_KEY);
if (!sessionId) {
sessionId = "vp_" + Math.random().toString(36).slice(2) + Date.now();
localStorage.setItem(STORAGE_KEY, sessionId);
}
// Encode page path (Firebase keys cannot contain dots or slashes)
const pagePath = btoa(window.location.pathname
+ window.location.search).replace(/=/g, "");
const presenceRef = ref(db, `presence/${pagePath}/${sessionId}`);
// Write presence; auto-remove when visitor leaves or tab closes
set(presenceRef, { active: true, ts: Date.now() });
onDisconnect(presenceRef).remove();
// Listen for live visitor count on this page
onValue(ref(db, `presence/${pagePath}`), (snap) => {
const count = snap.exists() ? Object.keys(snap.val()).length : 0;
const el = document.getElementById("uc-live-visitors");
if (el) el.textContent = count + " visitors active now";
});
</script>Why Firebase Works Without a Backend
Firebase's
onDisconnect()API automatically removes a visitor's entry when their browser tab closes or they navigate away — no server-side cleanup job required. This makes it inherently more accurate than a heartbeat-based approach and requires zero maintenance.
Option B: Cloudflare Workers + KV (Serverless)
Cloudflare Workers is a serverless edge computing platform. Your code runs on Cloudflare's global network with no server to provision or maintain. The free tier includes 100,000 requests per day, which is sufficient for most storefronts.
When to Use
Prefer a self-contained solution with full control over the logic
Want global low-latency delivery (Cloudflare's edge network)
Already using Cloudflare for DNS or CDN
Need more customization than Firebase allows
Limitations
Requires a free Cloudflare account and brief CLI setup
Slightly more technical than Firebase (requires running one terminal command)
KV storage has eventual consistency (fine for presence monitoring)
Setup: Deploy the Worker
Create a free account at cloudflare.com.
Install Wrangler (the Cloudflare CLI):
npm install -g wranglerLog in:
wrangler loginCreate a new Worker project:
wrangler init uc-presenceCreate a KV namespace:
wrangler kv:namespace create PRESENCECopy the KV namespace ID shown in the output.
Replace the contents of
wrangler.tomlwith the configuration below.Replace
src/index.jswith the Worker script below.Deploy:
wrangler deploy
wrangler.toml Configuration
toml
name = "uc-presence"
main = "src/index.js"
compatibility_date = "2024-01-01"
[[kv_namespaces]]
binding = "PRESENCE"
id = "YOUR_KV_NAMESPACE_ID" # paste from step 5 aboveWorker Script (src/index.js)
javascript
export default {
async fetch(request, env) {
const url = new URL(request.url);
const origin = request.headers.get("Origin") || "*";
const corsHeaders = {
"Access-Control-Allow-Origin": origin,
"Access-Control-Allow-Methods": "POST, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type",
};
// Handle CORS preflight
if (request.method === "OPTIONS")
return new Response(null, { headers: corsHeaders });
if (request.method !== "POST")
return new Response("Method not allowed", { status: 405 });
const { session_id, page_url } = await request.json();
if (!session_id || !page_url)
return new Response("Missing fields", { status: 400 });
const ACTIVE_WINDOW = 60; // seconds
const now = Math.floor(Date.now() / 1000);
const key = `${encodeURIComponent(page_url)}:${session_id}`;
// Store this session (TTL auto-expires stale sessions)
await env.PRESENCE.put(key, now.toString(),
{ expirationTtl: ACTIVE_WINDOW });
// List all active sessions for this page
const prefix = encodeURIComponent(page_url) + ":";
const listed = await env.PRESENCE.list({ prefix });
const active = listed.keys.length;
return new Response(
JSON.stringify({ page_url, active_visitors: active }),
{ headers: { ...corsHeaders,
"Content-Type": "application/json" } }
);
}
};Frontend Script for Cloudflare Option
Add the following to your UltraCart storefront. Replace the endpoint URL with your deployed Worker URL.
html
<div id="uc-live-visitors"></div>
<script>
(function () {
const ENDPOINT = "https://uc-presence.YOUR_SUBDOMAIN.workers.dev/presence";
const PAGE_URL = window.location.pathname + window.location.search;
const STORAGE_KEY = "uc_live_presence_id";
let sessionId = localStorage.getItem(STORAGE_KEY);
if (!sessionId) {
sessionId = "vp_" + Math.random().toString(36).slice(2) + Date.now();
localStorage.setItem(STORAGE_KEY, sessionId);
}
async function sendHeartbeat() {
try {
const res = await fetch(ENDPOINT, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ session_id: sessionId, page_url: PAGE_URL })
});
const data = await res.json();
const el = document.getElementById("uc-live-visitors");
if (el && typeof data.active_visitors === "number")
el.textContent = data.active_visitors + " visitors active now";
} catch (e) {
console.error("Presence heartbeat failed", e);
}
}
sendHeartbeat();
setInterval(sendHeartbeat, 20000); // every 20 seconds
})();
</script>Why Cloudflare Uses TTL Instead of Cleanup Jobs
Cloudflare KV supports native key expiration (
expirationTtl). Each heartbeat refreshes the 60-second TTL. If a visitor stops sending heartbeats (tab closed, navigated away), the key expires automatically — no cleanup script needed.
Step 7: Decide Where the Live Indicator Should Appear
There are two common patterns:
Public storefront indicator
Show a message such as:
3 visitors active now12 shoppers are currently viewing this page
Use this carefully. It may be useful for promotions or urgency messaging, but it should be approximate and should not mislead shoppers.
Private admin dashboard indicator
Send the counts to an internal dashboard or lightweight reporting view for the merchant team. This is usually the safer and more useful option.
Tip: For most merchants, a private dashboard is a better fit than showing live presence publicly on the storefront.
Troubleshooting
The report builder opens, but the query fails The most likely cause is missing access to the ultracart_dw_streaming dataset. Request the required dataset permission from the account owner or UltraCart administrator.
The merchant wants "real time," but the report is delayed Use the saved streaming report for analytics and supplement it with the live visitor script for a near-live page indicator.
The live visitor count looks too high (Firebase) Check for duplicate tabs sharing the same localStorage session ID. The Firebase option handles this correctly by design since each tab writes to the same key. If counts are still high, check whether test data or bots are inflating counts.
The live visitor count looks too high (Cloudflare) Check for duplicate tabs, short session IDs, or a stale TTL window that is set too long. Reduce the ACTIVE_WINDOW value in the Worker script and redeploy.
The live visitor count looks too low For the Cloudflare option, make sure the heartbeat interval is firing consistently (every 20 seconds) and that the TTL is long enough (60 seconds minimum). For the Firebase option, confirm onDisconnect is not removing entries prematurely due to unstable connections.
Firebase script shows 'Permission denied' Your Firebase Security Rules have expired (test mode lasts 30 days) or were not set correctly. Update the Realtime Database rules in the Firebase console to match the rules shown in Step 6.
Cloudflare Worker returns CORS errors Make sure your Worker is deployed and the ENDPOINT URL in the frontend script matches your actual Worker URL exactly. Verify the corsHeaders in the Worker include your storefront domain.
The merchant wants a prepared built-in report instead The UltraCart reporting documentation does not describe a built-in prepared report for literal real-time page presence. AI Reports are the better fit for this use case.
Best Practices
Use the streaming analytics report for reporting-grade analysis
Use the live script only for approximate presence monitoring
Save the report with a clear, reusable name
Add a visualization request in AI Report Builder (bar chart of top pages by total views, line chart of page views over time, table sorted by unique visitors)
Keep live heartbeat windows short — typically 60 seconds
Treat live counts as approximations, not exact analytics totals
Label public-facing indicators as "Active now" or "Visitors in the last minute" to set expectations
Use the Firebase option if you want zero backend maintenance
Use the Cloudflare option if you want more control or are already on Cloudflare's network
Expected Outcome
After completing this tutorial, the merchant should have:
A saved UltraCart custom report that shows page visits by page URL using streaming analytics
A clear path to resolve access issues for
ultracart_dw_streamingAn optional live visitor indicator using either Firebase (zero backend) or Cloudflare Workers (serverless edge)
A strong understanding of when to use reporting versus live presence monitoring
Conclusion
For UltraCart merchants who want to see page visits by page in near real time, the best starting point is a custom report built against uc_analytics_session_streaming. UltraCart’s AI Report Builder is well suited for this workflow because it can generate the SQL, show parameters, preview data, and save the report for reuse. ucdoc-AI-Powered Report Builder…
If the merchant also wants a simple “how many people are on this page right now?” indicator, add a lightweight heartbeat script and backend endpoint. That gives you a practical live-presence display, while the streaming report remains your authoritative analytics tool.