Skip to content
Logo

Klack documentation

A practical guide to installing Klack, creating local Slack extensions, and using its typed plugin API.

Overview

Klack is an experimental plugin loader for Slack’s Electron desktop app. It inserts a small bootstrap into the application bundle, loads local plugins before Slack finishes starting, and keeps the current renderer sandbox and context isolation settings intact.

Use it to prototype personal workflows, add small interface contributions, or observe Slack’s DOM from code you control. Klack is not a marketplace or a security boundary: every installed plugin is trusted code.

What the MVP supports

  • Reversible app.asar bootstrap installation.
  • Typed TypeScript plugin definitions and zero-build JavaScript definitions.
  • Plugin-scoped buttons, custom DOM mounts, styles, events, timers, and DOM watchers.
  • Automatic recompilation and reload across every open Slack window.
  • A built-in plugin manager, enablement overrides, and automatic resource cleanup.
  • A vanilla launch flag for starting through the bootstrap without injection.

Klack does not yet patch Slack’s remote Webpack modules or provide a plugin marketplace.

Installation

Klack currently supports release installs on macOS for Apple silicon and Intel. It has been developed against Slack 4.50.143 and requires Node.js 24 or newer.

Download Klack

The installer selects the release for your Mac, verifies its SHA-256 checksum, and adds a launcher at ~/.local/bin/klack. It does not patch Slack yet.

curl -fsSL https://klack.sh/install | sh

Quit Slack completely

Use Slack → Quit Slack and confirm no other Slack copy is running. Klack intentionally refuses to modify an app while Slack is open.

Inspect before changing anything

~/.local/bin/klack status
 
# Use a separate app path while testing:
~/.local/bin/klack status --app "$HOME/Applications/Slack-Klack.app"

Install the bootstrap

~/.local/bin/klack install
 
# Or install into a disposable copy:
~/.local/bin/klack install --app "$HOME/Applications/Slack-Klack.app"

To download Klack and patch Slack in one command, add --install to the installer:

curl -fsSL https://klack.sh/install | sh -s -- --install

Restore Slack

Quit every Slack copy, then run:

klack uninstall
 
# Match the custom app path if you used one:
klack uninstall --app "$HOME/Applications/Slack-Klack.app"

For protected canonical installs, Klack restores the complete vendor-signed bundle it preserved. Directly writable copies restore the original ASAR but remain ad-hoc signed.

Build from source

Source builds additionally require pnpm. Clone the repository, run its checks, then use pnpm klack in place of the release launcher.

git clone https://github.com/zygimantass/klack.git
cd klack
pnpm install
pnpm check
pnpm test
pnpm klack status

Quick start

Once Slack launches through Klack, verify the runtime and add a tiny plugin.

1. Confirm Klack loaded

Open Slack’s DevTools with Cmd + Option + I and run:

Klack.version
Klack.list()

The built-in LoadedIndicator plugin also adds a small badge in the lower-right corner of Slack.

2. Create the plugin directory

mkdir -p ~/.klack/plugins

3. Add a plugin

Create ~/.klack/plugins/hello.ts:

import { definePlugin } from "klack/sdk";
 
export default definePlugin({
  name: "Hello",
  description: "My first Klack plugin",
  setup(klack) {
    klack.ui.addButton({
      id: "hello",
      target: "body",
      label: "Hello from Klack",
      onClick(_event, { button }) {
        button.textContent = "Clicked!";
      },
    });
  },
});

Save the file. Klack bundles it in memory and applies the new plugin without a manual build or Slack restart.

How injection works

Klack uses a small bootstrap rather than disabling Electron’s renderer protections.

  1. The installer renames Slack’s app.asar to _app.asar and puts a tiny bootstrap archive in its place.
  2. On macOS, it updates Electron’s embedded ASAR-integrity metadata and ad-hoc signs the outer app while leaving Slack’s signed helpers and frameworks untouched.
  3. The bootstrap starts Klack’s main-process patcher, redirects Electron to Slack’s original package, and wraps BrowserWindow.
  4. Windows using Slack’s primary preload receive a generated file containing Klack followed by Slack’s exact original preload.
  5. Klack injects the renderer and the compiled local plugin set before Slack finishes loading.

Your first plugin

A typed plugin exports a definition created with definePlugin. The helper is intentionally an identity function at runtime; its job is to provide type checking and autocomplete.

plugin.ts
import { definePlugin } from "klack/sdk";
 
export default definePlugin({
  name: "Example", // A-Z, a-z, 0-9, _ and -
  description: "Example plugin",
  version: "1.0.0",
  defaultEnabled: true,
  setup(klack) {
    klack.logger.info("Example started");
 
    klack.cleanup(() => {
      klack.logger.info("Example stopped");
    });
  },
});

The filename is the source identity. A file in the user plugin directory replaces a built-in plugin with the same filename.

JavaScript without a build step

A CommonJS definition also works. It uses the same required name and setup fields:

simple.js
module.exports = {
  name: "Simple",
  setup(klack) {
    klack.logger.info("Simple plugin loaded");
  },
};

UI contributions

Everything registered through klack.ui, klack.dom, klack.events, and klack.timers belongs to the current plugin. Disabling or reloading that plugin removes the contribution automatically.

MethodPurpose
ui.addButton(options)Mount a resilient button at every matching target, with click context and accessible attributes.
ui.mount(target, render, options)Mount an arbitrary element with mount-scoped events and cleanup.
ui.addStyle(css, options?)Inject plugin-owned CSS with an optional style identifier.
ui.hide(selectors, options?)Hide one or more selectors with plugin-owned CSS.
dom.watch(selector, callback)Initialize current and future matches incrementally, with per-element cleanup.
events.on(target, type, listener)Add a listener that is removed automatically.

