Poradnik

Formularz HTML do Google Sheets: 2 sposoby

Dwa darmowe sposoby, by zgłoszenia z formularza trafiały do Arkuszy Google, z gotowym kodem Apps Script. Jeden bezpośredni, drugi dodaje powiadomienia i filtr spamu.

Krótka odpowiedź. Otwórz arkusz i dodaj Apps Script z funkcją doPost, która dopisuje wiersz. Wdróż go jako aplikację internetową dostępną dla wszystkich i wysyłaj formularz na jej adres. Chcesz też powiadomień e-mail lub na Telegram i filtra spamu? Wyślij formularz do backendu formularzy, a jego webhook niech wywoła taki sam skrypt. Oba sposoby są darmowe.

Formgong

Formularz HTML do Google Sheets: 2 sposoby
Formularz HTML do Google Sheets: 2 sposoby

Dwa sposoby w skrócie

  • Sposób 1: formularz → Apps Script. Formularz wysyła dane prosto do małego skryptu w Twoim arkuszu. Nic więcej nie trzeba. Dostajesz wiersze, ale bez powiadomień, bez filtra spamu poza tym, co sam napiszesz, a formularz wymaga JavaScriptu.
  • Sposób 2: formularz → backend formularzy → Apps Script. Formularz trafia do backendu. Ten odsiewa spam, wysyła Ci powiadomienie, a potem przekazuje każde prawdziwe zgłoszenie do skryptu webhookiem.

Jeśli potrzebujesz tylko listy, zacznij od sposobu 1. Jeśli ktoś musi szybko odpowiadać na zgłoszenia, wybierz sposób 2.

Sposób 1: formularz wysyła dane do Apps Script

Nazwy menu podajemy tak, jak w angielskim interfejsie Google. W polskim są przetłumaczone, ale stoją w tych samych miejscach.

  1. Utwórz arkusz, nazwij kartę Zgłoszenia i wpisz w 1. wierszu: Data, Imię, E-mail, Wiadomość.
  2. Otwórz Extensions → Apps Script, usuń przykładowy kod i wklej skrypt poniżej.
  3. Kliknij Deploy → New deployment, wybierz Web app. W Execute as ustaw Me, w Who has access Anyone.
  4. Zatwierdź uprawnienia i skopiuj adres aplikacji. Kończy się na /exec.
  5. Wstaw ten adres do kodu formularza z następnego bloku i opublikuj stronę.

Skrypt pomija zgłoszenia z wypełnionym polem-pułapką. Zakłada też blokadę, więc dwa zgłoszenia w tej samej chwili się nie nadpiszą. Funkcja clean() nie pozwala, by wartość typu =IMPORTXML(…) zadziałała jak formuła.

Apps Script
// W arkuszu: Extensions > Apps Script. W 1. wierszu karty "Zgłoszenia" są nazwy kolumn.
function doPost(e) {
  const p = e.parameter;
  if (p.botcheck) return json({ ok: true }); // pole-pułapka: boty je wypełniają, ludzie go nie widzą
  const lock = LockService.getScriptLock();
  lock.waitLock(20000); // jeden zapis naraz, żeby wiersze się nie nadpisywały
  try {
    SpreadsheetApp.getActive().getSheetByName("Zgłoszenia").appendRow([
      new Date(), clean(p.name), clean(p.email), clean(p.message),
    ]);
  } finally {
    lock.releaseLock();
  }
  return json({ ok: true });
}

// Wartość zaczynająca się od = + - lub @ zadziałałaby jak formuła. Apostrof zostawia ją jako tekst.
function clean(value) {
  const text = String(value || "");
  return /^[=+\-@]/.test(text) ? "'" + text : text;
}

