Skip to main content
← Guides

Charts in Slack

Two patterns for sending charts to Slack — file upload (recommended) and Block Kit image blocks. Both work in bots and automated workflows.

How it works

Slack supports two ways to display images in channels:

  1. File upload — POST the PNG bytes to Slack's Files API. Slack hosts the image internally. No public URL needed. Best for most bots.
  2. Block Kit image block — Pass a public URL. Slack fetches and displays the image. Simpler to implement if the image is publicly accessible.

Use padding: "slack" on your spec for a padding preset tuned for Slack's dark sidebar environment.

File upload (recommended)

Render the chart to a buffer, then upload it with the Slack SDK. No public URL, no CDN configuration required.

# Step 1: render chart to file curl -s -X POST https://chart-output.com/api/v1/render \ -H "Authorization: Bearer pk_live_YOUR_KEY" \ -H "Content-Type: application/json" \ --output chart.png \ -d '{ "type": "bar", "width": 800, "height": 400, "format": "png", "padding": "slack", "brandKitId": "polar", "data": { "labels": ["Mon", "Tue", "Wed", "Thu", "Fri"], "datasets": [{ "label": "Signups", "data": [34, 56, 42, 71, 88] }] } }' # Step 2: upload to Slack curl -X POST https://slack.com/api/files.getUploadURLExternal \ -H "Authorization: Bearer xoxb-YOUR-SLACK-TOKEN" \ -F "filename=chart.png" \ -F "length=$(wc -c < chart.png)"

Block Kit image block

Use returnUrl: true to get a CDN URL, then pass it to a Slack image block. The URL is publicly accessible and cached — Slack will fetch it once and display it inline.

# Render and get CDN URL URL=$(curl -s -X POST https://chart-output.com/api/v1/render \ -H "Authorization: Bearer pk_live_YOUR_KEY" \ -H "Content-Type: application/json" \ -d '{"type":"line","width":800,"height":400,"format":"png","returnUrl":true,"data":{"labels":["Jan","Feb","Mar"],"datasets":[{"data":[14200,17800,22900]}]}}' | python3 -c "import sys,json;print(json.load(sys.stdin)['url'])") # Post to Slack curl -X POST https://slack.com/api/chat.postMessage \ -H "Authorization: Bearer xoxb-YOUR-SLACK-TOKEN" \ -H "Content-Type: application/json" \ -d "{"channel":"C0123","blocks":[{"type":"image","image_url":"$URL","alt_text":"Chart"}]}"

Full card in Slack

A card composition with header, KPI strip, and the polar brand kit looks sharp in Slack's dark sidebar:

json
{ "type": "bar", "brandKitId": "polar", "width": 800, "height": 420, "format": "png", "padding": "slack", "header": { "eyebrow": "WEEKLY REPORT", "title": "Signups", "badge": { "text": "+14%", "backgroundColor": "rgba(96,165,250,0.15)", "color": "#60a5fa" } }, "kpiStrip": [ { "label": "Total", "value": "291", "color": "#f0f6fc" }, { "label": "Avg/day", "value": "41.6", "color": "#f0f6fc" }, { "label": "Peak", "value": "88 (Fri)", "color": "#60a5fa" } ], "data": { "labels": ["Mon", "Tue", "Wed", "Thu", "Fri"], "datasets": [{ "label": "Signups", "data": [34, 56, 42, 71, 88], "borderRadius": 4 }] } }

Incoming webhook

If you use Slack's incoming webhooks instead of the bot API, combine returnUrl: true with an image attachment:

javascript
const { url } = await renderChart({ returnUrl: true, ...spec }); await fetch(process.env.SLACK_WEBHOOK_URL, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ text: 'Weekly signups:', attachments: [{ image_url: url, fallback: 'Signups chart' }], }), });