Skip to main content

How to set up two-way sync between Formaloo and Google Sheets

Edit your form submissions both from your Formaloo dashboard and directly inside Google Sheets, and sync the data both ways.

Formaloo lets you connect your forms to Google Sheets in two different ways – using a one-way sync, or two-way sync.

ℹ️ What’s the difference between one-way and two-way Google Sheet sync?

➡️ With a standard, one-way Google Sheets sync, new submissions are automatically synced to your spreadsheet, and you can re-sync the form anytime to push updates made in Formaloo to Google Sheets.

↔️ With a two-way Google Sheets sync, you can also edit the data directly inside Google Sheets and sync those changes back to Formaloo.

A two-way sync can be useful if you:

  • Prefer reviewing or editing data in spreadsheets

  • Collaborate with teams inside Google Sheets

  • Want updates made in Google Sheets to reflect in Formaloo without manual copying

👀 No worries – you won't need to be any tech savvy to set a two-way sync up.

Simply follow the steps below carefully without skipping any part of the setup – this will only take a few minutes.


➡️ Connect your form to Google Sheets

Before setting up the two-way sync, you first need to connect your form to a Google Sheet from your Formaloo dashboard:

Step 1: Enable Google Sheet integration

  1. Click your Formaloo Profile icon in the top-right corner

  2. Open Apps & Integrations

  3. Find Google Sheets

  4. If needed, click Add to enable the integration

  5. Go to Add new integration to create a new Google Sheets connection

Step 2: Create a new connection

Under Add new integration, create a new connection:

  1. Select the Form you want to sync (search for it by its title or ID),

  2. Enter the Sheet title,

  3. Paste the Sheet URL,

  4. Click Connect

ℹ️️ Copy the URL of your Google Sheet directly from your browser’s address bar.

  • Make sure that when you copy the Sheet URL, you are in the correct Sheet (if your spreadsheet has more than one),

  • It’s also crucial that the Sheet title matches exactly between your spreadsheet and your connection settings in Formaloo:

Step 3: Sync the form data to the Sheet

Once the integration is created:

  1. Open Active integrations,

  2. Find the new connection,

  3. Click Resync data:

This helps verify the connection and syncs any existing submissions to your sheet.

ℹ️ Under Active integrations, you can always:

  • Review existing connections (see the status, and whether two-way sync is enabled)

  • Edit any connection's settings

  • Delete any connection

  • Re-sync the data


↔️ Enable two-way sync between your form and Google Sheet

Step 1: Enable two-way sync in Formaloo

First, edit the connection you’ve just created:

  1. Toggle Enable two-way Formaloo ↔ Google Sheets sync,

  2. Click Update to save the connection settings.

At this point, the Formaloo side of the setup is complete.

Next, you’ll configure the Google Sheets side required for the two-way connection.

Step 2: Prepare your Google Sheet for two-way sync

Open the Google Sheets file where you want to set up the Formaloo two-way sync.

➔ Open Apps Script

  • In Google Sheets, go to Extensions > Apps Script:

➔ Add the JavaScript file

  • Click the + icon in the top left corner and select Script,

  • Name the file formaloo.js,

  • Copy the contents of the JavaScript section and paste it into your formaloo.js file

You can also copy the JavaScript code right from here (click to expand)

