Time required. 30-50 minutes.

Prerequisites. A Google Workspace account with admin access, A running n8n instance (self-hosted or cloud), A Google account that can create GCP projects, A shared Google Calendar for the PTO events.

PTO Request Form Setup Guide

Install the PTO Request Form workflow. From employee submission to manager approval through a shared team calendar event, every step runs on n8n.

The workflow lets employees request paid time off through a single Google Form. The manager receives an approval email, and on approval the workflow adds the dates to a shared team calendar, updates the Sheet row to Approved or Rejected, and emails the employee with the outcome.

It uses Gmail, Google Sheets, Google Calendar, and a Google Service Account with Domain-Wide Delegation. Three credentials in n8n.

How the Flow Works

  1. Employee submits the form: The Google Form submission triggers an Apps Script on the linked Sheet. The script reads the new row by header name and posts the form data (signed with HMAC) to the n8n form webhook.

  2. n8n logs the request and notifies the manager: The workflow creates a new row in the Sheet with Status = Pending and sends the manager an HTML email with two buttons: Approve and Reject. The decision URL is derived from the form webhook URL by replacing /pto-request-form with /pto-request-decision and appending eight query parameters: action, email, name, start, end, row, type, and notes. Each value is percent-encoded (URL-encoded) so free-text fields like notes are safe.

  3. Manager clicks Approve or Reject: The decision webhook is a GET request (no body, no HMAC). It validates the action, then either:

    • Approve: creates a calendar event on the shared PTO calendar via the Google Calendar API, updates the Sheet row to Approved with a decision timestamp, and emails the employee with the outcome.
    • Reject: updates the Sheet row to Rejected with a decision timestamp and emails the employee.

Both paths (form submission and decision) are handled by a single workflow using two webhook triggers.

Form Fields and Sheet Columns

The Google Form must have these seven fields, in any order. The Apps Script matches them by header label, not position, so you can reorder the form fields freely:

Form field Sheet column header Required?
Employee name Employee name Yes
Employee email Employee email Yes
Manager email Manager email Yes
PTO type PTO type Yes (dropdown: Vacation, Sick, Personal)
Start date Start date Yes
End date End date Yes
Notes Notes No (optional)

The workflow writes these three columns to the sheet automatically. The Apps Script setup function adds them to the sheet and the workflow sets values on the row:

Column Set by When
Status n8n Pending on form submit, Approved/Rejected on decision
Decision timestamp n8n On decision (ISO 8601)
Calendar event ID n8n On approval (the event ID returned by the Calendar API)

In addition, the Apps Script setup function creates an Error Log tab with these columns:

Column Type Description
timestamp date ISO 8601, logged when an error occurs
error_type string validation_error, duplicate_error, or step_failure
employee_email string From the triggering record
employee_name string From the triggering record
node_name string Which node failed
error_message string Human-readable cause
resolved string Always "No" on insert; edit to "Yes" when resolved

The Error Log tab stays empty during normal operation. It only gets populated when something fails (missing fields, API errors, etc.).


Copy the Google Form and Google Sheet

  1. Visit tl-hr.com and enter your license key to access the configurator
  2. Download your pre-configured workflow and copy the Google Form and Google Sheet templates provided
  3. Make copies of both into your Google Workspace account

Link the Copied Form to the Copied Sheet

  1. Open your copied Google Form
  2. Go to the Responses tab
  3. Click the Link to Sheets icon (looks like a spreadsheet with a link)
  4. Select Select existing spreadsheet
  5. Choose your copied Google Sheet from the list
  6. Click Link
  7. Google Forms will create a Form Responses 1 tab with the 8 form columns. Do not add any extra columns to this tab yourself: the setup() function will add the n8n-managed columns after linking.

Create/Setup Google Cloud Project (if not created yet)

Enable Gmail API (skip if already done)

  1. Go to console.cloud.google.com
  2. Select your project
  3. In the left sidebar, go to APIs & Services → Library
  4. Search for Gmail API
  5. Click on Gmail API in the results
  6. Click Enable

