
By Yamuno Team
16 Jun 2026
6 min read
Confluence is where a lot of teams track project status, document KPIs, and share team metrics. But when it comes to actually visualizing that data, the options are limited. You can paste a static image (which goes stale), use a third-party integration (which requires a separate subscription), or accept that your Confluence page just won't have charts.
HTML Macro for Confluence opens a fourth option: embed Chart.js directly inside a Confluence page. The charts are interactive (hover for tooltips, click to toggle series), render instantly, and live alongside your documentation — no external service required.
This guide covers four chart types with copy-paste code for each.
Install HTML Macro for Confluence from the Atlassian Marketplace. It's free and runs on Atlassian Forge.
Security note: The examples below load Chart.js from a CDN (cdn.jsdelivr.net). Your Confluence admin needs to add that domain to the HTML Macro whitelist. Go to Confluence Settings → HTML Macro → Security Settings and add https://cdn.jsdelivr.net to the allowed domains.
Once that's done, add the macro to any Confluence page (Insert → Macro → HTML Macro), paste the code, and use the live preview to verify it before saving.
Good for: comparing values across categories — issues by status, story points per sprint, bugs per team member.
<canvas id="barChart" width="700" height="350"></canvas>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
new Chart(document.getElementById('barChart'), {
type: 'bar',
data: {
labels: ['To Do', 'In Progress', 'In Review', 'Done'],
datasets: [{
label: 'Issues',
data: [12, 8, 5, 34],
backgroundColor: ['#94a3b8', '#3b82f6', '#f59e0b', '#22c55e'],
borderRadius: 6,
}]
},
options: {
responsive: false,
plugins: {
legend: { display: false },
title: {
display: true,
text: 'Issue Status — Sprint 42',
font: { size: 15, weight: '600' },
color: '#1e293b',
}
},
scales: {
y: {
beginAtZero: true,
grid: { color: '#f1f5f9' },
ticks: { color: '#64748b' }
},
x: {
grid: { display: false },
ticks: { color: '#64748b' }
}
}
}
});
</script>
Replace the labels and data arrays with your actual values. Each label maps to the corresponding data value by index.
Good for: showing trends over time — sprint velocity, bug discovery rate, weekly active users, anything with a time axis.
<canvas id="lineChart" width="700" height="350"></canvas>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
new Chart(document.getElementById('lineChart'), {
type: 'line',
data: {
labels: ['Sprint 37', 'Sprint 38', 'Sprint 39', 'Sprint 40', 'Sprint 41', 'Sprint 42'],
datasets: [{
label: 'Story Points Completed',
data: [41, 38, 45, 52, 49, 55],
borderColor: '#6366f1',
backgroundColor: 'rgba(99, 102, 241, 0.08)',
borderWidth: 2,
pointBackgroundColor: '#6366f1',
pointRadius: 4,
fill: true,
tension: 0.3,
}]
},
options: {
responsive: false,
plugins: {
legend: { position: 'top' },
title: {
display: true,
text: 'Sprint Velocity — Last 6 Sprints',
font: { size: 15, weight: '600' },
color: '#1e293b',
}
},
scales: {
y: {
beginAtZero: false,
grid: { color: '#f1f5f9' },
ticks: { color: '#64748b' }
},
x: {
grid: { display: false },
ticks: { color: '#64748b' }
}
}
}
});
</script>
Good for: showing how a total is distributed — issue types, budget allocation, time spent by category. Keep it to five segments or fewer.
<canvas id="pieChart" width="500" height="400"></canvas>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
new Chart(document.getElementById('pieChart'), {
type: 'pie',
data: {
labels: ['Bug', 'Feature', 'Task', 'Tech Debt', 'Spike'],
datasets: [{
data: [18, 32, 25, 14, 11],
backgroundColor: [
'#ef4444',
'#6366f1',
'#3b82f6',
'#f59e0b',
'#94a3b8',
],
borderWidth: 2,
borderColor: '#fff',
}]
},
options: {
responsive: false,
plugins: {
legend: { position: 'right' },
title: {
display: true,
text: 'Open Issues by Type',
font: { size: 15, weight: '600' },
color: '#1e293b',
}
}
}
});
</script>
Good for: showing a single metric against a target — sprint completion, budget consumed, onboarding progress.
<div style="position:relative; width:400px; margin:0 auto;">
<canvas id="doughnutChart" width="400" height="400"></canvas>
<div style="position:absolute; top:50%; left:50%; transform:translate(-50%,-50%); text-align:center; font-family:sans-serif;">
<div style="font-size:36px; font-weight:700; color:#1e293b;">72%</div>
<div style="font-size:13px; color:#64748b;">Complete</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
new Chart(document.getElementById('doughnutChart'), {
type: 'doughnut',
data: {
datasets: [{
data: [72, 28],
backgroundColor: ['#6366f1', '#f1f5f9'],
borderWidth: 0,
cutout: '78%',
}]
},
options: {
responsive: false,
plugins: {
legend: { display: false },
tooltip: { enabled: false },
}
}
});
</script>
Change the 72 and 28 values (they must add to 100) and update the center label to match.
When you want to compare two data series on the same time axis:
<canvas id="multiLineChart" width="700" height="350"></canvas>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
new Chart(document.getElementById('multiLineChart'), {
type: 'line',
data: {
labels: ['Week 1', 'Week 2', 'Week 3', 'Week 4', 'Week 5', 'Week 6'],
datasets: [
{
label: 'Team Alpha',
data: [12, 19, 15, 22, 18, 25],
borderColor: '#6366f1',
backgroundColor: 'transparent',
borderWidth: 2,
pointRadius: 4,
tension: 0.3,
},
{
label: 'Team Beta',
data: [8, 14, 20, 17, 24, 21],
borderColor: '#22c55e',
backgroundColor: 'transparent',
borderWidth: 2,
pointRadius: 4,
tension: 0.3,
}
]
},
options: {
responsive: false,
plugins: {
legend: { position: 'top' },
title: {
display: true,
text: 'Weekly Issues Resolved',
font: { size: 15, weight: '600' },
color: '#1e293b',
}
},
scales: {
y: { beginAtZero: true, grid: { color: '#f1f5f9' } },
x: { grid: { display: false } }
}
}
});
</script>
Hard-code the data for now. Chart.js in Confluence can't call your Jira API or database — the data lives in the HTML. If your metrics change weekly, treat the Confluence page as a weekly snapshot: update the arrays when you update the commentary.
Use width and height attributes directly on <canvas>. Don't rely on responsive: true inside Confluence — the macro iframe has fixed dimensions and responsive sizing can behave unexpectedly.
Each chart needs a unique id. If you put multiple charts on the same page, give each canvas a different id (barChart, lineChart, etc.) to avoid conflicts.
Test in the live preview before saving. HTML Macro's side-by-side preview shows you exactly what Confluence page visitors will see — including CDN scripts loading.
Your admin needs to whitelist https://cdn.jsdelivr.net in HTML Macro's security settings for the Chart.js scripts to load. If the charts appear blank, that's the most common cause. Check the browser console for a CSP error and confirm the domain is in the allowlist.
For environments where all external CDNs are blocked, you can paste the minified Chart.js source directly into the <script> tag — it's about 60KB minified, which is manageable.
Install HTML Macro for Confluence — free on the Atlassian Marketplace
Featured App
Embed custom HTML, CSS, and JavaScript directly inside Confluence pages
Get product updates and tips straight to your inbox.
No spam, ever.
Keeping docs in GitHub and Confluence in sync is a constant maintenance problem. Here's how to automate it so changes in your repo show up in Confluence without anyone touching a button.
Read moreStop reformatting every PDF export by hand. Here's how to set up reusable PDF templates in Confluence so every export from your team looks the same — with cover pages, branded headers, watermarks, and the right page layout.
Read moreNot all Jira dashboard gadgets are worth the screen space. Here are the widgets and charts that actually help engineering and product teams track what matters.
Read more