ID
Tutorials

Apps Script Web App Tutorial

Build a live feedback and submission portal using Google Apps Script Web App URL.

Learn how to build a full-stack feedback submission portal using Google Apps Script without GCP credentials or API limits.


1. Google Sheets & Apps Script Setup

  1. Create a Google Sheet named User Feedback.
  2. Name the sheet tab feedback.
  3. Add column headers in Row 1: id, author, rating, comment.
  4. Open Extensions -> Apps Script and paste Code.gs:
Code.gs
function doGet(e) {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(e.parameter.sheet || 'feedback');
  const values = sheet.getRange(e.parameter.range || 'A1:Z100').getValues();
  return ContentService.createTextOutput(JSON.stringify(values)).setMimeType(ContentService.MimeType.JSON);
}

function doPost(e) {
  const data = JSON.parse(e.postData.contents);
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(data.sheet || 'feedback');
  if (data.values) {
    data.values.forEach(r => sheet.appendRow(r));
  }
  return ContentService.createTextOutput(JSON.stringify({ success: true })).setMimeType(ContentService.MimeType.JSON);
}
  1. Click Deploy -> New Deployment -> Select Web App (Execute as: Me, Who has access: Anyone).
  2. Copy Web App URL into .env:
.env
GSHEET_APPSCRIPT_URL=https://script.google.com/macros/s/AKfycb.../exec

2. Vue Component Implementation

pages/feedback.vue
<script setup lang="ts">
interface FeedbackItem {
  id: string
  author: string
  rating: string
  comment: string
}

// Read data
const { data: items, pending, refresh } = await useGSheetAsObject<FeedbackItem[]>('A1:D50', {
  sheet: 'feedback'
})

// Write data
const { append } = useGSheetWrite({ sheet: 'feedback' })

const author = ref('')
const rating = ref('5')
const comment = ref('')
const isSaving = ref(false)

const submitComment = async () => {
  if (!author.value || !comment.value) return
  isSaving.value = true

  try {
    await append('A1:D1', [
      [String(Date.now()), author.value, rating.value, comment.value]
    ])
    author.value = ''
    comment.value = ''
    await refresh()
  }
  finally {
    isSaving.value = false
  }
}
</script>

<template>
  <div class="feedback-page">
    <h1>Submit User Feedback</h1>

    <form @submit.prevent="submitComment">
      <input
        v-model="author"
        placeholder="Your Name"
        required
      >
      <select v-model="rating">
        <option value="5">
          5 Stars
        </option>
        <option value="4">
          4 Stars
        </option>
        <option value="3">
          3 Stars
        </option>
      </select>
      <textarea
        v-model="comment"
        placeholder="Comment"
        required
      />
      <button
        :disabled="isSaving"
        type="submit"
      >
        {{ isSaving ? 'Sending...' : 'Submit Feedback' }}
      </button>
    </form>

    <hr>

    <div v-if="pending">
      Loading comments...
    </div>
    <ul v-else>
      <li
        v-for="item in items"
        :key="item.id"
      >
        <strong>{{ item.author }}</strong> ({{ item.rating }} Stars): {{ item.comment }}
      </li>
    </ul>
  </div>
</template>