Integrate Google Forms and Trello

This post details a very common type of automation project: the integration between online services.

We’ll see how to get answers submitted via Google Forms and create a customized Trello board for project management. All the processing is done in the background, and the user is just “automagically” invited to a custom board created immediately after answering a form.

This tutorial covers how to:

  • Connect to APIs using the OAuth1 library for authorization
  • Trigger an event as soon as someone submits answers to a Google Forms
  • Manipulate Trello boards, lists, and cards using Google Apps Script

As always, the source code for this solution can be found on GitHub. The sample Trello board and client questionnaire are also publicly available:

Scenario

Let’s imagine we know a very creative freelancer, Bob, who provides web designing services. To have things more organized and visible for clients, Bob uses a project management system called Trello.

In Trello, each project can be represented as a board and each task as a card. Cards have many useful features like attaching files, assigning people, and tracking deadlines. We can see an example of this kind of board in Figure 1.

Trello Board Template
Figure 1 - Trello Template Board

To provide a more personalized experience and stand out from the competition, Bob likes to have his project boards customized for each client. This means that cards in different project boards have different client names, company names, and other details like branding guidelines and specific DOs and DON’Ts. All these fields are specified in a template using placeholders in the text of a card, as we can see in Figure 2.

 

Trello Template Board Card Sample
Figure 2 - Template Board Card

To collect all this data for a particular client, Bob usually sends them a link to a Google Form with questions about the client and about what they are trying to achieve. Then he creates a new board based on a template and replaces placeholders in cards with answers provided by a client. After the board is customized, Bob invites the client to the board and they start the project.

This on-boarding process works fairly well, but sometimes Bob has some problems with it:

  1. Even after repeating it so many times, it still takes from 30 minutes to one hour to copy Google Form answers, paste each answer in their correct card, polish the formatting and share the board with the client.
  2. After clients answer the Google Form questions, they still need to wait at least a few hours (sometimes a couple of days) to have their board created manually. Bob thinks that he could provide an even better customer experience by reducing the time between the client’s action of answering the questions and his own action of creating the board.
  3. Sometimes he copies data to the wrong card, so he needs to fix it later after realizing the mistake. It happens to the best of us, but it might give some clients a bad impression even before the project starts.

As you can imagine, we can use Google Apps Script to automate this on-boarding process for Bob, saving him time and improving his customer on-boarding experience.

What Needs to Be Done

First, let’s outline what needs to be done for this automation:
  1. Get answers from Google Forms submissions.
  2. Connect to Trello API, authorize, and prepare requests.
  3. Create a new board based on an existing template and customize its cards with answers from the Google Forms submission. At the end of the customization, the person who submitted the answers should be invited as a member of the board.

In the following sections, we’ll break down these steps and detail their implementation.

Getting Answers from Google Forms

Before creating the client board, we need to get the data that is used for customization. As mentioned before, this data is provided when the client answers a Google Forms questionnaire.

Since we want to have the board created immediately after the questionnaire is answered, we can set up a Google Forms trigger that is fired when the form is submitted. This trigger generates an event that contains the data from the submission, and it can be used to create and customize the client’s board.

Setting Up Form Trigger

Let’s start by opening the script editor of the questionnaire in Google Forms and creating a function to be called when the form is submitted:

function onSubmit() {
  // Nothing for now
} 

According to the documentation, the form submission event is an installable trigger, so we need to set up a trigger that calls the onSubmit() function. To do that, click on the Triggers menu on the script editor and add a new trigger, specifying which function to run (onSubmit), the event source (From form) and the event type (On form submit), as shown in Figure 3.

On Form Submit Trigger
Figure 3 - On Form Submit Trigger

Also as described in the documentation about Google Forms events, the trigger passes an event object (e) to the function it calls. This object exposes the response of that particular submission via the property response. We can then use that response to get data like the questionnaire’s questions and corresponding answers provided by the client. So let’s change the onSubmit() function a bit:

let formData = {};

function onSubmit(e) {
  formData = getAnswers(e.response);
} 

Note that, other than passing the response to a new function (getAnswers()) we also define a global variable (formData), which, as we’ll see, is used by many different functions in the project.

Before detailing the implementation of getAnswers(), let’s include another global variable to hold some settings used throughout the project and a function that initializes this settings object. Make sure to change the template board name (templateBoardName property) and the ID of the clients root folder in Google Drive (clientsRootFolderID property) to match your environment. We’ll see more about these settings later.

