How to Add Options in Google Forms Questions from Google Sheets

An international school is building a Google Form where students can register their details. The form would have a drop-down list of countries, the names of class teachers as a multiple choice question and a checkbox style question where students can pick one or more of their favorite subjects.

Adding Bulk Questions in Google Forms

It is easy to create such a form inside Google Forms - here’s a sample form - but there are two issues:

  1. There’s too much data to enter and type in the form. For instance, the country down-down alone has a list of 250 countries and it would take some work for the form editor to manually type each choice in the question.
  2. The question choices in the form may change with time. In the previous example, some teachers may move out, new teachers may join in and the drop-down list in the form has to be updated manually every time there’s a change in the staff.

Auto-Populate Questions in Google Forms with Google Sheets

As with everything else in the world of Google Forms and Google Sheets, we can easily automate the process of adding question choices in Google Forms in bulk with the help of, you guessed it right, Google Apps Script.

The idea is simple. We’ll have a Google Sheet that will be the data source and have all the answer choices for various questions in the Google Form.

The app will read the data from this Google Sheet and auto-populate the choices in the form with one click. You can even create a time-trigger that runs every hour, day or month to dynamically update your form using the most current data available in the spreadsheet.

Add Options in Dropdown Lists & Multiple Choice Questions

Create a Google Spreadsheet and add the question titles in the first row of the sheet, one per column. Next, write down all the options or choices that should be available per question.

Here’s how your spreadsheet structure would look like:

Google Form Answers in Google sheets

The important thing to note here is that your column headings in the spreadsheet should exactly match the form field labels of the Google Form. The Google Script can bulk add answers in multiple-choice questions with a single answer, drop-down lists and checkbox with multiple options.

Bulk Add Question Choices in Google Forms

Open the Google Sheet that has the question choices, go to the Tools menu and choose Script Editor. Replace the default code in the script editor with the Google Script below. Please watch the video tutorial to know in more detail how this code works.

/**
 * Auto-populate Question options in Google Forms
 * from values in Google Spreadsheet
 *
 * Written by Amit Agarwal (MIT License)
 *
 **/

const populateGoogleForms = () => {
  const GOOGLE_SHEET_NAME = "<<Put the name of Google sheet here>>";
  const GOOGLE_FORM_ID = "<<Put your Google Form ID here>>";

  const ss = SpreadsheetApp.getActiveSpreadsheet();

  const [header, ...data] = ss
    .getSheetByName(GOOGLE_SHEET_NAME)
    .getDataRange()
    .getDisplayValues();

  const choices = {};
  header.forEach((title, i) => {
    choices[title] = data.map((d) => d[i]).filter((e) => e);
  });

  FormApp.openById(GOOGLE_FORM_ID)
    .getItems()
    .map((item) => ({
      item,
      values: choices[item.getTitle()],
    }))
    .filter(({ values }) => values)
    .forEach(({ item, values }) => {
      switch (item.getType()) {
        case FormApp.ItemType.CHECKBOX:
          item.asCheckboxItem().setChoiceValues(values);
          break;
        case FormApp.ItemType.LIST:
          item.asListItem().setChoiceValues(values);
          break;
        case FormApp.ItemType.MULTIPLE_CHOICE:
          item.asMultipleChoiceItem().setChoiceValues(values);
          break;
        default:
        // ignore item
      }
    });
  ss.toast("Google Form Updated !!");
};

You need to replace the GOOGLE_SHEET_NAME and the GOOGLE_FORM_ID with your own values. Go to the Run menu inside the Script editor, choose populateGoogleForms function and it should instantly choices for all the specified questions in the Google Form.

Dynamically Update Answers in Google Forms

Going forward, whenever you need to update the answer choices in Google Forms, you can simply update the values inside the spreadsheet and run the same auto-populate function from the Script editor.

Or, to make things even more simple, you can add a button on the spreadsheet page that, when clicked, will automatically update the answers in Google Forms for you.

Go to the Insert menu in Google Sheets, choose drawing and pick any shape. You can also add overlay text to the shape. Once the shape is placed on the spreadsheet canvas, click the menu, choose assign script and type populateGoogleForms.

Bulk Import Button in Google Sheets

That’s it. You can click this button to update your Google Forms right within Google sheets. Do note that the script will not append choices, it will replace all existing choices with the ones that are available in your Google Sheet.

Also see: Send Pre-filled Google Forms via Email

How to Use Hyperlinks in Google Sheets

This guide explains how you can easily create and manage hyperlinks in Google Sheets. An entire cell in the sheet, or specific text inside the cell, can be linked to external web pages. A cell can also contain multiple hyperlinks.

If you type a web page address in a Google Sheet cell, it is automatically converted into a clickable hyperlink.

Text converted into hyperlink

You can add anchor text to plain hyperlinks for more accessible URLs. Hover your mouse over the hyperlink and click the Edit icon. Now add the anchor text in the Text input box and click the green Apply button.

Alternatively, you may use the built-in HYPERLINK function in Google Sheet to create web links with (optional) anchor text.

 =HYPERLINK("https://www.labnol.org", "Digital Inspiration")

Add anchor text to hyperlink

It is also possible to include multiple hyperlinks inside a single cell of the Google Sheet.

Just type any text, include URLs in plain text and when you move the cursor out of the cell, the URLs are converted into hyperlinks.

Bonus Tip: While a cell with multiple links is selected, press Alt+Enter and all the links with open at once in new tabs.

Multiple hyperlinks in Google Sheet Cell

You can use the previous technique to edit multiple hyperlinks contained in a single cell and add anchor text.

Hover your mouse over a link in the cell, click the edit icon and change the Anchor text. Repeat this for all other links in the same cell.

Format Muliple URLs

Also see Secret Google Drive URLs.

Here are some snippets that will help you manage your hyperlinks in Google Sheets using Google Script macros.

const createHyperLinkWithFormula = () => {
  const link = 'https://www.labnol.org';
  const text = 'Digital Inspiration';
  const value = `=HYPERLINK("${link}", "${text}")`;
  const sheet = SpreadsheetApp.getActiveSheet();
  const range = sheet.getRange('A1');
  range.setValue(value);
};
const createHyperLinkWithRichTextValue = () => {
  const link = 'https://www.labnol.org';
  const text = 'Digital Inspiration';
  const value = SpreadsheetApp.newRichTextValue()
    .setText(text)
    .setLinkUrl(link)
    .build();
  SpreadsheetApp.getActiveSheet().getRange('A1').setRichTextValue(value);
};
const createMultipleHyperLinks = () => {
  const value = SpreadsheetApp.newRichTextValue()
    .setText('Google acquired YouTube in 2006')
    .setLinkUrl(0, 6, 'https://www.google.com')
    .setLinkUrl(16, 23, 'https://www.youtube.com')
    .build();
  SpreadsheetApp.getActiveSheet().getRange('A1').setRichTextValue(value);
};
const extractLinkFromFormula = () => {
  const sheet = SpreadsheetApp.getActiveSheet();
  const range = sheet.getRange('A1');
  const formula = range.getFormula();
  const [, url, , text] =
    formula.match(/=HYPERLINK\("(.+?)"([;,]"(.+?)")?\)/) || [];
  Logger.log({ url, text: text || url });
};
const extractMultipleLinks = () => {
  const urls = SpreadsheetApp.getActiveSheet()
    .getRange('A1')
    .getRichTextValue()
    .getRuns()
    .map((run) => {
      return {
        url: run.getLinkUrl(),
        text: run.getText(),
      };
    })
    .filter(({ url }) => url);
  Logger.log(urls);
};

How to Prioritize Tasks Fast

Meetings and Frameworks

We tried to discuss all of our tasks during online sync-ups—long, inefficient, and honestly, impossible. There was no time to hear and clarify everyone's opinions. We managed to go over only about 20% of 100 issues planned—the team got tired and unfocused after the first hour. Communication was clearly a challenge for us.

“Communication is the biggest challenge. It is difficult to know what each team and person is prioritizing and how they like to work.”  Hiten Shah
humans walking around a goal, confused

We decided to filter the issues through prioritization frameworks. The frameworks introduced criteria that structured the evaluation. The teammates assigned scores by the criteria asynchronously at their convenience. We started with Google Sheets and discussed only tasks with high scores.

How to Use Google Sheets with D3.js and Google Visualization

The D3.js visualization library can be used for creating beautiful graphs and visualizations using data from external sources including CSV files and JSON data.

To give you an example, this D3.js animation inside the Google Sheets associated with the COVID-19 tracker project visualizes the growth of Coronavirus cases in India over time. It uses the Google Visualization API, D3.js and the very-awesome Bar Chart Race component built by Mike Bostock, the creator of D3.js.

Google Sheets and D3.js

This guide explains how you can use data in your Google Spreadsheets to create charts with D3.js using the Visualization API. The data is fetched in real-time so if the data in your Google Sheets is updated, it is reflected in the graph as well.

D3.js Chart with Google Sheets

Step 1: Make the Google Sheets public

Make your Google Spreadsheet public - you can either share the sheet with “anyone who has the link can view” access or make it public so even search engines that find your sheet that has the Charts data.

We are using this Google Sheet for this tutorial.

Step 2: Load the Libraries in HTML

Load the D3.js (v5) and the Google charts library in your index.html file. The JavaScript for rendering the D3 chart is written in the index.js file.

<!DOCTYPE html>
<html>
  <head>
    <script src="https://www.gstatic.com/charts/loader.js"></script>
    <script src="https://d3js.org/d3.v5.min.js"></script>
  </head>

  <body>
    <svg></svg>
  </body>
  <script src="./index.js"></script>
</html>

Step 3: Initialize the Google Visualization API

Here specify the URL of your publish Google Spreadsheet (the gid should point to the sheet that has the data). The Google Visualization API Query Language (reference) lets you use SQL like syntax to specify columns that should be used for fetching data from the Google sheet. You can also use offset, where and limit clauses to limit the data that is returned by Google Sheets.

google.charts.load('current');
google.charts.setOnLoadCallback(init);

function init() {
  var url =
    'https://docs.google.com/spreadsheets/d/1YpiTo7Fc3QvBdbuReCIcwtg7lnmZupQAH57phrDLotI/edit#gid=0';
  var query = new google.visualization.Query(url);
  query.setQuery('select A, B');
  query.send(processSheetsData);
}

Step 4: Prepare the Data for D3.js

After the spreadsheet data is available, manipulate the response in an Array of Objects that can be read by d3.js. Google Sheets returns numerical data as String so we can either use parseInt or the Unary (+) operator to convert the String to Integer.

function processSheetsData(response) {
  var array = [];
  var data = response.getDataTable();
  var columns = data.getNumberOfColumns();
  var rows = data.getNumberOfRows();
  for (var r = 0; r < rows; r++) {
    var row = [];
    for (var c = 0; c < columns; c++) {
      row.push(data.getFormattedValue(r, c));
    }
    array.push({
      name: row[0],
      value: +row[1],
    });
  }
  renderData(array);
}

Step 5: Render the D3.js chart

Next, we create a Bar Chart in D3.js using the data from Google Sheets. You may follow this tutorial on @ObservableHQ to understand how to make bar charts inside D3.js. The chart is rendered in SVG.

function renderData(data) {
  const margin = { top: 30, right: 0, bottom: 30, left: 50 };
  const color = 'steelblue';
  const height = 400;
  const width = 600;
  const yAxis = (g) =>
    g
      .attr('transform', `translate(${margin.left},0)`)
      .call(d3.axisLeft(y).ticks(null, data.format))
      .call((g) => g.select('.domain').remove())
      .call((g) =>
        g
          .append('text')
          .attr('x', -margin.left)
          .attr('y', 10)
          .attr('fill', 'currentColor')
          .attr('text-anchor', 'start')
          .text(data.y)
      );

  const xAxis = (g) =>
    g.attr('transform', `translate(0,${height - margin.bottom})`).call(
      d3
        .axisBottom(x)
        .tickFormat((i) => data[i].name)
        .tickSizeOuter(0)
    );
  const y = d3
    .scaleLinear()
    .domain([0, d3.max(data, (d) => d.value)])
    .nice()
    .range([height - margin.bottom, margin.top]);

  const x = d3
    .scaleBand()
    .domain(d3.range(data.length))
    .range([margin.left, width - margin.right])
    .padding(0.1);

  const svg = d3
    .select('svg')
    .attr('width', width)
    .attr('height', height)
    .attr('fill', color);

  svg
    .selectAll('rect')
    .data(data)
    .enter()
    .append('rect')
    .attr('x', (d, i) => x(i))
    .attr('y', (d) => y(d.value))
    .attr('height', (d) => y(0) - y(d.value))
    .attr('width', x.bandwidth());

  svg.append('g').call(xAxis);

  svg.append('g').call(yAxis);
}

How to Make Pixel Paintings with Google Spreadsheets

You have been using Google Sheets for budgeting and business but there’s another interesting use of spreadsheets - you can use them to create impressive pixel paintings in minutes.

Marina and Mallory created a bright and beautiful wall mural using Google Sheets. Japanese artist Tatsuo Horiuchi uses Microsoft Excel to draw masterpieces.

The idea is simple. Each cell in the spreadsheet corresponds to a pixel in the painting. You find the color of the pixel and set that as the background color of the corresponding cell in the sheet. Now resize the cells in small perfect squares and your spreadsheet will resemble the original artwork.

How to Paint with Google Spreadsheets

If you would like to create your own spreadsheet art but don’t have the time to carefully paint every cell manually, here’s a simple workaround for you. You can take any photograph, vector art, GIF, or any other image and use Google Sheets to convert that bitmap image into spreadsheet art.

Here’s some artwork created with Google Spreadsheets.

Pixel Painting

Emoji Painting

Emoji Art

Watch the video tutorial or open this Google Sheet to explore more artwork made with Google Sheets. You can increase the zoom to view individual pixels.

Create Pixel Art with Google Sheets

It takes three easy steps to make pixel art with Google Sheets.

  1. Install Pixel Art for Google Sheets.
  2. Type sheets.new in the browser to create a new Google Sheet. Go to Add-ons menu, choose Pixel Art and then select Open.
  3. Upload any image from your desktop, hit the Draw button and watch as your sheet magically transforms into beautiful art.

Check this Google Sheet for more examples.

How Pixel Art Works?

The underlying Google Script will now parse every single pixel of your image and write the corresponding hex color codes in the cells of the spreadsheet.

It will then set the background color of every cell as the color of the corresponding pixel in the uploaded image. If you have chosen the ‘emoji’ option, the cell will be filled with the closest emoji that matches the color of the pixel.

In the last step, the Google Script will resize the cells and turn them into perfect squares. That’s it. Your spreadsheet art is now ready.

Download Pixel Art

If you choose to transform your picture into emojis pixels, the script takes the average color of the pixel, finds the closest emoji and adds it to the corresponding cell in the sheet. The emoji module is based on the work of Monica Dinculescu.

How to Get Hidden and Filtered Rows in Google Sheets with Google Script

Hide Rows in Google Sheets

You can hide entire rows in Google Sheets manually or use filters to hide any rows that matches the specified criteria. For instance, if you have a sheet containing orders from different countries, you can set up a country filter to hide all rows where the country is not the United States.

If you have a Google Script that iterates through each row in the Google Sheet for performing actions on the row, like sending emails or merging documents, you can check for the hidden and filtered rows and easily skip them from the workflow.

