#nodejs
#monday
#tech
#guide
15 min read

How to Build monday.com Apps

Andriy Obrizan

The unbeatable power of the Monday.com platform comes from its customization capabilities.

Monday already has hundreds of integrations with other popular platforms in different fields, but it also enables everyone to develop their own by creating an app. Monday supports both private and public apps that extend the platform with custom features:

  • Board Views for visualizing and editing your boards
  • Item Views for displaying and updating the board items
  • Dashboard Widgets for visualizing or updating data on multiple boards
  • Integrations to enable new automations and sync data with other services
  • Workspace Templates to package boards and dashboard

What is Monday App

A Monday app is a package of features built with the Monday apps framework that provides new functionality. Each account that installs the app can use all its features on their boards and dashboards.

Apps features are available together with built-in ones when adding an integration or automation to your board. You can add custom widgets to any dashboard like any regular widget or change the board view to any other provided by the app as long as it’s installed on your account.

You can install private apps exclusive to your Monday account to integrate with other systems in your organization, build custom forms and reports, and automate your custom workflow.

Monday.com also builds its marketplace that will allow you to sell your public apps to more than 100k teams that already use the platform.

Anyone can build the app, and the possibilities are endless.

How Monday App Works

Monday Apps Framework is the foundation of every app.

Most of the guides, examples, and their SDK are in Javascript and React, but of course, you can use Typescript too. Monday aims to be platform-agnostic, and at the time of writing, the SDK is also available for Ruby and Kotlin. For UI, they also have a Design System that app developers should follow to be consistent with the excellent Monday UI and React Components package to speed up the development.

Your app has to be accessible over the internet for the Monday platform to communicate with it. UX elements are rendered from your server directly inside Monday views using iframes. The Monday platform will also send a request to your app in the following cases:

  • To subscribes to a custom trigger when a user adds a recipe to a board.
  • To unsubscribe from a custom trigger when a user removes a recipe.
  • To call you custom action when the recipe triggers.
  • To retrieve remote options for a dropdown field.
  • To retrieve field definitions for Item Mapping.
  • When a user approves your app permissions and get’s redirected to your app for authorization.

Security is one of the main concerns when developing a Monday app. The app must support HTTPS, proving SSL encryption to prevent sniffing your data and save from many different attacks possible with plain HTTP.

Every request to an app backend by Monday platform also contains a JSON web token singed by the apps Signing Secret that should be verified to prevent fraud requests by third parties. It also includes a short-lived API key to use the Monday API when processing this request. When invoking custom triggers webhooks, you also have to use your apps Signing Secret for the platform to validate if it’s a legitimate request.

How to Build a Monday.com App

First of all, you will need a Monday account. Even a free Developer account is enough, and you can sign up for it here.

Once you’ve logged in to your account, click on the avatar in the bottom left corner and chose the Developers section. There you’ll see My Apps and a Create App button. You can name your new app, give it a short description, logo, and color. The Basic Information screen also has the Client Id and Client Secret used for OAuth and Signing Secret to verify requests on the back-end.

The apps screen is the main point of apps configuration. You can make the app public or add it only to your account under the Manage section. It also allows adding collaborators that will be able to edit and use the app, managing OAuth scopes and permissions, and finally add and edit features of your app.

Install an app to your account by clicking the button in the Install section.

Building Monday Integration App

The extension possibilities provided by the platform are enormous and won’t fit in a single blog article. This tutorial will be testing the waters by building an integration app that will showcase a custom action, custom trigger, and item mapping capabilities.

Integration apps provide recipes that do some action when the trigger event happens. The user sees each recipe as a single sentence.

For example, Gmail integration provides a recipe

When status changes to something, send an email to someone.

Here, “When status changes to something” is the trigger part, and it’s a built-in trigger, and the integration provides custom action “send an email to someone.” Bold words are input fields that the user must provide when adding a recipe to his board.

Another Gmail recipe

When an email is received, create an item in group