let settings = {};

function initializeSettings() {
  return {
    // Base URL of Trello's API, including its current version
    apiBaseURL: 'https://api.trello.com/1/',
    // Name of the board used as template for clients' boards
    templateBoardName: 'Web Design Project Management (Template)',
    // ID of the folder in Google Drive where a client's folder should be created
    clientsRootFolderID: '1Ud11i2VojMCujQx6fugC3VVwGne0T1Rc',
    // Base URL of the development version of the website created for the client
    devWebsiteBaseURL: '.greatwebsites.com/dev',
    // Name of the script property that holds the Trello API key
    apiKeyProperty: 'TRELLO_API_KEY',
    // Name of the script property that holds the Trello API secret
    apiSecretProperty: 'TRELLO_API_SECRET',
    // URLs used for OAuth (from: https://developer.atlassian.com/cloud/trello/guides/rest-api/authorization/#using-basic-oauth)
    trelloGetTokenURL: 'https://trello.com/1/OAuthGetAccessToken',
    trelloRequestTokenURL: 'https://trello.com/1/OAuthGetRequestToken',
    trelloAuthorizeTokenURL:
      'https://trello.com/1/OAuthAuthorizeToken?scope=read,write',
  };
} 

Getting Submitted Answers

Next, let’s implement the getAnswers() function, which receives the form’s response object (FormResponse) and returns an object with the answers. Other than answers, the response object also provides methods to get metadata about the response, such as the response’s URL or its timestamp.

For now, let’s use the method getItemResponses() to retrieve the responses. This method returns an array of ItemResponse objects, which give us questions and answers, among other things. The indices used for the array are based on the order of the questions in the form, so the first question has index 0, the second question has index 1, and so on.

Other than the answers directly provided by the client, we also want to include four more properties in the object that is returned:

  1. The email of the client. In the form of this solution, there is not an explicit question to get the email, but we can change the form settings to collect the respondent’s email. In this case, the email can be retrieved by the getRespondentEmail() method of the response object.
  2. The URL to the Google Drive folder created especially for that client. The function createClientFolder() implements this part, using methods of the DriveApp class to create a folder and to return its URL.
  3. The URL for a development environment of the website to be designed for the client. This is implemented by the getWebsiteDevelopmentURL() function, which creates a simple URL based on the name of the client’s company.
  4. Questionnaire answers. To make it easy for all board members to visualize the submitted response, all questions and answers are included in one of the cards of the board. The function formatQuestionsAndAnswers() is responsible to get descriptions of questions and their corresponding answers, format them using Trello’s formatting syntax and return the string to be included in the card.

The following snippet includes the implementation of getAnswers() and of the other functions discussed above:

// Get answers from response submitted by the client
function getAnswers(response) {
  const responses = response.getItemResponses();

  return {
    COMPANY_NAME: responses[0].getResponse(),
    CONTACT_PERSON: responses[1].getResponse(),
    WEBSITE_URL: responses[2].getResponse(),
    SLOGAN_TAGLINE: responses[3].getResponse(),
    ORGANIZATION_SUMMARY: responses[4].getResponse(),
    TARGET_AUDIENCE: responses[5].getResponse(),
    LIKED_WEBSITES: responses[6].getResponse(),
    DONT_LIKE: responses[7].getResponse(),
    KEYWORDS: responses[8].getResponse(),
    FUNCTIONALITIES: responses[9].getResponse(),
    COLOR_SCHEME: responses[10].getResponse(),
    FONTS: responses[11].getResponse(),
    CONTACT_FREQUENCY: responses[12].getResponse(),
    CONTACT_METHOD: responses[13].getResponse(),
    PROJECT_MANAGEMENT: responses[14].getResponse(),
    EMAIL: response.getRespondentEmail(),
    CLIENT_FOLDER_URL: createClientFolder(responses[0].getResponse()).getUrl(),
    WEBSITE_DEVELOPMENT_URL: getWebsiteDevelopmentURL(
      responses[0].getResponse()
    ),
    QUESTIONNAIRE_ANSWERS: formatQuestionsAndAnswers(responses),
  };
}

// Create a folder for sharing files with client
function createClientFolder(companyName) {
  if (!companyName) {
    throw new Error(
      'Failed to create client folder: Company name not specified.'
    );
  }

  return DriveApp.getFolderById(settings.clientsRootFolderID).createFolder(
    companyName
  );
}

