Create a 'Hello World' app


In this tutorial, we're going to create and take a tour of an app based on the "Hello World" template.

We'll create an app and then interact with in our workspace, then review the components that made that interaction possible.

Before we begin, be sure you should have the slack CLI installed, and if you run slack auth list, you see your workspace listed as ACTIVE. If you don't, follow the Quickstart guide and then come on back.

Step 1Create an app using the Slack CLI

  • Scaffold a new app

    We're going to create an app using our "Hello World" app template to showcase some basic features of our next-generation platform.

    Let's go ahead and build a new app using the create command, passing along hello-world as the name of our new app:

    $ slack create hello-world

    When you enter the command, a selector prompt will ask which template you want to use. Select the "Hello World" template:

    $ slack create hello-world
    ? Select a template to build from:
    > Hello World
      A simple workflow that sends a greeting
      Scaffolded project
      A solid foundation to start building upon
      Blank starter project
      A, well.. blank project

    Once selected, you'll see some output confirming that an app named hello-world was successfully created in a folder with the same name.

    Let's head into that directory:

    $ cd hello-world

    In the next section, we'll take a look at the file and folder structure of our newly created app to get a feel for where things go and how they all work together.

Step complete!

Step 2Running the demo

  • Creating a trigger

    Inside the triggers folder, there's a file called greeting_trigger.ts.

    This is a trigger configuration file. It's used by the CLI to create a type of trigger called a "Shortcut trigger." Shortcut triggers are a new kind of interactive element in Slack that gives you the power of a Workflow with the ease and accessibility of a URL.

    Since this is a working sample app, it comes pre-baked with working code. The only thing we need to do so that the app will work correctly is to create the one trigger that it uses.

    To create the trigger, we'll use the trigger create command:

    $ slack trigger create --trigger-def "triggers/greeting_trigger.ts"

    You'll be asked to select the workspace in which to install the app. Since we're developing locally today, we'll select the option that has (dev) appended to the app name. This is our app's "development" version.

    You'll know the trigger was created successfully when you see something like this:

    ⚡ Trigger created
       Trigger ID:   Ft01234567
       Trigger Type: shortcut
       Trigger Name: Send a greeting
       Shortcut URL:

    See that "Shortcut URL" in the output? Copy that URL from the terminal output—that's going to be how we start our workflow—and head to the next section to try it out!

  • Starting a local development server

    With our Shortcut URL in hand (or, rather, in our clipboard), paste it into any public channel in your workspace. This will unfurl into a card with a Run button. You will also see your shortcut in the bookmarks bar in the Workflows folder.

    If you try to interact with your app right now, nothing will happen since our local development server isn't running yet. So let's get our local server running with the run command:

    $ slack run

    Once your development server is running, click the run button on the unfurl card or select your shortcut's name from the Workflows folder in the bookmark bar to start the workflow assigned to that trigger.

    In the modal that pops up, fill out the form and click the Send greeting button.

    In the channel you executed the workflow from, you'll see a new message for the user you selected in the form.

    So far we have:

    • created an app based on the "Hello World" app template
    • created a trigger to interact with our app, which produced a Shortcut URL
    • started a local development server

    But we've only scratched the surface of what's happening and how. The trigger you created is configured to call a Workflow, and each Workflow is configured to call one or more Functions.

    In the next section, let's dive in to see how everything is wired together!

Step complete!

