How to pull data from Google Spreadsheet to HTML: Code and no code approaches
Eliott Ardisson
Founder & CEO - Basalt Studio
Learn how to pull Google Sheets data into HTML using Google Apps Script or no-code tools like n8n and Make — with practical code, tips, and common pitfalls.
TL;DR
- You have two main paths: Google Apps Script (code-based, free, full control) or no-code automation tools like n8n or Make (lower technical barrier, usage-based pricing)
- Google Apps Script is the most direct integration since it lives inside Google’s ecosystem, but it requires basic JavaScript knowledge and ongoing maintenance
- No-code platforms handle authentication and rate limiting for you, at the cost of some customization flexibility and a monthly fee past free-tier limits
- Both approaches support scheduled and triggered updates, so your HTML table can stay current without manual intervention
- Datasets beyond roughly 10,000 rows, or workflows requiring real-time sync at high frequency, will outgrow both approaches and need a proper database layer
Why This Problem Comes Up
You have data in Google Sheets. It’s clean, it’s maintained, and your team already knows how to update it. Now you need that same data to appear on a website, an internal dashboard, or a client-facing HTML page.
The temptation is to just export a CSV and paste it somewhere. That works once. The second someone updates the spreadsheet, your page is stale.
What you actually need is a live connection between the sheet and the HTML output. There are two practical ways to build that: write a small script using Google Apps Script, or wire up an automation platform that does the heavy lifting visually. This post walks through both, explains the trade-offs clearly, and flags the mistakes most people hit along the way.
Key Concepts Before You Start
A few terms worth defining before diving in:
Google Apps Script is Google’s built-in scripting environment. It runs JavaScript in Google’s cloud, has direct access to Sheets and Docs, and can serve HTML pages as web apps. No server needed.
Web App deployment is a feature of Apps Script that gives your script a public URL. When someone visits that URL, your script runs and returns an HTML response. This is how you turn spreadsheet data into a live HTML page.
Automation platforms (Make, n8n, Zapier, and similar) are tools that connect different services through a visual interface. Instead of writing code to fetch a spreadsheet row and format it as HTML, you configure a sequence of steps that do the same thing.
Triggers are what make updates automatic. In Apps Script, a time-driven trigger runs your script every N minutes. In automation platforms, a schedule or a webhook fires the workflow. Without a trigger, you have a manual process, not a live feed.
Rate limits are caps that Google and automation platforms impose on how frequently you can call their APIs. Hit these and your script either slows down or throws errors. Both approaches need to account for this.
Method 1: Google Apps Script
Setting up your spreadsheet
Before touching the script, get your spreadsheet in order. Use row 1 as a header row. Keep one data type per column. Remove merged cells inside your data range. Empty rows cause filtering logic to fail, so clean those out too.
For the examples below, assume a simple product catalog with columns: Product Name, SKU, Price, and Stock Status.
Creating the Apps Script project
Open your spreadsheet, go to Extensions, and click Apps Script. This opens a linked script project. You can also go to script.google.com and start a standalone project, but keeping it linked to the sheet makes permissions simpler.
You will see a default Code.gs file. This is where your server-side logic lives.
The data retrieval function
function doGet() {
return HtmlService.createTemplateFromFile('Index')
.evaluate()
.setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL);
}
function getSheetData() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName('Products');
var data = sheet.getDataRange().getValues();
return data.filter(function(row) {
return row.some(function(cell) { return cell !== ''; });
});
}
doGet() is the entry point Google calls when someone hits your web app URL. It loads an HTML template file called Index and returns it as a response.
getSheetData() grabs everything from the named sheet using getDataRange(), which automatically finds the extent of your data without hardcoding a cell range. The filter removes empty rows.
The HTML template
Create a new file in the script project, set the type to HTML, and name it Index. Here is a minimal but functional template:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
body { font-family: sans-serif; padding: 20px; }
table { border-collapse: collapse; width: 100%; }
th, td { border: 1px solid #ddd; padding: 10px; text-align: left; }
th { background: #f4f4f4; }
tr:nth-child(even) { background: #fafafa; }
</style>
</head>
<body>
<h1>Product Catalog</h1>
<div id="status">Loading...</div>
<table id="table" style="display:none;"></table>
<script>
google.script.run
.withSuccessHandler(renderTable)
.withFailureHandler(showError)
.getSheetData();
function renderTable(rows) {
if (!rows || rows.length < 2) {
document.getElementById('status').textContent = 'No data found.';
return;
}
var html = '<thead><tr>';
rows[0].forEach(function(h) { html += '<th>' + h + '</th>'; });
html += '</tr></thead><tbody>';
for (var i = 1; i < rows.length; i++) {
html += '<tr>';
rows[i].forEach(function(cell) { html += '<td>' + cell + '</td>'; });
html += '</tr>';
}
html += '</tbody>';
var table = document.getElementById('table');
table.innerHTML = html;
table.style.display = 'table';
document.getElementById('status').style.display = 'none';
}
function showError(err) {
document.getElementById('status').textContent = 'Error: ' + err.message;
}
</script>
</body>
</html>
The browser-side JavaScript calls getSheetData() on the server via google.script.run. When it comes back, renderTable builds the table from the array of rows.
Deploying the web app
Click Deploy, then New Deployment. Set type to Web App. For Execute As, choose Me. For Who Has Access, choose Anyone if the page should be public, or restrict it if this is internal only.
After deploying, Google gives you a URL. That URL is your live HTML page. Every time someone loads it, the script fetches the current spreadsheet data and renders the table fresh.
To set up automatic refreshes, add a time-driven trigger in Apps Script: Triggers > Add Trigger, set function to getSheetData, and choose your interval. This does not push updates to users already on the page, but it can pre-warm a cache if you add that logic.
Method 2: No-Code with Automation Platforms
Choosing a platform
The three most relevant platforms for this use case are:
| Platform | Best fit | Free tier | HTML flexibility |
|---|---|---|---|
| n8n | Developers comfortable with visual tools, or teams wanting self-hosting | Open source, self-hostable | High — supports code nodes |
| Make | Teams wanting a visual workflow without touching code | 1,000 operations/month | Good templating support |
| Zapier | Simple one-step automations | 100 tasks/month | Limited HTML formatting |
For anything beyond a basic table, n8n or Make will serve you better than Zapier. Zapier excels at simple two-app connections, not at formatting and outputting structured HTML.
The basic workflow structure
Regardless of which platform you use, the logic is the same:
- Trigger — time-based schedule or incoming webhook
- Fetch rows — Google Sheets module reads your data range
- Process data — filter empty rows, format values, handle edge cases
- Generate HTML — a text/template module assembles the table markup
- Deliver output — send to a webhook endpoint, write to a file, post to a CMS, or email it
Step 4 is where platforms differ most. n8n lets you write a JavaScript code node, giving you the same level of control as Apps Script. Make has a built-in text aggregator that lets you loop through rows and build HTML strings. Zapier mostly relies on formatter steps, which is limiting for complex tables.
Practical example in n8n
In n8n, a working version of this looks like:
- Schedule Trigger (every 15 minutes)
- Google Sheets node (operation: Get Many Rows, sheet: Products)
- Code node (JavaScript): takes the array of row objects, loops through them, builds an HTML string
- HTTP Request node: POSTs the HTML to a webhook or writes to a file storage API
The Code node in n8n looks similar to the Apps Script function above, but you work with the output of the Google Sheets node directly rather than calling the Sheets API yourself.
When no-code makes more sense
No-code platforms earn their place when:
- Your team has no JavaScript experience and cannot maintain an Apps Script project
- You need to pull from multiple sheets or multiple data sources into one table
- You want built-in error notifications (most platforms will email you on failure)
- You are already using the platform for other automations and want everything in one place
In our work helping founder-led businesses set up data workflow automations, the most common breakdown with no-code approaches is poorly handled empty cells and inconsistent data types coming out of the sheet. Building a small validation step into the workflow before the HTML generation stage saves a lot of debugging later.
Handling Large Datasets and Performance
Both approaches have ceilings. Google Apps Script execution times out at six minutes. Automation platforms have operation counts that vary by plan. For datasets under 1,000 rows refreshed a few times per hour, neither limit is a real concern.
When you get into several thousand rows or need updates every minute, the options are:
- Pagination: break the data into chunks and process them sequentially
- Incremental updates: instead of re-reading the full sheet, only fetch rows modified since the last run
- Caching: store the processed HTML and only regenerate it when the source data changes
- Database migration: move from Google Sheets to a proper database (PostgreSQL, Supabase, Airtable with API access) and query it directly
The last option is the right call when Sheets starts to feel like it is doing a job it was not built for. A spreadsheet shared by ten people, updated continuously, is not the same as a read-optimized data store.
Common Mistakes Worth Avoiding
Hardcoding cell ranges. If you write A1:D50 and someone adds a column, your table silently drops data. Use getDataRange() in Apps Script or configure the Sheets module to detect bounds automatically.
Skipping error handling. A script that fails silently leaves users looking at a loading spinner forever. Add withFailureHandler on the client side and try/catch blocks on the server side. In automation platforms, configure error routing and notification steps.
Exposing sensitive columns. If your spreadsheet contains internal cost data or personal information alongside the columns you want to display, make sure your data retrieval function slices out only the columns you intend to publish. Do not return the full row blindly.
Ignoring mobile layout. An HTML table with six columns will overflow on a phone. Add a viewport meta tag and consider a CSS approach that collapses columns or switches to a card layout on small screens.
Not testing after spreadsheet changes. Column reordering, renaming headers, or adding a new sheet tab can all silently break your integration. Build a quick smoke test into your routine whenever the spreadsheet structure changes.
Deciding Which Approach to Use
Use Google Apps Script if:
- You are comfortable with basic JavaScript
- You want zero ongoing cost
- The data lives in a single sheet and the logic is straightforward
- You want tight control over the HTML output
Use a no-code platform if:
- Your team needs to maintain this without developer support
- You are pulling data from multiple sources
- You want built-in scheduling, error alerts, and logging without writing infrastructure code
- You are already running other automations on the same platform
Both are legitimate production approaches. The “best” one is whichever your team can actually maintain six months from now.
Where to Go From Here
Pulling spreadsheet data into HTML is a reasonable starting point, but it is often the first step toward a broader question: how much of your operational data is still living in spreadsheets when it should be in a structured system that other tools can reliably read and write?
If you are at the stage where that question is becoming urgent, it is worth mapping your data flows before you build more on top of Sheets. A short workflow audit usually reveals which integrations are load-bearing and which ones can be simplified.
If you want help thinking through the right architecture for your specific setup, Basalt Studio offers AI strategy calls focused on exactly this kind of operational infrastructure for founder-led businesses. No pitch, no obligation — just a clear picture of your options.
Book an AI strategy call and bring your current setup. We will tell you honestly what is worth building and what to leave alone.