There are two ways to check for hidden and filtered rows in Google Sheets. You can either use the SpreadsheetApp service of Google Scripts or use the Spreadsheet V4 API.

Check for hidden rows with Google Scripts

function getHiddenAndFilteredRows() {
  var sheet = SpreadsheetApp.getActiveSheet();
  var data = sheet.getDataRange().getValues();
  for (var d = 0; d < data.length; d++) {
    // Row Index starts from 1
    if (sheet.isRowHiddenByFilter(d + 1)) {
      Logger.log('Row #' + d + ' is filtered - value: ' + data[d][0]);
      continue;
    }
    // Row Index starts from 1
    if (sheet.isRowHiddenByUser(d + 1)) {
      Logger.log('Row #' + d + ' is hidden - value: ' + data[d][0]);
      continue;
    }
    // processRow(d)
  }
}

The next example uses (ES6 Chrome V8](/es6-google-apps-script-v8-200206). The script fetches all the rows in the currently active Google Sheet and iterates through each of them to finds all rows that are either hidden or filtered.

/**
 * Get the hidden and filtered rows in the specified Google Sheet
 * @param {string} spreadsheetId - Drive File ID of the Google Spreadsheet
 * @param {string} sheetId - The unique ID of the Google Sheet
 * @returns {Array} Index of the hidden rows (first row's position is 0)
 */
const getHiddenRowsinGoogleSheets = (
  spreadsheetId = SpreadsheetApp.getActiveSpreadsheet().getId(),
  sheetId = SpreadsheetApp.getActiveSheet().getSheetId()
) => {
  const fields =
    'sheets(data(rowMetadata(hiddenByFilter,hiddenByUser)),properties/sheetId)';
  const { sheets } = Sheets.Spreadsheets.get(spreadsheetId, { fields });

  const [sheet] = sheets.filter(({ properties }) => {
    return String(properties.sheetId) === String(sheetId);
  });

  const { data: [{ rowMetadata = [] }] = {} } = sheet;

  const hiddenRows = rowMetadata
    .map(({ hiddenByFilter, hiddenByUser }, index) => {
      return hiddenByUser || hiddenByFilter ? index : -1;
    })
    .filter((rowId) => rowId !== -1);

  return hiddenRows;
};

In order to use the Spreadsheet service in your Google Apps Script project, go to Resources > Advanced Google Services and enable the Google Sheets API.

Alternatively, you may enable the Sheets API directly in your appsscript.json file.

  "dependencies": {
    "enabledAdvancedServices": [{
      "userSymbol": "Sheets",
      "serviceId": "sheets",
      "version": "v4"
    }]
  }

Spreadsheets Quota Limitation

Google Spreadsheets Quota will allow your addon project to make up 100 Spreadsheet reads per 100 seconds and this limit is shared across all users of the project. Thus if your project has too many simultaneous users, the Spreadsheet service may fail with the error:

API call to sheets.spreadsheets.get failed with error: Quota exceeded for quota group 'ReadGroup' and limit 'Read requests per 100 seconds' of service 'sheets.googleapis.com'

To stay withing the quota, you can either cache the results of the expensive getHiddenRows method or use a try-catch block. If the Spreadsheet API fails due to quota error, use the SpreadsheetApp service to check for hidden rows.

Also, a row in the Google Sheet can be filtered and hidden at the same time.

How to Send Reminder Emails Based on Dates in Google Sheets

John Q Public runs a travel agency and they have thousands of clients across the globe. Other than managing tickets and travel itineraries, the agency also keeps a record of passports and visas of their clients to ensure that customers have valid documents at the time of travel.

Most countries require that a foreigner’s passport must be valid for at least six months from the date of entry. The US government, therefore, recommends that you renew your passport at least nine months before it expires.

Send Automatic Emails with Google Sheets

John’s agency is looking for a reminder system that will automatically send an email notification to their customers when their passports have expired or are due for renewal in the next few months. Let’s see how they can build such a workflow in 10 minutes with the help of Mail Merge for Gmail.

The idea is simple.

We have the customer data in a Google Sheet or a Microsoft Excel spreadsheet. The “Expiry Date” column in the spreadsheet contains the date when the passport is set to expire. We setup a cron job that runs in the background and monitors the expiry date. If any date in the sheet is less than, say, 12 months from today, an automatic email reminder is sent to the customer.

Create the Reminder Email Workflow

To get started, install the Mail Merge for Gmail add-on for Google Sheets. If you have never used the merge add-on earlier, please watch the Mail Merge tutorial for a quick overview.

Email Reminders Sheet

Next, create a new Google Sheet and choose Addons > Mail Merge with Attachments > Create Merge Template. If you have your customer data in an Excel sheet, you can easily import the records into this Google sheet using the File > Import menu.

Next, we’ll use the Array Formulas to populate the Scheduled Date column based on the date in the Expiry Date column. Go to row #2 of the scheduled date column and paste this formula:

=ArrayFormula(IF(ISBLANK(E2:E),"",E2:E-365))

The date in the Scheduled Date column will automatically be filled with a date that is 12 months before the date in the Expiry Date column. Thus if the passport expiration date is set to July 12, 2021, the follow-up reminder email would be sent exactly a year earlier on July 12, 2020.

Reminder Dates

Open the Gmail website, compose a new email message that will be the reminder template and save it in your drafts folder. The email body and subject can include column titles, enclosed inside double-curly braces and these will be replaced with actual values from the Google Sheet when the email is sent.

Auto Expiry Reminder Email

Here’s how are sample reminder email template looks like. You can also include emojis, images, and file attachments in your email drafts.

Automatic Email Reminder

Now that our customer data is ready to be merged, go to the Addons menu in the sheet and choose Configure Mail Merge.

Here follow the step-by-step wizard to add your sender’s name and also specify addresses that you wish to CC/BCC in every merged message.

In the Create Email Template section, choose “Use a Gmail Draft” and select the draft template that you’ve created in the previous step.

Send Date-based Reminder Emails

Expand the “Send Email Campaign” section and choose “Send a Test Email” to preview your message before it gets sent to external users. If all looks good, choose “Run Mail Merge” and hit “Go”.

That’s it. Mail Merge will setup a background task that will continuously run in the background and whenever a passport is due to expire, an email reminder is automatically sent to the client based on the date in the Scheduled Date column.

Send Reminder Emails

You can check the “Mail Merge Logs” sheet to track progress and a copy of all emails will also be saved in your Gmail Sent Items folder.

The reminder emails are sent based on the timezone of your spreadsheet. If you would like to send emails in a different timezone, go to the File menu inside Google Sheet, choose Spreadsheet Settings and update the timezone.

You can also make use of Filters in Google Sheets to send automatic emails to rows that meet specific criteria - when the country is “India” or when a cell value contains “Follow-up” and so on.

The same date-based workflow can be utilized to automate email sending in multiple scenarios like sending personalized wishes on birthdays and anniversaries, domain renewal reminders, fee payment reminders, appointments and more.

See the Mail Merge section for help.

Convert Numbers to Words using Indian Numbering in Google Sheets

The Indian numbering and currency system widely uses lakhs and crores for writing large numbers. The term lakh means one hundred thousand while a crore represents ten million. A billion is written as hundred crores and there exist even higher denominations like arab, kharabh or neel which is equivalent to 10 trillion.

If you would like to write financial numbers in Google Sheets using the Indian Numbering system (lakhs, crores), here’s a custom Sheets function INR() that can spell the numeric value in words for you.

Also see: Make Invoices with Google Sheets and Forms

Indian Rupee in Google Sheets

Convert Numbers to Indian Rupees in Google Sheets

To get started, go to your Google Sheet, click the Tools menu and select Script Editor. Copy-paste the function below inside the Script Editor and chose File > Save to save your changes.

