How to Send a Weekly KPI Report to Slack
A spreadsheet nobody opens is not a report. Here is how to get the numbers into the channel your team already reads — a working Apps Script, the no-code alternatives, and the message format that actually gets acted on.
Short version: create a Slack incoming webhook, then have Apps Script read your summary cells and UrlFetchApp.fetch them into the channel on a weekly trigger. Ten minutes of setup, no add-on, no subscription. The code below works as written.
Why the channel beats the attachment
A small-business owner on Reddit described the loop exactly: pull the numbers from Shopify, Google Ads and Facebook, build the charts, assemble the slides, then send it round the team — every week. The assembling is annoying. The part that hurts is that most of it goes unread, because opening a file is a decision and reading a message is not.
Posting into the channel your team already has open removes that step, and it puts the number where the conversation about it happens.
1. Create the Slack webhook
Go to api.slack.com/apps → Create New App → From scratch, pick your workspace, then open Incoming Webhooks and turn it on. Click Add New Webhook to Workspace, choose the channel, and copy the URL it gives you. It looks like https://hooks.slack.com/services/T000/B000/xxxx.
Treat that URL as a secret — anyone holding it can post to the channel. If other people can edit the sheet, store it in Apps Script's Script Properties rather than in the code.
2. Give the script something small to read
Do not make the script compute your KPIs. Build a small Summary tab — one row per metric, with the current value and the prior period beside it — using formulas you already trust:
=SUMIFS(Data!D:D, Data!A:A, ">="&B1, Data!A:A, "<"&B2)
The script then reads finished numbers. When a metric definition changes you edit a formula, not code, and the message keeps working.
3. The Apps Script
Open Extensions → Apps Script, delete the placeholder, and paste this. It reads a three-column summary range (metric, this period, last period), formats a message, and posts it:
const WEBHOOK = 'https://hooks.slack.com/services/XXX/YYY/ZZZ';
function postWeeklyReport() {
const sheet = SpreadsheetApp.getActive().getSheetByName('Summary');
const rows = sheet.getRange('A2:C6').getValues();
const lines = rows
.filter(function (r) { return r[0] !== ''; })
.map(function (r) {
const metric = r[0], now = r[1], prev = r[2];
const delta = prev ? (now - prev) / prev : 0;
const arrow = delta >= 0 ? '▲' : '▼';
const pct = (Math.abs(delta) * 100).toFixed(1);
return '• *' + metric + ':* ' + now.toLocaleString() +
' ' + arrow + ' ' + pct + '% vs last week';
});
const stamp = Utilities.formatDate(new Date(), Session.getScriptTimeZone(), 'd MMM');
const text = '*Weekly numbers — ' + stamp + '*\n' + lines.join('\n');
UrlFetchApp.fetch(WEBHOOK, {
method: 'post',
contentType: 'application/json',
payload: JSON.stringify({ text: text }),
});
}
Run it once from the editor. Apps Script asks for authorization the first time — it needs permission to read the sheet and to call an external URL. Check the message lands in the right channel before you schedule it.
4. Schedule it
In the Apps Script editor, open Triggers (the clock icon) → Add Trigger. Choose postWeeklyReport, event source Time-driven, type Week timer, then the day and the hour block. Google runs time-driven triggers within about an hour of the slot, so pick a window rather than a minute — the 8–9am Monday block for a 9:30 standup, not the 9am one.
The no-code alternatives
If you would rather not own a script, Zapier and Make both have Sheets-to-Slack templates and take a few minutes to configure. At weekly cadence the task cost is trivial; the reason to think twice is one more subscription and one more place the logic lives. If your data sits in Excel rather than Sheets, Power Automate has a Slack connector and the same Recurrence trigger described in emailing an Excel report on a schedule.
What to put in the message
The plumbing is the easy half. Most automated Slack reports get ignored because they dump twenty metrics with no interpretation. A format that survives:
- One headline number — the thing the team is judged on this quarter.
- The change, against a stated baseline. "Up 8% vs last week" beats a bare figure nobody can size.
- One sentence on why, if you know it. This is the part a script cannot write.
- A link to the detail, last, for whoever wants to check your work.
Three or four lines. If it needs scrolling on a phone, it is a document, not a report.
The follow-up problem
Here is what happens next in every channel that gets an automated report: someone replies "why is that down?" The script cannot answer, so the question routes to whoever owns the spreadsheet — and the manual work you automated comes back in a different shape.
That is the gap the Quiriz Slack bot is meant to close. The scheduled report posts as usual, and because the bot sits in the channel scoped to that project, anyone can @mention it and ask a follow-up against the same data — "break that down by channel", "which SKU dropped" — and get the answer in the thread rather than sending you a task. In the sheet, the same question works as =QUIRIZ("revenue by channel this week vs last", "table").
To be straight about it: the Slack bot and scheduled reports are on the paid tier, and the Apps Script above costs nothing. If your report is three numbers nobody ever questions, keep the script — it is the right answer.
=QUIRIZ("revenue by channel this week vs last", "table").Let the channel ask its own follow-ups
Schedule the report, then let your team @mention Quiriz in Slack to ask their own questions about the same data. Free to start.
Try Quiriz free →