Enable Google Sheets API (skip if already done)

  1. Go to console.cloud.google.com
  2. Select your project
  3. In the left sidebar, go to APIs & Services → Library
  4. Search for Google Sheets API
  5. Click on Google Sheets API in the results
  6. Click Enable

Enable Google Calendar API (skip if already done)

  1. Go to console.cloud.google.com
  2. Select your project
  3. In the left sidebar, go to APIs & Services → Library
  4. Search for Google Calendar API
  5. Click on Google Calendar API in the results
  6. Click Enable

Enable Admin SDK API (skip if already done)

  1. Go to console.cloud.google.com
  2. Select your project
  3. In the left sidebar, go to APIs & Services → Library
  4. Search for Admin SDK API
  5. Click on Admin SDK API in the results
  6. Click Enable

Create OAuth Consent Screen (skip if already done)

  1. Go to APIs & Services → OAuth consent screen
  2. Choose Internal (since this is for internal use)
  3. Click Create
  4. Fill in the required fields:
    • App name (e.g. "n8n")
    • User support email: select your email from the dropdown
    • Developer contact email: enter your email
  5. Click Save and Continue
  6. On the Scopes step, click Save and Continue: you can skip adding scopes here since n8n handles this automatically
  7. Click Back to Dashboard

Create OAuth Client (skip if already done)

  1. Go to console.cloud.google.com
  2. Select your project
  3. Go to APIs & Services → Credentials
  4. Click Create credentials → OAuth client ID
  5. Application type → Web application
  6. Name the OAuth client (e.g. "n8n")
  7. Authorized redirect URIs → https://your-n8n.com/rest/oauth2-credential/callback
  8. Click Create
  9. Copy Client ID and Client Secret

Note: You will only see the client secret once, so make sure to copy and/or download the JSON.


Set Up the Shared Google Calendar

The workflow needs a shared Google Calendar to check for conflicts and add approved PTO events.

Create a Shared Calendar

  1. Go to calendar.google.com
  2. In the left sidebar under Other calendars, click the + button
  3. Select Create new calendar
  4. Give it a name (e.g. "PTO Calendar" or "Time Off")
  5. Optionally add a description (e.g. "Shared PTO and time-off calendar")
  6. Click Create calendar

Find the Calendar ID

You can get the Calendar ID in two ways. The configurator accepts either format:

Option 1: Copy from Integrate calendar (recommended)

  1. In the left sidebar, hover over your new calendar
  2. Click the three dots (⋮) that appear
  3. Select Settings and sharing
  4. Scroll down to the Integrate calendar section
  5. Copy the Calendar ID: it looks like c_a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b@group.calendar.google.com

Option 2: Paste the share link

  1. In the left sidebar, hover over your new calendar
  2. Click the three dots (⋮) that appear
  3. Select Settings and sharing
  4. In the Get shareable link section, click Get shareable link
  5. Copy the entire link (it will be base64-encoded: the configurator decodes it automatically)

Important: The shared calendar must be owned by (or shared with) the Google Workspace admin account that the Service Account impersonates, with Make changes to events permission. This allows the Service Account to create events on the calendar via domain-wide delegation. No separate Google Calendar OAuth2 credential is needed; the workflow uses the HTTP Request node with the Service Account to call the Calendar API directly.

To share: go to the calendar's Settings and sharing, under Share with specific people, add the Service Account impersonated user's email with Make changes to events permission.

You'll need this Calendar ID when configuring the workflow.


Enable the Google Calendar API (skip if already done)

The Google Calendar API must be enabled in your Google Cloud project before the Service Account can use it. The Calendar API is not on by default, and the PTO workflow's Create Calendar Event node fails without it.

  1. Go to console.cloud.google.com
  2. Select your project
  3. Go to APIs & Services → Library
  4. Search for Google Calendar API
  5. Click Google Calendar API
  6. Click Enable
  7. Wait 1-2 minutes for the change to propagate

If you skip this step, the Create Calendar Event node will fail with: Google Calendar API has not been used in project [PROJECT_ID] before or it is disabled. Enable it by visiting https://console.developers.google.com/apis/api/calendar-json.googleapis.com/overview?project=[PROJECT_ID] then retry.


Create the Service Account (skip if already done)

  1. Go to console.cloud.google.com
  2. Select your project
  3. Go to APIs & Services → Credentials
  4. Click Create Credentials → Service Account
  5. Give it a name (e.g. "n8n-service-account")
  6. Copy the service account email address (e.g. n8n-service-account@project-name.iam.gserviceaccount.com)
  7. Click Create and Continue: skip the optional role and user access steps
  8. Click Done

Warning: New Google Workspace organizations have the following org policies enforced by default which will block service account and key creation:

  • iam.managed.disableServiceAccountCreation
  • iam.disableServiceAccountKeyCreation
  • iam.managed.disableServiceAccountKeyCreation

Before creating your service account key, you must temporarily unenforce these policies:

  1. Go to console.cloud.google.com
  2. In the top search bar, search for Organization Policies
  3. Note: The console only shows 50 policies at a time by default. Scroll past the first 50 or search by name to find each policy
  4. For each of the three policies listed above, click on it, select Override parent's policy, set to Not enforced, and save

Create a JSON Key (skip if already done)

  1. Click on your newly created service account
  2. Go to the Keys tab
  3. Click Add Key → Create new key
  4. Choose JSON and click Create
  5. The key file will download automatically
  6. Open n8n and import the key immediately (covered in the Connect Credentials section below)
  7. Delete the JSON file from your Downloads folder after importing
  8. If you need to keep a backup, store it somewhere secure. Not your Desktop, not email, and not a shared drive

Enable Domain-Wide Delegation (skip if already done)

  1. Go to the Details tab of your service account
  2. Click the Advanced settings tab
  3. Note the Client ID shown: you will need this in the next step

Authorize in Google Workspace Admin Console

  1. Go to admin.google.com

  2. Go to Security → Access and data control → API controls

  3. Scroll down and click Manage Domain Wide Delegation

  4. Find your Service Account's existing entry in the list and click Edit. If no entry exists yet, click Add new instead.

  5. Paste in the Service Account Client ID (if adding new) or confirm it matches (if editing)

  6. In the OAuth scopes field, add the following scopes (if editing, append to any existing scopes):

    https://www.googleapis.com/auth/calendar.events,https://www.googleapis.com/auth/spreadsheets.readonly
    

    These are the only two scopes the Service Account needs for this workflow. The Calendar Events API is used to create the PTO event on the shared calendar (POST /calendar/v3/calendars/{CALENDAR_ID}/events), and the Google Sheets API is used in read-only mode to fetch the existing row status before acting on Approve/Reject clicks (GET /v4/spreadsheets/{SHEET_ID}/values/Form Responses 1!A1:Z{row}). The native n8n Gmail and Google Sheets credentials used elsewhere in the workflow carry their own OAuth consent scopes and do not need to be added here.

    Using multiple templates with the same Service Account? The combined scopes across all Domain-Wide Delegation-using templates are admin.directory.user (for onboarding and offboarding), calendar.events (for PTO), and spreadsheets.readonly (for PTO). Comma-separated scopes go in one OAuth scopes field. Adding calendar.events, spreadsheets.readonly to an existing onboarding or offboarding Domain-Wide Delegation entry is all you need to also run PTO. Do not create a duplicate entry.

  7. Click Authorize

Note: Domain Wide Delegation can take up to 24 hours to propagate but in my experience it updates fairly immediately.


Re-enforce Organization Policies (skip if already done)

Once your service account credential is confirmed working in n8n, re-enforce the three org policies to secure your Google Workspace:

  1. Go to console.cloud.google.com

  2. In the top search bar, search for Organization Policies

  3. For each of the three policies below, click on it, select Override parent's policy, set to Enforced, and save:

    • iam.managed.disableServiceAccountCreation
    • iam.disableServiceAccountKeyCreation
    • iam.managed.disableServiceAccountKeyCreation