has a custom trigger that will signal the Monday platform to invoke an action when an email that meets the filter criteria is received.

Item Mapping enables data synchronization between Monday and external systems. For example, a Todoist integration

When a task is created in this project, create an item and sync future changes from Todoist

will sync data from Todoist to Monday board. It leverages item mapping inside a custom trigger that will tell the platform when a new task is created on Todoist.

You can also make a synchronization recipe in another direction, consuming data from Monday to do something on an external system. It’s accomplished by creating custom actions with item mapping as one of the input fields and using a trigger that supports it.

You can read more about Monday apps and how to build them in the Monday Developers portal.

Creating the Project

We will scaffold our test project using Monday’s monday-cli package:

npx @mondaydotcomorg/monday-cli scaffold run ./ quickstart-integrations

It will download everything, install the dependencies, and run the application.

Now copy your app’s signing secret from Monday and paste it into the .env file for MONDAY_SIGNING_SECRET. We would also suggest adding .env to .gitignore if you’ll forget that it contains the secret when adding your project to git.

The quickstart-integration project already has an integration that transforms text from an input column to uppercase and sets it’s to an output column. You can check the Monday integration Quickstart Guide to see how it works.

Building Your First Custom Action

Monday platform invokes custom action by requesting its run URL when a trigger occurs. The handler can do virtually anything and has access to the Monday API with the app permissions. It’s required to return status 200 Ok on time.

We’ll create a simple action handler that will print the request body to the console.

In the controllers folder add demo-controller.js file with a single function:

function printRequest(req, res) {
  console.log('printRequest', JSON.stringify(req.body));
  return res.status(200).send({});
}

module.exports = {
  printRequest,
};

Add a demo.js file with routes for this controller in the routes folder:

const express = require('express');
const router = express.Router();

const demoController = require('../controllers/demo-controller.js');
const authenticationMiddleware = require('../middlewares/authentication').authenticationMiddleware;

router.post('/demo/print-request', authenticationMiddleware, demoController.printRequest);

module.exports = router;

Notice that it uses authentication middleware from the quickstart guide to validate the requests.

Now add the routes to the main router in routes/index.js:

const demoRoutes = require('./demo');

router.use(demoRoutes);

Our most straightforward action handler is ready. Now let’s configure our custom action in the Monday App.

First, click on the + button next to features and choose Integrations to add an integration. Enter the name and past the link from the console output of the app. In case you’ve restarted the app, you can always go to http://localhost:4040 and find the ngrok URL in the Status section.

In the integration’s screen got to Custom Blocks and create new Action. Enter an action name and use https://your-url.ngrok.io/demo/print-request as the Run URL. Add an input field of type item with the default name itemId:

Now let’s add a recipe with this action and one of the built-in triggers. Go to the Recipes screen and create a new recipe. Choose “When an Item is Created” for the trigger, write any sentence you’ll like, and select Context for the boardId. Next, choose the action you’ve just created, write some sentence, and choose Trigger Output / itemId for the input fields:

Now click the Create Request button, and your simple recipe is ready.

Add integration to your board by clicking on the Integrate button in the top right corner of the board view. Your app should be available in the list by its name, and our recipe doesn’t have any parameters.

Now add an item to the board and you should see something like this in the console:

printRequest
{
  "payload": {
    "blockKind": "action",
    "blockMetadata": null,
    "inboundFieldValues": {
      "itemId": 958288242
    },
    "inputFields": {
      "itemId": 958288242
    },
    "recipeId": 406056,
    "integrationId": 29045102
  }
}

Your first integration is working, isn’t that cool? Of course, it doesn’t do anything useful but still lets you explore how the Monday platform works. You can use Monday API to get and manipulate data on the platform or write some code to do other useful things.

This handler doesn’t depend on the specific input, so you can create different actions with it in the Integrations UI, combine them with varying triggers in recipes, and explore the platform.

Creating a Custom Trigger