Step 3Taking a look around: workflows

  • Triggers invoke workflows

    Let's open up the Trigger file triggers/greeting_trigger.ts, and see how it relates to the rest of the app:

    import { Trigger } from "deno-slack-api/types.ts";
    import GreetingWorkflow from "../workflows/greeting_workflow.ts";
    const greetingTrigger: Trigger<typeof GreetingWorkflow.definition> = {
      type: "shortcut",
      name: "Send a greeting",
      description: "Send greeting to channel",
      workflow: "#/workflows/greeting_workflow",
      inputs: {
        interactivity: {
          value: "{{data.interactivity}}",
        channel: {
          value: "{{data.channel_id}}",
    export default greetingTrigger;

    Triggers take inputs and pass them along to an assigned Workflow. Our trigger above is configured to invoke the greeting_workflow; notice the special string formatting for calling the workflow's name.

    When you create a trigger using a trigger definition like this one, your app will look for that workflow in all the workflows that you have registered in your manifest.

    Let's go back to the parent folder of our project and open up the manifest.ts file next:

    import { Manifest } from "deno-slack-sdk/mod.ts";
    import GreetingWorkflow from "./workflows/greeting_workflow.ts";
    export default Manifest({
      name: "hello-world",
        "A sample that demonstrates using a function, workflow and trigger to send a greeting",
      icon: "assets/icon.png",
      workflows: [GreetingWorkflow],
      outgoingDomains: [],
      botScopes: ["commands", "chat:write", "chat:write.public"],

    Here you can see the workflows property in your app's manifest. This is where you will list out all the Workflows that your app should care about.

    Notice at the top there's something that we saw in the trigger file, too: importing GreetingWorkflow.

    So the manifest registers the workflow, and then the trigger is configured to invoke it. With that in mind, let's go ahead and open up that workflow to see what's going on:

    // workflows/greeting_workflow.ts
    import { DefineWorkflow, Schema } from "deno-slack-sdk/mod.ts";
    import { GreetingFunctionDefinition } from "../functions/greeting_function.ts";
    // Here we define a new workflow called GreetingWorkflow, configuring its 
    // required input parameters. Note how one of the input parameters is of type 
    // `Schema.slack.types.interactivity`:
    const GreetingWorkflow = DefineWorkflow({
      callback_id: "greeting_workflow",
      title: "Send a greeting",
      description: "Send a greeting to channel",
      input_parameters: {
        properties: {
          interactivity: {
            type: Schema.slack.types.interactivity,
          channel: {
            type: Schema.slack.types.channel_id,
        required: ["interactivity"],
    // Once the workflow is defined, we can "add steps" to the workflow with the 
    // titular method `addStep`. In this case, we're using the built-in function 
    // `OpenForm` to leverage that interactivity input parameter in order to 
    // interact with the user (with a form):
    const inputForm = GreetingWorkflow.addStep(
        title: "Send a greeting",
        interactivity: GreetingWorkflow.inputs.interactivity,
        submit_label: "Send greeting",
        fields: {
          elements: [{
            name: "recipient",
            title: "Recipient",
            type: Schema.slack.types.user_id,
          }, {
            name: "channel",
            title: "Channel to send message to",
            type: Schema.slack.types.channel_id,
          }, {
            name: "message",
            title: "Message to recipient",
            type: Schema.types.string,
            long: true,
          required: ["recipient", "channel", "message"],
    // After the first step, which is to send the form, we use the form data
    // in subsequent steps. Here, we are passing it along as inputs to 
    // a custom function defined by `GreetingFunctionDefinition`. You'll note that 
    // we also imported this into our workflow file. 
    const greetingFunctionStep = GreetingWorkflow.addStep(
        recipient: inputForm.outputs.fields.recipient,
        message: inputForm.outputs.fields.message,
    // Finally, we're using another built-in function called `SendMessage` to 
    // send the results of our custom function to a channel specified by the 
    // user filling out the form:
    GreetingWorkflow.addStep(Schema.slack.functions.SendMessage, {
      message: greetingFunctionStep.outputs.greeting,
    export default GreetingWorkflow;

    So the trigger invokes the workflow, and the workflow invokes one or more custom and built-in functions.

    The workflow is also registered in the app's manifest.

    Adding custom functions to your app is very similar to adding workflows, except you don't have to register them in the manifest; any functions that your workflows use are automatically registered with your app.

    In our next and last section, let's take a look at the custom function that our workflow uses.

Step complete!

Step 4Taking a look around: functions

  • Workflows invoke functions

    Inside the functions folder we'll find the star of the show, our "Greeting" function, in greeting_function.ts.

    This file contains both the function definition and its implementation.

    At the top, just after the imports, is the definition:

    import { DefineFunction, Schema, SlackFunction } from "deno-slack-sdk/mod.ts";
    export const GreetingFunctionDefinition = DefineFunction({
      callback_id: "greeting_function",
      title: "Send a greeting",
      description: "Send greeting to channel",
      source_file: "functions/greeting_function.ts",
      input_parameters: {
        properties: {
          recipient: {
            type: Schema.slack.types.user_id,
            description: "Greeting recipient",
          message: {
            type: Schema.types.string,
            description: "Message to the recipient",
        required: ["message"],
      output_parameters: {
        properties: {
          greeting: {
            type: Schema.types.string,
            description: "Greeting for the recipient",
        required: ["greeting"],

    Notice how it looks very similar to our workflow definition; we have inputs, outputs, and the option to mark parameters required.

    Below that is its implementation:

    export default SlackFunction(
      ({ inputs }) => {
        const { recipient, message } = inputs;
        const salutations = ["Hello", "Hi", "Howdy", "Hola", "Salut"];
        const salutation =
          salutations[Math.floor(Math.random() * salutations.length)];
        const greeting =
          `${salutation}, <@${recipient}>! :wave: Someone sent the following greeting: \n\n>${message}`;
        return { outputs: { greeting } };

    If you go back to the workflow file, you'll see that when this function is added as a step to the workflow, the first context property we pass along is its definition. This gives us strong typing right out of the box for our custom functions.

    With our trigger calling our workflow, our workflow calling our functions, and our functions automating things in our workspace, we've now seen a very small sampling of what our next-generation platform can do!

Step complete!

Step 5Wrapping up

  • There's so much more to explore!

    In this tutorial we've taken a tour of the "Hello World" sample app. You can create your own "Hello World" sample app by selecting it from the list of provided samples when you run slack create.

    This example only shows one trigger, workflow, and function, and is limited to essentially passing a string around. But the next-generation platform at Slack is packed full of workspace automation features, and they're all ready to be explored based on what you want to create!

    We hope you enjoy your journey here, and if you've got some ideas or thoughts you'd like to share, please feel free and encouraged to fill out our Developer Survey!

Step complete!

Was this page helpful?