Now switch to the Google Sheet, type =INR(123) in any cell and it will instantly spell the number in words using the lakhs and crores system. The function was originally written by Amit Wilson and adopted to use the V8 Runtime.

/**
 * Convert number to words in Indian Rupees
 *
 * @param {number} input The value to convert.
 * @return The number in lakhs and crores.
 * @customfunction
 */
function INR(input) {
  const rupees = Number(parseInt(input, 10));
  const output = [];

  if (rupees === 0) {
    output.push("zero");
  } else if (rupees === 1) {
    output.push("one");
  } else {
    const crores = Math.floor(rupees / 10000000) % 100;
    if (crores > 0) {
      output.push(`${getHundreds(crores)} crore`);
    }

    const lakhs = Math.floor(rupees / 100000) % 100;
    if (lakhs > 0) {
      output.push(`${getHundreds(lakhs)} lakh`);
    }

    const thousands = Math.floor(rupees / 1000) % 100;
    if (thousands > 0) {
      output.push(`${getHundreds(thousands)} thousand`);
    }

    const hundreds = Math.floor((rupees % 1000) / 100);
    if (hundreds > 0 && hundreds < 10) {
      output.push(`${getOnes(hundreds)} hundred`);
    }

    const tens = rupees % 100;
    if (tens > 0) {
      if (rupees > 100) output.push("and");
      output.push(`${getHundreds(tens)}`);
    }
  }

  return ["Rupees", ...output, "only"]
    .join(" ")
    .split(/\s/)
    .filter((e) => e)
    .map((e) => e.substr(0, 1).toUpperCase() + e.substr(1))
    .join(" ");
}

