A public dashboard is a product surface. It needs a reviewed data contract, independent authorization decision, accessible presentation, cache policy, freshness signal, and incident fallback.
Distinguish two jobs:
| Surface | Purpose |
|---|---|
| Public metrics | Explain aggregate usage, ecosystem, research, or company activity |
| Service status | Communicate current incidents, availability, and maintenance |
They can share visual language, but a marketing metric must not be mistaken for an authoritative status monitor.
Start with a publication contract#
For every public value, record:
- public name and plain-language definition
- unit and aggregation
- source collection and owner
- time zone and refresh cadence
- minimum cohort or suppression rule
- known delay and revision policy
- retention and historical range
- incident behavior
- approver
Example:
Metric: Reports generated in the last 30 days
Unit: successful terminal report jobs
Population: production, non-test workspaces
Deduplication: report_job_id
Refresh: hourly, up to 90 minutes late
Suppression: none; aggregate count only
Owner: data platform
Avoid vanity precision. If data settles hourly, a seconds-level “live” counter is misleading.
Create a sanitized publication collection#
Do not make a raw product or application-log collection public. Publish a small derived fact:
{
"event": "public_metric_snapshot",
"metric": "reports_generated_30d",
"value": 1842390,
"window_start": 1782493200,
"window_end": 1785085200,
"generated_at": 1785085500,
"definition_version": 3,
"source_complete_through": 1785081600
}
Use a dedicated public_metrics collection with an owner and allowlist.
Exclude:
- customer, account, user, request, and session identifiers
- low-count or re-identifiable segments
- internal error text and payloads
- unreleased product names
- commercially sensitive breakdowns
- values whose publication rights are unclear
The publication job, not the web browser, decides what is safe.
Choose the delivery pattern#
Shared GraphJSON dashboard#
Useful for a quick aggregate internal-to-public transition when the entire dashboard has been reviewed. A shared URL is a bearer capability. Password protection limits casual access but does not create per-viewer identity or a public API contract.
Use this only when every tile, filter, title, sample, and future edit is safe for the link’s audience.
Static image or periodically generated page#
Useful for low-frequency reports and editorial review. It reduces interactive exposure but can become stale silently. Display the data-through time and generation time.
Native page through the Data API#
Best when the public site needs brand-consistent, accessible, responsive components:
browser → graphjson.com public endpoint
→ server allowlisted report
→ GraphJSON Data API
The GraphJSON API key remains on the server. The endpoint accepts no arbitrary SQL, collection, account ID, or graph configuration.
Cache the sanitized normalized result, not the key-bearing upstream request.
Build a fixed public endpoint#
Example response:
{
"metric": "reports_generated_30d",
"label": "Reports generated in the last 30 days",
"value": 1842390,
"unit": "reports",
"data_through": "2026-07-26T08:00:00Z",
"generated_at": "2026-07-26T09:05:00Z",
"definition_version": 3,
"status": "current"
}
The server should:
- call a fixed report or sanitized collection query
- validate the upstream envelope and field types
- reject negative, non-finite, or impossible values
- calculate freshness from
source_complete_through - cache with a bounded lifetime and stale-if-error policy
- return a stable browser contract
- log safe request diagnostics
Do not turn an upstream error into zero.
Make freshness visible#
Use explicit states:
| State | Meaning | Presentation |
|---|---|---|
| Current | Within expected delay | Normal value and “data through” |
| Delayed | Past expected delay, within safe stale window | Last known value with warning |
| Unavailable | No trustworthy value | Neutral unavailable state |
| Revised | Historical value changed under policy | Annotation or revision note |
A stale last-known value can be useful if clearly labeled. It becomes harmful when rendered as live during an incident.
Use an independent clock comparison on the website. Do not trust a
source-provided status: current without validating its timestamp.
Avoid monitoring shared fate#
If GraphJSON is unavailable, a GraphJSON-powered status surface may also fail. Therefore:
- page from an independent monitoring system
- maintain incident status in an independent status channel
- let the public page render a static incident banner without querying GraphJSON
- keep contact and subscription instructions available during data failure
GraphJSON can explain historical availability and affected dimensions. It should not be the only detector or public incident publisher for its own availability.
Publish status context safely#
If you include incident history, publish compact, editorially approved facts:
{
"event": "public_incident_update",
"incident_id": "inc_2026_07_26",
"component": "data-api",
"state": "monitoring",
"started_at": 1785082500,
"updated_at": 1785084900,
"summary": "Data API latency has recovered; monitoring continues.",
"public": true,
"revision": 4
}
Do not automatically publish internal error text, customer names, exploit details, unverified root causes, or employee commentary. Incident communication needs an accountable publisher.
Design an accessible surface#
Every chart needs the underlying meaning in text:
- descriptive title
- current value and unit
- covered interval
- data-through time
- definition or methodology link
- keyboard-accessible table where a trend matters
- text status that does not rely on color
Use the visitor’s locale for number formatting but preserve the metric’s
documented reporting time zone. Avoid ambiguous dates such as 07/08/26.
On small screens, prioritize the value, status, and freshness before decorative charts.
Protect privacy at low counts#
Aggregate publication can still identify customers when:
- only one customer belongs to a segment
- a filtered total changes immediately after a known action
- geography and industry combinations are sparse
- exact usage exposes contract size
Use minimum cohort thresholds, coarser categories, delayed windows, and review. Do not rely on hiding a filter control while the underlying endpoint still accepts the segment.
Launch and operate#
Before launch:
- review publication rights and metric definition
- inspect the exact sanitized collection samples
- test current, empty, delayed, unavailable, and revised states
- verify API keys never appear in browser code, HTML, or logs
- exercise cache expiration and stale-if-error behavior
- test on mobile, keyboard, and screen reader
- simulate GraphJSON and publication-job outages
- confirm an independent incident channel remains available
After launch, monitor publication-job freshness, schema validation failures, endpoint error rate, and unexpected value movement. Review public definitions when the product changes.
Production checklist#
- Every public metric has a definition, owner, cadence, and approver.
- Only sanitized derived facts enter
public_metrics. - Public endpoints are fixed and server-side; no API key reaches the browser.
- Low-count and re-identification risks are reviewed.
- Current, delayed, unavailable, and revised states are distinct.
- Values show their covered interval and data-through time.
- Charts have accessible text or table equivalents.
- Paging and incident publication have an independent failure domain.
- Cache, outage, and stale-data behavior are tested.
- Publication inventory and retirement procedures are current.
Continue with native Data API frontend, accessible localized embedded analytics, and service status and incident communication.