Adding Grafana dashboards directly into your app lets users see monitoring data without switching tabs or tools. Using an iframe to embed Grafana does work, but it brings along some tricky authentication and security issues that aren’t always obvious at first.
In this blog, we’ll go over the practical ways to embed Grafana dashboards from easy public snapshots to secure, private dashboards that need authentication.
Pick the Right Way to Embed Grafana
There are four main ways to embed a Grafana dashboard in your app. Each has its own trade-offs around security, performance, and user experience.
1. Embed Public Dashboards (Fastest to Set Up)
For dashboards that don’t show sensitive data, you can use Grafana’s public dashboard option. This gives you a live, interactive view that’s easy to embed using a simple iframe.
<iframe
src="https://your-grafana-instance.com/public-dashboards/abc123?from=now-6h&to=now"
width="100%"
height="400px"
frameborder="0">
</iframe>
To enable this:
- Go to the dashboard → Share → Public dashboard
- Toggle the switch
- Copy the generated iframe code
Note: Anyone with the link can access the data. So, only use this for public or non-sensitive dashboards.
2. Use Snapshots for Read-Only Embeds
Snapshots create a static version of your dashboard at a specific point in time. They're still interactive (you can hover and explore panels), but they don’t update with new data.
<iframe
src="https://snapshots.raintank.io/dashboard-solo/snapshot/xyz789"
width="100%"
height="400px"
frameborder="0">
</iframe>
Good for:
- Incident reports
- Weekly status updates
- Sharing dashboards without hitting data sources
You can publish snapshots to Grafana’s public snapshot server or keep them internal.
3. Embed Private Dashboards with Authentication (More Secure, More Setup)
If your dashboards are private, users will hit a login screen inside the iframe unless you handle authentication. To even allow this kind of embedding, you need to tweak the Grafana config:
[security]
allow_embedding = true
cookie_samesite = lax
allow_embedding = true
disables theX-Frame-Options: deny
header, which normally blocks Grafana from loading in iframescookie_samesite = lax
helps session cookies work across your app and Grafana, especially if they’re on the same root domain
Without these settings, browsers will block the embed or break session handling.
4. Use the API to Build a Custom Dashboard View
For full control, no iframes, no login prompts, you can pull dashboard data from Grafana’s API and render it inside your own frontend.
Step 1: Get an API token
- Go to Administration → Users and access → Service accounts
- Create a service account with read permissions
- Generate a token
Step 2: Fetch dashboard data
const response = await fetch(`https://your-grafana-instance.com/api/dashboards/uid/${dashboardUid}`, {
headers: {
'Authorization': `Bearer ${serviceAccountToken}`
}
});
const dashboardData = await response.json();
You’ll need to handle the visualization logic yourself (charts, layout, filters, etc.), but this gives you:
- A seamless UX
- Full control over authentication and design
- Flexibility to integrate with other app data
Config Settings You’ll Need for Self-Hosted Grafana Embeds
If you’re hosting Grafana yourself, a few config tweaks are needed to get iframe embedding working smoothly, especially if you're dealing with authentication, cross-origin requests, or public access.
Update Your grafana.ini
File
Start by adjusting these core settings:
[security]
allow_embedding = true
cookie_samesite = lax
[panels]
disable_sanitize_html = true
allow_embedding = true
: Allows Grafana to be loaded inside an iframe by disabling theX-Frame-Options: deny
header.cookie_samesite = lax
: Makes sure session cookies work when Grafana is embedded on a related domain or subdomain.disable_sanitize_html = true
: Only needed if you're embedding third-party HTML inside a Grafana panel (like in the Text panel). You don’t need this if you're just embedding Grafana itself elsewhere.
Enable Anonymous Access (Optional)
If you want people to view dashboards without logging in—say for public status pages or internal tools—you can turn on anonymous access:
[auth.anonymous]
enabled = true
org_name = Main Org
org_role = Viewer
This lets unauthenticated users access dashboards with the permissions of a "Viewer" in the specified org. Combine this with network-level restrictions to avoid exposing sensitive data.
Configure CORS and Security Headers (If Needed)
When your Grafana instance and the app embedding it are on different domains, browsers can block cross-origin requests unless you explicitly allow them. Add the following to handle CORS properly:
[security]
allow_embedding = true
cors_allow_credentials = true
cors_allowed_origins = https://your-app.com
cors_allow_credentials = true
: Ensures cookies are sent with cross-origin requestscors_allowed_origins
: Should point to the origin where your application is hosted
What You Can and Can’t Do with Grafana Cloud
If you're using Grafana Cloud, there’s a key limitation to know:
You can’t enable allow_embedding
so embedding private, authenticated dashboards isn’t supported.
That’s a deliberate design choice for security reasons. The managed service doesn’t let you tweak this config.
Here’s what does work:
- Public dashboards – Same setup as self-hosted. If your data is safe to share publicly, you can embed it with an iframe.
- Dashboard snapshots – Good for sharing point-in-time views without live data.
- API-based embedding – Fetch dashboard data using Grafana’s API and render it yourself inside your app.
In short: with Grafana Cloud, embedding works for public or read-only use cases, but not for authenticated dashboards.
Customize Grafana Embeds with URL Parameters
Grafana lets you modify embedded dashboards and panels using query parameters. This gives you fine-grained control over time ranges, themes, layout, refresh intervals, and variable inputs, directly from the embed URL.
from
and to
: Define Custom Time Ranges
You can set the dashboard's time window using from
and to
parameters in the URL:
<iframe
src="https://your-grafana-instance.com/d-solo/dashboard-id/panel-name?from=now-24h&to=now"
width="100%"
height="400px">
</iframe>
Common formats:
from=now-1h&to=now
— Last hourfrom=now-7d&to=now
— Last 7 daysfrom=1609459200000&to=1609545600000
— Absolute timestamps (in milliseconds)
theme
, kiosk
, refresh
: Control UI and Behavior
Use these parameters to adjust the appearance and behavior of the embedded view:
<iframe
src="https://your-grafana-instance.com/d-solo/dashboard-id/panel-name?theme=dark&kiosk=1&refresh=30s"
width="100%"
height="400px">
</iframe>
theme=dark
ortheme=light
— Switch between dark and light modeskiosk=1
— Hide navigation bars for a cleaner displayrefresh=30s
— Enable auto-refresh at the specified interval
var-*
: Pass Dashboard Variable Values
You can set Grafana template variables via URL to dynamically control what data is shown:
?var-hostname=server1
This is especially useful when embedding the same dashboard for different hosts, environments, or services.
d-solo
and panelId
: Embed Specific Panels
To embed just one panel instead of the full dashboard, use the d-solo
path and include the panelId
parameter:
<iframe
src="https://your-grafana-instance.com/d-solo/dashboard-id/panel-name?panelId=2"
width="100%"
height="300px">
</iframe>
This approach works well for breaking up dashboards and displaying specific metrics in different parts of your UI.
Design Responsive Grafana Embeds
When embedding Grafana dashboards in your app, they need to scale well across different screen sizes, from wide desktop monitors to mobile devices. Here’s how to handle responsive behavior effectively.
Use CSS for Dynamic Sizing of the Iframe
Wrapping your iframe in a container and applying responsive CSS ensures it adapts to various viewports:
<div class="dashboard-container">
<iframe
src="https://your-grafana-instance.com/d-solo/dashboard-id/panel-name"
width="100%"
height="400px"
style="min-height: 300px;">
</iframe>
</div>
<style>
.dashboard-container {
width: 100%;
max-width: 1200px;
margin: 0 auto;
}
@media (max-width: 768px) {
.dashboard-container iframe {
height: 300px;
}
}
</style>
width: 100%
ensures the iframe stretches to fit its containermax-width
keeps it from overflowing on large displays- Media queries adjust height for smaller screens
Adjust Time Ranges and Content for Mobile Devices
Dashboards that look fine on a desktop may become unreadable on mobile. To improve usability:
- Limit the number of panels shown on smaller screens
- Use broader time ranges (e.g., 1h instead of 5m) to reduce visual noise
- Provide a full-screen or expand option for complex views
You can also dynamically adjust iframe parameters based on screen size:
// Detect screen size and adjust time range
const isMobile = window.innerWidth < 768;
const timeRange = isMobile ? 'from=now-1h&to=now' : 'from=now-6h&to=now';
const iframeSrc = `https://your-grafana-instance.com/d-solo/dashboard-id/panel-name?${timeRange}`;
Inject this iframeSrc
into your app’s DOM to serve a mobile-optimized view when needed.
Authentication Strategies for Embedded Grafana Dashboards
If you need to embed private or authenticated Grafana dashboards, a standard iframe won’t be enough. You’ll need to handle user auth in a way that balances security, usability, and integration effort. Here are two patterns that teams commonly use.
Pattern 1: Use a Backend Proxy for Authentication
Set up a proxy server that handles authentication with Grafana. Your application takes care of authenticating the user, and the proxy injects valid credentials when making requests to Grafana.
// Your app authenticates the user
const userToken = await authenticateUser(request);
// Your proxy injects this token when talking to Grafana
const grafanaResponse = await proxyToGrafana(dashboardUrl, userToken);
Why this works well:
- Keeps authentication logic on the server (not exposed to the browser)
- Avoids exposing tokens in iframe URLs
- Gives you more control over session management, headers, and cookie behavior
This is the go-to option for embedding private dashboards in production-grade apps.
Pattern 2: Embed Service Account Tokens in URLs (Use with Caution)
For internal tools or low-sensitivity use cases, you can pass a service account token directly in the iframe URL:
<iframe
src="https://your-grafana-instance.com/d-solo/dashboard-id?auth_token=your_service_token"
width="100%"
height="400px">
</iframe>
Important caveats:
- The token is visible in browser dev tools and network traffic
- Anyone with access to the token URL can reuse it
- Best reserved for internal apps with strong network-level access controls
While easy to set up, this method trades off security for speed, so use it only when you understand and can accept the risks.
Common Embedding Issues and How to Fix Them
Embedding Grafana dashboards in your app can run into browser quirks, security headers, and network configurations. Here’s a breakdown of the most frequent problems and how to solve them.
1. SameSite
Cookie Restrictions Break Authentication
Modern browsers limit third-party cookies by default. If your app and Grafana are on different domains, the browser might block Grafana's session cookies, causing login issues inside iframes.
Fix:
Set the following in grafana.ini
:
[security]
cookie_samesite = lax
For best results, serve your app and Grafana from the same domain or subdomain to avoid cross-site cookie issues entirely.
2. X-Frame-Options
Blocking the Embed
Even if you set allow_embedding = true
, some reverse proxies (like NGINX or cloud security layers) may override it by adding X-Frame-Options: DENY
to HTTP responses.
Fix:
Check your proxy, CDN, or web server configs for extra security headers that might block iframe rendering. Remove or override any conflicting X-Frame-Options
.
3. Service Account Token Expiry
If you're using tokens to authenticate embeds (e.g., in iframe URLs or API calls), they may expire and cause dashboards to fail silently or throw auth errors.
Fix:
- Use long-lived tokens for internal environments
- Or build token refresh logic into your backend
4. Embedded Dashboards Slow Down Page Load
Grafana dashboards can be heavy multiple panels, queries, and data loads that can slow down your app if loaded all at once.
Fix:
- Lazy-load iframes only when they're visible (e.g., using IntersectionObserver)
- Show placeholders or skeleton UIs until the user interacts with the section
5. CORS Errors on Cross-Domain Embeds
If your app and Grafana run on different origins, CORS restrictions may block resource loading or API calls.
Fix:
In grafana.ini
, set:
[security]
allow_embedding = true
cors_allow_credentials = true
cors_allowed_origins = https://your-app.com
Make sure the origin matches exactly, including protocol (https://
).
6. Inconsistent Behavior Across Browsers
Some browsers handle iframes more strictly, especially when it comes to cookies, JavaScript execution, and Content Security Policy (CSP).
Fix:
- Test embeds in Chrome, Firefox, Safari, and Edge
- Pay attention to:
- Third-party cookie behavior
- How each browser enforces CSP headers
- JS functionality inside the iframe
Performance Optimization for Embedded Grafana Dashboards
Embedding dashboards can increase page load times and memory usage, especially with multiple panels or real-time data updates. These optimization strategies can help keep performance under control.
Lazy Load Dashboards Using IntersectionObserver
Avoid loading dashboards until they're visible in the viewport. This reduces initial load time and avoids unnecessary network or rendering work.
// Lazy load dashboards only when they enter view
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const iframe = entry.target;
iframe.src = iframe.dataset.src;
observer.unobserve(iframe);
}
});
});
document.querySelectorAll('iframe[data-src]').forEach(iframe => {
observer.observe(iframe);
});
This is especially effective when multiple dashboards are embedded on a single page or behind tabs/expandable sections.
Implement Client-Side Caching for Repeated Loads
If you’re fetching dashboard data manually (via API), use a local in-memory cache to avoid repeated network calls:
// Simple cache for dashboard data
const dashboardCache = new Map();
async function loadDashboard(dashboardId) {
if (dashboardCache.has(dashboardId)) {
return dashboardCache.get(dashboardId);
}
const response = await fetch(`/api/dashboards/${dashboardId}`);
const data = await response.json();
// Cache for 5 minutes
dashboardCache.set(dashboardId, data);
setTimeout(() => dashboardCache.delete(dashboardId), 300000);
return data;
}
This reduces load on your backend and speeds up repeat visits to the same dashboards.
Monitor Dashboard Load and Resource Usage
Use the browser's PerformanceObserver API to measure load times and track how much time embedded dashboards take to render:
const observer = new PerformanceObserver((list) => {
list.getEntries().forEach((entry) => {
if (entry.name.includes('grafana')) {
console.log(`Dashboard load time: ${entry.duration}ms`);
}
});
});
observer.observe({ entryTypes: ['navigation', 'resource'] });
This helps identify heavy dashboards and optimize the layout or data queries accordingly.
Security Best Practices for Embedded Dashboards
Embedding monitoring dashboards into your app surface exposes internal data, latency, error rates, and service health to a broader audience. Here’s how to do it without opening up unnecessary risk.
Enforce HTTPS for All Resources
Always serve both your application and Grafana over HTTPS.
Mixed content (HTTPS page embedding HTTP iframe) will:
- Trigger browser security warnings
- Block dashboard loads entirely in modern browsers
Secure transport also protects tokens, cookies, and session data in transit.
Apply Fine-Grained Access Controls
Embedding doesn't bypass authentication requirements. Users still need to be authorized to view the data.
Recommendations:
- Restrict access to private dashboards using role-based access in Grafana
- Apply user-level permissions in your own application layer before rendering embeds
- Avoid hardcoding public links unless the data is truly non-sensitive
Rotate Service Account Tokens Regularly
If you use tokens for authentication (e.g., via API or iframe URLs), treat them as secrets:
- Rotate tokens on a regular schedule
- Store them securely (avoid exposing them in frontend code or logs)
- Revoke tokens if no longer needed
Even internal-use tokens can leak through browser tools or developer environments.
Monitor Access Patterns and API Usage
Embedded dashboards can be scraped or accessed programmatically. Use monitoring to detect:
- Unusually high request volumes
- Requests from unexpected IPs or geographies
- API token usage patterns that don’t match user behavior
Grafana logs and reverse proxy logs can help here. You can also set rate limits or IP-based restrictions on your endpoints.
Conclusion
Embedding Grafana dashboards isn’t just a UI decision; it’s part of your observability surface. You’re exposing telemetry data to end users, which makes performance, access control, and reliability critical.
At Last9, we help teams go beyond basic embeds. Our managed observability platform handles high-cardinality metrics at scale, integrates seamlessly with Prometheus and OpenTelemetry, and connects dashboards to logs and traces, so you can debug slow loads, auth errors, and data issues without guesswork.
If you’re embedding dashboards, monitor them like any other production component. With Last9, you get full visibility without the cost spikes.
Get started with us today!
FAQs
How to embed the next app to WP blog?
While this guide focuses on Grafana embedding, the same iframe principles apply to Next.js apps. Export your Next.js app as static files, host them, and embed using an iframe. For WordPress, use the HTML block to add iframe code, ensuring your hosting allows iframe embedding.
How do I handle authentication for embedded Grafana dashboards? Authentication is the trickiest part of Grafana embedding. You have several options: enable anonymous access for public dashboards, use service account tokens in URLs for internal apps, implement proxy authentication to handle auth server-side, or use API-based approaches with custom authentication flows.
Can I use an iframe?
Yes, iframe is the primary method for embedding Grafana dashboards. You need allow_embedding = true
in your Grafana configuration to remove X-Frame-Options restrictions. However, iframes aren't supported in Grafana Cloud for private dashboards due to security policies.
What are the performance implications of embedding Grafana dashboards? Embedded dashboards can slow page load times, especially with complex queries or many panels. Optimize by using lazy loading, limiting data points, setting appropriate refresh intervals, and considering placeholder content until users interact with the dashboard.
Is it public with no authentication? It depends on your setup. Public dashboards and snapshots are accessible without authentication. Private dashboards require authentication, which creates login prompts in iframes. Anonymous access can be enabled for internal applications, but this removes access controls.
Why Integrate Grafana into Your Web Application? Integrating Grafana dashboards into your application provides users with monitoring data without context switching. It creates a unified experience where operational metrics sit alongside application features, improving workflow efficiency and data accessibility for your team.
How can I embed a Grafana dashboard in my website using an iframe? First, ensure allow_embedding = true
that in your Grafana configuration. Then use standard iframe HTML with your dashboard URL. For public dashboards, use the Share → Public dashboard option. For private dashboards, you'll need to handle authentication separately.
How do I embed a Grafana dashboard in an iframe on my website? Navigate to your Grafana dashboard, click Share → Embed, and copy the provided iframe code. Make sure your Grafana instance has allow_embedding = true
configured. Paste the iframe code into your website's HTML where you want the dashboard to appear.
How can I customize the appearance of a Grafana dashboard embedded via iframe? Use URL parameters to customize embedded dashboards: theme=dark
for dark mode, kiosk=1
to hide navigation, from=now-1h&to=now
for time ranges, and var-hostname=server1
for dashboard variables. For deeper customization, consider API-based approaches with custom rendering.