// Create URL for the development version of the website based on the company name
function getWebsiteDevelopmentURL(companyName) {
  if (!companyName) {
    throw new Error(
      'Failed to create development URL: Company name not specified.'
    );
  }

  return (
    encodeURIComponent(
      companyName.toLowerCase().replace(/[^\x00-\x7F\s]/g, '')
    ) + settings.devWebsiteBaseURL
  );
}

// Create a formatted string with questions and answers submitted by the client
function formatQuestionsAndAnswers(responses) {
  let formattedQuestionsAndAnswers = [];
  for (let i = 0; i < responses.length; i++) {
    formattedQuestionsAndAnswers.push(
      `**Q: ${responses[i].getItem().getHelpText()}**
      A: ${responses[i].getResponse()}`
    );
  }

  return formattedQuestionsAndAnswers.join('\r\n\r\n');
}
 

To finalize the first step, let’s update the onSubmit() function one more time to make sure it initializes the settings object as well:

function onSubmit(e) {
  settings = initializeSettings();
  formData = getAnswers(e.response);
} 

Connecting to Trello API

According to its documentation, requests done to the Trello API need to be authorized and authenticated.

One option to do this is to generate an API token based on a user’s API key, and then send both along with subsequent requests. This can be a good option for initial tests with the API.

Another option is to use OAuth1, which is a popular protocol used for authorization. Since many other APIs have support for OAuth, we’ll take this opportunity to see how we can combine OAuth and Google Apps Script. More specifically, we’ll use a Google Apps Script library provided by Google that takes care of most details and makes things easier for us: OAuth1 for Apps Script.

Authorizing with OAuth1

The documentation for the Google Apps Script library OAuth1 details all the steps necessary to use it, but here’s a summary of what we’ll do in our case:

  1. Import the library to our Google Apps Script project.
  2. Get the parameters we’ll need from Trello: API key, API secret and OAuth URLs.
  3. Set up functions to handle the authorization flow.

Importing OAuth1 Library

As shown in Figure 4, to import a library to our project, we simply need to click on the plus sign close to the Libraries menu on the left side of the script editor, paste the script ID provided at the library’s documentation (1CXDCY5sqT9ph64fFwSzVtXnbjpSfWdRymafDrtIZ7Z_hwysTY7IIhi7s) and select its latest version. After this, we can access the library using the specified identifier (OAuth1 in this case).

Import Library
Figure 4 - Import Library

Getting Trello API Parameters

Now we go to the Trello’s developer API keys page and do the following:

  1. Copy the API key (Figure 5).
  2. Include https://script.google.com as an allowed origin (Figure 6).
  3. Copy the OAuth secret (Figure 7).
Trello API Key
Figure 5 - Trello API Key
Figure 6 - Inclusion of Allowed Origin
Trello API Secret
Figure 7 - OAuth Secret

The key and the secret are used by OAuth1 library to make requests, so we need to have them available in our script. Instead of just creating variables for those values, we can add them as script properties. Although these properties aren’t encrypted, this makes the implementation a bit more organized and at least prevent the values to be in plain sight.

The legacy script editor allowed the specification of properties via its interface, but that is not yet(?) available in the new editor. So we have to add the new properties programmatically by creating a simple function and running it once:

function addProperties() {
  const scriptProperties = PropertiesService.getScriptProperties();
  scriptProperties.setProperty('TRELLO_API_KEY', 'abcdefghijklm1234567890');
  scriptProperties.setProperty('TRELLO_API_SECRET', '1234567890abcdefghijklm');
} 

Note that the names of the properties match what is used in the settings object created before. The values should be the values copied from the Trello’s developer API key page.

For the authorization to be successful, we’ll also need OAuth URLs provided by Trello. These are the same URLs previously included in the settings object.

Implementing Authorization Flow

Now we go back to the script editor and add a few functions used to implement the authorization flow. These functions are described in the documentation of the OAuth1 library, and we just need to modify their names and a few parts:

  • Since we’re using Google Forms, we use its UI to show a modal dialog asking the user (ourselves, in this case) to start the authorization flow.
  • We used script properties to save the API key and secret, so in the function getTrelloService() we add a couple of constants (apiKey and apiSecret) to retrieve their values. In addition, we get the OAuth URLs from the settings object.