This ensures your organization is protected against unauthorized service account and key creation going forward.

Verify before moving on. All three policies should now read Enforced. If any still reads Not enforced, your Workspace org can still create service accounts and JSON keys without restriction. The workflow above this section will keep running either way, so this step won't fail loudly if it slips past you.


Connect Credentials in n8n

Create the Gmail Credential (skip if already done)

  1. Open your n8n instance
  2. Click Overview in the left sidebar
  3. Click Credentials
  4. Click Create credential in the top right
  5. Search and select Gmail OAuth2 API
  6. Paste your Client ID and Client Secret from the OAuth client step
  7. Click Sign in with Google
  8. A browser window will open. Sign in with the Google Workspace admin account
  9. Grant the requested permissions
  10. You will be redirected back to n8n. The credential will show as connected

Create the Google Sheets Credential (skip if already done)

  1. Open your n8n instance
  2. Click Overview in the left sidebar
  3. Click Credentials
  4. Click Create credential in the top right
  5. Search and select Google Sheets OAuth2 API
  6. Paste your Client ID and Client Secret from the OAuth client step
  7. Click Sign in with Google
  8. A browser window will open. Sign in with the Google Workspace admin account
  9. Grant the requested permissions
  10. You will be redirected back to n8n. The credential will show as connected

Create the Google Service Account Credential (skip if already done)

When you create this credential in n8n, it is labeled Google Service Account (with DWD) by default. Keep that name during initial setup so the imported workflow auto-links the credential; after the link is established, n8n resolves it by credential ID at runtime, so any later rename is harmless.

  1. Open your n8n instance
  2. Click Overview in the left sidebar
  3. Click Credentials
  4. Click Create credential in the top right
  5. Search and select Google Service Account API
  6. Select the region closest to you (this does not matter)
  7. Open the downloaded JSON key file. Find the Service Account Email (client_email) and paste it into the Service Account Email field
  8. From the downloaded JSON, find the Private Key (private_key) and paste the full value into the private key field (everything between and including -----BEGIN PRIVATE KEY----- and -----END PRIVATE KEY-----\n)
  9. Toggle Impersonate a User on and enter your email (the Google Workspace admin)
  10. Toggle Set up for use in HTTP Request node on
  11. Add the scope: https://www.googleapis.com/auth/calendar.events
  12. Set Allowed HTTP Request Domains to Specific Domains and enter sheets.googleapis.com,www.googleapis.com
  13. When all fields are complete, you should see Connection tested successfully

The PTO workflow's service-account-impersonated calls reach only two Google APIs: the Google Calendar API at www.googleapis.com (the Create Calendar Event node POSTs to https://www.googleapis.com/calendar/v3/calendars/{CALENDAR_ID}/events), and the Google Sheets API in read-only mode at sheets.googleapis.com (the Read Decision Status node GETs https://sheets.googleapis.com/v4/spreadsheets/{SHEET_ID}/values/Form Responses 1!A1:Z{row_number} to look up the existing row by row_number before applying a decision). If sheets.googleapis.com is missing from this allow-list, that lookup fails with: Domain not allowed: This credential is restricted from accessing https://sheets.googleapis.com/v4/... Only the following domains are allowed: www.googleapis.com.

Required n8n credential settings for the PTO workflow

The Service Account credential in n8n must have the following four settings configured correctly, otherwise the Create Calendar Event node will fail with Forbidden - perhaps check your credentials? / Request had insufficient authentication scopes. The same credential is used by Read Decision Status to call the Google Sheets API; if its allow-list excludes sheets.googleapis.com, the lookup returns a domain error and the workflow falls back to treating the row as Pending (the approve/reject still lands but idempotency is lost for clicks that hit a row already updated).