let formaloo={};function onOpen(){SpreadsheetApp.getUi().createMenu("🧡 Formaloo").addItem("Sync Form","showSyncForm").addToUi(),showSyncForm()}function showSyncForm(){var e=HtmlService.createHtmlOutputFromFile("formaloo").setTitle("Formaloo Two-Way Sync App");SpreadsheetApp.getUi().showSidebar(e)}function getSheetId(){return SpreadsheetApp.getActiveSpreadsheet().getId()}function getActiveSheet(){return SpreadsheetApp.getActiveSpreadsheet().getActiveSheet()}function getSheetTitle(){return getActiveSheet().getName()}function getSheetInfo(){return{sheetTitle:getSheetTitle(),sheetId:getSheetId()}}function rangesIntersect(e,t){e.getA1Notation(),t.getA1Notation();return e.getSheet().getSheetId()===t.getSheet().getSheetId()&&e.getRow()<=t.getLastRow()&&e.getLastRow()>=t.getRow()&&e.getColumn()<=t.getLastColumn()&&e.getLastColumn()>=t.getColumn()}function autoWidth(){var t=getActiveSheet(),e=t.getRange(1,1,1,t.getMaxColumns()).getValues()[0],o=["submitted at",formaloo.settings.slugColumnName],n=/^[a-zA-Z0-9\s]+\(.*\)$/,r=[];e.forEach(function(e,t){o.includes(e)&&r.push(t+1),n.test(e)&&r.push(t+1)}),r.forEach(function(e){t.autoResizeColumn(e)})}function cleanUpSheet(){var e=getActiveSheet(),o=e.getDataRange(),n=o.getValues();if(n.length<2)Logger.log("The sheet does not contain enough data.");else{var t=n[0],r=t.indexOf(formaloo.settings.slugColumnName),a=t.indexOf("submitted at");if(-1===r||-1===a)Logger.log(`One or both columns "${formaloo.settings.slugColumnName}" and "submitted at" do not exist.`);else{let t=0;for(let e=1;e<n.length;e++){var i=n[e][r],s=n[e][a];i&&s&&(t=e+1)}t<n.length&&e.getRange(t+1,1,n.length-t,o.getWidth()).clearContent()}}}function protectEntireSheet(){var e=getActiveSheet();e.protect().setDescription("Formaloo-Protection sheet protection").setWarningOnly(!0),Logger.log("Sheet protected: "+e.getName())}function protectColumns(){return formaloo.protectColumns()}function removeOldProtections(e=null,t="Formaloo-Protection"){return formaloo.removeOldProtections(e,t)}function removeEntireSheetProtection(){return formaloo.removeEntireSheetProtection()}function getVersion(){return formaloo.getVersion()}function getSettings(){return formaloo.settings}function resetStyles(){return formaloo.resetStyles()}formaloo.settings={appVersion:"2.17.4-beta",idColumnName:"#",slugColumnName:"Formaloo Record ID"},formaloo.getVersion=()=>formaloo.settings.appVersion,formaloo.resetStyles=()=>{var e=getActiveSheet(),t=e.getMaxRows(),o=e.getMaxColumns(),e=e.getRange(1,1,t,o);e.setBackground("#ffffff"),e.setFontColor("#000000"),e.setFontWeight("normal"),e.setFontSize(10),e.setFontFamily("Arial"),e.setBorder(!1,!1,!1,!1,!1,!1),e.setNumberFormat("@STRING@"),e.setHorizontalAlignment("left"),e.setVerticalAlignment("middle"),e.setWrap(!1),e.setWrapStrategy(SpreadsheetApp.WrapStrategy.CLIP)},formaloo.applyDisableStyle=(e,t=!1)=>{e.setBackground("#fff9f4"),t?(e.setFontColor("#F76015"),e.setBackground("#fff9f4"),e.setFontWeight("bold")):e.setFontColor("#ffa776")},formaloo.protectColumns=()=>{var e=getActiveSheet(),t=e.getLastColumn(),o=e.getMaxRows(),t=(formaloo.removeOldProtections(e,"Formaloo-Protection"),formaloo.resetStyles(),e.getRange(1,1,1,t).getValues()[0]),n=t.indexOf(formaloo.settings.idColumnName)+1,r=t.indexOf(formaloo.settings.slugColumnName)+1,t=t.indexOf("submitted at")+1;0<r&&0<t?(n=e.getRange(1,n,o),formaloo.applyDisableStyle(n),n.protect().setDescription("Formaloo-Protection id protection").setWarningOnly(!0),n=e.getRange(1,r,o),formaloo.applyDisableStyle(n),n.protect().setDescription("Formaloo-Protection slug protection").setWarningOnly(!0),n=e.getRange(1,t,o),formaloo.applyDisableStyle(n),n.protect().setWarningOnly(!0).setDescription("Formaloo-Protection submitted at protection"),t=e.getRange(1,1,1,r),formaloo.applyDisableStyle(t,!0),t.protect().setWarningOnly(!0).setDescription("Formaloo-Protection header protection")):Logger.error(`Error: Columns "${formaloo.settings.slugColumnName}" or "submitted at" not found.`)},formaloo.removeOldProtections=(e=null,t="Formaloo-Protection")=>{(e=e||getActiveSheet()).getProtections(SpreadsheetApp.ProtectionType.RANGE).forEach(function(e){e.getDescription().startsWith(t)&&e.remove()})},formaloo.removeEntireSheetProtection=()=>{getActiveSheet().getProtections(SpreadsheetApp.ProtectionType.SHEET).forEach(function(e){e.getDescription().startsWith("Formaloo-Protection")&&e.remove()})};

