Category Archives: Google Shopping ads

Update woocommerce product via google sheet API

To update woocommerce products (price, sale price, quantity…), we can use Google sheet as database & Google sheet API to automatically update woocommerce. Below is the workflow: 

Prepare the product template => update the template with real data => upload to woocommerce (manual/API): 

+ Download from woocommerce (export product data – built-in function of woocommerce)

+ Put the template product data to Google sheet

+ Update the product data in Google sheet (edit price, quantity…)

+ Upload edited data to Woocommerce: 

           . For manual upload: Download file from Goolge sheet as csv file (default file downloaded from Google sheet is encoded UTF8 csv file – supported by Woo). Note: Excel doesnt support the encoded utf 8 csv file => Dont use Excel.

           . For API work:


Woocommerce Rest API – link data với google sheet

1. Generate Keys – tạo API key trong Woocommerce setting

To start using REST API, you first need to generate API keys.

  1. Go to WooCommerce > Settings > Advanced
  2. Go to the REST API tab and click Add key.
  3. Give the key a description for your own reference, choose a user with access to orders etc, and give the key read/write permissions.
  4. Click Generate api key.
  5. Your keys will be shown – do not close this tab yet, the secret will be hidden if you try to view the key again.

To test API in test tool (ex:, username = consumer key, password = consumer secret 
Note: + Using https, header with authentication (username & password) can not be read.

+ If error 401 => check security measures (server firewall and/or security plugin – ex: ithemes security … to block API access)

2. Woocommerce Rest API endpoints: (orders/products)

Endpoints are located at WooCommerce > Settings > Advanced.

List all products: domain//wp-json/wc/v3/products


WooCommerce GET request examples

+ GET request to view all orders
+ GET request to view a single order{insert order ID}
+ GET request to view all products     //Type = variable products & 1<per_page<100

+ Get all variations of a product: => to get all variations of all products, we need to loop through all variable product IDs.

+ GET request to view a single product{insert product ID}
+ GET request to view a single product variation{insert product ID}/variations/{insert variation ID}
+ GET request to view all customers
+ GET request to view a single customer{insert customer ID}


Routes vs Endpoints

A route is the “name” you use to access endpoints, used in the URL. A route can have multiple endpoints associated with it, and which is used depends on the HTTP verb.

For example, with the URL

  • The “route” is wp/v2/posts/123 – The route doesn’t include wp-json because wp-json is the base path for the API itself.
  • This route has 3 endpoints:
    • GET triggers a get_item method, returning the post data to the client.
    • PUT triggers an update_item method, taking the data to update, and returning the updated post data.
    • DELETE triggers a delete_item method, returning the now-deleted post data to the client.

3. Authentication with google scripts:

Solution from google – basic authentication

var USERNAME = 'your_username';
var PASSWORD = 'your_password';
var API_URL = 'http://<place_api_url_here>';

var authHeader = 'Basic ' + Utilities.base64Encode(USERNAME + ':' + PASSWORD);
var options = {
  headers: {Authorization: authHeader}
// Include 'options' object in every request
var response = UrlFetchApp.fetch(API_URL, options);


4. Output Woocommerce data to Google Sheet

After all 3 above steps, we need to output data to Google Sheet.

Issue 1: To protect server, Woocommerce API returns 100 result/page (Ref: Rest API pagination)

=> Solution: loop to all API URL để lấy toàn bộ các API URL  => Loop through all Url returned data to append each product/order to Google Sheet.

Issue 2: Setup the script to run on a specific time (ex: at 00:00 each day)

=> Solution: Using trigger

4.1 Woo API Pagination

Woocommerce API limit per page: maximum 100 results/page => If the result set is 500 products/order => 5 API URLs 

Ex: URL 1: yourdomain/wp-json/wc/v3/products?per_page=100&page=1 (first 100 results)

      URL 2: yourdomain/wp-json/wc/v3/products?per_page=100&page=2 (2nd 100 results)

      URL 3: yourdomain/wp-json/wc/v3/products?per_page=100&page=3 (3rd 100 results)

      URL 4: yourdomain/wp-json/wc/v3/products?per_page=100&page=4 (4th 100 results)

      URL 5: yourdomain/wp-json/wc/v3/products?per_page=100&page=5 (5th 100 results)

How to know how many pages & products/orders does the data set have:  

based on the API header below:  

  • X-WP-Total: Total returned records – Using Postman or to see 
  • X-WP-TotalPages: Total returned pages

Ref: Retrieve entire data from paginated API recursively 

4.2 Actual code:

Script example:

function myFunction() {
var USERNAME = 'comsumer key';
var PASSWORD = 'secret key';
var headers = {
"Accept": "application/xml",
"Content-Type": "application/xml",
"Authorization": "Basic "+ Utilities.base64Encode(USERNAME+":"+PASSWORD)
var options = {
"method" : "get",
"headers" : headers 
for (page = 1; page < 4; page++) { //see header X-WP-TotalPages to know total how many pages returned
var URL = 'Yourdomain/wp-json/wc/v3/products?per_page=20'+'&page='+page;
var response = UrlFetchApp.fetch(URL,options);
var data = JSON.parse(response); 
var sheet = SpreadsheetApp.getActiveSheet();
// loop through the map and output to sheet
for (i = 0; i < data.length; i++) { 


Ref: get all api url& fetch:

5. Using time-based script to automate script: 

+ Using time-driven trigger to wake up the main script everyday/week…

+ Clear the spreadsheet everytime the main script run to get fresh data

function startTimeTrigger() {

6. Cell formatting in google sheet: 

Format a single column: 

var column = sheet.getRange("B2:B"); //select column B from cell B2



Tìm giá trị thỏa mãn nhiều điều kiện trong sheet – array formula

Issue: cần tìm value thỏa mãn nhiều điều kiện (các điều kiện thuộc các dòng khác nhau) => trả lại value thuộc dòng đó.

Solution: Sử dụng Index & Match

{=INDEX(return_range, MATCH(1, (criteria1=range1) * (criteria2=range2) * (…), 0))}

Important note! This is an array formula and it must be completed with Ctrl + Shift + Enter. This will enclose your formula in {curly brackets}, which is a visual sign of an array formula in Excel. Do not try typing the braces manually, that won’t work!


Google apps script: Creates a Date Stamp if a column is edited

* Creates a Date Stamp if a column is edited.

// The column you want to check if something is entered.
var COLUMNTOCHECK = 6;   //cot can track khi co su thay doi
// Where you want the date time stamp offset from the input location. [row, column]
var DATETIMELOCATION = [0,-4];   //cot se dien thoi gian thay doi
// Sheet you are working on
var SHEETNAME = ‘Inventory’       //ten sheet co su thay doi

function onEdit(e) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
//checks that we’re on the correct sheet.
if( sheet.getSheetName() == SHEETNAME ) {
var selectedCell = ss.getActiveCell();
//checks the column to ensure it is on the one we want to cause the date to appear.
if( selectedCell.getColumn() == COLUMNTOCHECK) {
var dateTimeCell = selectedCell.offset(DATETIMELOCATION[0],DATETIMELOCATION[1]);
dateTimeCell.setValue(new Date());

Code trộn template với google apps script

Tham khảo: example trộn data từ google sheet => docs template:

cần 4 file: google sheet, google docs template, google docs output => tạo file output.



Code trộn data từ Google sheet vào template có sẵn (lưu trong google sheet):

function formSubmit(e) {
if (e.values[1] == "Gửi"){
function sendEmails() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var dataSheet = ss.getSheetByName("Data"); //Sheet "Data" chứa thông tin cần sử dụng
var dataRange = dataSheet.getRange(2, 1, dataSheet.getMaxRows() - 1, dataSheet.getLastColumn());

var templateSheet = ss.getSheetByName("Template"); //Sheet "template" chứa template mẫu 
var emailTemplate = templateSheet.getRange("B3").getValue(); //Email template ở ô B3 - sheet "template"

// Create one JavaScript object per row of data.
var objects = getRowsData(dataSheet, dataRange);

// For every row object, create a personalized email from a template and send
// it to the appropriate person.
for (var i = 0; i < objects.length; ++i) {
// Get a row object
var rowData = objects[i];

// Generate a personalized email.
// Given a template string, replace markers (for instance ${"First Name"}) with
// the corresponding value in a row object (for instance rowData.firstName).
var emailText = fillInTemplateFromObject(emailTemplate, rowData);
var emailSubject = templateSheet.getRange("B2").getValue(); //Email subject lấy từ ô B2 - sheet "Template"

MailApp.sendEmail(, emailSubject, emailText,{
name: templateSheet.getRange("B1").getValue().toString(),
// Replaces markers in a template string with values define in a JavaScript data object.
// Arguments:
// - template: string containing markers, for instance ${"Column name"}
// - data: JavaScript object with values to that will replace markers. For instance
// data.columnName will replace marker ${"Column name"}
// Returns a string without markers. If no data is found to replace a marker, it is
// simply removed.
function fillInTemplateFromObject(template, data) {
var email = template;
// Search for all the variables to be replaced, for instance ${"Column name"}
var templateVars = template.match(/\$\{\"[^\"]+\"\}/g);

// Replace variables from the template with the actual values from the data object.
// If no value is available, replace with the empty string.
for (var i = 0; i < templateVars.length; ++i) {
// normalizeHeader ignores ${"} so we can call it directly here.
var variableData = data[normalizeHeader(templateVars[i])];
email = email.replace(templateVars[i], variableData || "");

return email;

// The code below is reused from the 'Reading Spreadsheet data using JavaScript Objects'
// tutorial.

// getRowsData iterates row by row in the input range and returns an array of objects.
// Each object contains all the data for a given row, indexed by its normalized column name.
// Arguments:
// - sheet: the sheet object that contains the data to be processed
// - range: the exact range of cells where the data is stored
// - columnHeadersRowIndex: specifies the row number where the column names are stored.
// This argument is optional and it defaults to the row immediately above range;
// Returns an Array of objects.
function getRowsData(sheet, range, columnHeadersRowIndex) {
columnHeadersRowIndex = columnHeadersRowIndex || range.getRowIndex() - 1;
var numColumns = range.getEndColumn() - range.getColumn() + 1;
var headersRange = sheet.getRange(columnHeadersRowIndex, range.getColumn(), 1, numColumns);
var headers = headersRange.getValues()[0];
return getObjects(range.getValues(), normalizeHeaders(headers));

// For every row of data in data, generates an object that contains the data. Names of
// object fields are defined in keys.
// Arguments:
// - data: JavaScript 2d array
// - keys: Array of Strings that define the property names for the objects to create
function getObjects(data, keys) {
var objects = [];
for (var i = 0; i < data.length; ++i) {
var object = {};
var hasData = false;
for (var j = 0; j < data[i].length; ++j) {
var cellData = data[i][j];
if (isCellEmpty(cellData)) {
object[keys[j]] = cellData;
hasData = true;
if (hasData) {
return objects;

// Returns an Array of normalized Strings.
// Arguments:
// - headers: Array of Strings to normalize
function normalizeHeaders(headers) {
var keys = [];
for (var i = 0; i < headers.length; ++i) {
var key = normalizeHeader(headers[i]);
if (key.length > 0) {
return keys;

// Normalizes a string, by removing all alphanumeric characters and using mixed case
// to separate words. The output will always start with a lower case letter.
// This function is designed to produce JavaScript object property names.
// Arguments:
// - header: string to normalize
// Examples:
// "First Name" -> "firstName"
// "Market Cap (millions) -> "marketCapMillions
// "1 number at the beginning is ignored" -> "numberAtTheBeginningIsIgnored"
function normalizeHeader(header) {
var key = "";
var upperCase = false;
for (var i = 0; i < header.length; ++i) {
var letter = header[i];
if (letter == " " && key.length > 0) {
upperCase = true;
if (!isAlnum(letter)) {
if (key.length == 0 && isDigit(letter)) {
continue; // first character must be a letter
if (upperCase) {
upperCase = false;
key += letter.toUpperCase();
} else {
key += letter.toLowerCase();
return key;

// Returns true if the cell where cellData was read from is empty.
// Arguments:
// - cellData: string
function isCellEmpty(cellData) {
return typeof(cellData) == "string" && cellData == "";

// Returns true if the character char is alphabetical, false otherwise.
function isAlnum(char) {
return char >= 'A' && char <= 'Z' ||
char >= 'a' && char <= 'z' ||

// Returns true if the character char is a digit, false otherwise.
function isDigit(char) {
return char >= '0' && char <= '9';


Sử dụng Google sheet làm phần mềm quản lý tồn kho

Quản lý tồn kho bằng Google sheet


+ Đổ dữ liệu hàng hóa dựa trên ID & tên & quantity từ website => google sheet

+ Xuất đơn hàng mới (tự động từ Website API/webhook) & nhập hàng (manual/quét mã vạch) => google sheet tự tính tồn kho

+ Google Sheet tự động xuất dữ liệu tồn kho làm đầu vào cho Google merchant/ Zalo shop/ Shopee …

          => Đồng bộ tự động tất cả các kênh bán hàng.

Ưu điểm: free, cộng đồng hỗ trợ google apps script lớn – các scripts có sẵn nhiều, chỉ cần lấy về & tùy biến cho phù hợp nhu cầu.

Nhược điểm: Google sheet có các limits về tính toán => cần giữ sheet tồn kho càng đơn giản càng tốt (hạn chế link với các sheet khác => mỗi khi có thay đổi tồn kho thì xuất ra một file static và send dữ liệu đến các platform) 


  1. Tạo sheet – đơn hàng xuất => nhận dữ liệu đơn hàng tự động từ Website (woo) với các chi tiết đơn hàng (product ID, variation ID, tên sp, quantity, chi tiết khách hàng)
  2. Tạo sheet – đơn hàng nhập => nhận dữ liệu nhập hàng (manual hoặc từ barcode scanner)
  3. Xây dựng sheet tồn kho:

          + Tồn kho cập nhật = Nhập + tồn – xuất (theo variation ID)  => Sử dụng SumIF & IFErrror (nếu cần) & Vlookup (nếu cần)

          + Export dữ liệu tồn kho (static data) mỗi khi có thay đổi vào 1 sheet mới: Sheet tồn kho có nhiều công thức => khi có thay đổi sẽ export ra một sheet mới (chỉ có static data – không có công thức) => làm đầu vào cho các dịch vụ khác (Google merchant) => sử dụng funtction ImportRange

* Sử dụng function ImportRange để xuất toàn bộ dữ liệu từ sheet tồn kho sang 1 sheet khác (values only)  

           + Tạo một spreadsheet để lấy dữ liệu variable products từ sheet “Tồn kho” (Google remarketing chỉ cần tồn kho còn hay không để chạy quảng cáo bám đuôi cho khách đã xem sản phẩm – không cần cụ thể theo size/variation product) => sử dụng vlookup tìm theo product ID => điền tự động dữ liệu tồn kho

           + Từ file merchant Data, link với sheet variable products mới tạo ở trên – sử dụng ImportRange => ImportRange xuất dữ liệu “Values only”

           + Lệnh ImportRange chạy tự động by default 30 mins/lần (thay đổi trong File/Spreadsheet settings/Calculation)

          Câu lệnh function IMPORTRANGE: IMPORTRANGE("", "sheet1!A1:C10")      


Next development: Điền template sử dụng dữ liệu từ Google Sheet – tham khảo hướng dẫn sau:

Giải thích chi tiết từ Google:

Google sheet limits

40,000 new rows

18,278 columns

5 million cells per workbook

50,000 characters for a single cell.

Tips to speed up Google sheet:

+ Convert formulas to static values wherever possible

Làm việc với JSON

Giới thiệu chung về JSON:

+ JSON viết tắt bởi JavaScript Object Notation
“type”: “laptop”,
“brand”: “Sony”,
“operating system”: “Windows 7”,
“graphic card”: “NVIDIA”

ví dụ trên cũng chính là biểu diễn cho một Object trong JavaScript. Trong đó, gồm có hai thành phần:

keys: type, brand, operating system, graphic card
values: laptop, Sony, Windows 7, NVIDIA

Đặc trưng của JSON: 

+ Chuỗi JSON được bao lại bởi dấu ngoặc nhọn {}

+ Key: luôn luôn phải được đóng gói trong cặp dấu ngoặc kép, không phải ngoặc đơn, cũng không được phép là biến số (variable)
+ Value: Chỉ được phép là những dữ liệu cơ bản như numbers, strings, Boolean, array, object, null, không được phép là function, date, undefined hoặc là một biểu thức tính toán. Trường hợp trong value của bạn có chứa dấu nháy kép thì hãy dùng dấu () để đặt trước nó nhé, ví dụ học “json là gì”.
+ Không được phép tồn tại dấu phẩy cuối cùng như JavaScript Object.
+ Đối với number thì không được phép có chữ số 0 ở đầu. Ngoài ra, nếu đó là chữ số thập phân thì phải có ít nhất 1 chữ số sau dấu chấm (.).

Cách sử dụng

+ JSON.stringify dùng để convert một JavaScript Object thành JSON string.
+ JSON.parse dùng để convert string biểu diễn JSON thành JavaScript Object.

Khai thác dữ liệu JSON: 

Dữ liệu trong mảng được đánh số từ 0 (zero-based): Số đánh dấu đối tượng đầu tiền là 0, sau đó là 1, 2, 3…


“cars”:[ “Ford”“BMW”“Fiat” ]

x =[0]; //Kết quả là Ford. 

Tham khảo:

Xem thêm về sử dụng Json khi Sync Woocommerce data với goolge sheet

Export woocommerce data to google sheet automatically

I. Workflow:

+ Mục tiêu: cập nhật đơn hàng mà khách mới đặt trên Woocommerce => Google sheet theo thời gian thực

+ Cách làm: sử dụng data cung cấp từ Woocommerce Webhooks và Google Apps script ghi dữ liệu vào Google sheet.  

+ Workflow thực tế: Khi Woocommerce nhận được đơn hàng mới  => 1 request “post” gửi đến apps script => apps script sẽ turn data nhận được về dạng actual Json object thông qua method Json.parse => ghi các biến được khai báo vào google sheet theo trật tự được khai báo trong scripts.

II. Các bước triển khai chi tiết:

1. Tạo google sheet => tool script editor => google apps script 

+ Vào Woocommerce => Nâng cao => Webhook => thêm google apps script url (tạo ở trên vào).

2. Viết Google Apps Script để xử lý dữ liệu gửi từ Woocommerce đến khi có đơn hàng mới:  

Tiêu chuẩn để Google sử dụng script như một web app: 
+ It contains a doGet(e) or doPost(e) function.
+ The function returns an HTML service HtmlOutput object or a Content service TextOutputobject.

[e.postData.contents]: e is the object of the HTTP call. It will have the data that we’re sending it. So it’s a good idea to check if it’s NULL. If it is, then there’s no need to run the script.

2.1 Cần xem nội dung dữ liệu json từ Woocommerce: Order meta data gồm có:

Order meta data được Woocommerce gửi ra thông qua Webhook

+ công cụ post http request:

+ Cách test order meta data: Sử dụng – không cần phải đăng ký, chỉ cần tạo webhook trong Woo & nhập delivery Url vào là xong. cách thứ 2 là sử dụng: WooCommerce Logs (WooCommerce > Status > Logs) => Cần enable WP_debug

+ Xử lý raw data trả về theo json format cho dễ nhìn: Json formatter

– Ví dụ về Order meta data (cần xem Order data gửi từ Woo ra để viết apps script tương ứng – dưới đây là ví dụ):

"number":"1194",  //đây là ID đơn hàng => apps script: var order_number = myData.number;
"status":"on-hold", //tình trạng đơn hàng => var order_status = myData.status;
"date_created":"2019-07-20T02:08:30",  //thời gian tạo đơn hàng => apps script: var order_created = myData.date_created;
"total":"4252.00",  //tổng giá trị order => var Order_total =
"customer_ip_address":"::1", //ip khach hang - chua su dung
"customer_user_agent":"Mozilla\/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit\/537.36 (KHTML, like Gecko) Chrome\/75.0.3770.142 Safari\/537.36",
"customer_note":"",   //Ghi chu don hang => can dua them vao google sheet
  "company":"Tyche Softwares",
  "address_1":"B 201, Apsara Building, Rd. 7,",   //billing address => chua su dung
  "address_2":"Daulat Nagar, Borivali East",     
  "first_name":"Vishal",    //ten nguoi nhan - first name
  "last_name":"Kothari",    //ten nguoi nhan - last name   
  "company":"Tyche Softwares",
  "address_1":"B 201, Apsara Building, Rd. 7,",  //dia chi nhan hang 1
  "address_2":"Daulat Nagar, Borivali East",     //dia chi nhan hang 2
"payment_method_title":"Direct bank transfer",  //Phuong thuc thanh toan
    "key":"Delivery Date",
    "value":"Wednesday, 24 July, 2019"
    "key":"Time Slot",
    "value":"10:30 PM - 10:45 PM"
    "value":"22:30 - 22:45"
"line_items":[      //line_items[0]
  "name":"500 GB Hard disk drive", //Tên sản phẩm 1: var product_name = myData.line_items[0].name; 
  "product_id":375,     //id sản phẩm 1 - simple product
  "quantity":1,     //Số lượng sản phẩm 1: Var myData.line_items[0].quantity
  "total":"4000.00", //Số tiền thanh toán sp 1: Var myData.line_items[0].total
  "id":464,  //line_item[1]
  "name":"Panera Sandwich - Mozzarella",
  "variation_id":447, //id san pham 2 - varation product
  "quantity":1,       //so luong san pham 2
  "method_title":"Flat rate",
    "value":"500 GB Hard disk drive × 1, Panera Sandwich - Mozzarella × 1"
  "name":"Delivery Charges",

2.2 Viết apps script tương ứng để thêm những dữ liệu này vào google sheet:

Sau khi hoàn thành apps script => cần publish apps: Deploy as web app => copy “delivery url” và nhập vào Woocommerce webhook. 

Chú ý: 

+ Chọn mục “Execute the app as me”

+ chọn mục “Who has access to the app: Anyone, even anonymous” 

Code mẫu Google apps script để hứng dữ liệu từ Woo Webhook – cần tùy biến dựa trên order data thực tế: 

//this is a function that fires when the webapp receives a GET request
function doGet(e) {
  return HtmlService.createHtmlOutput("request received");

//this is a function that fires when the webapp receives a POST request, e is the object of the HTTP call. It will have the data that we’re sending it.
function doPost(e) {
  var myData = JSON.parse([e.postData.contents]); //Chuyen du lieu nhan duoc sang actual Json object
  var order_number = myData.number;
  var order_created = myData.date_created;
  var order_status = myData.status;
  var timestamp = new Date();
  var sheet = SpreadsheetApp.getActiveSheet();
  sheet.appendRow([timestamp,order_number,order_created,order_status]); //loop through order data to get all products

3. Deploying the script as a web app: 

Khi thực hiện chỉnh sửa apps script, cần chọn publish as new ở phần “Project version option” thì những sửa đổi mới có hiệu lực.
4. Creating the WooCommerce webhook

Full code dành cho order meta data trên:

//this is a function that fires when the webapp receives a GET request
function doGet(e) {
  return HtmlService.createHtmlOutput("request received");

//this is a function that fires when the webapp receives a POST request
function doPost(e) {
  var myData             = JSON.parse([e.postData.contents]);
  var order_number       = myData.number;
  var order_created      = myData.date_created;
  var order_status       = myData.status;
  var order_total        =;
  var billing_email      =;
  var billing_first_name = myData.billing.first_name;
  var billing_last_name  = myData.billing.last_name;
  var payment_method     = myData.payment_method_title;
  var shipping_method    = myData.shipping_lines[0].method_title;
  var shipping_total     = myData.shipping_lines[0].total;
  var timestamp = new Date();
  var sheet = SpreadsheetApp.getActiveSheet();
for (i = 0; i < myData.line_items.length; i++) { //loop through all products in the order
            //customize and add the detail order to sheet as you want                                   }


Detail Ref:





+    – Google Apps Script for POSTING JSON data to a Google Sheet




=> lookup values in multi columns: