Looking for the overview? See the widget feature page. This article assumes you've already decided to use it and want to know how it actually works.
What you need
- A Pingoru account (free or Pro — the widget is identical between tiers).
- At least one monitor for a service you want shown. /app/monitors is where you add them.
- A page on your site where you can paste two lines of HTML.
Creating your widget
Open /app/settings/widget in your
dashboard. The editor is a two-column page: configuration on the
left, live preview on the right. The first save creates the widget
and assigns it a public id starting with w_; subsequent
saves update the existing one.
Each account can have one widget in v1. If you need different widgets for different sites, the workaround is to swap layouts or component filters via the editor — both changes go live within the 60-second cache window.
Picking which services appear
The Services in this widget section lists every monitor on your account. Tick the ones you want shown. For each ticked monitor, click Filter components to narrow further — useful for big providers where you only depend on a subset (e.g. monitor AWS as a whole, but only show EC2 and S3 in the widget).
Two filter rules to know:
- Empty filter = use the monitor's existing component filter unchanged. If your AWS monitor is configured to watch EC2/S3/RDS, the widget shows status across all three.
- Non-empty filter = override. The widget will only consider the components you tick here, even if the underlying monitor watches more.
Layouts
Three layouts:
- Full — vertical list, one row per service, with logo, name, status pill, and an optional incident-count subline. Best for status pages where the widget is the main content.
- Compact — horizontal row of service logos with coloured status dots, plus an "all systems operational" banner. Best for support docs or footers where space is at a premium.
- Minimal — a single status pill ("All systems operational" / "Partial outage in progress" / etc.) and a Pingoru backlink. Best for the bottom of a dashboard, beside a footer copyright, anywhere you want a tiny indicator that doesn't take up real estate.
Themes
Three themes:
- Auto — follows the embedding page's
color-schemeviaprefers-color-scheme. A dark customer site gets a dark widget without configuration. - Light — explicitly light, regardless of the host page.
- Dark — explicitly dark, regardless of the host page.
All colours come from CSS variables scoped to .pgw. Customer
page styles cannot leak into the widget and the widget's styles
cannot leak out — every selector is namespaced to that prefix.
Embed formats
After saving the widget, the editor shows three embed-snippet tabs. Pick based on your site's constraints:
Script (recommended)
<div data-pingoru-widget="w_yourid"></div>
<script async src="https://pingoru.io/widget.js"></script> The script finds every data-pingoru-widget attribute on
the page, fetches the widget data for each id, and renders DOM
directly into your page. The recommended option because the rendered
content lives in your page's DOM, which search engines can index and
count as a real backlink.
Script size is ~17KB raw, ~5.6KB gzipped. Loads async and renders after first paint — never blocks the parent page's hydration or layout.
iFrame
<iframe src="https://pingoru.io/widget/w_yourid"
width="100%" height="100%" frameborder="0"
loading="lazy" title="Service status"></iframe> Use when your page has a strict Content-Security-Policy that blocks third-party scripts. The widget renders in an isolated frame; styles and JavaScript cannot interact with the parent page. Trade-off: search engines see the iframe as opaque content, so the backlink inside doesn't count toward your domain's SEO.
Bare URL
https://pingoru.io/widget/w_yourid The widget's standalone URL. Drop it into a chat message, email
signature, or anywhere a clickable URL works. The page sets noindex,nofollow so it won't compete with your real
status surfaces in search results.
Content-Security-Policy
If your site has a CSP, the script embed needs these directives:
script-src 'self' https://pingoru.io;
img-src 'self' data: https:;
style-src 'self' 'unsafe-inline';
connect-src 'self' https://pingoru.io; Notes:
'unsafe-inline'instyle-srcis needed because the widget injects a<style>element once on first render. A nonce-based approach is on the roadmap.img-src https:lets the widget load service logos frompingoru.io/logos/*.connect-src https://pingoru.iocovers the widget's fetch to/api/widget/render/<id>and its ping to/api/widget/render/<id>/ping.- If you can't add any of these, use the iFrame embed instead — it runs in its own origin and doesn't share any CSP context with the parent page.
Caching and freshness
The widget data is cached at the edge for 60 seconds with a 5-minute stale-while-revalidate window. Typical end-to-end staleness from an upstream vendor posting an incident to the widget reflecting it: 90 seconds in the median, under 3 minutes p99.
The script bundle (widget.js) is cached longer — 5
minutes in the browser, 1 hour at the edge — because it changes only
on Pingoru deploys, at which point we purge the cache automatically.
Impression tracking
Each time the widget renders on a customer page, the script fires a
one-off ping to /api/widget/render/<id>/ping. This
uses navigator.sendBeacon when available — it doesn't
block the page, doesn't count against the page's fetch budget, and
survives page unload.
The ping is de-duplicated per page-load, so multiple re-renders of the same widget on one page count as one impression. The editor surfaces total impressions, last-render timestamp, and the top referring hosts (host only, never full URLs).
The "Status data by Pingoru" footer
Every widget renders a "Status data by Pingoru →" link at the bottom in Full and Compact layouts, and a "Pingoru →" link in Minimal. This link is non-removable on Free and Pro tiers. It carries a UTM tag so we can attribute referral traffic.
Whitelabel removal will land in a future Business tier; reach out at /contact if you have an immediate need.
Why the script embed is better than the iframe
The script embed injects rendered DOM directly into your page. Search engines reading your page see:
- Real
<a href="https://pingoru.io">backlinks that count toward our PageRank. - Visible text ("AWS · Up", "Stripe · Up", "All systems operational") that strengthens the topical relevance of your page.
An iframe is opaque to the crawler — Google sees the <iframe> tag but does not index its contents as
part of your page. That isolation is the right answer when you
need strict CSP separation; otherwise the script embed is the
better default.
Multiple widgets on one page
The script is idempotent — including the <script src="/widget.js"> tag multiple times on
the same page does no extra work after the first load. Likewise,
multiple <div data-pingoru-widget="..."> elements
on the same page all render independently from the same script load.
Per v1 constraint, each account still has one widget; this section is about embedding it in multiple places on the same page, not about running multiple distinct widgets.
SPA hosts (re-render after route change)
If you embed the widget on a single-page-app where pages mount and unmount client-side without a full page load, the script needs a nudge to re-scan after route transitions. After your router commits a new route, call:
window.PingoruWidget.refresh(); The refresh helper re-scans the DOM for data-pingoru-widget placeholders and renders any new
ones without re-fetching widgets that were already rendered on the
previous route.
Deleting your widget
Click Delete widget in the editor footer (button appears only when a widget exists). The delete is two-stage — one click arms the action, a second click within 4 seconds completes it. Deleting a widget invalidates its public id permanently; any embeds using that id will show an empty slot.
Troubleshooting
The widget slot stays empty on my page.
Open devtools → console and look for a [pingoru-widget] warning. Common causes: (1) the script tag failed to load — check
your CSP's script-src; (2) the widget id was deleted
in the dashboard; (3) your page is served from file:// or another non-http(s) origin which the script doesn't try to
talk to.
The widget renders but shows no services.
Open the editor and verify at least one monitor is ticked. An empty monitor list means the widget will render with the banner only and no list rows.
The widget shows yesterday's status, not today's.
Edge cache is 60 seconds. Force-refresh the embedding page with
cache disabled to bypass the browser cache; the edge cache will
re-validate on the first user request after the 60s window
elapses. If you see consistently stale data, check that your
monitor is healthy — it may be in auto-paused state
if Pingoru couldn't reach the upstream status page recently.
My CSP blocks the script. Can I still use the widget?
Switch to the iFrame embed. It runs in its own origin so it doesn't share any CSP context with your parent page. The trade-off: search engines can't see the rendered contents inside the iframe.
The Pingoru logo / footer link is messing with my page layout.
The widget is contained in a max-width box (480px for Full, 360px
for Compact, 320px for Minimal) and centered inside its parent
via flexbox. If your parent container has its own display: rules, wrap the <div data-pingoru-widget> in an explicit
flex/grid item that can size itself.
API endpoints
The widget surface exposes three public endpoints:
GET /widget/<public_id>— the standalone widget page (used by the iFrame and bare-URL embeds). Renders the same component the script bundle does. Markednoindex.GET /api/widget/render/<public_id>— the JSON data the script bundle fetches. Returns the widget's config plus the current status of each included monitor. CORS-permissive, 60s edge cache.POST /api/widget/render/<public_id>/ping— fire-and-forget impression tracker. Uncached. Returns 204.
These endpoints are stable and intended to be called from any
client. There's no rate limit on the render endpoint (it's heavily
cached); the ping endpoint accepts unlimited bursts because sendBeacon has no retry behaviour and we'd rather
drop excess hits than reject them.
Didn't find what you needed? Let us know — we'll add it to the guides.