Slack Handler Reference
Technical reference for all Slack event handlers, action routers, submission processors, and view builders.
Last Updated: 2026-05-15
Handler Registration
All handlers are registered in src/app.ts:
// Events
app.event('app_home_opened', handleAppHomeOpenedEvent);
// View Submissions (catches ALL callback_ids)
app.view(/.*/, handleViewSubmission);
// Actions (catches ALL action_ids)
app.action(/.*/, handleActions);
// Shortcuts
app.shortcut('add_to_hub', handleSaveMessageShortcut);
app.shortcut('quick_add_knowledge', handleQuickAddKnowledgeShortcut);
// Error handling
app.error(async (error) => { logger.error('[App] Unhandled error:', error); });Event Handlers
events.ts
| Event | Handler | Description |
|---|---|---|
app_home_opened | handleAppHomeOpenedEvent() | Delegates to home/index.ts |
The bot currently only subscribes to app_home_opened. URL/file detection events (message.channels, file_shared, reaction_added) are defined in the manifest but the handlers for them are deferred — the current code focuses on the App Home dashboard.
home/index.ts
Entry point that delegates to home/dynamic.ts:
export async function handleAppHomeOpened(args) → calls handleDynamicAppHome()home/dynamic.ts
Renders the dynamic App Home tab with three view modes:
| Mode | Constant | What it Shows |
|---|---|---|
| Categories | HOME_TAB_MODES.CATEGORIES | Paginated category list with overflow menus |
| Knowledge Items | HOME_TAB_MODES.KNOWLEDGE_ITEMS | All knowledge items with details |
| AI Summaries | HOME_TAB_MODES.AI_SUMMARIES | AI-generated summaries per item |
Users switch modes via a dropdown (VIEW_MODE_SELECTOR action). The home tab auto-refreshes after mutations (add knowledge, create category, etc.).
Shortcuts
shortcuts.ts
| Shortcut | Type | Handler | Callback ID |
|---|---|---|---|
| Add to YapHub | Message | handleSaveMessageShortcut() | add_to_hub |
| Quick Add to YapHub | Global | handleQuickAddKnowledgeShortcut() | quick_add_knowledge |
Message Shortcut Flow:
- User right-clicks a message > "Add to YapHub"
- Extracts message text, timestamps, channel info
- Detects URLs in the message text
- Opens
save_message_modalwith pre-filled content
Global Shortcut Flow:
- User clicks lightning bolt > "Quick Add to YapHub"
- Fetches categories from cache
- Opens
add_knowledge_modalwith empty form
Action Handler
actions.ts — Main Router
Routes all button clicks and menu selections. Uses a switch statement for known action IDs and prefix matching for dynamic actions.
Direct Actions:
| Action ID | Handler | Description |
|---|---|---|
add_list_from_home | Opens add_list_modal | Create new category |
home_add_knowledge | Opens add_knowledge_modal | Add knowledge from home tab |
open_add_modal | Opens add_knowledge_modal | Add knowledge (generic) |
home_open_digest | Opens generate_digest_modal | Generate digest |
home_open_distribution | Opens distribute_digest_modal | Distribute digest |
home_open_settings | Delegates to categorySettings.ts | Category digest settings |
Prefix-Based Actions:
| Prefix | Handler | Example |
|---|---|---|
category_overflow_ | handleCategoryOverflowAction() | category_overflow_123 |
Category Overflow Operations (value format: actionType:categoryId):
| Action Type | What Happens |
|---|---|
add_knowledge | Opens add modal with category pre-selected |
digest | Opens digest modal with category pre-selected |
distribute | Delegates to distribution.ts |
settings | Delegates to categorySettings.ts |
subscribe | Delegates to subscription.ts |
unsubscribe | Delegates to subscription.ts |
actions/categorySettings.ts
Handles category-level digest configuration:
- Opens settings modal for a category
- Handles category selection within settings
- Processes digest config updates (enabled, frequency, day, time)
actions/distribution.ts
Handles digest distribution to subscribers:
- Category selection in distribution modal
- Digest list loading for a category
- Distribution confirmation (sends to subscribers)
- Direct distribution from category overflow menu
actions/subscription.ts
Handles subscribe/unsubscribe from category overflow menus.
Submission Handler
submissions.ts — Main Router
Routes all modal submissions by callback_id (view ID).
Callback IDs (from VIEW_IDS constant):
| Callback ID | Handler | Description |
|---|---|---|
add_list_modal | Inline | Creates a new category via backend API |
add_knowledge_modal | Inline | Creates a new knowledge item |
save_message_modal | Inline | Saves a Slack message as knowledge |
generate_digest_modal | digestHandler.ts | Generates AI digest |
distribute_digest_modal | distribution.ts | Confirms digest distribution |
category_settings_modal | Inline | Saves digest config (via services/categorySettings.ts) |
| Unknown | Logs warning, acks | Fallback |
Common Pattern:
- Acknowledge immediately (
await ack()) - Extract form values from
view.state.valuesusingBLOCK_IDSandACTION_IDSconstants - Call backend API via services
- Send confirmation DM to user
- Refresh App Home tab
submissions/digestHandler.ts
Handles the heavy digest generation work:
validateDigestSubmission(view)— validates form, returns errors or nullhandleDigestSubmission(view, userId, client)— async digest generation- Runs in background (not awaited in the router) to avoid Slack's 3-second timeout
- Errors are caught and sent to the user as DMs
submissions/api/sendDigest.ts
Express API handler for POST /api/slack/send-digest. Called by the Symfony backend to deliver digests via DM. Requires Bearer token auth matching BACKEND_API_TOKEN.
View Builders
All modals are built in src/views/modals/ using Slack Block Kit. Builders are pure functions that return view objects.
Knowledge Modals
| Builder | File | Modal |
|---|---|---|
buildAddKnowledgeModal() | views/modals/knowledge/addKnowledge.ts | add_knowledge_modal |
buildSaveMessageModal() | views/modals/knowledge/saveMessage.ts | save_message_modal |
Add Knowledge Modal Fields:
- Title (
title_block/title_input) - Content (
content_block/content_input) - Category (
list_block/list_select) - URL (
url_block/url_input) - Tags (
tags_block/tags_input)
Category Modals
| Builder | File | Modal |
|---|---|---|
buildAddListModal() | views/modals/categories/addList.ts | add_list_modal |
| Category settings | views/modals/categories/categorySettings.ts | category_settings_modal |
Add List Modal Fields:
- Name (
name_block/name_input) - Description (
description_block/description_input) - Icon (
icon_block/icon_input)
Digest Modals
| Builder | File | Modal |
|---|---|---|
buildGenerateDigestModal() | views/modals/digest/generateDigest.ts | generate_digest_modal |
buildDistributionModal() | views/modals/digest/distributeDigest.ts | distribute_digest_modal |
Home Tab
| Builder | File |
|---|---|
| Dynamic home tab | views/home/dynamicHomeTab.ts |
| Category list | views/home/categoriesView.ts |
Home tab supports three modes via HOME_TAB_MODES: categories, knowledge items, AI summaries.
Services
All services in src/services/ communicate with the Symfony backend via HTTP using a shared Axios client (utils/api.ts).
| Service | File | Backend Endpoints |
|---|---|---|
categories.ts | listCategories(), addCategory() | /api/categories |
knowledge.ts | addKnowledge(), listKnowledge() | /api/knowledge |
digest.ts | generateDigest(), distributeDigest(), getDigestsByCategory() | /api/digests/generate, /api/digests/{id}/distribute, /api/digests/category/{categoryId} |
subscriptions.ts | getUserSubscriptions(), subscribeUserToCategory(), unsubscribeUserFromCategory() | /api/subscriptions/user/{userId}, /api/subscriptions/subscribe, /api/subscriptions/unsubscribe |
categorySettings.ts | getDigestConfig(), updateDigestConfig() | /api/categories/{id}/settings/digest |
All requests include Authorization: Bearer $BACKEND_API_TOKEN header.
Constants Reference
All IDs are centralized in src/constants/ to prevent magic strings.
Action IDs (action_ids.ts)
| Constant | Value | Used In |
|---|---|---|
HOME_ADD_KNOWLEDGE | home_add_knowledge | Add knowledge button on home tab |
ADD_LIST_FROM_HOME | add_list_from_home | Create category button |
HOME_OPEN_DIGEST | home_open_digest | Open digest modal |
HOME_OPEN_DISTRIBUTION | home_open_distribution | Open distribution modal |
HOME_OPEN_SETTINGS | home_open_settings | Open category settings |
VIEW_MODE_SELECTOR | view_mode_selector | Home tab mode dropdown |
View IDs / Modal Callback IDs (view_ids.ts)
| Constant | Value |
|---|---|
ADD_KNOWLEDGE | add_knowledge_modal |
SAVE_MESSAGE | save_message_modal |
ADD_LIST | add_list_modal |
GENERATE_DIGEST | generate_digest_modal |
DISTRIBUTE_DIGEST | distribute_digest_modal |
CATEGORY_SETTINGS | category_settings_modal |
Action Prefixes (action_ids.ts)
| Constant | Prefix | Purpose |
|---|---|---|
CATEGORY_OVERFLOW | category_overflow_ | Category overflow menus |
SETTINGS_OPEN_DIGEST | settings_open_digest: | Quick-open digest from settings |
SETTINGS_OPEN_DISTRIBUTION | settings_open_distribution: | Quick-open distribution from settings |
Error Handling
Pattern
All handlers follow the same error pattern:
try {
// handler logic
} catch (error) {
logger.error(`[Handler] Error:`, error);
// User-facing error as DM
await client.chat.postMessage({
channel: body.user.id,
text: '❌ Er is een fout opgetreden. Probeer het opnieuw.',
});
}API Error Extraction
The getApiErrorMessage() helper in submissions.ts extracts human-readable messages from backend errors:
data.message— standard backend errorsdata.detail— Symfony validation errorsdata.violations— constraint violations (joined)
Acknowledgment
- All actions and submissions are acknowledged immediately
- Digest generation is acked first, then runs in background
- Validation errors use
ack({ response_action: 'errors', errors: {...} })
Testing
See Slack Bot Testing for the full test suite reference.
cd slack
npm test # Run all tests
npm run test:watch # Watch mode
npm run test:coverage # Coverage report