Lewati ke konten

Google Forms dan Apps Script

Add-on Sheets membuat dokumen saat Anda mengklik Buat. Untuk membuatnya tanpa ada yang mengklik, misalnya sertifikat untuk setiap jawaban formulir atau penawaran saat sebuah baris dicentang, Apps Script singkat mengirim data ke API dokumen AutomaDoc dan melakukan apa yang Anda inginkan dengan file-nya: menyimpannya ke Drive, mengirimkannya lewat email, atau menulis tautannya ke sheet.

Script berjalan di akun Google Anda, dengan aturan Apps Script yang biasa dari Google. AutomaDoc hanya melihat JSON yang dikirimnya.

  1. Template. Template AutomaDoc apa pun bisa dipakai: JSON yang dikirim script adalah record-nya, jadi sumber data template itu sendiri tidak dipakai. Placeholder-nya dinamai sesuai kunci yang dikirim script (lihat Nama).
  2. ID template. Di Portal Perusahaan, buka Template, buka menu template tersebut, lalu pilih Salin ID template.
  3. Kunci API. Kunci API perusahaan, yang dibuat admin di Portal Perusahaan di bawah Kunci API (diawali amk_ dan hanya ditampilkan sekali), atau, untuk template Lark Base, kunci milik template itu sendiri, yang hanya membuka template tersebut. Makin sempit jangkauan kunci, makin baik: siapa pun yang dapat mengedit script dapat membacanya.

Resep: dokumen untuk setiap jawaban formulir

Section titled “Resep: dokumen untuk setiap jawaban formulir”

Script ini terikat pada formulir. Untuk setiap jawaban, script membuat PDF, menyimpannya di folder Drive, dan mengirimkannya lewat email ke orang yang menjawab.

  1. Buka formulir di Google Forms, buka menu ⋮, lalu pilih Apps Script.
  2. Di editor Apps Script, buka Project Settings dan tambahkan script property bernama AUTOMADOC_API_KEY dengan kunci Anda sebagai nilainya. Dengan begitu, kunci tidak tertulis di dalam kode.
  3. Ganti isi Code.gs dengan script di bawah, lalu isi TEMPLATE_ID dan FOLDER_ID (bagian terakhir alamat folder Drive, setelah /folders/).
  4. Pilih setUp di daftar fungsi dan klik Run sekali. Google meminta Anda mengizinkan script terhubung ke layanan eksternal, memakai Drive Anda, dan mengirim email atas nama Anda.

Sejak itu, setiap pengiriman formulir menjalankan handleSubmit. Run dan error-nya tercantum di Executions di editor.

