@tightknitai/slack-hono
v0.1.3
Published
Slack app framework as a Hono plugin — powered by slack-edge
Readme
slack-hono
Slack app framework as a Hono plugin — powered by slack-edge.
Build Slack apps that run on Cloudflare Workers, Deno, Bun, Vercel Edge, and any other edge runtime, using Hono's middleware and routing.
Install
pnpm add @tightknitai/slack-hono honoQuick Start
import { Hono } from "hono";
import { SlackHonoApp } from "@tightknitai/slack-hono";
const slack = new SlackHonoApp({
env: {
SLACK_SIGNING_SECRET: "your-signing-secret",
SLACK_BOT_TOKEN: "xoxb-your-bot-token",
},
});
slack.command("/hello", async () => "Hello from slack-hono!");
slack.event("app_mention", async ({ context, payload }) => {
await context.say({ text: `You said: ${payload.text}` });
});
const app = new Hono();
app.route("/", slack.handler());
export default app;Before / After
Before (raw slack-edge + Hono glue)
import { Hono } from "hono";
import { SlackApp } from "slack-edge";
const slackApp = new SlackApp({
env: {
SLACK_SIGNING_SECRET: signingSecret,
SLACK_BOT_TOKEN: botToken,
},
routes: { events: "/slack/events" },
});
slackApp.command("/hello", async () => "Hello!");
const app = new Hono();
app.post("/slack/events", async (c) => {
// Manual glue: pass raw request + execution context
let ctx;
try { ctx = c.executionCtx; } catch { ctx = { waitUntil() {} }; }
return await slackApp.run(c.req.raw, ctx);
});
export default app;After (slack-hono)
import { Hono } from "hono";
import { SlackHonoApp } from "@tightknitai/slack-hono";
const slack = new SlackHonoApp({
env: {
SLACK_SIGNING_SECRET: signingSecret,
SLACK_BOT_TOKEN: botToken,
},
});
slack.command("/hello", async () => "Hello!");
const app = new Hono();
app.route("/", slack.handler());
export default app;API
SlackHonoApp
Extends SlackApp from slack-edge. Adds a handler() method that returns a Hono sub-app.
const slack = new SlackHonoApp({
env: {
SLACK_SIGNING_SECRET: "...",
SLACK_BOT_TOKEN: "...",
},
// Optional: custom authorize function (default: singleTeamAuthorize)
authorize: myAuthorize,
// Optional: custom event path (default: "/slack/events")
path: "/slack/events",
});All slack-edge listener methods are available:
// Slash commands
slack.command("/hello", ackHandler, lazyHandler);
// Events API
slack.event("app_mention", lazyHandler);
slack.event("message", lazyHandler);
slack.anyMessage(lazyHandler);
slack.message("pattern", lazyHandler);
// Block actions
slack.action("action_id", ackHandler, lazyHandler);
slack.action(/regex/, ackHandler, lazyHandler);
slack.action({ type: "button", action_id: "id" }, ackHandler, lazyHandler);
// Block suggestions
slack.options("action_id", ackHandler);
// Shortcuts
slack.globalShortcut("callback_id", ackHandler, lazyHandler);
slack.messageShortcut("callback_id", ackHandler, lazyHandler);
// Views
slack.viewSubmission("callback_id", ackHandler, lazyHandler);
slack.viewClosed("callback_id", ackHandler, lazyHandler);
// Middleware
slack.beforeAuthorize(middleware);
slack.afterAuthorize(middleware);SlackHonoOAuthApp
For multi-workspace apps that need OAuth installation flow:
import { SlackHonoOAuthApp } from "@tightknitai/slack-hono";
const slack = new SlackHonoOAuthApp({
env: {
SLACK_SIGNING_SECRET: "...",
SLACK_CLIENT_ID: "...",
SLACK_CLIENT_SECRET: "...",
SLACK_BOT_SCOPES: "commands,chat:write",
},
installationStore: myInstallationStore,
routes: {
events: "/slack/events", // default
oauth: {
start: "/slack/install", // default
callback: "/slack/oauth_redirect", // default
},
},
});
const app = new Hono();
app.route("/", slack.handler());slackVerify()
Standalone Hono middleware for Slack request signature verification. Useful when building custom handlers outside of SlackHonoApp:
import { slackVerify } from "@tightknitai/slack-hono";
app.post("/webhook", slackVerify(signingSecret), (c) => {
const rawBody = c.var.slackRawBody;
// ... handle verified request
});slackAuthorize()
Standalone Hono middleware for Slack authorization. Must be used after slackVerify():
import { slackVerify, slackAuthorize, singleTeamAuthorize } from "@tightknitai/slack-hono";
app.post(
"/slack/events",
slackVerify(signingSecret),
slackAuthorize({
authorize: singleTeamAuthorize,
env: { SLACK_BOT_TOKEN: botToken },
}),
(c) => {
const auth = c.var.slackAuth;
// auth.botToken, auth.botId, auth.botUserId, etc.
}
);env is forwarded to the authorize function as req.env, the same way SlackApp
does internally. Authorize functions that resolve credentials from the
environment — including the default singleTeamAuthorize, which reads
SLACK_BOT_TOKEN — need it to be populated, or authorization fails with 401.
When env is omitted it defaults to the Hono runtime bindings (c.env), so on
Cloudflare Workers and other edge runtimes the secrets are picked up for you:
type Env = { Bindings: { SLACK_SIGNING_SECRET: string; SLACK_BOT_TOKEN: string } };
const app = new Hono<Env>();
app.post(
"/slack/events",
slackVerify(signingSecret),
// SLACK_BOT_TOKEN comes from the Worker bindings
slackAuthorize({ authorize: singleTeamAuthorize }),
(c) => {
const auth = c.var.slackAuth;
}
);Type Definitions
import type { SlackHonoEnv, SlackContext } from "@tightknitai/slack-hono";
// SlackHonoEnv provides typed Hono variables:
// c.var.slack — SlackContext (client, botToken, authorizeResult, etc.)
// c.var.slackRawBody — string (raw request body)
// c.var.slackAuth — AuthorizeResult (from slackAuthorize middleware)Deployment
Cloudflare Workers
import { Hono } from "hono";
import { SlackHonoApp } from "@tightknitai/slack-hono";
type Env = { SLACK_SIGNING_SECRET: string; SLACK_BOT_TOKEN: string };
const app = new Hono<{ Bindings: Env }>();
app.post("/slack/events", async (c) => {
const slack = new SlackHonoApp({
env: {
SLACK_SIGNING_SECRET: c.env.SLACK_SIGNING_SECRET,
SLACK_BOT_TOKEN: c.env.SLACK_BOT_TOKEN,
},
});
slack.command("/hello", async () => "Hello from Workers!");
return await slack.run(c.req.raw, c.executionCtx);
});
export default app;Cloudflare Workers with OAuth (multi-workspace)
For apps installed across multiple workspaces, use slack-cloudflare-workers for KV-backed installation and state stores:
pnpm add @tightknitai/slack-hono slack-cloudflare-workers honoimport { Hono } from "hono";
import { SlackHonoOAuthApp } from "@tightknitai/slack-hono";
import { KVInstallationStore, KVStateStore } from "slack-cloudflare-workers";
type Env = {
Bindings: {
SLACK_SIGNING_SECRET: string;
SLACK_CLIENT_ID: string;
SLACK_CLIENT_SECRET: string;
SLACK_BOT_SCOPES: string;
SLACK_INSTALLATIONS: KVNamespace;
SLACK_OAUTH_STATE: KVNamespace;
};
};
const app = new Hono<Env>();
app.all("/slack/*", async (c) => {
const slack = new SlackHonoOAuthApp({
env: c.env,
installationStore: new KVInstallationStore(c.env, c.env.SLACK_INSTALLATIONS),
stateStore: new KVStateStore(c.env.SLACK_OAUTH_STATE),
});
slack.command("/hello", async () => "Hello from a multi-workspace app!");
return await slack.run(c.req.raw, c.executionCtx);
});
export default app;Acknowledgements
Built on top of slack-edge by @seratch — a fantastic Slack app framework for edge runtimes. slack-hono wouldn't exist without it.
License
MIT
Maintained by the Tightknit team.