Targets and positions

A UI target can be a CSS selector, one Element, or a function that returns an element or iterable of elements. Buttons and mounts support four positions:

PositionBehavior
appendAdd inside the target, after its existing children. This is the default.
prependAdd inside the target, before its existing children.
beforeAdd as the target’s previous sibling.
afterAdd as the target’s next sibling.

Mount custom DOM

custom-mount.ts
klack.ui.mount(
  "[data-qa='message_input']",
  ({ cleanup, plugin, target }) => {
    const badge = document.createElement("span");
    badge.textContent = `Mounted by ${plugin}`;
    cleanup(() => {
      klack.logger.debug("Removed from", target);
    });
    return badge;
  },
  { position: "before" },
);

Observe Slack’s DOM

observer.ts
klack.dom.watch("[data-qa='message_container']", (message) => {
  message.classList.add("my-plugin-message");
 
  return () => {
    message.classList.remove("my-plugin-message");
  };
});

Klack reconciles contributions when Slack changes the DOM. If the target or mounted element disappears, cleanup runs and the contribution is recreated when a target returns.

Hot reload

At startup, Klack loads its built-in plugins, then scans ~/.klack/plugins/*.{ts,tsx,js,jsx}. TypeScript and JavaScript modules are bundled in memory, so no generated files appear beside your source.

  • Changes, additions, and deletions trigger a new plugin set.
  • Current plugin resources are cleaned up before the replacement set starts.
  • Every Slack window receives the update.
  • If compilation fails, Klack logs the error and keeps the last working set active.

Use a different plugin directory

KLACK_PLUGIN_DIR="$HOME/code/slack-plugins" open /Applications/Slack.app

The environment variable must be present in the process that launches Slack.

DevTools

Klack adds Toggle DevTools to Slack’s View menu—or to a Klack menu if Slack has no View menu.

Use Cmd + Option + I on macOS or Ctrl + Alt + I on Windows and Linux. The element picker helps find stable selectors for UI targets.

Klack.list()
Klack.disable("Example")
Klack.enable("Example")
Klack.isEnabled("Example")

Enablement choices are stored in Slack renderer local storage under klack:plugin-overrides.

CLI reference

Use klack after a release installation. From a source checkout, prefix the same commands with pnpm.

CommandDescription
klack statusPrint the app path, Klack state, and detected Slack version.
klack installInstall or refresh the bootstrap and generated preload.
klack uninstallRemove Klack and restore Slack’s original application resources.

Options

OptionDescription
--app <path>Operate on a Slack app other than /Applications/Slack.app.
--no-resignSkip macOS ad-hoc signing. The changed app will not launch until it is signed.
-h, --helpPrint command usage.

Launch without injection

To start through the installed bootstrap without Klack’s renderer or local plugins, pass --klack-vanilla directly to Slack:

/Applications/Slack.app/Contents/MacOS/Slack --klack-vanilla

Plugin API

Definition fields

FieldTypeNotes
namestringRequired. Letters, numbers, underscores, and hyphens only.
setupfunctionRequired. Receives KlackApi; register cleanup with the provided methods.
descriptionstringOptional human-readable summary.
versionstringOptional plugin version shown by Klack.list().
defaultEnabledbooleanDefaults to true. A saved user override takes precedence.

Setup context

PropertyDescription
klack.uiThe scoped UI methods described above.
klack.domIncremental selector watching and scoped mutation observers.
klack.eventsPlugin-owned event listeners.
klack.timersPlugin-owned timeouts, intervals, and animation frames.
klack.cleanupRegisters cleanup for direct changes and third-party APIs.
klack.loggerSlack renderer’s console, suitable for namespaced development logs.
klack.versionThe currently running Klack version.

Browser global

The frozen window.Klack object exposes:

Klack.version
Klack.list()
Klack.isEnabled(name)
Klack.enable(name)
Klack.disable(name)

Plugin modules must default-export a definition with name and setup.

Security

Klack is unofficial, unsupported, and intended for local experimentation. Its plugin system is deliberately powerful.

  • Plugins are trusted code. They can inspect or modify anything visible to Slack’s renderer. Never install a plugin you have not reviewed.
  • Plugins are not isolated from one another. Plugin ownership provides lifecycle cleanup, not a security boundary.
  • Installation changes Slack’s bundle. This invalidates Slack’s vendor signature and may violate Slack or workspace policies.
  • Ad-hoc signing has consequences. Keychain, notifications, updates, and device permissions can behave differently.
  • Managed devices may block changes. Get explicit authorization before using Klack on workplace hardware.

Troubleshooting

“A Slack app is running”

Quit every Slack copy, including test copies. Klack checks for the Slack process before install and uninstall operations.

The modified app does not launch

If you passed --no-resign on macOS, the bundle needs a valid signature before it can launch. Re-run installation without that option or restore Slack.

Browser sign-in opens another Slack copy

macOS chooses a slack:// handler by bundle identifier, not app path. Quit all other Slack copies during sign-in or use the canonical /Applications/Slack.app.

A plugin edit does not apply

Open DevTools and inspect the console for a compile error. Klack intentionally keeps the last working plugin set active when a new source file fails to bundle.

Slack updated and Klack disappeared

Slack updates replace application resources. Re-run the release download if you want to update Klack too, quit Slack, and run klack install again.

Back to Klack home · Open an issue