const AUTOMADOC_API = 'https://api.automadoc.cc';
const TEMPLATE_ID = 'PASTE-THE-TEMPLATE-ID';
const FOLDER_ID = 'PASTE-THE-DRIVE-FOLDER-ID';
/** Run once: creates the trigger that calls handleSubmit on every response. */
function setUp() {
ScriptApp.newTrigger('handleSubmit')
.forForm(FormApp.getActiveForm())
.onFormSubmit()
.create();
}
function handleSubmit(e) {
const response = e.response;
// One key per question: its title is the placeholder's name.
const data = {};
response.getItemResponses().forEach(function (itemResponse) {
data[itemResponse.getItem().getTitle()] = answerOf(itemResponse.getResponse());
});
data['Submitted'] = Utilities.formatDate(
response.getTimestamp(), Session.getScriptTimeZone(), 'yyyy-MM-dd HH:mm');
const doc = createDocument(data);
const file = DriveApp.getFolderById(FOLDER_ID).createFile(downloadFile(doc));
console.log('Saved ' + file.getName() + ': ' + file.getUrl());
// Needs "Collect email addresses" in the form's settings.
const email = response.getRespondentEmail();
if (email) {
MailApp.sendEmail({
to: email,
subject: doc.filename,
body: 'Thank you. Your document is attached.',
attachments: [file.getBlob()],
});
}
}
/** Grid questions answer with a list of lists, which the API refuses: one text instead. */
function answerOf(value) {
if (Array.isArray(value) && value.some(Array.isArray)) {
return value.map(function (row) { return [].concat(row).join(', '); }).join('; ');
}
return value;
}
/** Fills the template and waits until the document is finished. */
function createDocument(data) {
let doc = callApi(AUTOMADOC_API + '/api/v1/templates/' + TEMPLATE_ID + '/documents?wait=20', {
method: 'post',
contentType: 'application/json',
payload: JSON.stringify({ data: data, format: 'pdf' }),
});
// Not finished within the 20 seconds: read its status every 5 seconds, for up to 4 minutes.
for (let i = 0; doc.status === 'processing' && i < 48; i++) {
Utilities.sleep(5000);
doc = callApi(doc.statusUrl);
}
if (doc.status !== 'completed') {
throw new Error(doc.error || 'The document is still being made: ' + doc.statusUrl);
}
if (doc.warnings) console.log(doc.warnings.join('\n'));
return doc;
}
/** The finished file, as a blob named like the document. */
function downloadFile(doc) {
const response = UrlFetchApp.fetch(doc.statusUrl + '/file', {
headers: { 'X-Webhook-API-Key': apiKey() },
muteHttpExceptions: true,
});
if (response.getResponseCode() !== 200) {
throw new Error('Download failed: ' + response.getContentText());
}
return response.getBlob().setName(doc.filename);
}
function callApi(url, options) {
options = options || {};
options.headers = { 'X-Webhook-API-Key': apiKey() };
options.muteHttpExceptions = true;
const response = UrlFetchApp.fetch(url, options);
const body = JSON.parse(response.getContentText());
if (response.getResponseCode() >= 400) throw new Error(body.code + ': ' + body.error);
return body;
}
function apiKey() {
return PropertiesService.getScriptProperties().getProperty('AUTOMADOC_API_KEY');
}

Yang terjadi pada setiap panggilan:

  • POST /api/v1/templates/{templateId}/documents?wait=20 mengirim { "data": …, "format": "pdf" }. Dengan wait, jawaban datang saat dokumen selesai (200) atau setelah 20 detik (202, masih processing). format juga bisa "native" (format Word, Excel, atau PowerPoint milik template itu sendiri) atau "png", dan filename menetapkan pola nama file untuk panggilan ini.
  • GET /api/v1/documents/{generationId} (statusUrl dalam jawaban) membaca status-nya: processing, completed, atau failed, dengan alasannya di error. Dokumen yang selesai memiliki filename dan downloadUrl.
  • GET /api/v1/documents/{generationId}/file mengembalikan file itu sendiri, dengan kunci yang sama.

Jika Anda hanya memerlukan tautan, lewati downloadFile: doc.downloadUrl membuka file tanpa kunci, selama perusahaan Anda menyimpan dokumen yang dibuat (30 hari jika disimpan tanpa batas waktu). Setiap pembacaan status memberikan tautan baru.

Resep: dokumen saat kotak dicentang di sheet

Section titled “Resep: dokumen saat kotak dicentang di sheet”

Helper yang sama juga bekerja dari spreadsheet. Buka spreadsheet, pilih Ekstensi → Apps Script, tambahkan script property-nya, lalu tempel helper di atas (createDocument, downloadFile, callApi, apiKey, dan ketiga konstanta) dengan kode ini sebagai pengganti setUp dan handleSubmit.

Di contoh ini, tab-nya bernama Orders. Baris 1 berisi nama kolom; kolom kotak centang bernama Generate memulai sebuah dokumen, dan kolom bernama Document menerima tautannya.

