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).