ID
Tutorials

Service Account HR & Payroll Tutorial

Build a secure, private enterprise HR management app using Service Account credentials.

Learn how to securely access unshared, private Google Sheets for enterprise HR and payroll records.


1. Setup Service Account Credentials

  1. In GCP Console, create a Service Account (e.g. hr-bot@project.iam.gserviceaccount.com).
  2. Create and download a JSON Key File.
  3. Open your private Google Sheet -> Share with hr-bot@project.iam.gserviceaccount.com as Editor.
  4. Configure .env:
.env
GSHEET_SPREADSHEET_ID=your-private-spreadsheet-id
GSHEET_CLIENT_EMAIL=hr-bot@project.iam.gserviceaccount.com
GSHEET_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC...\n-----END PRIVATE KEY-----\n"

2. Vue Component Implementation (Read & Write)

pages/hr/employees.vue
<script setup lang="ts">
interface Employee {
  empId: string
  name: string
  department: string
  salary: string
}

// 1. Secure Server-Side Read
const { data: employees, pending, refresh } = await useGSheetAsObject<Employee[]>('A1:D50', {
  sheet: 'payroll'
})

// 2. Private Write Operations
const { append } = useGSheetWrite({ sheet: 'payroll' })

const name = ref('')
const department = ref('Engineering')
const salary = ref('85000')

const addEmployee = async () => {
  if (!name.value) return
  await append('A1:D1', [
    [`EMP-${Date.now()}`, name.value, department.value, salary.value]
  ])
  name.value = ''
  await refresh()
}
</script>

<template>
  <div class="hr-portal">
    <h1>Enterprise HR & Payroll Portal</h1>

    <form @submit.prevent="addEmployee">
      <input
        v-model="name"
        placeholder="Employee Name"
        required
      >
      <select v-model="department">
        <option value="Engineering">
          Engineering
        </option>
        <option value="Marketing">
          Marketing
        </option>
      </select>
      <input
        v-model="salary"
        placeholder="Salary"
        required
      >
      <button type="submit">
        Add Employee
      </button>
    </form>

    <div v-if="pending">
      Authenticating JWT & loading private records...
    </div>
    <table v-else>
      <thead>
        <tr>
          <th>ID</th>
          <th>Name</th>
          <th>Dept</th>
          <th>Salary</th>
        </tr>
      </thead>
      <tbody>
        <tr
          v-for="emp in employees"
          :key="emp.empId"
        >
          <td>{{ emp.empId }}</td>
          <td>{{ emp.name }}</td>
          <td>{{ emp.department }}</td>
          <td>${{ emp.salary }}</td>
        </tr>
      </tbody>
    </table>
  </div>
</template>