Skip to main content

Hooks

Communication between the CLI and the application SDK is managed by a project-level configuration file called hooks.json. This file is included in our app templates and defines script hooks.

Hooks are small scripts that are executed by the CLI and implemented by the SDK. These commands perform actions on the project.

The hooks.json file allows the CLI and SDK a standard way to communicate while remaining decoupled and abstracted. This interface is a key design of the Slack CLI: many application and project level tasks are delegated from the CLI to the SDK. This delegation, decoupling and abstraction allows for language-agnostic SDK implementations.

Core concepts​

Hooks: How the CLI and the SDK communicate​

Hooks are scripts that execute when a specific event happens or a Slack CLI command is invoked.

A hook script may be triggered when:

  • generating the app manifest
  • bundling function code before deployment to Slack infrastructure
  • handling an application event during local development runs

When an event occurs, the CLI will execute a hook script by spawning a separate process, possibly passing a JSON object through STDIN and/or other parameters via command line flags to the hook script process, and waiting for a JSON response via the spawned process’ STDOUT. This system is heavily inspired by git hooks.

Since communication over hooks involves inter-process communication (one process running the CLI communicating with another process managed by the SDK ie. the hook), the exit code of the hook process signals success or failure to the CLI process. The CLI will assume an exit code of 0 from a hook process signals success, while any other exit code signals failure.

Some hooks may return data as part of their functionality. The CLI will use the STDOUT and STDERR of the hook process to transmit its response. For details on how a hook process can shape its response, and delineate diagnostic data from response data, see the section on protocol negotiation.

Discover hook scripts and default configuration with get-hooks​

In order for the CLI to reliably discover the hooks for the Deno SDK, Bolt Frameworks, and future community-driven SDKs, the CLI employs a service-discovery-like approach to querying the SDK for what functionality it supports.

A project includes a hooks.json file in its .slack directory which by default contains a single hook (get-hooks). The SDK is responsible for implementing this hook and returning to the CLI a JSON object with all hook definitions and their relevant default configuration (one hook to rule them all πŸ’).

App developers do not need to edit or change their hooks.json file when upgrading their SDK because the interface contents are dictated by the SDK. If an App Developer would like to override specific hooks, they can do so by overriding the relevant hook section itself within hooks.json with their own implementation. More details on overriding behaviour can be found in the Hook resolution section.

Refer to the CLI-SDK JSON interface section for other examples.

CLI-SDK protocol negotiation​

As the needs of app developers evolve, so will the interface and the rules of communication between the CLI and the SDK. These rules are negotiated via the initial get-hooks handshake and are specified via the protocol-version field returned by the SDK.

At the time of writing, only two protocol versions are supported:

  1. v1: The base and default protocol
  2. v2: The second revision of the protocol, implementing message boundaries. This enables delineating responses to hook invocations from diagnostic/additional data such as logging.

If at any point protocol negotiation fails or does not adhere to the rules of communication, the CLI will fall back to using the default protocol.

Working implementations of protocol negotiation​

Ensuring backwards compatibility​

A hook’s name space (CLI) and its associated script implementation (SDK) will change over time. This can break backwards compatibility and require App Developers to juggle different CLI versions and SDK versions in order to maintain compatibility. It’s a frustrating situation that can ruin the developer experience.

An additive approach to hook names or configuration settings allows us to keep hooks backwards-compatible for as long as possible and allows for a smoother upgrade experience. This approach also allows for tools to provide generous timeframes for supporting old hooks vs. new ones, allowing for deprecation windows and gradual rollouts. For configuration settings, an additive approach is accomplished by adding new configuration values that are not Golang defaults (e.g. bool defaults to false).

For example, the hook name run-v2 may be the successor to the hook named run. The SDK can implement either hook and the CLI will trigger the latest version, possibly falling back to earlier versions of the hook where applicable. The CLI can also warn of impending removal of older hooks, providing hints to the developer when tooling behaviour changes.

Hook specification​

Hooks are entry points for the CLI to initiate inter-process communication with the SDK. SDKs should implement one hook: get-hooks. It is the recommended approach for SDKs to enable communication with the CLI (for more details, see the Hook Resolution section).

A hook encapsulates an isolated piece of functionality that the SDK provides to app developers; the CLI delegates execution of the functionality to the SDK via hook invocation in a separate process. SDKs are responsible for ensuring that hook processes exit with a status code of 0; otherwise the CLI will surface the sdk_hook_invocation_failed error to the end-user and report the hook process’ STDOUT and STDERR to the CLI process’ STDOUT.

