Skip to main content

Slash Commands

Register slash commands with registerCommand. Each command gets a typed argument parser and an optional description (used in the Slack manifest and help text).

Basic Command​

The simplest form receives the raw command text as a String:

def basicCommand(slack: SlackGateway[IO] & SlackSetup[IO]): IO[Unit] =
slack.registerCommand[String]("status", "Check system status") { cmd =>
IO.pure(CommandResponse.Ephemeral(s"All systems operational. You said: ${cmd.args}"))
}

Command Responses​

Handlers return a CommandResponse:

ResponseBehavior
CommandResponse.Ephemeral(text)Reply visible only to the user who ran the command
CommandResponse.InChannel(text)Reply visible to everyone in the channel
CommandResponse.SilentNo visible reply

Derived Case Class Parsing​

For commands with multiple arguments, derive CommandParser from a case class. Each field needs a CommandArgCodec instance (built-in for String, Int, Long, Double, Float, BigDecimal, Boolean, and Option[T]):

case class ScaleArgs(service: String, replicas: Int) derives CommandParser

def derivedCommand(slack: SlackGateway[IO] & SlackSetup[IO]): IO[Unit] =
// /scale api 3 → parses to ScaleArgs("api", 3)
slack.registerCommand[ScaleArgs]("scale", "Scale a service") { cmd =>
IO.pure(
CommandResponse.Ephemeral(
s"Scaling *${cmd.args.service}* to *${cmd.args.replicas}* replicas.",
),
)
}

Custom CommandParser​

For single-argument commands where you want to validate or constrain the input, define a CommandParser instance manually:

opaque type ServiceName <: String = String

object ServiceName {
private val valid = Set("api", "web", "worker")

def parse(text: String): Either[String, ServiceName] = {
val name = text.trim.toLowerCase
if (valid.contains(name)) Right(name)
else Left(s"Unknown service '$text'. Valid: ${valid.mkString(", ")}")
}
}

given CommandParser[ServiceName] with {
def parse(text: String): Either[String, ServiceName] = ServiceName.parse(text)
override def usageHint: String = "[service name]"
}
def customParserCommand(slack: SlackGateway[IO] & SlackSetup[IO]): IO[Unit] =
// /service-status api → "Service api is healthy."
// /service-status foo → error: "Unknown service 'foo'. Valid: api, web, worker"
slack.registerCommand[ServiceName]("service-status", "Check service status") { cmd =>
IO.pure(CommandResponse.Ephemeral(s"Service *${cmd.args}* is healthy."))
}

When parsing fails, the error message is shown to the user as an ephemeral response.

Usage Hints​

The usageHint from your CommandParser is included in the generated Slack manifest, so users see help text when typing your command. For derived case classes, hints are auto-generated from field names (e.g. [service] [replicas]).

You can also provide an explicit usage hint when registering:

def usageHint(slack: SlackGateway[IO] & SlackSetup[IO]): IO[Unit] =
slack.registerCommand[String]("deploy", "Deploy a service", usageHint = "[service] [version]") { cmd =>
IO.pure(CommandResponse.Ephemeral(s"Deploying ${cmd.args}"))
}

Note that usage hints are part of the Slack app manifest — they're not dynamic. If you change a hint, you need to update the manifest in your Slack app settings (the validateSetup call will tell you when this is needed).