➔ Add the HTML file

  • Click the + icon again, this time selecting HTML,

  • Name this file formaloo,

  • Copy the contents of the HTML section and paste it into your formaloo file

You can also copy the HTML code right from here (click to expand)

<!doctype html><html lang="en"><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Formaloo</title><link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet"><link href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css" rel="stylesheet"><script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/js/materialize.min.js"></script><script src="https://unpkg.com/vue@3"></script><style>.container{max-width:300px;margin:0 auto}.progress{height:4px;background-color:#f3f3f3;margin:5px 0}.progress .indeterminate{background-color:#f76015;animation:indeterminate 2s infinite linear}@keyframes indeterminate{0%{left:-100%;width:100%}50%{left:100%;width:100%}100%{left:100%;width:100%}}.orange-btn{background-color:#f76015!important;color:#fff!important}footer{text-align:left;position:fixed;bottom:0;width:100%;font-size:.9rem;max-width:300px;margin:auto}footer .version{background-color:#ececec;width:100%;color:#666;padding:5px}a:visited,footer a{color:#f76015;text-decoration:underline}.warning-message{color:red;background-color:#ffe6e6;padding:1rem;border-radius:5px;margin-bottom:1rem}.warning-message li{text-align:justify;margin-bottom:.5rem}.button-group{display:inline-flex;flex-direction:row;align-items:center}.button-group button{margin-right:5px}.accordion-item{margin:3px 0;padding:1px;border:1px solid #e3e3e3;border-radius:5px}.accordion-header{padding:10px;cursor:pointer;background-color:#f9f9f9;transition:background-color .3s}.accordion-header:hover{background-color:#e0e0e0}.accordion-content{padding:0 10px;overflow:hidden;transition:max-height .3s ease;max-height:0}.accordion-content.active{max-height:300px}.accordion-content p{margin:10px 0}.faq-title{font-size:1.2rem;font-weight:700;color:#f3915b;padding:0;margin:0}@keyframes slideOut{0%{opacity:1;transform:translateX(0)}100%{opacity:0;transform:translateX(100%)}}.toast.slide-out{animation:slideOut .5s forwards}.sync-status{background-color:#fff7f5;border-radius:8px;padding:10px 20px 5px 20px}.sync-header{display:flex;align-items:center;margin-bottom:12px}.sync-title{font-size:1rem;color:#f3915b;font-weight:700}.sync-details{list-style-type:none;padding:0}.sync-details li{display:flex;align-items:center;margin:5px 0;justify-content:space-between}.sync-details strong{white-space:nowrap;margin-right:5px}.loader{border:2px solid transparent;border-radius:50%;border-top-color:#fff;width:20px;height:20px;animation:spin 1s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}.text-success{color:green;font-weight:700}.text-danger{color:red;font-weight:700}.opacity{opacity:.5}.waiting-message{color:#b22222;background-color:#ffe6db;font-size:.9rem;font-weight:700;margin:7px auto;padding:5px 15px;border-radius:5px;text-align:left}</style><div id="app"><div class="container"><div class="row"><div class="col s12"><div class="header"><img src="https://cdn.prod.website-files.com/656ce8668244222553627f66/656ceeed63564c91d4c9c581_Formaloo%20navbar%20logo.svg" alt="Formaloo Logo" style="width:70%;height:auto;margin-top:1rem"></div></div></div></div><div class="row"><div class="col s12"><div class="button-group"><button class="btn orange-btn waves-effect waves-light" @click="submitForm" :disabled="isLoading" title="Sync data with Formaloo"><i class="material-icons left">sync</i> Sync</button> <button class="btn orange-btn waves-effect waves-light" @click="getSyncStatus" :disabled="getSyncStatusLoading" title="Get the latest sync status"><i class="material-icons" v-if="getSyncStatusLoading">hourglass_empty</i> <i class="material-icons" v-if="!getSyncStatusLoading">info</i></button> <button class="btn orange-btn waves-effect waves-light" @click="resetStyles" :disabled="isResettingStyle" title="Reformat the sheet"><i class="material-icons" v-if="isResettingStyle">hourglass_empty</i> <i class="material-icons" v-if="!isResettingStyle">imagesearch_roller</i></button></div></div><div class="col s12"><div class="waiting-message" v-if="isLoading || isResettingStyle || getSyncStatusLoading">Please wait, do not make changes.<div class="progress"><div class="indeterminate"></div></div></div></div></div><footer><div class="row"><div class="col s12"><div class="sync-status" v-if="syncStatus" :class="{'opacity': getSyncStatusLoading}"><div class="sync-header"><span class="sync-title" v-if="!getSyncStatusLoading">Latest Sync Status</span> <span class="sync-title" v-if="getSyncStatusLoading">Retrieving data ...</span></div><ul class="sync-details"><li><strong>Start Time: </strong><span>{{ formatDate(syncStatus.started_at) }}</span><li><strong>Stop Time: </strong><span>{{ syncStatus.stopped_at ? formatDate(syncStatus.stopped_at) : '-' }}</span><li><strong>Status: </strong><span :class="{