Hooks list​

The following Slack CLI commands invoke hooks:

CommandHook(s)
slack deployget-manifest, build, deploy
slack doctorcheck-update, doctor
slack manifest infoget-manifest
slack runget-manifest, start
slack trigger createget-trigger
slack upgradecheck-update, install-update

More details on these hooks can be found in their dedication sections below.

get-hooks (required)​

Implementing this hook allows for the SDK to control what hooks, and therefore features, it implements and allows for interoperability with the CLI.

For more details on how this hook is resolved, looked up, and how it interacts with resolution of other hooks, please see the Hook resolution section.

This hook acts as the very first interaction between the CLI and SDK, and thus special rules apply. It is invoked before any other hook and therefore its implementation should be performant. It does not support sending diagnostic information.

Output​

This hook should return the CLI-SDK interface in JSON format via STDOUT.

Support​

DenoBolt for JavaScriptBolt for PythonBolt for Java
βœ… Supportedβœ… Supportedβœ… Supported❌ Unsupported

get-manifest (optional)​

This hook allows for the application under development to be created on api.slack.com/apps as well as installed to workspaces that the CLI has been authorized to.

Implementing this hook signals to the CLI that the SDK manages the application manifest.

Output​

The app manifest in JSON format to STDOUT.

Support​

DenoBolt for JavaScriptBolt for PythonBolt for Java
βœ… Supportedβœ… Supportedβœ… Supported❌ Unsupported

build (optional)​

Implementing this hook allows for the CLI to deploy function code to Slack's managed infrastructure. The work of assembling the application bundle according to Slack's application bundle format is delegated to the SDK via this hook.

The application bundle format has few restrictions, but the critical ones are:

  • It must have a manifest.json at the root of the package
  • This manifest must contain a functions key which is an object, with the keys of this object being each custom developer-defined function's callback_id. The contents of this object follow the standard app manifest format.
  • Each custom function's source code should be bundled into a single .js file with its callback_id as its filename, and exist inside a functions/ subdirectory within the app bundle. For example, a custom function with a callback_id of greeting_function should bundle all of its code, including dependencies, into a greeting_function.js file that would exist at functions/greeting_function.js within the application bundle.

The above requirements come from the deno-slack-runtime project, which implements the expected Slack deployment bundle format. It contains a hard-coded reference to the above-mentioned functions/ sub-directory, and combines it with the specific custom function callback_id to resolve an import path for userland function source code.

This hook should only be implemented by official Slack SDKs and is only relevant to apps deployed to Slack's managed infrastructure.

Input​

Two command-line flags are provided as part of the hook invocation:

  • --source: the path to the root of the application project directory
  • --output: the path to a temporary directory that the SDK should output the application bundle to, as per the application bundle format

Output​

No further output over STDOUT required from the SDK other than writing the application bundle to the filesystem location specified by the --output flag.

Support​

DenoBolt for JavaScriptBolt for PythonBolt for Java
βœ… Supported❌ Unsupported❌ Unsupported❌ Unsupported

start (optional)​

The slack run CLI command will invoke the start hook to initiate a local-run development mode allowing for quick iteration during app development. This hook is responsible for actually running the app, but has two operating modes: one where the hook manages the connection to Slack via Socket Mode, and one where it does not. Which mode should be employed by the CLI when invoking this hook is dictated by the config.sdk-managed-connection-enabled property in the response from get-hooks.

Non-SDK-managed connection​

This section applies when config.sdk-managed-connection-enabled is undefined or set to false in hooks.json or in the get-hooks hook response.

When the app developer wants to initiate a development-mode local run of their application via the slack run CLI command, by default the CLI will create a Socket Mode connection to the Slack backend and start listening for events on behalf of the app.

Any events coming down the wire from Slack will be fed over STDIN to this hook for the SDK to process. Each event incoming from Slack will invoke this hook independently, meaning one start hook process will be spawned per incoming event. The SDK should process each incoming event and output a JSON object to STDOUT representing the response to send back to Slack over the socket connection managed by the CLI.

Input​

Several parameters are passed to the SDK over STDIN as JSON. The format of this JSON payload is as follows:

{
"body": {
"type": "app_deleted",
...
},
"context": {
"bot_access_token": "xoxb-1234",
"app_id": "A1234",
"team_id": "T1234",
"variables": {}
}
}
FieldDescriptionRequired
bodyObject whose keys represent the incoming Slack event payload as described in Events API Event Types. The particular content of this key is dependent on the incoming event type.Required
contextObject representing variables relevant for the locally-running application.Required
context.bot_access_tokenString; a bot access token that the SDK can provide to developer functions for issuing calls to the Slack API.Required
context.app_idString; the current application ID.Required
context.team_idString; the ID of the team or workspace the locally-running app is installed to.Required
context.variablesObject containing environment variables that may or may not be defined by the application via a .env file in the root of the application directory.Required

Note: The CLI provides the SLACK_APP_TOKEN and SLACK_BOT_TOKEN environment variables if the respective scopes are requested.

Output​

Each incoming event from the socket connection will invoke this hook separately. As such, this hook's response to STDOUT should be the JSON response to the Slack event sent to the app. The CLI will handle acknowledging events by sending back the proper envelope_id attribute to the Slack backend. Therefore, the SDK’s start hook response STDOUT should be the payload of the response and nothing else. It is recommended to use the v2 (message-boundaries) protocol to more easily delineate logging/diagnostics from event responses to be sent to Slack.

Support​
DenoBolt for JavaScriptBolt for PythonBolt for Java
βœ… Supported❌ Unsupported❌ Unsupported❌ Unsupported

SDK-managed connection​

This section applies when config.sdk-managed-connection-enabled is set to true in hooks.json or in the get-hooks hook response.

Implementing the start hook with config.sdk-managed-connection-enabled set to true will instruct the CLI to delegate connection management to the hook implementation as defined in our Implementing Socket Mode documentation. Because establishing a network connection and handling incoming events is assumed to be a long-running process, invoking this hook will block the CLI process.

Input​

The application's app-level token and bot access token will be provided as environment variables to the hook process (SLACK_CLI_XAPP and SLACK_CLI_XOXB respectively, as well as SLACK_APP_TOKEN and SLACK_BOT_TOKEN). The SDK should use the app token to create a socket connection with the Slack backend on behalf of the app. Additionally, the SDK may use the provided bot token to facilitate API calls to the Slack API.

All Bolt SDKs leverage this start hook operating mode.

A custom start path can be set with the SLACK_CLI_CUSTOM_FILE_PATH variable.

Output​

Any STDOUT received from the this hook would be immediately streamed to the CLI process’ STDOUT.

Support​
DenoBolt for JavaScriptBolt for PythonBolt for Java
❌ Unsupportedβœ… Supportedβœ… Supported❌ Unsupported

check-update (optional)​

Check that an update for the SDK is available.

For Deno, the SDK would check deno.land for the latest version. Likewise for npm. The install-update hook would then update any files required for managing dependencies.

Output​

The format for the output JSON is as follows:

{
"name": "",
"releases": [
{
"name": "",
"current": "",
"latest": "",
"update": true,
"breaking": false,
"message": "",
"url": "",
"error": ""
},
...
],
"message": "",
"url": "",
"error": ""
}
FieldDescriptionRequired
nameString containing the name corresponding to the overall package/library, in which individual component releases are bundled.Required
releasesArray of objects representing individual releases.Required
releases.nameString containing the name of the dependency.Required
releases.currentString containing the current version.Optional
releases.latestString containing the latest version available.Optional
releases.updateBoolean indicating whether there is an update available.Optional
releases.breakingBoolean indicating whether the update is breaking.Optional
releases.messageString containing a message about the dependency.Optional
releases.urlString containing a URL with update information.Optional
releases.errorObject with a single key, "message", which has a string value containing error information.Optional
messageString containing any additional details that should be surfaced to the user. For example, this could include how to manually update or a warning that certain files will be overwritten.Optional
urlString containing a URL where one can learn more about the release.Optional
errorObject with a single key, "message", which has a string value containing error information.Optional

Support​

DenoBolt for JavaScriptBolt for PythonBolt for Java
βœ… Supportedβœ… Supportedβœ… Supported❌ Unsupported

install-update (optional)​

Update the SDK version and/or any dependencies required by the app.

Output​

The format for the output JSON is as follows:

{
"name": "",
"releases": [
{
"name": "",
"current": "",
"latest": "",
"update": true,
"breaking": false,
"message": "",
"url": "",
"error": ""
},
...
],
"message": "",
"url": "",
"error": ""
}
FieldDescriptionRequired
nameString containing the name corresponding to the overall package/library, in which individual component releases are bundled.Required
releasesArray of objects representing individual releases.Required
releases.nameString containing the name of the dependency.Required
releases.currentString containing the current version.Optional
releases.latestString containing the latest version available.Optional
releases.updateBoolean indicating whether there is an update available.Optional
releases.breakingBoolean indicating whether the update is breaking.Optional
releases.messageString containing a message about the dependency.Optional
releases.urlString containing a URL with update information.Optional
releases.errorObject with a single key, "message", which has a string value containing error information.Optional
messageString containing any additional details that should be surfaced to the user. For example, this could include how to manually update or a warning that certain files will be overwritten.Optional
urlString containing a URL where one can learn more about the release.Optional
errorObject with a single key, "message", which has a string value containing error information.Optional

Support​

DenoBolt for JavaScriptBolt for PythonBolt for Java
βœ… Supported❌ Unsupported❌ Unsupported❌ Unsupported

get-trigger (optional)​

Used by the triggers create and triggers update CLI commands when the --trigger-def argument is passed. This hook should convert the app developer's code into a valid JSON blob that the CLI can upload to the trigger endpoints. The CLI handles adding the trigger_id when using the trigger update command.

Output​

Below is a sample JSON blob representing the trigger:

{
"type": "shortcut",
"name": "Submit an issue",
"description": "Submit an issue to the channel",
"workflow": "#/workflows/submit_issue",
"workflow_app_id": "A0168GS8ZFV",
"inputs": {
"channel": {
"value": "{{data.channel_id}}"
},
"interactivity": {
"value": "{{data.interactivity}}"
}
}
}

Support​

DenoBolt for JavaScriptBolt for PythonBolt for Java
βœ… Supported❌ Unsupported❌ Unsupported❌ Unsupported

doctor (optional)​

Used as part of the doctor CLI command to check against lower-level App / SDK requirements and ensure that the app developer has everything in place on their system to use the SDK.

For example, deno-slack-sdk should ensure that the Deno runtime is in place, bolt-js should ensure that Node is in place, bolt-python should ensure that Python is in place, and so on.

Output​

The format for the output JSON is as follows:

{
"versions": [
{
"name": "",
"current": "",
"message": "",
"error": ""
},
...
]
}
FieldDescriptionRequired
versionsArray of objects containing runtime details.Required
versions.nameString containing the name of the runtime dependency.Required
versions.currentString containing the current system version.Required
versions.messageString containing a message about the runtime dependency.Optional
versions.errorString containing error information for the runtime.Optional

Support​

DenoBolt for JavaScriptBolt for PythonBolt for Java
βœ… Supportedβœ… Supportedβœ… Supported❌ Unsupported

deploy (optional)​

This script is invoked by the slack deploy CLI command. It takes as input the app and bot token as environment variables to manage connections and authentication.

This script is provided by the developer and is meant to simplify app management. There is no restriction on the script.

If this script isn't provided, an attempt is made to deploy the app to Slack's managed infrastructure. This is expected for Deno apps but will fail for Bolt apps.

Input​

Access to STDIN is necessary for certain scripts.

Output​

Any STDOUT or STDERR received from the this hook is immediately streamed to the CLI process’ STDOUT and STDERR.

Support​

DenoBolt for JavaScriptBolt for PythonBolt for Java
❌ Unsupportedβœ… Supportedβœ… Supported❌ Unsupported

CLI-SDK JSON interface format​

The format for the JSON representing the CLI-SDK interface is as follows:

{
"runtime": "deno",
"hooks": {
"get-hooks": "command to be invoked",
"get-manifest": "...",
"build": "...",
"start": "...",
"check-update": "...",
"install-update": "...",
"get-trigger": "...",
"doctor": "...",
"deploy": "..."
},
"config": {
"protocol-version": ["message-boundaries"],
"sdk-managed-connection-enabled": false
}
}
FieldDescriptionRequired
runtimeString denoting the target runtime for app functions to execute in. For apps deployed to Slack's managed infrastructure, the only accepted value today is deno.Required
hooksObject whose keys must match the hook names outlined in the above Hooks Specification. Arguments can be provided within this string by separating them with spaces.Required
configObject of key-value settings.Optional
config.protocol-versionArray of strings representing the named CLI-SDK protocols supported by the SDK, in descending order of support, as in the first element in the array defines the preferred protocol for use by the SDK, the second element defines the next-preferred protocol, and so on. The only supported named protocol currently is message-boundaries. The CLI will use the v1 protocol if this field is not provided.Optional
config.watchObject with configuration settings for file-watching.Optional
config.sdk-managed-connection-enabledBoolean specifying whether the WebSocket connection between the CLI and Slack should be managed by the CLI or by the SDK during slack run executions. If true, the SDK will manage this connection. If false or not provided, the CLI will manage this connection.Optional
config.trigger-pathsArray of strings that are paths to files of trigger definitions.Optional