const SHEET_NAME = 'Orders';
/** Run once: creates the trigger that calls handleEdit on every edit. */
function setUp() {
ScriptApp.newTrigger('handleEdit')
.forSpreadsheet(SpreadsheetApp.getActive())
.onEdit()
.create();
}
function handleEdit(e) {
const sheet = e.range.getSheet();
// Only a single checkbox ticked on a data row of the right tab
if (sheet.getName() !== SHEET_NAME || e.range.getRow() < 2 || e.value !== 'TRUE') return;
const headers = sheet.getRange(1, 1, 1, sheet.getLastColumn()).getDisplayValues()[0];
if (headers[e.range.getColumn() - 1] !== 'Generate') return;
const row = e.range.getRow();
const values = sheet.getRange(row, 1, 1, headers.length).getDisplayValues()[0];
const data = {};
headers.forEach(function (header, i) {
if (header && header !== 'Generate' && header !== 'Document') data[header] = values[i];
});
const doc = createDocument(data);
const file = DriveApp.getFolderById(FOLDER_ID).createFile(downloadFile(doc));
const linkColumn = headers.indexOf('Document') + 1;
if (linkColumn > 0) sheet.getRange(row, linkColumn).setValue(file.getUrl());
}

Trigger-nya harus trigger installable yang dibuat oleh setUp. Fungsi bernama onEdit akan berjalan sebagai simple trigger, yang tidak diizinkan Google untuk memanggil layanan luar.

getDisplayValues() mengirim setiap sel seperti yang ditampilkan sheet, sehingga angka tiba sebagai teks yang diformat oleh sheet. Untuk memakai filter seperti number, currency, atau sum pada sebuah kolom, kirim nilai kolom itu dari getValues().

Setiap kunci menjadi nama placeholder dengan cara yang sama seperti nama kolom atau field: spasi, titik, dan simbol lain menjadi _, huruf dan angka dari bahasa apa pun dipertahankan, dan huruf besar/kecil dibedakan.

Pertanyaan atau kolom Placeholder
Full name {Full_name}
Invoice No. {Invoice_No_}
Submitted (ditambahkan oleh script) {Submitted}

Judul tanpa huruf atau angka ditolak (DATA_INVALID_NAME), dan dua judul yang menjadi nama yang sama juga ditolak (DATA_NAME_COLLISION). Pertanyaan kotak centang menjawab dengan daftar, yang dicetak dengan pemisah koma.

Tanggal yang dikirim sebagai teks tanpa zona waktu, seperti 2026-10-17 09:30, dibaca dalam zona waktu template, sehingga filter date dapat memformatnya ulang.

Gambar harus berupa alamat web publik (https://, atau http://), dikirim ke tempat template memiliki gambar placeholder. File yang diunggah melalui pertanyaan formulir adalah file pribadi di Drive pemilik formulir: AutomaDoc tidak dapat mengambilnya, dan jawabannya hanya berupa ID file. Jangan masukkan pertanyaan seperti itu ke template.

Permintaan per template 60 per menit
Dokumen per perusahaan 600 per menit, untuk semua otomatisasi bersama-sama
Pembacaan status per dokumen 60 per menit; unduhan file punya batas 60 sendiri
wait Paling lama 25 detik
Isi permintaan 1 MB
Karakter dalam satu nilai 100.000

Penolakan dikembalikan dengan code dan error, dan script di atas berhenti dengan keduanya tercatat di log Executions. RATE_LIMITED (429) berarti terlalu banyak panggilan dalam satu menit: formulir yang mungkin menerima banyak jawaban sekaligus sebaiknya menangkapnya dan mencoba lagi sebentar kemudian. Referensi API mencantumkan setiap kode.

Google menghentikan satu kali run script setelah 6 menit, itulah sebabnya script berhenti membaca status setelah 4 menit. Dokumen yang masih dibuat saat itu tidak hilang: statusUrl-nya di pesan error tetap menjawab.

Dokumen yang dibuat dengan cara ini adalah dokumen otomatis. Dokumen ini dihitung lebih dulu dari kuota otomatisasi seat Business Anda, lalu dari kuota bulanan gratis, lalu dari paket dokumen Anda; jika ketiganya habis, API menjawab QUOTA_EXCEEDED. Di paket Gratis, dokumennya memuat footer “Generated with AutomaDoc”, kecuali perusahaan Anda memiliki paket dokumen yang dibeli.