// Verify whether the service has access to the API
// If it doesn't, start the authorization flow
function checkTrelloAccess() {
  const trelloService = getTrelloService();
  if (!trelloService.hasAccess()) {
    const authorizationUrl = trelloService.authorize();
    const template = HtmlService.createTemplate(
      '<a href="<?= authorizationUrl ?>" target="_blank">Authorize integration with Trello</a>. <br/><br/>Rerun the script after authorizing.'
    );
    template.authorizationUrl = authorizationUrl;
    const page = template.evaluate();
    FormApp.getUi().showModalDialog(page, 'Authorization');
  }
}

// Get script properties and create service
function getTrelloService() {
  const apiKey = PropertiesService.getScriptProperties().getProperty(
    settings.apiKeyProperty
  );
  const apiSecret = PropertiesService.getScriptProperties().getProperty(
    settings.apiSecretProperty
  );

  return OAuth1.createService('trello')
    .setAccessTokenUrl(settings.trelloGetTokenURL)
    .setRequestTokenUrl(settings.trelloRequestTokenURL)
    .setAuthorizationUrl(settings.trelloAuthorizeTokenURL)
    .setConsumerKey(apiKey)
    .setConsumerSecret(apiSecret)
    .setCallbackFunction('authCallback')
    .setPropertyStore(PropertiesService.getUserProperties());
}

// Create HTML output for authorization callback
function authCallback(request) {
  settings = initializeSettings();
  const trelloService = getTrelloService();
  const isAuthorized = trelloService.handleCallback(request);
  if (isAuthorized) {
    return HtmlService.createHtmlOutput('Success! You can close this tab.');
  } else {
    return HtmlService.createHtmlOutput('Denied. You can close this tab');
  }
}
 

Let’s create a small test function to get Trello boards and confirm whether the authorization step is working properly. We also add a new menu to run this test function from the interface.

function onOpen() {
  const menuName = 'Automation';
  const menuItemName = 'Test Trello connection';
  FormApp.getUi()
    .createMenu(menuName)
    .addItem(menuItemName, 'testAPIConnection')
    .addToUi();
}

function testAPIConnection(){
  settings = initializeSettings();
  checkTrelloAccess();
  const trelloService = getTrelloService();
  const response = trelloService.fetch('https://api.trello.com/1/members/me/boards');
  FormApp.getUi().alert(response.getContentText());
} 

It’s necessary to reload the form or run the onOpen() function for the new menu to appear. After reloading, a new icon in the shape of a puzzle piece appears on the right top side of the form.

After clicking on the new icon, then on the Automation menu and finally on Test Trello connection, the authorization modal (Figure 8) appears asking us to authorize the connection to Trello. You might also notice that the script itself ended in failure, which is expected since we haven’t authorized it yet.

Authorization Modal
Figure 8 - Authorization Modal Dialog

Clicking on the link takes us to a Trello authorization window. We can authorize the integration by clicking on the button Allow at the bottom of the page. If the authorization is successful, it calls the authCallback() function we implemented before, which shows us the message Success! You can close this tab.

After that, we run the test function from the menu again, but this time there should not be an authorization modal and the script should run without any errors, showing an alert window at the end with data about your boards.

Making Requests

As we saw in the test function above, we can make requests to the API by using the fetch() function of the service created. This function is similar to the fetch() method provided by the UrlFetchApp class, but it implicitly includes the user token to requests.

In the simple request to get boards, we just need to pass the URL and a GET request is made by default. But what about when we need to send some data as well, like when creating a board or editing a card? In such cases, we can pass additional configuration parameters and options to the request, just like it is supported by UrlFetchApp’s fetch() method. By doing so, we can do POST and PUT requests as well as send data via the request’s payload.