This format must be adhered to, in order of preference, either:

  1. As the response to get-hooks, or
  2. Comprising the contents of the hooks.json file

Hook resolution​

The CLI will employ the following algorithm in order to resolve the command to be executed for a particular hook:

  1. If the hooks.json file contains a key for the desired hook, then the CLI will use the command defined under this key directly and end resolution. This might occur in the case where a user has defined an override of an existing hook.
  2. If a key for the desired hook does not exist in hooks.json, the CLI will look for a key in the get-hooks response’s hooks key sub-object. If it exists, the CLI will execute the command for that hook and end resolution.
  3. If neither of the prior attempts succeed, then the CLI should error out with an sdk_hook_not_found error.

Examples​

Simple example with only get-hooks​

{
"hooks": {
// If the SDK implements the below hook, then the hook is responsible for
// returning as output any required vs. optional hooks.
// Alternatively, you can skip implementing get-hooks and just use the below
// contents in your hooks.json. Beware, though! This makes upgrading the SDK a
// more error-prone process because developers need to edit this file directly.
"get-hooks": "deno run -q --unstable --allow-read --allow-net https://deno.land/x/deno_slack_hooks@0.0.4/mod.ts"
}
}

Overriding a specific hook with a custom command​

{
"hooks": {
"get-hooks": "deno run -q --unstable --allow-read --allow-net https://deno.land/x/deno_slack_hooks@0.0.4/mod.ts",
// This is a user-defined custom hook that overrides the default "get-manifest"
"get-manifest": "deno run -q --unstable --config=deno.jsonc --allow-read --allow-net https://deno.land/x/deno_slack_builder@0.0.8/mod.ts --manifest",
// This is a user-defined custom hook that adds new functionality
"custom-hook": "deno run my-custom-hook.ts"
}
}

Complete example returned by SDK from get-hooks script implemented by Bolt (this is in-memory)​

{
"hooks": {
"get-manifest": "deno run -q --unstable --config=deno.jsonc --allow-read --allow-net https://deno.land/x/deno_slack_builder@0.0.8/mod.ts --manifest",
"build": "deno run -q --unstable --config=deno.jsonc --allow-read --allow-write --allow-net https://deno.land/x/deno_slack_builder@0.0.8/mod.ts",
"start": "deno run -q --unstable --config=deno.jsonc --allow-read --allow-net https://deno.land/x/deno_slack_runtime@0.0.5/local-run.ts"
},
"config": {
"watch": {
"filter-regex": "^manifest\\.(ts|js|json)$",
"paths": ["."]
},
"sdk-managed-connection-enabled": "true"
}
}

Complete example returned by SDK from the get-hooks script implemented by Deno SDK (this is in-memory)​

{
"runtime": "deno",
"hooks": {
"get-manifest": "deno run -q --unstable --config=deno.jsonc --allow-read --allow-net https://deno.land/x/deno_slack_builder@0.0.8/mod.ts --manifest",
"build": "deno run -q --unstable --config=deno.jsonc --allow-read --allow-write --allow-net https://deno.land/x/deno_slack_builder@0.0.8/mod.ts",
"start": "deno run -q --unstable --config=deno.jsonc --allow-read --allow-net https://deno.land/x/deno_slack_runtime@0.0.5/local-run.ts"
},
"config": {
"watch": {
"filter-regex": "^manifest\\.(ts|js|json)$",
"paths": ["."]
}
}
}

Terms​

Types of developers​

  1. App Developers are using the CLI and SDK to create and build a project
  2. SDK Developers are building and maintaining the CLI and/or SDKs (controlled by Slack or community-driven)

Types of SDKs​

  1. Slack Deno SDK - Run on Slack
  2. Bolt Frameworks (JavaScript, Python, and Java) - Remote using Slack's Bolt Framework
  3. No SDK at all - Run on Slack, Remote Self-Hosted
  4. Community SDKs, frameworks, and custom apps (e.g. Ruby, Golang, etc), if they exist

Other definitions​

  • hooks.json - The CLI-SDK Interface implemented as a JSON object ({...}) or JSON file (hooks.json).
  • command - This refers to a CLI command, eg. slack doctor
  • hook - This refers to some CLI functionality delegated to the SDK and is defined at the level of the hooks.json file