Skip to content

Instantly share code, notes, and snippets.

@braden-w
Last active April 14, 2025 04:11
Show Gist options
  • Save braden-w/1ac6e8df49c2d32f95ef237a88d963da to your computer and use it in GitHub Desktop.
Save braden-w/1ac6e8df49c2d32f95ef237a88d963da to your computer and use it in GitHub Desktop.
Google Sheets Script for Fetching Data from Supabase

Google Sheets Script for Fetching Data from Supabase

This Google Sheets script fetches data from a Supabase database and writes the selected headers and data to the active sheet. The script first clears the sheet, writes the headers, then fetches the data from the Supabase API, and finally writes the data to the sheet.

Prerequisites

  • Make sure your Supabase Database RLS policy allows reads (you might need to tweak some RLS policies).

How to Use

  1. Create a new Google Sheet or open an existing one.
  2. Click on "Extensions" in the menu, then select "Apps Script." (If you don't see "Extensions," click on "Tools" and then "Script editor.")
  3. In the Apps Script editor, replace the default Code.gs content with the content of supabase_to_google_sheets.gs.
  4. Replace the SUPABASE_URL and SUPABASE_ANON_KEY variables with your actual Supabase URL and anon key.
  5. Adjust the TABLE and SHEET values according to your needs.
  6. Save the script by clicking on the floppy disk icon or pressing Ctrl+S (Cmd+S on Mac).
  7. To run the script, go to the "Select function" dropdown in the toolbar, choose mainFunction, and click the "Run" button (the triangle icon).
  8. Grant the necessary permissions when prompted.
  9. The script will clear the active sheet and fetch data from the Supabase API, then write the selected headers and data to the sheet.

Remember to save your changes before running the script. You can customize the SELECTED_HEADERS arrays as needed, and you can modify the script's behavior if necessary.

const SUPABASE_URL = 'https://your_supabase_url_here';
const SUPABASE_ANON_KEY = 'your_supabase_anon_key_here';
const SUPABASE_TABLE_NAME = 'your_supabase_table_name_here';
const GOOGLE_SHEET_NAME = 'your_google_sheet_name_here';
const HEADER_ROW_INDEX = 1;
const DATA_START_ROW_INDEX = HEADER_ROW_INDEX + 1;
/**
* Processes a row from Supabase to ensure it's properly serialized for Google Sheets.
*
* You can customize this function to handle different data types as needed.
*
* @param {Record<string, any>} row - Object where the keys are the column names and the values are the column values from a Supabase row that need to be written to Google Sheets
* @returns {Array<string>} An array of serialized values safe for Google Sheets insertion
*
* @remarks
* - Handles complex data types (objects, arrays) by converting them to JSON strings
* - Ensures data consistency when writing to Google Sheets
* - Used by writeToSheet() to process each row before insertion
*
* @example
* const row = { text: 'text', complex: { complex: 'object' }, array: ['array'], null: null };
* const processed = processRow(row);
* // Returns: ['text', '{"complex":"object"}', '["array"]', '']
*/
function processRow(row) {
return Object.entries(row).map(([key, value]) => {
if (value === null || value === undefined) {
return '';
}
if (value instanceof Date) {
return value.toISOString();
}
if (typeof value === 'object') {
return JSON.stringify(value);
}
return String(value);
});
}
/**
* Main execution function that orchestrates the data synchronization process.
* Clears the spreadsheet and then fetches and writes new data.
*
* @returns {void}
*/
function mainFunction() {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(GOOGLE_SHEET_NAME);
sheet.clear();
const data = fetchDataFromSupabase(SUPABASE_URL, SUPABASE_ANON_KEY, SUPABASE_TABLE_NAME);
writeDataToSheet(data, GOOGLE_SHEET_NAME);
}
/**
* Fetches data from Supabase and returns it as an array of objects.
* Uses the Supabase REST API to retrieve data and processes it for sheet insertion.
*
* @param {string} supabaseUrl - The URL of the Supabase instance
* @param {string} supabaseAnonKey - The anonymous key for the Supabase instance
* @param {string} supabaseTableName - The name of the table to fetch data from
*
* @returns {Array<Record<string, any>>} An array of objects containing the data from Supabase
* @throws {Error} If the Supabase API request fails or if data processing encounters an error
*/
function fetchDataFromSupabase(supabaseUrl, supabaseAnonKey, supabaseTableName) {
const response = UrlFetchApp.fetch(`${supabaseUrl}/rest/v1/${supabaseTableName}`, {
headers: {
Apikey: supabaseAnonKey,
Authorization: `Bearer ${supabaseAnonKey}`,
'Content-Type': 'application/json',
},
});
const jsonData = JSON.parse(response.getContentText());
return jsonData;
}
/**
* Writes the provided data to the specified Google Sheet.
* Handles both the header row and data rows, processing values for proper sheet insertion.
*
* @param {Array<Record<string, any>>} data - Array of objects containing the data to be written to the sheet
* @returns {void}
* @throws {Error} If the sheet is not found or if writing operations fail
*/
function writeDataToSheet(data, sheetName) {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(sheetName);
const cols = Object.keys(data[0]);
sheet.getRange(HEADER_ROW_INDEX, 1, 1, cols.length).setValues([cols]);
sheet.getRange(DATA_START_ROW_INDEX, 1, data.length, cols.length).setValues(data.map(processRow));
}
@TsarPrince
Copy link

Get's even simpler if you want all your headers directly populated + added support for specific sheet to write in:

const SUPABASE_URL = "https://your_supabase_url_here";
const SUPABASE_ANON_KEY = "your_supabase_anon_key_here";
const TABLE = "your_table_name_here"
const SHEET = "your_sheet_name_here"

const HEADER_ROW = 1;
const DATA_START_ROW = HEADER_ROW + 1;

function mainFunction() {
  clearEntireSpreadsheet();
  fetchDataAndWriteToSheet();
}

function fetchDataAndWriteToSheet() {
  const url = `${SUPABASE_URL}/rest/v1/${TABLE}`;
  const options = {
    headers: {
      Apikey: SUPABASE_ANON_KEY,
      Authorization: `Bearer ${SUPABASE_ANON_KEY}`,
      'Content-Type': 'application/json'
    },
    method: 'get',
  }
  const response = UrlFetchApp.fetch(url, options);
  const jsonData = JSON.parse(response.getContentText());
  writeToSheet(jsonData);
}

function writeToSheet(data) {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(SHEET);

  const cols = Object.keys(data[0]);
  const rows = data.map((row) => Object.values(row));

  sheet.getRange(HEADER_ROW, 1, 1, cols.length).setValues([cols]);
  sheet.getRange(DATA_START_ROW, 1, rows.length, cols.length).setValues(rows);
}

function clearEntireSpreadsheet() {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(SHEET);
  sheet.clear();
}

@PypkoVadym
Copy link

const SUPABASE_URL = "https://your_supabase_url_here";
const SUPABASE_ANON_KEY = "your_supabase_anon_key_here";
const TABLE = "your_table_name_here"
const SHEET = "your_sheet_name_here"

const HEADER_ROW = 1;
const DATA_START_ROW = HEADER_ROW + 1;

function mainFunction() {
try {
// First clear the existing sheet
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(SHEET);
if (!sheet) {
throw new Error(Sheet ${SHEET} not found!);
}
sheet.clear();

// Then fetch and write data
fetchDataAndWriteToSheet();

} catch (error) {
Logger.log(Error in mainFunction: ${error.toString()});
SpreadsheetApp.getUi().alert(Error: ${error.toString()});
}
}

function fetchDataAndWriteToSheet() {
try {
// Log the full URL and headers for debugging
Logger.log(Supabase URL: ${SUPABASE_URL});
Logger.log(Table: ${TABLE});
Logger.log(Anon Key Length: ${SUPABASE_ANON_KEY.length});

const url = `${SUPABASE_URL}/rest/v1/${TABLE}?select=*`;

const options = {
  headers: {
    'apikey': SUPABASE_ANON_KEY,
    'Authorization': `Bearer ${SUPABASE_ANON_KEY}`,
    'Content-Type': 'application/json',
    'Accept': 'application/json',
    // Add Range header to test if it affects retrieval
    'Range': '0-99'
  },
  method: 'GET',
  muteHttpExceptions: true
};

// Log full request details
Logger.log(`Full Request URL: ${url}`);
Logger.log(`Request Headers: ${JSON.stringify(options.headers)}`);

const response = UrlFetchApp.fetch(url, options);

// Log full response details
Logger.log(`Response Code: ${response.getResponseCode()}`);
Logger.log(`Response Headers: ${JSON.stringify(response.getAllHeaders())}`);

const responseContent = response.getContentText();
Logger.log(`Raw Response Content: ${responseContent}`);

if (response.getResponseCode() !== 200) {
  throw new Error(`HTTP Error: ${response.getResponseCode()} - ${responseContent}`);
}

const jsonData = JSON.parse(responseContent);

// Log parsed data in detail
Logger.log(`Parsed Data Type: ${typeof jsonData}`);
Logger.log(`Parsed Data Length: ${jsonData.length}`);
Logger.log(`First Row (if exists): ${JSON.stringify(jsonData[0])}`);

writeToSheet(jsonData);

} catch (error) {
Logger.log(Detailed Error fetching data: ${error.toString()});
Logger.log(Error Stack: ${error.stack});
SpreadsheetApp.getUi().alert(Fetch Error: ${error.toString()});
}
}

function writeToSheet(data) {
// More robust data checking
if (!data || (Array.isArray(data) && data.length === 0)) {
Logger.log("No data returned from Supabase, or the table is empty.");
SpreadsheetApp.getUi().alert("No data was retrieved from Supabase.");
return;
}

const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(SHEET);

if (!sheet) {
throw new Error(Sheet ${SHEET} not found!);
}

// Dynamically get columns from the first row
const cols = Object.keys(data[0]);
const rows = data.map((row) => cols.map(col => row[col]));

// Write headers
sheet.getRange(1, 1, 1, cols.length).setValues([cols]);

// Write data rows
if (rows.length > 0) {
sheet.getRange(2, 1, rows.length, cols.length).setValues(rows);
}

Logger.log(Successfully wrote ${rows.length} rows to sheet.);
}

function onOpen() {
const ui = SpreadsheetApp.getUi();
ui.createMenu('Supabase Data')
.addItem('Fetch Supabase Data', 'mainFunction')
.addToUi();
}

@braden-w
Copy link
Author

@TsarPrince Great solution! Ended up amending the gist to use your implementation :) Added one other function, processRowValues, which allows the user to customize how they want to serialize rows.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment