Building a Slack App Home Tab as a Personal Ops Control Panel
Use the Slack App Home tab to give each engineer a private ops dashboard: on-call status, open incidents, and actions. AI scaffolds the views; you review them.
- #slack
- #chatops
- #block-kit
- #app-home
Most of our ops bots shout. They post into channels, ping people, and add to the noise. The one surface I kept ignoring was the quietest and most useful: the App Home tab. It’s the personal space you get when you click on a bot in the sidebar, and almost nobody builds anything there. I turned ours into a per-engineer control panel — your on-call status, the incidents you own, a one-tap “ack” button — and it became the first thing people open in the morning. No channel noise, just your ops state.
I scaffolded it with AI, and the App Home is a good reminder of why I keep AI scoped as a fast junior engineer. It generates a beautiful Block Kit view in seconds and will cheerfully render another user’s open incidents into your home tab if you’re sloppy about user scoping. A human reviews who-sees-what before this ships to a workspace, because the Home tab is private-by-promise and trivial to leak through.
How the App Home works
Enable the Home tab in your app config, then publish a view per user with views.publish, keyed on user_id. Slack stores it and shows that user their view. The trigger is the app_home_opened event:
app.event('app_home_opened', async ({ event, client }) => {
await client.views.publish({
user_id: event.user,
view: await buildHomeView(event.user), // user-scoped!
});
});
The critical detail: buildHomeView must take the user ID and return only that user’s data. The view is private to event.user, so any data you put in it is implicitly “for their eyes.” Cross-wire the user ID and you’ve shown Alice Bob’s incidents.
Build a user-scoped view
A control panel is just a home view with sections and actions:
async function buildHomeView(userId) {
const onCall = await isOnCall(userId);
const incidents = await incidentsOwnedBy(userId); // scoped query
return {
type: 'home',
blocks: [
{ type: 'header', text: { type: 'plain_text', text: '🏠 Your Ops Home' } },
{ type: 'section', text: { type: 'mrkdwn',
text: onCall ? '🔴 *You are on-call*' : '🟢 You are not on-call' } },
{ type: 'divider' },
...incidents.flatMap(inc => ([
{ type: 'section', text: { type: 'mrkdwn', text: `*${inc.title}* · sev ${inc.sev}` },
accessory: { type: 'button', text: { type: 'plain_text', text: 'Ack' },
action_id: 'ack_incident', value: inc.id } },
])),
],
};
}
Notice the scoped queries do the security work. The view layer just renders what the data layer is allowed to return for this user.
Make the buttons act, and re-publish
Home tab buttons fire normal action handlers. After handling one, re-publish the view so the panel reflects the new state immediately:
app.action('ack_incident', async ({ body, ack, client, action }) => {
await ack();
await acknowledgeIncident(action.value, body.user.id);
await client.views.publish({
user_id: body.user.id,
view: await buildHomeView(body.user.id), // refresh
});
});
Always ack() within three seconds, then do the work. The re-publish is what makes the Home tab feel live instead of stale.
Pro Tip: The Home tab has no “refresh” button for users, so publish on app_home_opened every time rather than caching aggressively. Cheap data is worth re-fetching so nobody stares at yesterday’s incident list.
Personalize without leaking
The temptation is to show “team status” on the Home tab. Be careful: the moment you render team-wide data, you’ve made a private surface that exposes shared state, and you need to be sure every viewer is allowed to see all of it. My rule is that the Home tab shows what’s true about you — your on-call, your incidents, your pending approvals — and links out to channels for team-wide views. It composes well with your incident response dashboard: the dashboard is the shared source of truth, the Home tab is your personal slice.
Use AI for the view, not the policy
Block Kit views are verbose and repetitive, which is exactly the kind of thing AI nails. I let Claude or Cursor generate the view JSON from a description and iterate on the prompt in the prompt workspace. The model is fast and the output is mechanical enough to verify by eye in the Block Kit Builder.
What the model must not author is the authorization. It does not know that a contractor shouldn’t see prod incident titles, or that on-call status for the security team is sensitive. You write and review the scoped queries; the model only renders their results. And keep real tokens out of it — give the model the view schema, not your workspace credentials. Verify request signatures on the events endpoint that drives all of this, since app_home_opened arrives over the same untrusted path as everything else.
Conclusion
The App Home tab is the calmest, most underused surface in Slack, and it makes a perfect personal ops control panel: on-call status, your incidents, one-tap actions, refreshed on every open. Scope every query to the viewing user, re-publish after actions, and keep team-wide data behind links. Let AI generate the verbose Block Kit fast while a human owns who-sees-what. More patterns in the Slack category, and starter prompts for the view scaffolding.
Download the Free 500-Prompt DevOps AI Toolkit
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.