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
Click your Formaloo Profile icon in the top-right corner
Open Apps & Integrations
Find Google Sheets
If needed, click Add to enable the integration
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:
Select the Form you want to sync (search for it by its title or ID),
Enter the Sheet title,
Paste the Sheet URL,
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:
Open Active integrations,
Find the new connection,
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
🔗 Learn more about re-syncing, editing, or deleting your Google Sheets connections
↔️ 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:
Toggle Enable two-way Formaloo ↔ Google Sheets sync,
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.jsfile
You can also copy the JavaScript code right from here (click to expand)
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
formaloofile
You can also copy the HTML code right from here (click to expand)
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.