To check or edit these on an existing credential:

  1. Open your n8n instance
  2. Click Overview in the left sidebar
  3. Click Credentials
  4. Find Google Service Account API in the list and click on it
  5. Verify the Service Account Email field is filled in
  6. Verify the Private Key field is filled in (starts with -----BEGIN PRIVATE KEY-----)
  7. Verify Impersonate a User is toggled on, and the impersonated user is your Google Workspace admin email
  8. Toggle Set up for use in HTTP Request node to on: this is required for the credential to work with HTTP Request nodes
  9. In the Scope field, add https://www.googleapis.com/auth/calendar.events
  10. Under Allowed HTTP Request Domains, select Specific Domains and enter sheets.googleapis.com,www.googleapis.com
  11. Click Save
  12. Click Connection test: it should report success

If any of these four settings is wrong:

Common errors when setting up the Google Service Account

unauthorized_client

n8n validates all Domain-Wide Delegation scopes when authenticating, not just the ones your workflow uses. If any scope is missing the entire authentication fails. Make sure the full scope list is added exactly as shown in the Google Workspace Admin Console under Security → Access and data control & API controls → Manage Domain Wide Delegation. No extra spaces, no missing scopes.

invalid_grant (Private key validation failed)

This is usually a propagation delay. After setting up or updating Domain-Wide Delegation in the Admin Console, it can take up to a few hours to take effect. If you see this error immediately after setup, wait a few hours and try again before troubleshooting further.

HTTP Request Domain blocked

The "Set up for use in HTTP Request node" toggle on the Google Service Account credential in n8n must be turned on, and the Allowed HTTP Request Domains must be set to Specific Domains with sheets.googleapis.com,www.googleapis.com. If the Calendar API calls fail, double-check that www.googleapis.com is included.

Forbidden - Request had insufficient authentication scopes

The Calendar API rejected the request because the OAuth token does not include the calendar scope. Two things must both be true:

  1. The n8n Service Account credential has https://www.googleapis.com/auth/calendar.events in its Scope field (per the Required n8n credential settings section above)
  2. The Google Workspace Admin Console has the calendar scope in the Service Account's Domain-Wide Delegation entry (per the Domain-Wide Delegation step in this guide)

If both are set, propagation may be the cause; Domain-Wide Delegation changes can take up to a few hours to take effect.


Find your Credential IDs in n8n (skip if already done)

Each credential you create in n8n has a unique ID. You need these IDs to configure your workflow.

  1. Open your n8n instance
  2. Click Credentials in the left sidebar
  3. Find the credential you need and click on it to open it
  4. Go to the Details tab
  5. Copy the ID field

You need three credential IDs (a separate Calendar credential is not required, since the Service Account handles Calendar API access via HTTP Request):


Understand the Approval Flow

The PTO Request Form workflow uses a two-step approval process:

  1. Employee submits form: The Google Form submission triggers the Apps Script, which sends the data to the n8n webhook. The workflow creates a record in the Google Sheet with status Pending and sends an email to the manager.

  2. Manager approves or rejects: The email sent to the manager contains two links. Both point to the same webhook URL at the pto-request-decision path and append eight query parameters (action, email, name, start, end, row, type, notes). All values are percent-encoded (URL-encoded) so free-text fields like the notes parameter are safe when they contain spaces or other reserved characters.

    • Approve: https://your-n8n.com/webhook/pto-request-decision?email=…&name=…&start=YYYY-MM-DD&end=YYYY-MM-DD&row=N&type=Vacation&notes=…&action=approve
    • Reject: https://your-n8n.com/webhook/pto-request-decision?email=…&name=…&start=YYYY-MM-DD&end=YYYY-MM-DD&row=N&type=Vacation&notes=…&action=reject

    The action parameter (approve or reject) is what tells the workflow which decision was made. The other seven parameters are used by the workflow to identify the row (row), confirm the request dates (start and end), and enrich the manager-email context (name, email, type, notes).

  3. Workflow processes the decision .

    • Approved: The workflow creates a calendar event on the shared PTO calendar, sends a confirmation email to the employee, and updates the Sheet row to Approved with a decision timestamp.
    • Rejected: The workflow sends a notification email to the employee and updates the Sheet row to Rejected with a decision timestamp.