Now that we played around with our action let’s create the horrible, most straightforward trigger. Monday already has a Recurring trigger that enables you to run your action on the scheduler. But the highest frequency is daily, which is not cool. We want to overload the platform and drain the account’s action usage by running actions a lot more frequently.

Trigger needs subscribe and unsubscribe handlers and must notify the platform when it’s triggered using webhooks. The code will be a bit more complex than our print action, so let’s create a demo-service.js in the services folder:

const fetch = require('node-fetch');

const triggers = {};
let nextTriggerId = 1;

class DemoService {
 static addIntervalTrigger(webhookUrl, interval) {
   if (typeof interval !== 'number' || interval < 1000) throw new Error('Bad interval');

   const intervalId = setInterval(async () => {
     try {
       const body = JSON.stringify({
         trigger: { outputFields: { columnValue: { value: new Date().toISOString() } } },
       });

       const resp = await fetch(webhookUrl, {
         method: 'POST',
         body,
         headers: {
           authorization: process.env.MONDAY_SIGNING_SECRET,
           contentType: 'application/json',
         },
       });

       const text = await resp.text();

       console.log('Triggered: ', body, 'response', text);
     } catch (e) {
       console.error('Oops, trigger failed', e);
     }
   }, interval);

   const triggerId = nextTriggerId++;
   triggers[triggerId] = intervalId;

   console.log('addIntervalTrigger, triggerId=', triggerId);
   return triggerId;
 }

 static removeIntervalTrigger(triggerId) {
   console.log('removeIntervalTrigger triggerId=', triggerId);
   if (typeof triggerId !== 'number') throw new Error('Bad trigerId');

   const intervalId = triggers[triggerId];
   if (intervalId === undefined) return;

   clearInterval(intervalId);
   delete triggers[triggerId];
 }
}

module.exports = DemoService;

This service can create interval triggers that will post the current date&time in ISO format to a passed webhook URL using Monday platform trigger output schema. The app must send it’s Signing Secret in the Authorization header for the platform to verify the webhook requests.

This code is for learning purposes only, and you shouldn’t use it in a real application. When the process restarts, all existing triggers will be lost, they won’t fire, and even the id sequence would start over again, causing some issues.

Now let’s add this service to our demo-controller.js:

const demoService = require('../services/demo-service');

function printRequest(req, res) {
 console.log('printRequest', JSON.stringify(req.body));
 return res.status(200).send({});
}

function subscribeMinutes(req, res) {
 console.log('subscribeMinutes', JSON.stringify(req.body));
 const {
   payload: {
     inputFields: {
       duration: { seconds, minutes },
     },
     webhookUrl,
   },
 } = req.body;

 if (seconds === 'undefined' && minutes === 'undefined') throw new Error('Duration not supported');
 const interval = (minutes || 0) * 60000 + (seconds || 0) * 1000;

 const intervalId = demoService.addIntervalTrigger(webhookUrl, interval);

 return res.status(200).send({ webhookId: intervalId });
}

function unsubscribeMinutes(req, res) {
 console.log('unsubscribeMinutes', JSON.stringify(req.body));
 const {
   payload: { webhookId },
 } = req.body;
 demoService.removeIntervalTrigger(webhookId);
 return res.status(200).send({ result: 'Thanks for stopping me!' });
}

module.exports = {
 printRequest,
 subscribeMinutes,
 unsubscribeMinutes,
};

This code converts Monday’s duration object to milliseconds interval. Notice that only seconds and minutes are supported. It also logs the request body for debugging purposes. A trigger subscribe call receives the following payload:

  • subscriptionId - unique identifier of the specific subscription.
  • inputFields - an object containing all the input fields configured in Monday UI
  • webhookUrl - callback URL to call when the trigger occurs
  • recipeId - unique id of the recipe in your app across different accounts

The subscribe call should return status 200, Ok with JSON {“webhookId”: any}. webhookId is a unique identifier of the app’s subscription that Monday will pass to the unsubscribe URL. It should be persistent in a real application, along with all the information required to handle trigger events and unsubscribe from them.

