Create a new Slack app


Setting sails on the seas of a Slack app

Ahoy matey! Welcome to the ship that is "Building a Run on Slack app"!

We're setting sail on our maiden voyage – creating and deploying a new Slack app – and would love to show you the ropes. By the end of the expedition, we'll have an app to parrot personal "kudos" throughout a workspace.

A curiosity of the waters is all you need to sail the open seas, so climb aboard to discover what it takes to be captain of your own ship – that is, developing your own Slack app!

Step 1Charting the seas

Take a captain's view of the expedition that is building a Slack app.

  • Comprehending the manifest

    Before undocking, knowing the cargo of our ship is always a good first check. An inspection of the manifest.ts reveals a collection of workflows, functions, and other app-related attributes.

    // manifest.ts
    import { Manifest } from "deno-slack-sdk/mod.ts";
    import FindGIF from "./functions/find_gif/definition.ts";
    import { ShareKudos } from "./workflows/share_kudos.ts";
    export default Manifest({
      name: "Kudo",
      description: "Brighten someone's day with a heartfelt thank you",
      icon: "assets/icon.png",
      functions: [FindGIF],
      workflows: [ShareKudos],
      outgoingDomains: [],
      botScopes: ["commands", "chat:write", "chat:write.public"],

    The details of this expedition’s manifest are soon to follow, but an explanation of workflows, functions, and even triggers may shine a light through these foggy concepts.

  • Defining the building blocks of an app

    A workflow is a collection of processes, executed in response to certain events. In nautical terms, repairing the hull is a common workflow that happens when yours truly runs the ship aground.

    The processes that make up a workflow are better known as "functions". Functions can be built-in, everyday actions such as sending a message or opening a form, or made custom, defined by your own logic with various inputs and outputs.

    On the sea, raising the sails and scrubbing the decks are oft run functions.

    Triggers act as inspiration for the actions of a workflow, defining when a workflow is invoked. Certain events in Slack (such as a clicked link or pressed button) or a schedule of specified time intervals can serve as triggers for a workflow.


    After taking in the view from the mast, we’re ready to set sail!

Step complete!

Step 2Weaving a workflow

Sequence the steps behind sending an individualized kudo message.

  • Planning a workflow

    Parroting a personalized message is a task often given to "Kudo", the peaceful app under construction on this expedition, so a workflow called "Share kudos" was created in workflows/share_kudos.ts.

    // workflows/share_kudos.ts
    import { DefineWorkflow, Schema } from "deno-slack-sdk/mod.ts";
    const ShareKudos = DefineWorkflow({
      callback_id: "share_kudos_workflow",
      title: "Share kudos",
      description: "Acknowledge the impact someone had on you",
      input_parameters: {
        properties: {
          interactivity: {
            type: Schema.slack.types.interactivity,
        required: ["interactivity"],
  • Sketching the steps

    Kudo found that the task of sharing kudos can be accomplished in three steps – collecting information about a message, finding the right GIF, then sharing the love. These were added as steps to a workflow, to be executed in this order each time.

    These steps are composed of built-in functions and a custom function that help Kudo collect and post kudos to a channel.

    The built-in functions OpenForm and SendMessage are called to open a form and send a message. FindGIF is our own custom function that finds a GIF related to the "vibe" someone gives a message.

    // workflows/share_kudos.ts
    import FindGIF from "../functions/find_gif/definition.ts";
    /* Step 1. Collect message information */
    const kudo = ShareKudos.addStep(Schema.slack.functions.OpenForm, { ... });
    /* Step 2. Find the right GIF */
    const gif = ShareKudos.addStep(FindGIF, { ... });
    /* Step 3. Share the love */
    ShareKudos.addStep(Schema.slack.functions.SendMessage, { ... });
  • Connecting the dots

    Each step also accepts an input object, allowing the outputs of one step to become inputs to another in a chain of functions.

    Notice how the OpenForm step contains the interactivity context of the workflow and FindGIF uses kudo_vibe, an output parameter of the OpenForm step:

    // workflows/share_kudos.ts
    const kudo = ShareKudos.addStep(Schema.slack.functions.OpenForm, {
        title: "Write someone kudos",
        interactivity: ShareKudos.inputs.interactivity,
        submit_label: "Share",
        description: "Continue the positive energy through your written word",
        fields: {
          elements: [{
          }, {
            name: "kudo_vibe",
            title: 'What is this kudo\'s "vibe"?',
            description: "What sorts of energy is given off?",
            type: Schema.types.string,
          required: [ ... ]
    const gif = ShareKudos.addStep(FindGIF, {
      vibe: kudo.outputs.fields.kudo_vibe

    These function inputs and outputs can seem like dots scattered across a map, but you're soon to draw the connections needed to plan a successful voyage!

Step complete!

Step 3Customizing a function

Add custom code as a step within a workflow, expanding the horizons of our expedition.

  • Defining the custom function

    The FindGIF function is unique to Kudo and defined in functions/find_gif/definition.ts, where inputs, outputs, and other attributes are described. This definition allows FindGIF to be used within workflows, as when sharing a kudo!

    // functions/find_gif/definition.ts
    import { DefineFunction, Schema } from "deno-slack-sdk/mod.ts";
    const FindGIF = DefineFunction({
      callback_id: "find_gif",
      title: "Find a GIF",
      description: "Search for a GIF that matches the vibe",
      source_file: "functions/find_gif/mod.ts",
      input_parameters: {
        properties: {
          vibe: {
            type: Schema.types.string,
            description: "The energy for the GIF to match",
        required: ["vibe"],
      output_parameters: {
        properties: {
          URL: {
            type: Schema.types.string,
            description: "GIF URL",
          alt_text: {
            type: Schema.types.string,
            description: "description of the GIF",
        required: ["URL"],
    export default FindGIF;
  • Finding a GIF

    Implementation of this function is found in the functions/find_gif/mod.ts file, where input from the form is parsed and converted into a random GIF that's obtained from the GIF catalog.

    // functions/find_gif/mod.ts
    import { SlackFunction } from "deno-slack-sdk/mod.ts";
    import FindGIF from "./definition.ts";
    import gifs from "./gifs.json" assert { type: "json" };
    interface GIF {
      URL: string;
      tags: string[];
    // return a GIF that matches a certain vibe
    const matchVibe = (vibe: string): string => {
      const matches = gifs.filter((g: GIF) => g.tags.includes(vibe.toLowerCase()));
      if (matches.length === 0) {
        return "";
      const randomGIF = Math.floor(Math.random() * matches.length);
      return matches[randomGIF].URL;
    export default SlackFunction(FindGIF, { inputs }) => {
      const { vibe } = inputs;
      const gif = matchVibe(vibe);
      return { outputs: gif };
  • A collection of GIFs

    The GIF catalog – the treasure of this expedition – is listed in functions/find_gif/gifs.json, but could safely be stored in a datastore if you wish to explore more!

    // functions/find_gifs/gifs.json
      "URL": "",
      "tags": ["excited", "dance", "monkey"]
    }, {
      "URL": "",
      "tags": ["casual", "pirate", "cat"]
    }, {
      "URL": "",
      "tags": ["happy", "bliss", "dog"]
    }, {
      "URL": "",
      "tags": ["happy", "cheer", "whistle", "mickey"]
    }, {
      "URL": "",
      "tags": ["strong", "popeye"]
    }, {
      "URL": "",
      "tags": ["happy", "dance", "simpsons"]
    }, {
      "URL": "",
      "tags": ["casual", "beach"]
Step complete!

Step 4Invoking with a trigger

Create an entry point into the workflow with triggers for a convenient way to share kudos.

  • Create a link trigger

    The workflows and functions discussed above cannot be invoked until a trigger is created, similar to how a sailor may need motivation to get started on a workflow.

    To invoke Kudo’s "share kudo" workflow, a Link Trigger is defined in triggers/share_kudos.ts and created using the Slack CLI with the slack trigger create command, generating a Shortcut URL that can be clicked from a channel.

Step complete!

Step 5Ending our expedition

After sailing the seas of a Slack app, our journey now comes to an end.

  • What an adventure!

    Returning back to harbor, we can reminisce about the journey of sharing a kudo; recalling that functions compose workflows and workflows are invoked by triggers.

    With these ropes, you're ready to take the seas yourself! There are many more knots to learn while on these waters, but you'll stay afloat just fine with what you now know!

    Bon voyage, captain!

Step complete!

Was this page helpful?