'text-success': syncStatus.status === 'succeeded',

'text-danger': syncStatus.status === 'failed'

}">{{ capitalizeFirstLetter(syncStatus.status) }} <span v-if="syncStatus.status === 'failed' && syncStatus.extra && syncStatus.extra.error"><i class="material-icons left" style="font-size:19px;margin-right:3px;cursor:pointer" :title="syncStatus.extra.error">info</i></span></span><li><strong>Duration: </strong><span v-if="syncStatus.duration > 0">{{ syncStatus.duration }} seconds</span> <span v-if="!syncStatus.duration">-</span><li><strong>Sheet Title:</strong> <span style="overflow:hidden;white-space:nowrap;text-overflow:ellipsis" :title="sheetTitle">{{ sheetTitle }}</span></ul></div></div></div><div class="row"><div class="col s12"><div class="faq-title">FAQ</div><hr style="border:1px solid #ffd3b7;margin:10px 0"><div v-for="(faq, index) in faqs" :key="index" class="accordion-item"><div class="accordion-header" @click="toggleAccordion(index)"><span v-html="faq.question"></span> <i class="material-icons right">{{ activeIndex === index ? 'expand_less' : 'expand_more' }}</i></div><div class="accordion-content" :class="{ active: activeIndex === index }"><p v-html="faq.answer"></div></div></div></div><div class="version"><strong>Version {{ appVersion }}</strong> | <a href="https://help.formaloo.com/" target="_blank">Support</a></div></footer></div><script>const env="stage";apiKey="e2afd323b824a38e10a07d86ad5c527c2c007c0d",apiUrl="https://api.formaloo.me";const faqs=[{question:'<strong style="color: #ff2f00; font-size: 1rem;">⛔ Essential Guidelines!</strong>',answer:'\n <div style="color: #ff0000">\n 1- Do not change the column names.\n <br>\n 2- Do not modify data in the <strong>"submitted at"</strong>, and <strong>"Formaloo Record ID"</strong>\n columns.\n <br>\n 3- Avoid adding any data in cells after the "Formaloo Record ID" column.\n <br>\n 4- Please avoid from changing any data while syncing is in progress.\n </div>\n '},{question:"Sync the sheet's data.",answer:"\n After you have changed your data in the Google Sheet, click the Sync button to store your\n new changes in the corresponding form's data in Formaloo.\n "},{question:"Delete row(s).",answer:'\n 1- Select the row(s) that you want to delete.\n <br>\n 2- Right-click and select "Delete row(s)" from the context menu.\n <br>\n 3- Click the Sync button and wait for the data to sync with your Formaloo data.\n '},{question:"Check the latest sync status.",answer:"\n You can click the info button (info icon) to get the latest status information from the last\n sync you performed.\n "}],app=Vue.createApp({data:()=>({sheetId:"",sheetTitle:"",isLoading:!1,isResettingStyle:!1,getSyncStatusLoading:!1,appVersion:"",activeIndex:0,syncStatus:null,faqs:faqs}),mounted(){google.script.run.withSuccessHandler((t=>{this.appVersion=t.appVersion})).getSettings(),this.getSheetInfo(),this.resetStyles()},methods:{toBase64:t=>btoa((new TextEncoder).encode(t).reduce(((t,e)=>t+String.fromCharCode(e)),"")),getSheetInfo(t=()=>null){google.script.run.withSuccessHandler((({sheetId:e,sheetTitle:s})=>{this.sheetId=e,this.sheetTitle=s,t()})).withFailureHandler((t=>{console.error("Failed to fetch sheet info:",t)})).getSheetInfo()},showToast(t,e="green",s=1e4){M.toast({html:t,classes:e,displayLength:s,completeCallback:()=>{const t=document.querySelector(".toast");t&&(t.classList.add("slide-out"),setTimeout((()=>t.remove()),500))}})},handleErrors(t){t&&t.errors?(t.errors.general_errors?.length&&t.errors.general_errors.forEach((t=>{this.showToast(t,"red")})),t.errors.form_errors?.length&&t.errors.form_errors.forEach((t=>{this.showToast(t,"red")}))):this.showToast("An unknown error occurred.","red")},toggleAccordion(t){this.activeIndex=this.activeIndex===t?null:t},submitForm(){this.isLoading=!0,this.getSheetInfo((async()=>{if(!this.sheetId)return this.showToast("Sheet ID not found, please try again.","red"),void(this.isLoading=!1);setTimeout((()=>{this.getSyncStatus()}),1e3);const t={sheet_id:this.sheetId,sheet_title:this.sheetTitle};try{const e=await fetch(`${apiUrl}/v3/sync-with-gsheet/`,{method:"POST",headers:{"x-api-key":apiKey,"Content-Type":"application/json"},body:JSON.stringify(t)});if(e.ok){await e.json();this.showToast("Data synced successfully!","green"),this.resetStyles()}else{const t=await e.json();this.handleErrors(t)}}catch(t){console.error("Unexpected error:",t),this.showToast("An unexpected error occurred. Please try again later.","red")}finally{this.isLoading=!1,this.getSyncStatus(),google.script.run.cleanUpSheet(),google.script.run.protectColumns()}}))},formatDate(t){const e=new Date(t);return`${e.getFullYear()}-${String(e.getMonth()+1).padStart(2,"0")}-${String(e.getDate()).padStart(2,"0")} ${String(e.getHours()).padStart(2,"0")}:${String(e.getMinutes()).padStart(2,"0")}:${String(e.getSeconds()).padStart(2,"0")}`},capitalizeFirstLetter:t=>t?t.charAt(0).toUpperCase()+t.slice(1):"",getSyncStatus(){this.getSyncStatusLoading=!0,this.getSheetInfo((async()=>{const t=this.toBase64(this.sheetTitle),e=`${apiUrl}/v5/two-way-sync/google-sheets/${this.sheetId}/${t}`;try{const t=await fetch(e,{method:"GET",headers:{"x-api-key":apiKey,"Content-Type":"application/json"}});if(t.ok){const e=await t.json();this.syncStatus=e||null}else{const e=await t.json();this.handleErrors(e),this.syncStatus=null}}catch(t){console.error("Unexpected error:",t),this.showToast("An unexpected error occurred. Please try again later.","red"),this.syncStatus=null}finally{this.getSyncStatusLoading=!1}}))},resetStyles(){this.isResettingStyle=!0,google.script.run.withSuccessHandler((()=>{this.isResettingStyle=!1})).protectColumns(),setTimeout((()=>{this.isResettingStyle=!1}),3e4)}}});app.mount("#app")</script>

