Guide
Embed a web analytics dashboard in your app
Put an app's visits, visitors, pageviews and top pages inside another application — here, the analytics page of a fictional internal team portal. You track the portal with lytics, make an embed for it, and paste one iframe. Then you decide, with the access model in front of you, who should hold the link.
The plan
The fictional company runs Example team portal at https://portal.example.com, a site its staff sign in to. It wants a page there, /analytics, that shows how the portal itself is used. Four steps:
- An app in lytics for the portal, with its domain.
- The app's snippet in the portal, until lytics confirms a real pageview.
- An embed: a label, the exact origin that will show it, and a range.
- The iframe on
/analytics, sized for phones as well as laptops.
The dashboard can be any app's — a product's numbers shown in an internal tool works the same way. Swap in your own names as you go.
1. Create an account and an app
Create an account, or sign in. On Your apps, under Add an app:
| Field | What to enter |
|---|---|
| Name | Example team portal. The embedded dashboard shows it as its heading, so pick a name you are happy for its viewers to read. |
| Domains | portal.example.com — every hostname the tracked site is served from. Events from other hosts are dropped. |
Press Add. lytics opens the app's page with Set up this app at the top.
2. Install the snippet and receive a pageview
Copy the snippet from the app's setup into the <head> every page of the portal shares. It looks like this, with your app's key where this one says lyt_REPLACE_WITH_YOUR_SITE_KEY — so copy it from lytics, not from here:
<script>window.lytics=window.lytics||function(){(lytics.q=lytics.q||[]).push(arguments)};</script>
<script defer src="https://lytics.lp-data.cloud/api/t/lyt_REPLACE_WITH_YOUR_SITE_KEY.js"></script>Deploy, open the portal in another tab, and press Check for pageview. Carry on once it confirms one. The installation reference covers the details — domains, single-page apps, signed-in users — and Add analytics to an Astro site covers Astro.
An embed can be made before any data arrives, but it would show zeros: a real pageview first means you see the dashboard working in the next step.
3. Make the embed
On the app's page, go to Embeds and fill in the form:
| Field | What to enter |
|---|---|
| Label | Team portal — analytics page. Only you see it, in the list of embeds. Name where the link lives, so you know which one to revoke. |
| Shown on | https://portal.example.com — the exact origin of the page that will show it: https://, the host and any port, no path. Up to ten, comma-separated. |
| Range | 30 days. 7, 30 or 90 days, fixed for this embed. Viewers cannot change it. |
Press Make an embed. lytics shows the iframe for it, once:
<iframe src="https://lytics.lp-data.cloud/api/embed/REPLACE_WITH_YOUR_EMBED_TOKEN" title="Team portal — analytics page" style="width:100%;height:900px;border:0" loading="lazy"></iframe>
Use Open it next to the iframe to check the dashboard on its own. The embed then appears in the list with its range, its origins and when it was last viewed — updated at most once an hour.
4. Add the iframe to the portal
The iframe lytics gives you works as it is. For the portal, give it a title that tells screen-reader users what the frame contains, and a height that suits phones:
<!-- /analytics in the team portal. Paste your own link from lytics in
place of the src below — and keep that link out of public repositories. -->
<iframe
class="lytics-embed"
src="https://lytics.lp-data.cloud/api/embed/REPLACE_WITH_YOUR_EMBED_TOKEN"
title="Team portal usage, last 30 days (lytics)"
loading="lazy"
></iframe>.lytics-embed {
display: block;
width: 100%;
height: 1300px;
border: 0;
}
/* On a phone the tiles and tables stack, so the page gets taller. */
@media (max-width: 640px) {
.lytics-embed { height: 1800px; }
}Replace the whole src with the link from step 3. REPLACE_WITH_YOUR_EMBED_TOKEN is not a token: left in, the frame says the embed does not exist.
The embedded page has no script, so it cannot tell the portal how tall it is: the frame's height is yours to set, and when the page inside is taller, the frame scrolls. The page reflows to the frame's width — tiles wrap and the tables stack on a phone — which makes it taller. Measured with every list full and no database panels:
| Frame | Page height |
|---|---|
| 1280 px wide (a laptop) | about 1,280 px |
| 390 px wide (a phone) | about 1,780 px |
The CSS above leaves a little room over those. Add more for each pinned panel. If the portal sends its own Content Security Policy, its frame-src has to allow https://lytics.lp-data.cloud.
What the embedded dashboard shows
| Part | What it shows |
|---|---|
| Heading | The app’s name, the range (“Last 30 days, from …”, in UTC) and how many visitors were on it in the last five minutes. |
| Tiles | Visits, Visitors, Pageviews, Signed-in users, Logins and Other events — totals for the range. |
| Visits per day | A bar chart, one bar per UTC day. |
| Top pages | The ten most viewed paths, as the tracked app sent them. |
| Referrers | The ten hostnames that sent the most visits. |
| Events | The fifteen most frequent event names other than pageviews, logins included. |
| From the database | Only when panels are pinned to the app: each panel’s stored result and when it was last updated. |
The numbers are counted when the page is requested, for the embed's fixed range ending today (UTC), and mean what they mean on your own dashboard — see the numbers. A browser may reuse a copy for up to 60 seconds. The page runs no script and sets no cookies.
Who can read the link
Before the link goes anywhere, be clear about what protects it:
| Who can read the dashboard? | Anyone who has the link. The URL is the credential — a bearer secret — and there is no sign-in behind it. Opened directly in a browser tab, or fetched by any program, it serves the dashboard. |
|---|---|
| What do the allowed origins do? | They tell browsers which pages may show it in a frame: lytics sends them as the page's
frame-ancestors. A page on any other origin gets a blocked frame. That is all they do. They do not
check who is looking, and they do not apply when the link is opened on its own. |
| Does the portal’s sign-in protect it? | It protects the portal page, not the link. Everyone who can open that page can see the iframe's
src in the page source and copy it, and the copy keeps working outside the portal until you revoke
it. |
| Can one link show different data to different people? | No. Every holder of a link sees the same thing: the whole app's aggregate numbers and every panel pinned to it. There is no per-viewer or per-customer filtering, so an embed is not a way to keep one tenant's data from another. |
| Is it indexed by search engines? | It asks not to be (noindex), but that only holds while nobody publishes the link. Treat a
link in a public page, repository, ticket or screenshot as leaked, and replace it. |
So: only put on an embed what you are content for everyone who could ever receive the link to read — everyone who can open the portal page, and anyone they pass it to. Never publish a real embed link, and never put one in example code, a public repository or documentation; use a placeholder, as this page does.
Review what the link will show
Open the link on its own, read it as an outsider would, and check:
- Paths. Top pages shows paths exactly as the tracked app has them. A path like
/customers/4312or/reports/q3-layoffstells everyone holding the link what it names. Query strings are never stored, but anything in the path is. - Event names. Every event name your app or your tags send is listed. Names should describe what happened, not who did it.
- Referrers. The hostnames that sent visitors — usually harmless, but an internal tool's hostname can be one you would rather not share.
- Pinned panels — all of them. Every panel pinned to the app appears in every one of its embeds, including panels pinned after the link was made. A table panel shows up to 25 rows of whatever its query returns, and a database table can hold names, emails or other personal information.
- Individual user ids are left out. The embed shows how many signed-in users and logins there were, never which ones — the per-user list on your own dashboard is not part of it.
Revoke and replace a link
On the app's page, under Embeds, press Revoke next to its label. From then on lytics answers the link with “This embed does not exist, or it was revoked.” — in the portal the frame shows an error. A browser that loaded it in the last 60 seconds may keep showing its copy until that runs out; after that, nothing.
To replace a link — when it may have leaked, when someone who had it should no longer, or to change its origins or range:
- Make a new embed with the settings you want, and copy its iframe.
- Put the new link in the portal and deploy.
- Revoke the old embed.
If the old link leaked, revoke it first and accept a moment of broken frame. Embeds cannot be edited — their origins and range are fixed — and an app can have up to 20.
Optional: database panels
Not needed for any of the above. If you connect a Postgres database on Databases and pin a panel to the app, it appears under From the database on the app's dashboard and in every one of its embeds. An embed never runs a query: it shows each panel's stored result with the time it was last updated, in UTC. Panels refresh on their own interval, 60 minutes unless you chose another. Review them as you would the rest, before you pin them. Build a Postgres signup dashboard walks through making them.
Troubleshooting
| Symptom | What to check |
|---|---|
| The frame is blank or says the page refused to connect. | The page showing it is not on one of the embed's origins. An origin is the scheme, the host and the port,
compared exactly: https://portal.example.com does not cover https://www.portal.example.com
or https://portal.example.com:8443. The browser's console names the frame-ancestors it
was refused by. Origins cannot be edited — make an embed with the right ones and revoke the old one. |
| “This embed does not exist, or it was revoked.” | The link is incomplete, still has REPLACE_WITH_YOUR_EMBED_TOKEN in it, or was revoked. A lost link cannot
be shown again; make a new embed. |
| The portal’s console reports its own Content Security Policy blocking the frame. | The portal sends a CSP that does not allow frames from lytics. Add https://lytics.lp-data.cloud to
its frame-src (or child-src) directive. |
| The dashboard shows zeros. | No pageviews in the embed's range. Open the app's page in lytics: if it still shows Set up this app, the snippet is not sending yet — finish that first. |
| The dashboard has a scroll bar of its own. | The iframe is shorter than the page inside it, which grows with longer lists, narrower screens and pinned panels. The embedded page has no script, so it cannot resize the frame; raise the heights in the CSS above. |
| “Too many requests.” | Each address may load embeds 60 times a minute. An office whose people all reach the internet from one address shares that, so a page that reloads the frame on a timer can run into it. |
Next
- Create your dashboard, then an embed for it.
- Not ready yet? Explore a sample dashboard with illustrative data.
- The embeds reference.