Thinking a bit ahead, we can expect most requests to follow a similar pattern:

  • The request method (i.e., GET, POST, PUT and DELETE) can vary, but they’re all specified in the same property (called method) of the object passed to the fetch() function.
  • They receive an API endpoint, which is a URL that starts with the API’s base URL (https://api.trello.com/1/ in this case).
  • Data can be sent with the request via its payload or via query parameters appended to the endpoint URL.
  • According to Trello API’s documentation, successful requests return the status code 200, so we can also check for that and throw an exception if it’s a different status code.

To reduce a bit the amount of duplicated code generated to do operations common to different requests, let’s create a generic function that uses the Trello service to make requests for us. Let’s also include an additional function that prepares query parameters based on an object’s key/value pairs.

// Prepare and execute a HTTP request
function makeRequest(config) {
  const requestOptions = {};
  if (config.hasOwnProperty('method')) {
    requestOptions.method = config.method;
  }
  if (config.hasOwnProperty('payload')) {
    requestOptions.payload = config.payload;
  }
  let url = settings.apiBaseURL + config.endpoint;
  if (config.hasOwnProperty('params')) {
    url = url + stringifyParameters(config.params);
  }
  const trelloService = getTrelloService();
  const response = trelloService.fetch(url, requestOptions);
  if (response.getResponseCode() !== 200) {
    throw new Error(config.errorMessage);
  }

  return JSON.parse(response.getContentText());
}

// Create a query parameters string based on the received object
function stringifyParameters(params) {
  return (
    '?' +
    Object.keys(params)
      .map((key) => {
        return encodeURIComponent(key) + '=' + encodeURIComponent(params[key]);
      })
      .join('&')
  );
} 

This function is one of the most important of this solution, so let’s make sure we understand it well:

  • The function receives an object called config, which we construct whenever we need to make a request (more about this later).
  • We define an empty object, requestOptions, which is used to specify request options, just like what is accepted by UrlFetchApp’s fetch(). We add properties to this object depending on what is included in config.
  • The first property that we might include is the method for the request (i.e., GET, POST, PUT or DELETE). We check if the config object has a property called method and, if it does, it is assigned to the method property of the requestOptions object. Similarly, we check whether the config object has a property named payload, eventually adding it to the payload property of requestOptions.
  • We then initialize the request’s URL with the API’s base URL, retrieved from the settings object, and the endpoint property of config. If config has parameters that should be included as query parameters, we call the function stringifyParameters() to create a string with them. This string is appended to the request’s URL.
  • After all the preparation is done, we get the Trello service created by the OAuth1 library and use its fetch() function. If the status code of the response is different than 200, we throw an error message that is also specified in config.
  • Finally, we return an object with the parsed JSON returned by Trello as the response for the request.

At this point, it might not be so clear why we created this function, but don’t worry. The requests we make in the next sections will probably make things more concrete.

Creating and Customizing Client Board

Since we can now make authorized requests, we can finally start doing operations on Trello via its API. Let’s recap what we need to do at this point:

  • Create a new board based on existing one.
  • Customize the new board, update cards and lists with data provided by the client
  • Invite the client as a board member.

Creating new board

We need to create a new board based on a another board that already exists (our template), so we first need to find the existing board and its ID, which is used for the create board request.

Searching for a Board

To find the ID of the board used as a template board, we have to make a request using the search endpoint. This endpoint is not exclusive to board search, and it can also be used to search for other entities (e.g., cards, members, and organizations).

According to the documentation, we can restrict what kind of entities to search via the modelTypes property. And we can also specify a query search (query property) to determine the name of the board to search. In our case, we want to search only for boards and the name of the template board is defined in the settings object. An object containing these two properties is assigned to the params property of the config object received by the makeRequest() function. This means that their keys and values are transformed into a string and appended to the request’s URL.

function getTemplateBoard() {
  const searchResult = makeRequest({
    method: 'GET',
    endpoint: `/search`,
    params: {
      modelTypes: 'boards',
      query: `name:"${settings.templateBoardName}"`,
    },
    errorMessage: 'Failed to get template board ID.',
  });

  return searchResult.boards[0];
} 

As we can see, thanks to the makeRequest() function, we can make requests to the API by just passing it an object with a few properties. There is no need to handle the Trello service or to verify all request options at this point, since makeRequest() already takes care of all that.

Requests made to the search endpoint return an object with the search results. In this case, we assume there’s only one board with the name we specified, so we select just the first board (index 0).

Creating a Board Based on Another

Now that we have the board to be used as template, we can make a request to create a new board for the client:

function createBoard(templateBoardID, boardName) {
  return makeRequest({
    method: 'POST',
    endpoint: `/boards`,
    payload: {
      name: boardName,
      idBoardSource: templateBoardID,
      keepFromSource: 'cards',
    },
    errorMessage: 'Failed to create board based on template.',
  });
} 
Note that the structure of the createBoard() function is similar to the getTemplateBoard() function, as we simply create a request configuration object and call the makeRequest() function defined before. The main difference is that, instead of sending data via query parameters, we send it using the payload property. In this case, the API returns an object representing the newly created board. We can then use its ID to customize its elements, such as cards and lists.

Customizing Board

With the new board created, we can finally start to customize it according to the client’s answers for the questionnaire.

Before we start doing the modifications, we first need to get the cards and lists of the board, since we need their IDs to perform updates. So let’s follow the same pattern for requests and create functions for getting cards and for getting lists in a board:

function getBoardCards(boardID) {
  return makeRequest({
    method: 'GET',
    endpoint: `/boards/${boardID}/cards`,
    errorMessage: 'Failed to get board cards.',
  });
}

function getBoardLists(boardID) {
  return makeRequest({
    method: 'GET',
    endpoint: `/boards/${boardID}/lists`,
    errorMessage: 'Failed to get board lists.',
  });
} 

Now we can finally go over the cards and updates the ones we want. Based on the placeholders of the template board, we need to update the descriptions of the following cards with data from the client:

  • Welcome! – START HERE: Replace {{CONTACT_PERSON}} and {{COMPANY_NAME}} with the contact person’s name and the name of the company.
  • Sharing Files and Your Logo and Branding Materials: Replace {{CLIENT_FOLDER_URL}} with the URL to the client’s folder in created by the this automation in Google Drive.
  • Viewing the Work In Progress: Replace {{WEBSITE_DEVELOPMENT_URL}} with the URL to the website’s development environment.
  • Questionnaire: Replace {{QUESTIONNAIRE_ANSWERS}} with the formatted string with questions and answers provided by the client.
  • Customer Avatar: Replace {{TARGET_AUDIENCE}} with information about the target audience of the website.
  • Website Design Inspiration: Replace {{LIKED_WEBSITES}} and {{DONT_LIKE}} with  DOs and DON’Ts about design of the website.
  • Potential Themes: Replace {{COLOR_SCHEME}} and {{FONTS}} with preferences for colors and fonts.

Other than updating the description, to make things more organized, we also create a checklist in the Functionality card based on the functionalities selected by the client. Each functionality selected by the client in the form needs to be converted into an item of this checklist.

As for the list, we just need to update the name of the list whose name is {{CONTACT_PERSON}} To Do, replacing the placeholder with the name of the client’s contact person.

Finally, after the customization is done, we invite the client to the board.

function customizeBoard(boardID) {
  // Update cards
  const boardCards = getBoardCards(boardID);
  for (const card of boardCards) {
    switch (card.name) {
      case 'Welcome! - START HERE':
        updateCardDescription(card, {
          CONTACT_PERSON: formData.CONTACT_PERSON,
          COMPANY_NAME: formData.COMPANY_NAME,
        });
        break;
      case 'Sharing Files':
      case 'Your Logo and Branding Materials':
        updateCardDescription(card, {
          CLIENT_FOLDER_URL: formData.CLIENT_FOLDER_URL,
        });
        break;
      case 'Viewing the Work In Progress':
        updateCardDescription(card, {
          WEBSITE_DEVELOPMENT_URL: formData.WEBSITE_DEVELOPMENT_URL,
        });
        break;
      case 'Questionnaire':
        updateCardDescription(card, {
          QUESTIONNAIRE_ANSWERS: formData.QUESTIONNAIRE_ANSWERS,
        });
        break;
      case 'Customer Avatar':
        updateCardDescription(card, {
          TARGET_AUDIENCE: formData.TARGET_AUDIENCE,
        });
        break;
      case 'Website Design Inspiration':
        updateCardDescription(card, {
          LIKED_WEBSITES: formData.LIKED_WEBSITES,
          DONT_LIKE: formData.DONT_LIKE,
        });
        break;
      case 'Potential Themes':
        updateCardDescription(card, {
          COLOR_SCHEME: formData.COLOR_SCHEME,
          FONTS: formData.FONTS,
        });
        break;
      case 'Functionality':
        createFunctionalitiesChecklist(card.id, formData.FUNCTIONALITIES);
        break;
      default:
        break;
    }
  }

  // Update client's To Do list name
  const boardLists = getBoardLists(boardID);
  for (const list of boardLists) {
    if (list.name === '{{CONTACT_PERSON}} To Do') {
      const listNewName = list.name.replace(
        '{{CONTACT_PERSON}}',
        formData.CONTACT_PERSON
      );
      updateListName(list.id, listNewName);
    }
  }

  // Invite client to board
  invitePersonToBoard(boardID);
} 

Updating Cards

First, let’s implement the updateCardDescription() function that is called by customizeBoard().

The first lines of updateCardDescription() replace the placeholders in the description with the values provided by the client. The parameter updateFields is an object whose keys indicates the placeholders to be replaced and whose values should be used in the replacement.

After that, an update request is done with the new card description defined in the payload.

function updateCardDescription(card, updateFields) {
  let description = card.desc;
  for (const [key, value] of Object.entries(updateFields)) {
    description = description.replace(`{{${key}}}`, value);
  }

  return makeRequest({
    method: 'PUT',
    endpoint: `/cards/${card.id}`,
    payload: {
      desc: description,
    },
    errorMessage: "Failed to update card's description.",
  });
} 

Next, we create the functionalities checklist in the card Functionality.

The creation of the checklist is done by createCardChecklist(), which invokes makeRequest() in a way similar to what was done before. The request has a payload defining in what card the checklist should be created and the name of the checklist.

Once the checklist is created, we add items to it using the functionalities chosen by the client. Each functionality is added to the checklist in a separate request made by the createChecklistItem() function.

function createFunctionalitiesChecklist(cardID, functionalities) {
  const checklistName = 'What the website needs';
  const checklist = createCardChecklist(cardID, checklistName);
  for (const functionality of functionalities) {
    createChecklistItem(checklist.id, functionality);
  }
}

function createCardChecklist(cardID, checklistName) {
  return makeRequest({
    method: 'POST',
    endpoint: '/checklists',
    payload: {
      idCard: cardID,
      name: checklistName,
    },
    errorMessage: 'Failed to create Functionalities checklist.',
  });
}

function createChecklistItem(checklistID, itemDescription) {
  return makeRequest({
    method: 'POST',
    endpoint: `/checklists/${checklistID}/checkItems`,
    payload: {
      name: itemDescription,
    },
    errorMessage: 'Failed to add item to checklist.',
  });
} 

Updating Lists

Other than the cards, the template board has a list of cards whose name depends on the name of the client.

The updateListName() function makes a request to change the name of the list from {{CONTACT_PERSON}} To Do to a name that uses the name of the contact person provided by the client (e.g., “Alice To Do”).

Note that, other than the ID of the card to be modified, the endpoint contains the field to be updated, but the new value for the field is passed as a payload of the request.

function updateListName(listID, newName) {
  return makeRequest({
    method: 'PUT',
    endpoint: `/lists/${listID}/name`,
    payload: {
      value: newName,
    },
    errorMessage: "Failed to update list's name.",
  });
}
 

Inviting Member to Board

The customization of the board is finally complete! The last step is to invite the client as a member of the board, which is done by the function invitePersonToBoard().

The main point of attention during this step is that the request to invite a member is a PUT request, not a POST, as some might expect.

function invitePersonToBoard(boardID) {
  return makeRequest({
    method: 'PUT',
    endpoint: `/boards/${boardID}/members`,
    payload: {
      email: formData.EMAIL,
      fullName: formData.CONTACT_PERSON,
    },
    errorMessage: 'Failed to invite person to board.',
  });
} 

Time Saved

Now let’s see how much time we can save with this automation.

The initial estimation was the Bob spends at least 30 minutes to create and fully customize a Trello board for a new client.

Based on simple measurements, the automation can create the board and invite the client in less than ten seconds after the client answers the questionnaire. If we suppose we take 3 hours to implement and test the solution, it only takes 6 clients for the solution to be worth the time invested in its development.

Summary

In this tutorial, we saw how to create a trigger from a form so that the answers provided by a client are used to immediately create a Trello board for project management. We covered how to use the OAuth1 library authorize requests to Trello API, and how to make requests to create and customize a Trello board.

With this solution, Bob doesn’t have to spend time copying and pasting the client’s answers to a new board, saving time and avoiding mistakes at the same time. On top of that, I’m sure Bob will have many clients impressed with the responsiveness of his services!

As mentioned, the source code for this solution can be found on GitHub. Questions and feedback are welcomed in the comments!

Get in touch!

Want this Automation done for you?

If you don’t have time to implement it and just want to get this task done, send me a message and I can assist you with that!