➔ Save the project

  • Rename the project to Formaloo for easy reference (optional),

  • Click the Save icon at the top of the Apps Script editor:

➔ Refresh your Google Sheet

  • Go back to your Google Sheets file and refresh the page,

  • After refreshing, you should see a new menu item named Formaloo at the top of the screen:

Step 3: Authorize the Formaloo app

Click Formaloo in the menu bar and select Sync Form:

A Google authorization prompt will appear. Make sure to:

  • Continue through all authorization steps

  • Grant all requested permissions

⚠️ If some permissions are skipped or denied, the sync may not work correctly.

Once authorization is complete, the Formaloo sidebar will appear inside your spreadsheet.


🔄 Start syncing!

Now, with your form connected to your Google Sheet, you can easily sync the data both ways.

Most updates made in Formaloo will sync to the connected Google Sheet automatically. If needed, you can always Re-sync data from Active integrations:

To push the updates made directly in your Google Sheet back to Formaloo, you'd need to open Formaloo sidebar, and run the .

Wait for the sync to complete to see the updates reflected in Formaloo.

⚠️ Make sure you don't make any changes while the sync is in progress.


Syncing new submissions:

  • Formaloo Google Sheet
    All new submissions (submitted by respondents, imported, or manually created in Formaloo dashboard) will sync to your Google Sheet automatically.

  • Google Sheet Formaloo
    If you add a new row directly in your Google Sheet, make sure to the data from the Formaloo sidebar. A new submission will be created in Formaloo once the sync is complete.