Both paths (form submission and decision) are handled by a single workflow using two webhook triggers. The forms webhook (pto-request-form) receives submissions from the Apps Script, and the decision webhook (pto-request-decision) receives approval or rejection clicks.


Want to skip the manual setup?

Our templates include a configuration tool. Enter your details once and download a ready-to-import workflow. View templates →

Configure PTO Request Form Workflow

  1. Go to https://tl-hr.com/configure/pto-request-form-google-workspace
  2. Enter the passcode (provided by your account)
  3. Fill in the form:
Field Description
Company name Your company name
Google Workspace domain e.g. acme.com
Gmail credential ID The ID from the Gmail credential in n8n
Google Sheets credential ID The ID from the Sheets credential in n8n
Google Service Account credential ID The ID from the Service Account credential in n8n
n8n webhook URL (base) e.g. https://your-n8n-instance.com/webhook/pto-request-form. That is the form webhook path
Shared Calendar ID The Calendar ID from your shared PTO calendar (looks like c_abcdef123456@group.calendar.google.com)
Google Sheets ID The ID from your Google Sheet URL (the long string in https://docs.google.com/spreadsheets/d/THIS-ID/edit)
Alert email Email to receive error messages if the workflow fails
  1. Click Download Workflow JSON to download the configured workflow
  2. Click Download Apps Script to download the configured Google Apps Script file

Important: The Apps Script is installed on the Google Sheet (not the Form). Unlike other templates where the script goes on the Form, the PTO script uses a Sheet-based trigger that fires when a new form response row is added. This allows it to pass the row number to the workflow so the workflow can update the correct row when the manager approves or rejects.


Import the Apps Script into the Sheet

  1. Open your copied Google Sheet
  2. Click Extensions → Apps Script
  3. In the new window, the Code.gs file should be empty
  4. Open the downloaded pto-request-form-google-workspace-appscript.gs file in a text editor
  5. Copy and paste the entire contents into the empty Code.gs file
  6. Select setup in the function dropdown and click the Run button
  7. Approve the permissions when prompted
  8. You will see Execution started and then Execution completed in the log
  9. Close the Apps Script editor

Import the Workflow into n8n

  1. Create a new workflow in n8n
  2. Click the kebab menu button (three dots) on the top right
  3. Select Import from file
  4. Select the downloaded pto-request-form-google-workspace-configured.json file
  5. Click Save to save the workflow
  6. Click Publish to set the workflow live

Ready to automate?

The full PTO Request Form workflow includes the complete workflow, Apps Script, error handling, automatic retry, and the configuration tool. Setup takes 30-60 minutes if you follow this guide.

Test the Workflow

1. Submit a PTO Request

  1. Open your Google Form and submit a test PTO request using your own email and manager's email

2. Verify the Initial Submission

  1. Open your Google Sheet and check the PTO Records tab
  2. Verify a new row was created with status Pending
  3. Check that all fields are populated correctly

3. Verify the Manager Receives the Approval Email

  1. Check the manager's email inbox (or your own if you used the same address)
  2. Verify the email contains:
    • The employee's name and details
    • An Approve link with action=approve in the URL
    • A Reject link with action=reject in the URL

4. Test the Approved Path

  1. Click the Approve link in the email
  2. You should see a confirmation page saying "Approved!"
  3. Verify the following happened:
    • A calendar event was created on the shared PTO calendar
    • The employee received a confirmation email
    • The Sheet row updated to Approved with a decision timestamp

5. Test the Rejected Path

  1. Submit another PTO request through the form
  2. Click the Reject link in the email
  3. You should see a confirmation page saying "Declined"
  4. Verify the following happened:
    • The employee received a rejection notification email
    • The Sheet row updated to Rejected with a decision timestamp

6. Verify Error Handling

  1. Intentionally submit a form with missing required fields (e.g. leave the email blank)
  2. Verify the error is logged in the Error Log tab
  3. Verify the alert email is sent to the configured alert address