How to Generate a PDF Certificate from a Google Form Response (2026)
Workshop sign-ups, course completions and event check-ins often live in a Google Form. The responses already land as new rows in a linked Google Sheet. From there you can fill a certificate template and email a PDF to each person, automatically, when they submit.
This is different from generating certificates in a batch from a roster you already have (see our invoices and certificates guide). Here the trigger is a new form response.
Step 1: link the Form to a Sheet
- In Google Forms, add questions you want on the certificate, at least Full name and Email. Keep those titles short and stable; they become column headers.
- Optional: add Course, Completion date, or a quiz. If you use a Forms quiz, the linked sheet will include a Score column.
- Open the Responses tab and click the green Sheets icon to create (or choose) a destination spreadsheet. Each submission becomes a new row, with a Timestamp in column A.
Step 2: design the certificate in Google Docs
Create a Google Doc that looks like your certificate: logo, title, a line for the name, course and date. Where a value should go, type the column header in double braces, matching the Form question text exactly, for example {{Full name}}, {{Email}}, {{Course}}. The match is case-sensitive.
Copy the template's ID from its URL (the long string between /d/ and /edit). Create a Drive folder for finished PDFs and copy its ID from the folder URL (after /folders/).
Google Slides is a popular alternative when you want a landscape, highly designed certificate. The script below uses Docs so the template stays simple and editable; the same replace-and-export idea applies to Slides if you prefer that route.
Step 3: a script that runs on form submit
In the linked spreadsheet, open Extensions › Apps Script, paste the script, fill in the two IDs, and save. Then, importantly, add an installable trigger: the clock icon › Add trigger › sendCertificateForResponse › event source From spreadsheet › event type On form submit. A function that is merely named onFormSubmit in the file, without that installable trigger, will not get the authorization it needs to create files and send email.
/**
* On each Google Form submission, fill a Docs certificate template and
* email the PDF to the respondent. Write the PDF link back to the row.
*
* Set TEMPLATE_ID and FOLDER_ID, then add an *installable* trigger:
* Triggers > Add trigger > sendCertificateForResponse > From spreadsheet > On form submit.
* (A simple onFormSubmit function in the editor is not enough.)
*
* Column headers in row 1 must match the {{placeholders}} in the Doc,
* including the Form question text (for example "Full name", "Email").
* Adds "Certificate PDF" and "Certificate sent" columns if missing.
*/
const TEMPLATE_ID = 'PASTE_TEMPLATE_DOC_ID';
const FOLDER_ID = 'PASTE_OUTPUT_FOLDER_ID';
function sendCertificateForResponse(e) {
const sh = e.range.getSheet();
const row = e.range.getRow();
const lastCol = Math.max(sh.getLastColumn(), 1);
const headers = sh.getRange(1, 1, 1, lastCol).getValues()[0].map(String);
const values = sh.getRange(row, 1, 1, lastCol).getDisplayValues()[0];
const rec = {};
headers.forEach((h, i) => { if (h) rec[h] = values[i] || ''; });
const email = rec['Email'] || rec['Email address'] || rec['email'];
const name = rec['Full name'] || rec['Name'] || rec['Full Name'] || 'Participant';
if (!email) throw new Error('No Email column value on row ' + row);
const template = DriveApp.getFileById(TEMPLATE_ID);
const folder = DriveApp.getFolderById(FOLDER_ID);
const copy = template.makeCopy('Certificate — ' + name, folder);
const doc = DocumentApp.openById(copy.getId());
const body = doc.getBody();
headers.forEach((h) => {
if (!h) return;
const escaped = h.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
body.replaceText('\\{\\{' + escaped + '\\}\\}', rec[h] || '');
});
doc.saveAndClose();
const pdf = copy.getAs(MimeType.PDF).setName('Certificate — ' + name + '.pdf');
const pdfFile = folder.createFile(pdf);
copy.setTrashed(true);
if (MailApp.getRemainingDailyQuota() < 1) {
throw new Error('Gmail daily quota exhausted before sending to ' + email);
}
MailApp.sendEmail(email, 'Your certificate: ' + name, 'Your certificate is attached.', {
attachments: [pdfFile.getBlob()],
});
const tz = Session.getScriptTimeZone();
ensureHeader_(sh, 'Certificate PDF');
ensureHeader_(sh, 'Certificate sent');
const pdfCol = headerIndex_(sh, 'Certificate PDF');
const sentCol = headerIndex_(sh, 'Certificate sent');
sh.getRange(row, pdfCol).setValue(pdfFile.getUrl());
sh.getRange(row, sentCol).setValue(Utilities.formatDate(new Date(), tz, 'yyyy-MM-dd HH:mm'));
}
function ensureHeader_(sh, name) {
const headers = sh.getRange(1, 1, 1, Math.max(sh.getLastColumn(), 1)).getValues()[0];
if (headers.indexOf(name) === -1) sh.getRange(1, headers.length + 1).setValue(name);
}
function headerIndex_(sh, name) {
const headers = sh.getRange(1, 1, 1, sh.getLastColumn()).getValues()[0];
return headers.indexOf(name) + 1;
}
Submit a test response with your own email. You should see a new row, receive a PDF, and find Certificate PDF and Certificate sent columns filled in. If nothing happens, confirm the installable trigger exists and that you authorized the script after adding it (run the function once from the editor with a dummy event, or submit again after approving the prompt).
Optional: only issue a certificate when a score is high enough
If the Form is a quiz and the sheet has a Score column, add a check near the top of the function, after you build rec, such as if (Number(rec['Score']) < 80) return;. Adjust the threshold and the header to match your sheet.
Caveats
- One submission, one run. This pattern is built for a single new row. That usually stays well under Apps Script's six-minute execution limit. Batching a backlog of old responses is closer to the one PDF per row script.
- Daily email limit. Each PDF is sent from your Gmail or Workspace account and counts against the Apps Script email quota.
- Header names. Renaming a Form question later changes the column header and will break placeholders until you update the Doc.
- Ownership. The trigger runs as the person who installed it. If they leave, certificates stop until someone else adds the trigger and re-authorizes.
See SheetWeave
Related Google Sheets guides
- How to generate a PDF or Google Doc for each row in Google Sheets
- Best Autocrat alternatives for mail merge to PDF and Docs
- Make invoices or certificates from Google Sheets rows
- Create sales quotes or estimates from Google Sheets
- Generate receipts from Google Sheets rows
- Create purchase orders from Google Sheets
- Make packing slips from Google Sheets orders
- Generate a PDF certificate from a Google Form response
- Issue numbered training completion certificates with expiry dates
- Make a PDF progress report for each student
- Generate offer letters from a Google Sheets hiring tracker
- Email each row’s PDF as an attachment
- Automatic email reminders from a date column
- Contract renewal and expiration alerts from Google Sheets
- Track certificate of insurance (COI) expirations in Google Sheets
- Track employee license and certification expirations
- Track equipment and vehicle maintenance with reminders
- Track warranty expiration dates with reminders
- Birthday and work anniversary reminders from Google Sheets
- Rent due and lease renewal reminders from Google Sheets
- Send invoice payment reminders from Google Sheets
- Highlight overdue tasks and send email reminders
- Follow-up reminders for leads, clients and candidates
- Membership renewal reminders for clubs and associations
- SheetWeave vs Autocrat
- SheetWeave vs Document Studio
- SheetWeave vs RowDocs
Instructions refer to Google Sheets, Google Docs, Google Forms, Google Drive and Apps Script as they worked on November 10, 2026. Not affiliated with Google or any other vendor named here. Google Sheets, Google Docs, Google Forms, Google Slides and Google Drive are trademarks of Google LLC. Features change, so verify in your own account. Questions or corrections: hello@loomwise.app.