Let’s quickly add a route for both actions in routes/demo.js:

router.post('/demo/minutes-subscribe', authenticationMiddleware, demoController.subscribeMinutes);
router.post('/demo/minutes-unsubscribe', authenticationMiddleware, demoController.unsubscribeMinutes);

Now, let’s create a new trigger in the Monday UI’s integration screen:

Finally, create a new recipe with this trigger:

Notice that the sentence contains {duration, duration}. It’s a particular format for recipe sentence fields. Text displayed to the user goes first, and the input field name is second.

For action, let’s choose Create item in group and select Context for the boardId:

We’ve created the first recipe with our custom trigger. After adding it to the board, we should see items added on a regular interval:

Now remove the trigger to see the unsubscribe request in the console and stop spamming the platform.

Getting Data From Monday Using Item Mapping

With item mapping, you can either push data to Monday using custom triggers or receive data from the platform in your custom actions. The trigger must have itemValues as one of the output fields to be suitable for item mapping. At the time of writing, these built-in triggers support mapping:

  • When an item is created
  • When a column changes
  • When status changes
  • When status changes to something

Item mapping lets the user map data between Monday items and entities on an external platform. Custom entity definition contains dependency fields that should be provided by the recipe and an URL to get all mappable fields. Dependency fields along with the side (“source” or “target”) will be passed to this URL when a user is about to configure the mapping. The server should respond with status 200 and the list of entity fields. You can read about the concept and supported field types in the Item Mapping and Custom Entities tutorial on Monday’s developers portal.

Let’s extend our demo-controller.js:

function getDemoFieldDefs(req, res) {
 console.log('getDemoFieldDefs', JSON.stringify(req.body));

 return res.status(200).send([
   { id: 'field1', title: 'Field 1', outboundType: 'text', inboundTypes: ['text', 'text_array', 'text_with_label'] },
   { id: 'field2', title: 'Field 2', outboundType: 'date', inboundTypes: ['empty_value', 'date', 'date_time'] },
 ]);
}

module.exports = {
 printRequest,
 subscribeMinutes,
 unsubscribeMinutes,
 getDemoFieldDefs,
};

Add a route in routes/demo.js:

router.post('/demo/field-defs', authenticationMiddleware, demoController.getDemoFieldDefs);

Create a new Field Type in the Integrations screen and select Type Dynamic Mapping:

We will create a recipe that receives data from Monday. To do that, we would need a slightly different action. Let’s make it:

Notice that our Demo Entity is now available as a field type.

We can create a recipe with this action. Let’s choose one of the built-in triggers that support item mapping, for example, “When an item is created” and our new action:

We add our new entity in the recipe sentence so that the user could configure the mapping when adding a recipe:

You should see that getDemoFieldDefs gets called during recipe configuration. More importantly, you should see the printRequest calls with our mapping when adding an item:

printRequest 
{
  "payload": {
    "blockKind": "action",
    "blockMetadata": null,
    "inboundFieldValues": {
      "demoEntity": {
        "field1": "The New item and some text"
      }
    },
    "inputFields": {
      "demoEntity": {
        "field1": "The New item and some text"
      }
    },
    "recipeId": 406614,
    "integrationId": 29058126
  }
}

You’re now able to get data from monday.com and do whatever you want with it. Creating a trigger that will post a custom entity to the platform isn’t much harder. It should just send an object to the webhook URL with all the entity fields and values of their outbound types.

Conclusion

Monday apps enormously extend the possibilities of the platform by providing custom UI elements and integrations. Those building blocks are fully interchangeable with built-in ones and are available everywhere alongside them.

We’ve just scratched the surface with our integration app example in this article. With a real app, you can connect to any third-party system, build custom recipes, report widgets, or even replace the whole board view.

When something’s missing on the Monday work OS, you can always add it with an appropriate app.