Syncing deleted submissions:

  • Formaloo Google Sheet
    If you delete a submission in Formaloo, the corresponding row will automatically be deleted in your Google Sheet.

  • Google Sheet Formaloo
    If you delete a row in your Google Sheet, make sure to the data from the Formaloo sidebar. The submission will get deleted from your Formaloo dashboard once the sync is complete.


Syncing changes in existing submissions:

  • Formaloo Google Sheet
    All changes made to existing submissions in Formaloo will automatically sync to your Google Sheet.

  • Google Sheet Formaloo
    If you've made any changes directly in your Google Sheet, make sure to the data from the Formaloo sidebar. The changes to be pushed to Formaloo once the sync is complete.


Syncing new form fields:

  • Formaloo Google Sheet
    If you add a new field on your Formaloo form, a new column will automatically appear in your connected Google Sheet.

  • Google Sheet Formaloo
    If you add a new column in your Google Sheet, it will not create a new field in your Formaloo form. Two-way sync works as data-only sync from Google's end.


Syncing deleted form fields:

  • Formaloo Google Sheet
    If you delete a field in your Formaloo form, you’ll need to manually Re-sync data with your Google Sheet for the change to be reflected. Unlike adding or editing fields, deletions do not sync automatically.

  • Google Sheet Formaloo
    You cannot delete a form field by deleting a column in Google Sheets. Even if you your Google Sheet, the column will be added back to it as long as the field still exists on the connected form.


⚠️ Crucial two-way Google sync guidelines ⚠️

  • DON'T edit any data during syncing
    While syncing is in progress, avoid making any changes to the sheet to prevent potential errors.

  • DON'T change column names
    The column names in your Sheet should remain exactly as they are set up. Renaming columns may disrupt the sync.

  • DON'T modify system columns
    Avoid editing data in the Formaloo columns, as these are essential for the sync process:

    • Generated Files

    • Submitted at

    • Formaloo Record ID

  • AVOID adding data beyond the "Formaloo Record ID" column
    Additional data entered beyond this column may not sync correctly and could interfere with data updates.

🚫 Non-supported field types for Google Sheet sync

Most fields in Formaloo can be synced with Google Sheets. However, the following read-only fields are not supported in Google Sheets sync:

  • New Page

  • Embed

  • Content

  • Section Divider

  • Video


Additional two-way sync options

You can reformat your Google Sheet anytime from the Formaloo sidebar:

Since Formaloo doesn’t have access to Google Sheets’ formatting options, newly-synced data or added fields can sometimes lead to a disorganized sheet layout.

Use the button in the Formaloo sidebar. This allows you to manually apply a consistent style to your sheet, ensuring it stays clear and easy to read without affecting the sync process.

You can always check the status of your latest sync:

Click the icon in the Formaloo sidebar to check the most recent sync details, so you can confirm that changes were applied successfully.

Did this answer your question?