function getOnes(number) {
  const ones = ["", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine"];
  return ones[number] || "";
}

function getTeens(number) {
  const teens = ["ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen", "seventeen", "eighteen", "nineteen"];
  return teens[number] || "";
}

function getTens(number) {
  const tens = ["", "", "twenty", "thirty", "forty", "fifty", "sixty", "seventy", "eighty", "ninety"];
  return tens[number] || "";
}

function getHundreds(num) {
  if (num > 0 && num < 10) {
    return getOnes(num);
  }
  if (num >= 10 && num < 20) {
    return getTeens(num % 10);
  }
  if (num >= 20 && num < 100) {
    return `${getTens(Math.floor(num / 10))} ${getOnes(num % 10)}`;
  }
  return "";
}

The Google Sheets function will only be available in the spreadsheet where you have added the above code. If you create a copy of the spreadsheet, the function would be copied as well.

Find Free Udemy Courses with Google Sheets and the Udemy API

Whether you are looking to learn a programming language, enhance your Microsoft Excel skills, or acquire knowledge in Machine Learning, Udemy probably has a video course for you. Udemy courses are usually affordable, there are no subscription fee and you can learn at your own pace.

Free Udemy Courses on Programming

While most video tutorials on Udemy require payment, the website also offers some of their highly-rated courses for free. I’ve prepared a Google Sheet that lists all the free programming courses currently available on Udemy. The spreadsheet is updated automatically every few hours. You can also access the web version for easy browsing.

Free Udemy Courses ✨ You may use the search function of the browser (Ctrl + F) to find courses for a specific programming language or topic. The courses are sorted by popularity.

There’s no secret sauce. Udemy has an developer API that provides access to all the course data available on the website, including user ratings, number of students who have taken the course, duration, preview video lectures, and more.

Use the Udemy API with Google Sheets

The Udemy API is free to use but requires authentication. You can generate the credentials for your Udemy account and then use the /courses endpoint to fetch the list of free courses.

const parseCourseData_ = (courses) =>
  courses
    .filter(
      ({ is_paid, primary_category }) =>
        is_paid === false && ['Development', 'IT & Software'].includes(primary_category.title)
      // We are primarily interested in programming courses on Udemy
    )
    .map((e) => [
      `=IMAGE("${e.image_240x135}")`,
      `=HYPERLINK("https://www.udemy.com${e.url}";"${e.title}")`,
      e.visible_instructors.map(({ display_name }) => display_name).join(', '),
      e.num_subscribers,
      Math.round(e.avg_rating * 100) / 100,
      e.num_reviews,
      e.content_info_short,
      e.num_lectures,
      new Date(e.last_update_date),
    ]);

const listUdemyCoursesGoneFree = () => {
  // Put your Udemy credentials here
  const CLIENT_ID = '';
  const CLIENT_SECRET = '';

  const params = {
    page: 1,
    page_size: 100,
    is_paid: false,
    'fields[course]': '@all',
  };

  const query = Object.entries(params)
    .map(([key, value]) => `${key}=${encodeURIComponent(value)}`)
    .join('&');

  const apiUrl = `https://www.udemy.com/api-2.0/courses/?${query}`;
  const bearer = Utilities.base64Encode(`${CLIENT_ID}:${CLIENT_SECRET}`);
  const options = {
    muteHttpExceptions: true,
    headers: {
      Authorization: `Basic ${bearer}`,
    },
  };

  const courses = [];

  do {
    const response = UrlFetchApp.fetch(apiUrl, options);
    const { results = [], next } = JSON.parse(response);
    courses.push(...parseCourseData_(results));
    url = next;
  } while (url && courses.length < 500);

  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const [sheet] = ss.getSheets();
  sheet.getRange(2, 1, sheet.getLastRow(), sheet.getLastColumn()).clearContent();
  sheet.getRange(2, 1, courses.length, courses[0].length).setValues(courses);
};

We use the UrlFetch service of Google Scripts to fetch the data from the Udemy API and the data is then parsed and inserted into the Google Sheet. The course thumbnail image is rendered using the IMAGE formula and the course title is linked to the Udemy website using the HYPERLINK formula.

How to Send SMS Messages with Google Sheets and Android Phone

The Mail Merge for Gmail add-on lets you send personalized emails via Gmail but wouldn’t it be nice if a similar solution existed for sending personalized SMS to your contacts directly from your mobile phone?

There are services, Twilio SMS for example, that let you send text messages programmatically to any phone number in the world. You can either build an SMS solution on top of these messaging APIs or you can take a simpler and less expensive route - build your own text sending app with Google Sheets and MIT’s App Inventor.

https://www.youtube.com/watch?v=PReU4ITp37I

Before getting the implementation, let me give you a quick demo of our text-messaging app for sending SMS from any Android phone. You can send texts to any number in your country as well as global phone numbers if international texting is enabled on your phone. You’ll pay the standard text messaging rates as per your cellular plan.

Here’s my Google Sheet with the source data.

SMS Messages in Google Sheets

The Google Sheet can have multiple columns for SMS personalisation but the three essential columns that should be present in the sheet are Phone (for your contact’s phone number), Status (whether the SMS was sent to that phone) and Text (the personalized text message).

You can use ArrayForumula with simple concatenation to build the text messages string from different columns as shown below:

=ArrayFormula(
    IF(NOT(ISBLANK(A2:A)),
      A2:A & " " & B2:B & " - I will see you in " & C2:C,
    )
)

Now that your source data is ready in the sheets, we will use Google Apps Script to convert our Google sheets data into an API. This would enable our Android app to read the sheets data with a simple HTTPS request.

Inside the sheets, go to Tools, Script Editor and paste this code.

const SHEET_URL = 'YOUR_GOOGLE_SHEET_URL';
const SHEET_NAME = 'SMS';

const doGet = () => {
  const sheet = SpreadsheetApp.openByUrl(SHEET_URL).getSheetByName(SHEET_NAME);
  const [header, ...data] = sheet.getDataRange().getDisplayValues();

  const PHONE = header.indexOf('Phone');
  const TEXT = header.indexOf('Text');
  const STATUS = header.indexOf('Status');

  const output = [];

  data.forEach((row, index) => {
    if (row[STATUS] === '') {
      output.push([index + 1, row[PHONE], row[TEXT]]);
    }
  });

  const json = JSON.stringify(output);

  return ContentService.createTextOutput(json).setMimeType(ContentService.MimeType.TEXT);
};

const doPost = (e) => {
  const sheet = SpreadsheetApp.openByUrl(SHEET_URL).getSheetByName(SHEET_NAME);
  const [header] = sheet.getRange('A1:1').getValues();
  const STATUS = header.indexOf('Status');
  var rowId = Number(e.parameter.row);
  sheet.getRange(rowId + 1, STATUS + 1).setValue('SMS Sent');
  return ContentService.createTextOutput('').setMimeType(ContentService.MimeType.TEXT);
};

Next, go to the Publish menu in the Google Script Editor, choose Deploy as web app. Choose “Me” under “Execute the App” setting and “Anyone, even anonymous” under the “Who has access” setting.

Click the Deploy button and you’ll be presented with a secret API URL that we’ll require in the next step. Do NOT share this API URL with anyone.

Now that our sheets API is ready, we’ll build an Android app that will read the list of text messages and phone numbers from our Google Sheets and send the SMS messages. The texts go directly from your phone SIM instead of using any third-party SMS gateway service.

Build SMS App for Android without Coding

You’d normally need to know programming languages like Flutter or Java to build Android apps but in this tutorial, we’ll use MIT’s App Inventor, a simple way to develop fully functional apps with drag-n-drop.

SMS Android App Inventor

Sign-in to the appinventor.mit.edu website with your Google account and create a new App. While you are in the design mode, drag the following components on to your app:

  • User Interface, ListView -> For displaying the message list fetched from Google Sheets.
  • User Interface, Button -> For fetching messages from Google Sheets and for sending SMS messages from the Android app.
  • Connectivity, Web -> For making GET and POST requests to Apps Script.
  • User Interface, Notifier -> For displaying progress bars and alerts
  • Social, Texting -> For sending the SMS messages.

Next switch to the Blocks section inside App Inventor and design the blocks as explained in the video tutorial.

SMS Android App Blocks

We are almost done.

Go to the Build menu inside App Inventor, choose App (provide QR code for .apk) and scan the QR code with your phone. It will download an APK file on the phone, install the APK and you are ready to send text messages.

Creating an Editable Site with Google Sheets and Eleventy

Remember Tabletop.js? We just covered it a little bit ago in this same exact context: building editable websites. It’s a tool that turns a Google Sheet into an API, that you as a developer can hit for data when building a website. In that last article, we used that API on the client side, meaning JavaScript needed to run on every single page view, hit that URL for the data, and build the page. That might be OK in some circumstances, but let’s do it one better. Let’s hit the API during the build step so that the content is built into the HTML directly. This will be far faster and more resilient.

The situation

As a developer, you might have had to work with clients who keep bugging you with unending revisions on content, sometimes, even after months of building the site. That can be frustrating as it keeps pulling you back, preventing you from doing more productive work.

We’re going to give them the keys to updating content themselves using a tool they are probably already familiar with: Google Sheets.

A new tool

In the last article, we introduced the concept of using Google Sheets with Tabletop.js. Now let’s introduce a new tool to this party: Eleventy

We’ll be using Eleventy (a static site generator) because we want the site to be rendered as a pure static site without having to ship all of the under workings of the site in the client side JavaScript. We’ll be pulling the content from the API at build time and having Eleventy create a minified index.html that we’ll push to the server for the production website. By being static, this allows the page to load faster and is better for security reasons.

The spreadsheet

We’ll be using a demo I built, with its repo and Google Sheet to demonstrate how to replicate something similar in your own projects. First, we’ll need a Google Sheet which will be our data store.

Open a new spreadsheet and enter your own values in the columns just like mine. The first cell of each column is the reference that’ll be used later in our JavaScript, and the second cell is the actual content that gets displayed.

In the first column, “header” is the reference name and “Please edit me!” is the actual content in the first column.

Next up, we’ll publish the data to the web by clicking on File → Publish to the web in the menu bar.

A link will be provided, but it’s technically useless to us, so we can ignore it. The important thing is that the spreadsheet(and its data) is now publicly accessible so we can fetch it for our app.

Take note that we’ll need the unique ID of the sheet from its URL  as we go on.

Node is required to continue, so be sure that’s installed. If you want to cut through the process of installing all of thedependencies for this work, you can fork or download my repo and run:

npm install

Run this command next — I’ll explain why it’s important in a bit:

npm run seed

Then to run it locally:

npm run dev

Alright, let’s go into src/site/_data/prod/sheet.js. This is where we’re going to pull in data from the GoogleSheet, then turn it into an object we can easily use, and finally convert the JavaScript object back to JSON format. The JSON is stored locally for development so we don’t need to hit the API every time.

Here’s the code we want in there. Again, be sure to change the variable sheetID to the unique ID of your own sheet.


module.exports = () => {
  return new Promise((resolve, reject) => {
    console.log(`Requesting content from ${googleSheetUrl}`);
    axios.get(googleSheetUrl)
      .then(response => {
        // massage the data from the Google Sheets API into
        // a shape that will more convenient for us in our SSG.
        var data = {
          "content": []
        };
        response.data.feed.entry.forEach(item => {
          data.content.push({
            "header": item.gsx$header.$t,
            "header2": item.gsx$header2.$t,
            "body": item.gsx$body.$t,
            "body2": item.gsx$body2.$t,
            "body3":  item.gsx$body3.$t,
            "body4": item.gsx$body4.$t,
            "body5": item.gsx$body5.$t,
            "body6":  item.gsx$body6.$t,
            "body7": item.gsx$body7.$t,
            "body8": item.gsx$body8.$t,
            "body9":  item.gsx$body9.$t,
            "body10": item.gsx$body10.$t,
            "body11": item.gsx$body11.$t,
            "body12":  item.gsx$body12.$t,
            "body13": item.gsx$body13.$t,
            "body14": item.gsx$body14.$t,
            "body15":  item.gsx$body15.$t,
            "body16": item.gsx$body16.$t,
            "body17": item.gsx$body17.$t,
            
          })
        });
        // stash the data locally for developing without
        // needing to hit the API each time.
        seed(JSON.stringify(data), `${__dirname}/../dev/sheet.json`);
        // resolve the promise and return the data
        resolve(data);
      })
      // uh-oh. Handle any errrors we might encounter
      .catch(error => {
        console.log('Error :', error);
        reject(error);
      });
  })
}

In module.exports, there’s a promise that’ll resolve our data or throw errors when necessary. You’ll notice that I’m using a axios to fetch the data from the spreadsheet. I like that it handles status error codes by rejecting the promise automatically, unlike something like Fetch that requires monitoring error codes manually.

I created a data object in there with a content array in it. Feel free to change the structure of the object, depending on what the spreadsheet looks like.

We’re using the forEach() method to loop through each spreadsheet column while equating it with the corresponding name we want to allocate to it, while pushing all of these into the data object as content. 

Remember that seed command from earlier? We’re using seed to transform what’s in the data object to JSON by way of JSON.stringify, which is then sent to src/site/_data/dev/sheet.json

Yes! Now have data in a format we can use with any templating engine, like Nunjucks, to manipulate it. But, we’re focusing on content in this project, so we’ll be using the index.md template format to communicate the data stored in the project.

For example, here’s how it looks to pull item.header through a for loop statement:

<div class="listing">
{%- for item in sheet.content -%}
  <h1>{{ item.header }} </h1>
{%- endfor -%}
</div>

If you’re using Nunjucks, or any other templating engine, you’ll have to pull the data accordingly.

Finally, let’s build this out:

npm run build

Note that you’ll want a dist folder in the project where the build process can send the compiled assets.

But that’s not all! If we were to edit the Google Sheet, we won’t see anything update on our site. That’s where Zapier comes in. We can “zap” Google sheet and Netlify so that an update to the Google Sheet triggers a deployment from Netlify.

Assuming you have a Zapier account up and running, we can create the zap by granting permissions for Google and Netlify to talk to one another, then adding triggers.

The recipe we’re looking for? We’re connecting Google Sheets to Netlify so that when a “new or updated sheet row” takes place, Netlify starts a deploy. It’s truly a set-it-and-forget-it sort of deal.

Yay, there we go! We have a performant static site that takes its data from Google Sheets and deploys automatically when updates are made to the sheet.

The post Creating an Editable Site with Google Sheets and Eleventy appeared first on CSS-Tricks.

Track Coronavirus (COVID-19) Cases in India with Google Sheets

The Government of India website has a live dashboard that provides, in near real-time, the number of Coronavirus (COVID-19) cases in various states of India. This is the best resource to get updates around active COVID-19 cases in India.

COVID-19 Tracker for India

The official website provides the current data but if you were to check how the number of confirmed cases increased in India over time, there’s no historic data available. That’s one reason I built the COVID-19 Tracker with Google Sheets.

The tracker scrapes data from the official website every few minutes and uses Sparklines to help you visualize how the coronavirus outbreak is spreading in India over time. The Government has been actively publishing reports since March 10 and all the data can also be accessed through the Google Sheet.

Covid-19 India tracker

COVID-19 Sheets Tracker

COVID-19 JSON API

If you are a developer, I’ve also published the data as a JSON API that will provide you the latest state-wise data of COVID-19 cases as available on the Ministry of Health and Family Welfare website of India.

How the COVID-19 Tracker Works

The Coronavirus Tracker is written in Google Apps Script and it uses time-based triggers to scrape numbers from the mohfw.gov.in website every few minutes.

/**
 * Scrape the homepage of mohfw.gov.in (Ministry of Health, India)
 * website for latest numbers on Coronovirus positive cases in India
 */
const scrapeMOHWebsite = () => {
  const url = 'https://www.mohfw.gov.in/';
  const response = UrlFetchApp.fetch(url);
  const content = response.getContentText();
  return content.replace(/[\r\n]/g, '');
};

Google Apps Script doesn’t support HTML parsers like Cheerio so we had to quickly build one from scratch using regex. It grabs the HTML content of the page, looks for the table tag and then extracts data from individual cells of the table.

If they change the layout of the website, this parser is likely to break.

/**
 * Parse the webpage content and extract numbers from the HTML
 * table that contains statewise data on Covid-19 Cases in India
 */
const getCurrentCovid19Cases = (json = true) => {
  const states = {};
  const html = scrapeMOHWebsite();
  const [table] = html.match(/<div id="cases".+?>(.+)<\/div>/);
  const rows = table.match(/<tr>(.+?)<\/tr>/g);
  rows.forEach((row) => {
    const cells = row
      .match(/<td.+?>(.+?)<\/td>/g)
      .map((cell) => cell.replace(/<.+?>/g, ''));
    const [, stateName, indianNationals, foreignNationals] = cells;
    if (/[a-z\s]/i.test(stateName)) {
      states[stateName] = Number(indianNationals) + Number(foreignNationals);
    }
  });
  return json ? states : JSON.stringify(states);
};

Once we have the data in JSON format, we can easily write to a Google Spreadsheet using Apps Script. The script adds a new column per day while retaining the old data for comparison.

/**
 * Write the parsed data into a new column in Google Sheet
 * All the historic data is also preserved in the sheet.
 */
const writeNewCovid19CasesToSheets = (covid19Cases) => {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(
    'Dashboard'
  );
  const states = sheet
    .getRange(3, 1, sheet.getLastRow() - 2, 1)
    .getValues()
    .map(([state]) => [covid19Cases[state] || 0]);
  sheet
    .getRange(2, sheet.getLastColumn() + 1, states.length + 1, 1)
    .setValues([[new Date()], ...states.map((count) => [count])]);
};

The COVID-19 tracker in Google Sheets also provides a JSON API that you can use to import data directly in your apps and websites.

To publish a JSON API, we have published the script as a web app with the doGet callback function. The ContentService service returns the raw JSON output whenever an external app invokes the Google script URL.

const doGet = () => {
  const key = 'Covid19India';
  const cache = CacheService.getScriptCache();
  let data = cache.get(key);
  if (data === null) {
    data = getCurrentCovid19Cases(false);
    cache.put(key, data, 21600);
  }
  return ContentService.createTextOutput(data).setMimeType(
    ContentService.MimeType.JSON
  );
};

All the code is open-source and you are free to use in any project.

How to Import MailChimp Subscribers to Google Sheets

The Gmail Mail Merge addon can now import the email addresses of subscribers from your MailChimp mailing lists into Google Sheets. If you wish to send emails to your subscribers directly from Gmail, instead of using MailChimp mail servers, this is the way to go.

As a developer, you can use Google Apps Script to import subscriber lists, HTML campaigns, performance reports and any other data from MailChimp to Google Sheets for analysis. You can use the MailChimp OAuth2 library but in this example, we’ll use the developer key directly to connect to MailChimp.

Get the MailChimp Developer Key

In your Mailchimp account, navigate to the Account page. In the drop-down menu, select Extras, and then API keys. Click Create A Key and make a note of it.

Google Apps Script - Get MailChimp Audiences

const MAILCHIMP_API_KEY = '<<API_KEY_HERE>>';

// MailChimp API key includes the data center id
// that your MailChimp account is associated with
const makeHttpRequest = (endpoint, params = {}) => {
  const [, mailchimpDataCenter] = MAILCHIMP_API_KEY.split('-');
  const url = `https://${mailchimpDataCenter}.api.mailchimp.com/3.0/${endpoint}`;
  const qs = Object.keys(params)
    .map(key => `${key}=${params[key]}`)
    .join('&');
  const apiUrl = qs ? `${url}?${qs}` : url;
  const request = UrlFetchApp.fetch(apiUrl, {
    method: 'GET',
    headers: {
      Authorization: `Basic ${Utilities.base64Encode(`labnol:${MAILCHIMP_API_KEY}`)}`
    }
  });
  return JSON.parse(request);
};

const getListMembers = (id, offset) => {
  const { members } = makeHttpRequest(`lists/${id}/members`, {
    count: 100,
    offset,
    fields: 'members.email_address',
    status: 'subscribed',
    sort_field: 'last_changed',
    sort_dir: 'DESC'
  });
  return members.map(({ email_address: email }) => [email]);
};

// Get a list of all subscribers of a specific
// MailChimp mailing list, you can retrieve the email address,
// name and subscription statues of subscribers
const getMailChimpListMembers = id => {
  let hasMore = true;
  let data = [];
  do {
    const emails = getListMembers(id, data.length);
    data = [...data, ...emails];
    hasMore = emails.length > 0;
  } while (hasMore);
  return data;
};

// Get a list of all audiences / lists from MailChimp
const getMailChimpLists = () => {
  const params = { count: 10, fields: 'lists.id,lists.name', sort_field: 'date_created', sort_dir: 'DESC' };
  const { lists = [] } = makeHttpRequest('lists', params);
  return lists.map(({ id, name }) => ({ id, name, members: getMailChimpListMembers(id) }));
};

The GetMailChimpLists method will bring all the lists and associated email addresses in a JSON object that you can easily write to Google Sheets using the SpreadsheetApp service.

How to Scrape Reddit with Google Scripts

Reddit offers a fairly extensive API that any developer can use to easily pull data from subreddits. You can fetch posts, user comments, image thumbnails, votes and most other attributes that are attached to a post on Reddit.

The only downside with the Reddit API is that it will not provide any historical data and your requests are capped to the 1000 most recent posts published on a subreddit. So, for instance, if your project requires you to scrape all mentions of your brand ever made on Reddit, the official API will be of little help.

You have tools like wget that can quickly download entire websites for offline use but they are mostly useless for scraping Reddit data since the site doesn’t use page numbers and content of pages is constantly changing. A post can be listed on the first page of a subreddit but it could be pushed to the third page the next second as other posts are voted to the top.

Reddit Data in Google Sheets

Download Reddit Data with Google Scripts

While there exist quite a Node.js and Python libraries for scraping Reddit, they are too complicated to implement for the non-techie crowd. Fortunately, there’s always Google Apps Script to the rescue.

Here’s Google script that will help you download all the user posts from any subreddit on Reddit to a Google Sheet. And because we are using pushshift.io instead of the official Reddit API, we are no longer capped to the first 1000 posts. It will download everything that’s every posted on a subreddit.

  1. To get started, open the Google Sheet and make a copy in your Google Drive.
  2. Go to Tools -> Script editor to open the Google Script that will fetch all the data from the specified subreddit. Go to line 55 and change technology to the name of the subreddit that you wish to scrape.
  3. While you are in the script editor, choose Run -> scrapeReddit.

Authorize the script and within a minute or two, all the Reddit posts will be added to your Google Sheet.

Technical Details - How to the Script Works

The first step is to ensure that the script not hitting any rate limits of the PushShift service.

const isRateLimited = () => {
  const response = UrlFetchApp.fetch('https://api.pushshift.io/meta');
  const { server_ratelimit_per_minute: limit } = JSON.parse(response);
  return limit < 1;
};

Next, we specify the subreddit name and run our script to fetch posts in batches of 1000 each. Once a batch is complete, we write the data to a Google Sheet.

const getAPIEndpoint_ = (subreddit, before = '') => {
  const fields = ['title', 'created_utc', 'url', 'thumbnail', 'full_link'];
  const size = 1000;
  const base = 'https://api.pushshift.io/reddit/search/submission';
  const params = { subreddit, size, fields: fields.join(',') };
  if (before) params.before = before;
  const query = Object.keys(params)
    .map(key => `${key}=${params[key]}`)
    .join('&');
  return `${base}?${query}`;
};

const scrapeReddit = (subreddit = 'technology') => {
  let before = '';
  do {
    const apiUrl = getAPIEndpoint_(subreddit, before);
    const response = UrlFetchApp.fetch(apiUrl);
    const { data } = JSON.parse(response);
    const { length } = data;
    before = length > 0 ? String(data[length - 1].created_utc) : '';
    if (length > 0) {
      writeDataToSheets_(data);
    }
  } while (before !== '' && !isRateLimited());
};

The default response from Push Shift service contains a lot of fields, we are thus using the fields parameter to only request the relevant data like post title, post link, date created and so on.

If the response contains a thumbnail image, we convert that into a Google Sheets function so you can preview the image inside the sheet itself. The same is done for URLs.

const getThumbnailLink_ = url => {
  if (!/^http/.test(url)) return '';
  return `=IMAGE("${url}")`;
};

const getHyperlink_ = (url, text) => {
  if (!/^http/.test(url)) return '';
  return `=HYPERLINK("${url}", "${text}")`;
};

Bonus Tip: Every search page and subreddit on Reddit can be converted into JSON format using a simple URL hack. Just append .json to the Reddit URL and you have a JSON response.

For instance, if the URL is https://www.reddit.com/r/todayIlearned, the same page can be accessed in JSON format using the URL https://www.reddit.com/r/todayIlearned.json.

This works for search results as well. The search page for https://www.reddit.com/search/?q=india can be downloaded as JSON using https://www.reddit.com/search.json?q=india.

GDPR Compliance

The General Data Protection Regulation (GDPR) is aimed at giving citizens throughout the European Union (EU) control over their personal data. GDPR applies to you if you are a European Union citizen or, as a business, if your email subscribers are EU citizens, or you deal with any kind of personal data of EU citizens.

How our add-ons enable you to comply with EU’s GDPR policies

As a customer, you operate as the data controller and we are considered a data processor. You have the responsibility for ensuring that the personal data of subjects you are collecting is being processed lawfully and, similar to controllers, processors, that processes personal data on behalf of a data controller, are expected to comply with the GDPR.

Data Collection

With GDPR, you must have explicit consent from your email subscribers that they would like to receive emails from you. It is recommended that you use double opt-in to align with GDPR compliance requirements. For EU individuals who are already on your marketing lists, you may need to contact them by email asking them to confirm their consent.

You should include a visible unsubscribe link in your marketing emails that your subscribers can click to instantly unsubscribe from all your future communications.

Data Storage and Processing

All your customer’s data is stored in your Google account, inside Google Sheets, Docs, Gmail, Google Drive or Google Forms and not on our servers. Our addons read the data directly from your data source and perform the necessary actions (like sending emails, generating documents, saving emails) without transferring any personal data.

The email messages are not stored on our servers. If you choose to attach Google Drive files in your emails, the content of the files are not stored on our servers. Your form submissions are not stored on our servers. If you enable optional campaign tracking (opens, clicks, unsubscribes), the email addresses of recipients, unsubscribers, and bounced emails are stored in the database for reporting.

We store and process user data in Google Cloud database (us-east) and its servers are located in the United States (East) data center.

We use Google’ Stackdriver logging tool for error tracking and debugging errors. It includes stack traces, error messages and the logs do not include any PII data.

We use PayPal, Stripe, and Paddle to manage your payments. The payment processors only provide the customer’s email address and, in case of PayPal, the shipping address for generating invoice. We do not have access to any banking or credit card information of our customers.

Data Portability

We do not transfer, sell, make copies, or share any of your data processed by our Google Add-ons to third party services or companies. We only store data that is absolutely necessary for our add-ons to function.

You can use download and export all your subscriber’s information in Google Sheets. This allows for easier migration to other services.

Data Erasure (Right to be forgotten)

All addons have a deactivate option that will permanently delete all user’s data from the database. You can also contact us to submit a deletion requires and, in compliance towards GDPR, we’ll permanently delete all your data.

If you uninstall a Google Addon, or revoke access to the addon from your Google Account, the add-on will not be able to access any of your data and will instantly stop functioning.

Our Google Addons use your own Gmail account to send emails. You can feed your customer profile data directly into our Google Add-ons - through Google Sheets and Google Forms - to send marketing emails, transaction emails and form notifications. Our tools only facilitate your compliance to GDPR, your sending practice is key to complying with GDPR.

Contact Us

If you have any questions, please contact us by email: amit@labnol.org

Best office suites for 2019

Microsoft Office 365 (web, Windows, Mac, Android, iOS)

Generally, you would purchase a boxed duplicate of Office for $100 or more and install it from the CD on your PC. That would give you Word, Excel, and PowerPoint, alongside additional tools like Outlook and Access.

Office 365 changed that all. From $6.99/month, you can get each Office application on all your devices. Or then again you can use the free Office Online versions of those programs from your browser or the nearly full-featured mobile applications so you can complete your cheap essay writing thirst anywhere.

Start writing a Word document on your desktop PC, make some edits on your phone, and then print it out from the program once you get to the workplace. You can collaborate in Office Online, and edit a document along with associates in Word Online or adding remarks to an Excel Online spreadsheet that your collaborators can check later.

Office applications each incorporate a larger number of features than those in most of the suites featured in this roundup, with many years of improvement behind each one. Excel includes an amazing number of functions and data analysis tools, while OneNote is a standout amongst the most innovative note applications with a paper-like design that gives you a chance to compose what you need.

If you intend to work using a PC or a tablet more often and don’t have any desire to work using your browser, Microsoft Office is still likely your best option for most office work. Its applications are some of the best ways to create documents and spreadsheets, with enough collaboration features and new applications to make them work in the modern workplace. Office 365 also incorporates 1TB of capacity in OneDrive so you can back up your documents online.

  • Office 365 tools include: Word processor, spreadsheets, presentations, notes, database developer (on PCs), email, file sync
  • Office 365 Excel spreadsheet functions supported: 465
  • Office 365 price: Free Office Online web applications; $6.99/month Personal plan for individual use; $9.99/month Home for up to 5 PCs or Macs; from $10/month Business plan for organization use per client

Google G Suite (Web, Android, iOS)

Microsoft Office may have been the first and most often the default “office suite” in the enterprise, yet it took Google to take it beyond desktops and into the cloud. G Suite was once named Writely, a basic internet composing application from 2005, which Google obtained and transformed into the foundation of the present best Microsoft Office competitor.

G Suite’s individual applications are modern tools with all that you need to make documents, spreadsheets, and presentations.

What makes G Suite incredible is the way it works for collaboration. You can share your records to anybody — even freely on the web if you like — and let them all jump in and help make a masterpiece. You can live edit with others in the meantime or leave remarks that Google will email for input later. Since it accompanies any Google account, G Suite is for all intents and purposes universally.

It’s getting smarter, as well. The new Explore tool in Google Docs and Sheets can discover bits of information from your data, making outlines from your spreadsheets and finding related documents automatically. It can even work while you sleep. Add a Google Form to your Google Sheets spreadsheet and new answers will appear in your sheet consequently, ready for whenever you sign in.

G Suite is one of the best on the web. It has versatile applications, however with fewer features than the core web applications. You can’t add recommended edits or most formatting to a document on mobile, for instance. Also, G Suite possibly works offline when you use it in Chrome. But if you mainly work from a PC and are online most of the time, G Suite is one of the best options to work on documents online with your team or any individual who has a Gmail account.

  • G Suite tools include: Word processor, spreadsheet, presentations, notes, email, file sync
  • G Suite sheets spreadsheet functions supported: 351
  • G Suite price: free for individual use; from $5/month per client Basic plan for groups

Apple iWork (web, Mac, iOS)

Need to make catchy documents, spreadsheets, and presentations? Apple iWork applications (Pages, Keynote, and Numbers) are the easiest way to create them, and they’re free with Macs and iOS devices. You’ll either discover them preloaded on your new devices or in the App Store as a free download.

Each includes various formats and templates to help you instantly make the files you want in a simple interface that conceals the most part of its tools until the point when you require them. You can begin with a template, pull in your very own pictures and illustrations, and wind up with a print-quality file in minutes. Or then again, transform your file into a book, as Pages give you a chance to export any document as an ePub book that is immaculate to distribute to an eBook store.

Numbers is an amazing spreadsheet application, acting more like a document than the common matrix filled sheet you’ll discover in Excel or Google Sheets. Do the math you require, include content and graphs around it in the blank document to create a full report around your core table.

Keynote, also, is amazing for something other than your standard slideshow. Its animations are so fluid that it can be used as a tool for prototyping new applications and making short animated videos. Or on the other hand, you can use it to make your next meeting more interesting: With the new Keynote Live, you can stream your presentation online directly from the application.

iWork is best in its local applications on Mac and iOS, however, you can also use it online from iCloud.com. This is an amazing way to impart your documents to colleagues who don’t use iWork or to quickly tweak a presentation from a work PC when you forget your laptop.

  • Apple iWork tools include: Word processor, spreadsheet, presentations (notes, mail, and calendar applications also included with iOS and macOS)
  • Apple Numbers spreadsheet functions supported: 266
  • Apple iWork price: Free for Mac and iOS; free web application with an iCloud account, which comes free with any Apple gadget

Zoho Workplace (web, Android, iOS)

Zoho offers a whole bunch of applications, yet everything began with their online word processor, Zoho Writer. New applications joined the group step by step, in the end transforming into Zoho Workplace — a full office suite on the web, with many other Zoho applications that you can add on if necessary.

Writer keeps on being the leading application in the suite, with another design that rethinks how a word processor ought to be designed. It keeps about the majority of its features hidden by default, for a distraction-free interface. Need to change something? Open the left sidebar to discover the wide array of tools sorted out in toolbars. Acquire Microsoft Office documents, and Zoho completes an amazing activity at retaining the most of the original formatting.

For anything else you have to do, there’s a Zoho application to take care of it. Its presentations and spreadsheet applications pursue a progressively conventional style, with menus and toolbars blended so you can work in the way you want. Each application includes a Zoho chatbox where you can chat with your collaborators and keep the discussion going on regardless of which Zoho application you’re using.

  • Zoho Workplace tools included: Word processor, spreadsheets, presentations, email, group visit, document match up, sites (with 25+ other Zoho applications accessible)
  • Zoho Sheet spreadsheet functions supported: 362
  • Zoho Workplace price: Free for 25 clients with 5GB capacity; $3/month per client Standard arrangement for unlimited users and 30GB storage

This is a guest article by Xenia Infinity.

How to Send Emails to Only Specific Rows of a Google Sheet with Mail Merge

The Mail Merge spreadsheet contains a mandatory column called Mail Merge Status that indicates the sending/scheduling status of merge for the recipient of that particular row.

For instance, the mail merge status column of value “Mail Sent” indicates that the email has already been sent to the recipient in that row. If you run Mail Merge again, the row will be skipped and the email will not go out to recipients mentioned in that row.

mail-merge-status.png

Rows #4 and #5 will not be included in Mail Merge since the Mail Status column for these rows was manually set to SKIP

Mail Merge status column can have 5 values:

  1. Email Sent - The messages has been sent and this row will not be included in future merge until you clear the cell value.
  2. Email Scheduled - The message is queued for future delivery. The status will auto change to Email Sent after the scheduled message is delivered.
  3. Draft Created - If you are using Mail Merge to create drafts in Gmail, this status indicates that the draft has been created in Gmail for that recipient.
  4. Quota Over - This indicates that you have run out of your daily email sending limit, new emails won’t be sent but you can still schedule messages.
  5. Error Message - If merge encountered an error, maybe the email address was not valid, the status column will show the full error message.

How to Skip Sending Mails to Specific Rows

Let’s say you have a Google Spreadsheet with a dozen entries but you would only like to include, say, 5 rows in Mail Merge and skip the other rows.

You can easily do this by manually setting the Mail Merge Status column as SKIP for rows that you would not like to be included in the merge.

Also, rows with status as “Email Sent” will also not included in the merges.

Use Google Sheets Filters to Hide Rows

Mail Merge for Gmail will only send emails to rows that are visible (not hidden) in your Google Sheet. If you are using Filters inside Google Sheets to hide rows that don’t meet a certain criteria, the emails will not be sent to the hidden rows.

Say you have a Google Sheet with hundreds of rows and the column titles are Name, Country, Age and Email Address. You would only like to send emails to rows where the Country is United States and Age > 18 years.

All you have to do is create a filter inside Google Spreadsheet with the specified criteria and all the unmatched rows will be hidden. Mail merge will automatically skip the rows hidden by the Google Sheets filter.

Step 1: Select the Data Range, go to Data and Choose Filter.

google-sheets-filter.png

Step 2: Click the Dropdown next to the column name and specify the criteria.

setup-filter.png

You can either Filter by Values (show rows that have United States as value) or Filter by Contain (show rows where the age > 18).

Google Sheets will now display rows that match your filter criteria and when you run Mail Merge, it will only email visible rows provided the corresponding Mail Merge Status column is blank.