HollyHR Developer Docs
  • HollyHR
  • Sign in
  • Manage API keys
  • Start Here
  • Core API
  • AI and MCP
  • API Reference
  • Recipes
  • Resources
Recipe indexGitHub Actions recipesMCP smoke testSafe MCP leave bookingPeople syncTest SDK from sourceSlack who's awayGoogle Sheets exportReceive webhooks in NodeCreate person + webhookWebhook + payroll referencesPayroll readiness export
Recipes

Google Sheets export

Sync a safe read-only view of HollyHR people and time-off data into a Google Sheet. This is useful for lightweight reporting, operations trackers, or a spreadsheet-native team that wants HollyHR data in one place.

This recipe exports data from HollyHR into a sheet. It does not import people or time off into HollyHR.

What it reads

  • GET /people
  • GET /time-off

You can also read reference-data endpoints if your own sheet needs lookup tables for custom reporting, but this starter example only needs people and time-off records.

Optional reference endpoints:

  • GET /reference/time-off-categories
  • GET /reference/time-off-statuses

Apps Script setup

Create a Google Sheet, then open Extensions > Apps Script. Add script properties for:

  • HOLLYHR_API_BASE_URL
  • HOLLYHR_API_TOKEN

Use your organisation's HollyHR app origin, for example:

Code
https://{workspace}.hollyhr.com/api/v1

Apps Script example

Code
function holly(path) { const props = PropertiesService.getScriptProperties(); const baseUrl = props.getProperty("HOLLYHR_API_BASE_URL"); const token = props.getProperty("HOLLYHR_API_TOKEN"); if (!baseUrl || !token) { throw new Error("Missing HOLLYHR_API_BASE_URL or HOLLYHR_API_TOKEN script property"); } const response = UrlFetchApp.fetch(baseUrl + path, { headers: { Authorization: "Bearer " + token, Accept: "application/json", }, muteHttpExceptions: true, }); if (response.getResponseCode() >= 400) { throw new Error("HollyHR API request failed: " + response.getContentText()); } return JSON.parse(response.getContentText()); } function listAll(path) { const items = []; let cursor = null; do { const separator = path.indexOf("?") === -1 ? "?" : "&"; const page = holly(path + (cursor ? separator + "cursor=" + encodeURIComponent(cursor) : "")); items.push.apply(items, page.data); cursor = page.pagination && page.pagination.next_cursor; } while (cursor); return items; } function writeRows(sheetName, headers, rows) { const spreadsheet = SpreadsheetApp.getActive(); const sheet = spreadsheet.getSheetByName(sheetName) || spreadsheet.insertSheet(sheetName); sheet.clearContents(); sheet.getRange(1, 1, 1, headers.length).setValues([headers]); if (rows.length > 0) { sheet.getRange(2, 1, rows.length, headers.length).setValues(rows); } sheet.autoResizeColumns(1, headers.length); } function syncHollyHr() { const people = listAll("/people?limit=100"); const timeOff = listAll("/time-off?limit=100"); const peopleById = {}; people.forEach(function (person) { peopleById[person.id] = person; }); writeRows( "People", ["Display name", "Work email", "Department", "Job title", "Status", "Start date", "Updated at"], people.map(function (person) { return [ person.display_name, person.work_email, person.department ? person.department.name : "", person.job_title || "", person.status || "", person.start_date || "", person.updated_at, ]; }), ); writeRows( "Time off", ["Person", "Type", "Status", "Start date", "End date", "Length", "Updated at"], timeOff.map(function (entry) { const person = peopleById[entry.person_id]; return [ person ? person.display_name : entry.person_id, entry.type, entry.status || "", entry.start_date, entry.end_date, entry.length, entry.updated_at, ]; }), ); }

Schedule it

In Apps Script, add a time-driven trigger for syncHollyHr. Hourly or daily is usually enough for spreadsheet reporting. Use updated_since later if you need to reduce polling volume.

Privacy notes

This recipe uses the default people:read directory projection, which excludes dates of birth and home contact details. Elevated personal profile fields need people:personal:read and a separate detail call per person. Compensation, bank details, tax identifiers, and document contents are not exposed. Keep the sheet permissions at least as strict as the HollyHR data it contains.

Last modified on June 24, 2026
Slack who's awayReceive webhooks in Node
On this page
  • What it reads
  • Apps Script setup
  • Apps Script example
  • Schedule it
  • Privacy notes
Javascript