function json(data) {
  return ContentService.createTextOutput(JSON.stringify(data))
    .setMimeType(ContentService.MimeType.JSON);
}
Formularz HTML, który wysyła dane do aplikacji
HTML
<form id="lead-form">
  <label>Imię <input type="text" name="name" required autocomplete="name"></label>
  <label>E-mail <input type="email" name="email" required autocomplete="email"></label>
  <label>Wiadomość <textarea name="message" required></textarea></label>
  <div aria-hidden="true" style="position:absolute;inset-inline-start:0;top:0;width:1px;height:1px;overflow:hidden;clip-path:inset(50%)">
    <input type="text" name="botcheck" tabindex="-1" autocomplete="off">
  </div>
  <button type="submit">Wyślij</button>
  <p id="lead-status" role="status"></p>
</form>
<script>
  const form = document.getElementById("lead-form");
  const status = document.getElementById("lead-status");
  form.addEventListener("submit", async (event) => {
    event.preventDefault();
    status.textContent = "Wysyłanie…";
    try {
      // Treść w formacie formularza to prosty request, więc przeglądarka nie wysyła preflight.
      const response = await fetch("https://script.google.com/macros/s/YOUR_DEPLOYMENT_ID/exec", {
        method: "POST",
        body: new URLSearchParams(new FormData(form)),
      });
      if (!response.ok) throw new Error(String(response.status));
      form.reset();
      status.textContent = "Dziękujemy, wiadomość dotarła.";
    } catch {
      status.textContent = "Nie udało się wysłać. Spróbuj ponownie albo napisz do nas e-mail.";
    }
  });
</script>

Czego nie robi sposób 1

  • Brak powiadomień. Wiersze pojawiają się po cichu. Żeby zobaczyć nowe zgłoszenie, trzeba otworzyć arkusz.
  • Słaba ochrona przed spamem. Adres jest publiczny, więc każdy, kto go znajdzie, może coś wysłać. Pole-pułapka zatrzymuje tylko proste boty.
  • Wymaga JavaScriptu. Formularz używa fetch, bo zwykłe wysłanie zostawiłoby odwiedzającego na stronie Google.
  • Zmiany to nowa wersja. Po edycji skryptu otwórz Manage deployments, edytuj wdrożenie i wybierz nową wersję. Adres zostaje ten sam.
  • Limity. Google ogranicza Apps Script na użytkownika, np. 30 równoczesnych uruchomień i 6 minut na jedno (stan na 05.10.2026). Mała strona tego nie odczuje.

Sposób 2: webhook z backendu formularzy do arkusza

Tu formularz to zwykły formularz HTML, który wysyła dane do Formgong. Działa bez JavaScriptu i pokazuje stronę z podziękowaniem. Formgong odsiewa spam, wysyła zgłoszenie e-mailem lub na Telegram, a potem przekazuje JSON do skryptu. Plan Free obejmuje jeden webhook na formularz.

  1. Utwórz arkusz i kartę Zgłoszenia z kolumnami: ID, Data, Imię, E-mail, Wiadomość.
  2. Wklej skrypt webhooka poniżej, zmień SECRET i wdróż go jako aplikację, jak w sposobie 1.
  3. W Formgong otwórz ustawienia formularza i dodaj webhook. Podaj adres aplikacji z dopiskiem ?key= i swoim sekretem.
  4. Kliknij test webhooka w panelu, a potem wyślij prawdziwe zgłoszenie ze strony.
Apps Script dla webhooka Formgong
Apps Script
// URL webhooka w Formgong: adres aplikacji internetowej + "?key=" + ten sam SECRET.
// Apps Script nie widzi nagłówków żądania, więc klucz w adresie zastępuje sprawdzanie podpisu.
const SECRET = "change-me";

function doPost(e) {
  if (e.parameter.key !== SECRET) return reply();
  const body = JSON.parse(e.postData.contents);
  if (body.event !== "submission.created") return reply(); // test z panelu wysyła zdarzenie "webhook.test"
  const lock = LockService.getScriptLock();
  lock.waitLock(20000);
  try {
    const sheet = SpreadsheetApp.getActive().getSheetByName("Zgłoszenia");
    const id = body.submission.id;
    // Ponowna próba ma to samo id: pomijamy, jeśli wiersz już jest.
    if (!sheet.getRange("A:A").createTextFinder(id).matchEntireCell(true).findNext()) {
      const f = body.submission.fields;
      sheet.appendRow([id, body.submission.created_at, clean(f.name), clean(f.email), clean(f.message)]);
    }
  } finally {
    lock.releaseLock();
  }
  return reply();
}

function clean(value) {
  const text = String(value || "");
  return /^[=+\-@]/.test(text) ? "'" + text : text;
}

// HtmlService, nie ContentService: ContentService odpowiada przekierowaniem,
// a nadawca webhooków, który nie idzie za przekierowaniami, uznaje to za błąd.
function reply() {
  return HtmlService.createHtmlOutput("ok");
}

Pułapka z przekierowaniem, która tworzy duplikaty

Google opisuje, że skrypt odpowiadający przez ContentService zwraca przekierowanie na jednorazowy adres w script.googleusercontent.com. Przeglądarka za nim idzie, więc w sposobie 1 wszystko gra. Nadawca webhooków, który nie idzie za przekierowaniami, widzi jednak błąd, choć wiersz już się zapisał.

Formgong ze względów bezpieczeństwa nie idzie za przekierowaniami webhooków. Traktuje je jako nieudaną dostawę i próbuje ponownie później, do pięciu prób. Każda próba uruchamia skrypt, więc jedno zgłoszenie może pojawić się pięć razy. Skrypt webhooka zapobiega temu na dwa sposoby:

  • Odpowiada przez HtmlService. Programiści zgłaszają, że taka odpowiedź wraca od razu, bez przekierowania.
  • Zapisuje ID zgłoszenia w kolumnie A i pomija ID, które już tam są. Nawet przy ponownej próbie wiersz będzie jeden.

Po konfiguracji otwórz dziennik webhooków w panelu. Prawdziwe zgłoszenie powinno mieć status 200 przy pierwszej próbie.

Bez kodu: Make, n8n albo Zapier

Nie chcesz ruszać Apps Script? Skieruj webhook do narzędzia automatyzacji i dodaj tam krok Google Sheets. Mamy instrukcje dla Make i n8n na własnym serwerze. Zapier też się nada, ale jego wyzwalacz webhooków wymaga płatnego planu Zapier. Darmowe opcje to więc Make albo n8n.

Format JSON i sprawdzanie podpisu na własnym serwerze opisuje dokumentacja webhooków.

Pytania i odpowiedzi

Czy formularz HTML może zapisywać do Google Sheets bez kodu?

Bezpośrednio nie. Potrzebujesz małego Apps Script albo pośrednika, np. backendu formularzy z Make lub n8n. Skrypt z tego artykułu to około 20 linii do skopiowania.

Dlaczego po wysłaniu otwiera się strona Google?

Formularz wysyła dane prosto do aplikacji, a przeglądarka pokazuje jej odpowiedź. Wysyłaj formularz przez fetch, jak w sposobie 1, albo przez backend formularzy, który przekieruje na Twoją stronę z podziękowaniem.

Skąd duplikaty wierszy z webhooka?

Nadawca zobaczył błąd i spróbował ponownie. W Apps Script ContentService odpowiada przekierowaniem, które część nadawców uznaje za błąd. Odpowiadaj przez HtmlService i pomijaj ID zgłoszeń, które już są w arkuszu.

Czy dostęp „Anyone” do aplikacji jest bezpieczny?

Każdy z adresem może wysłać dane, ale nikt nie odczyta przez niego arkusza. Trzymaj skrypt prosty, sprawdzaj tajny klucz dla webhooków i nigdy nie zwracaj danych z arkusza w odpowiedzi.

Źródła i dokumentacja

Oficjalne materiały pomocnicze: Apps Script: web apps, Apps Script: Content service redirects, Apps Script: quotas, Apps Script: Lock service, Stack Overflow: HtmlService avoids the redirect, Zapier pricing. Webhooki Formgong, Przechowywanie danych.

Artykuł w formacie Markdown

Oceń ten poradnik

Twoja ocena

Czytamy każdą ocenę. Komentarz pojawi się tutaj dopiero po naszym zatwierdzeniu.

← Wróć do bloga