Issue certificates after quiz completion
Generate a certificate Doc when someone passes a Northwind training quiz.
Published Aug 10, 2025
Northwind runs an internal training quiz once a week. Passing it has always meant a PDF certificate, but generating that certificate has always meant someone opening the template, copy-pasting a name and a date, exporting to PDF, and emailing it. Forty hires later, that someone is exhausted and the certificates start arriving a week late.
This script catches the form-submit event, checks the score, and — for everyone who clears the pass mark — clones a Doc template, swaps the placeholders for their name and the date, exports it as a PDF, and emails it straight to them. The trainer never opens Docs again.
What you’ll need
- A Google Form configured as a quiz with auto-grading. Make sure the total
score question is titled exactly
Total scoreso the script can read it. - A Google Doc template with
{{name}}and{{date}}placeholders where you want them to appear. Save its file ID inTEMPLATE. - The pass mark. The example uses 7 — change
PASS_SCOREto suit. - A form-submit trigger pointing at
onFormSubmit.
The script
// Doc template with {{name}} and {{date}} placeholders.
const TEMPLATE = '1abcCertTemplateId';
// Minimum total score for a pass. Anything below is silently skipped.
const PASS_SCORE = 7;
// Date format that prints on the certificate. Long-form for human reading.
const CERT_DATE_FORMAT = 'd MMM yyyy';
// Subject + body of the email that delivers the PDF.
const EMAIL_SUBJECT = 'Your Northwind certificate';
const EMAIL_BODY =
'Congratulations on passing the Northwind training quiz.\n\n' +
'Your certificate is attached.\n\n— Awadesh';
/**
* Form-submit handler. Issues a PDF certificate to anyone who passed.
*
* @param {GoogleAppsScript.Events.FormsOnFormSubmit} e
*/
function onFormSubmit(e) {
// 1. Read the auto-graded total score. Form values are always strings —
// parseInt is what turns "8" into a number we can compare.
const score = parseInt(e.namedValues['Total score']?.[0], 10);
if (!Number.isFinite(score) || score < PASS_SCORE) {
Logger.log(`Skipping — score ${score} below pass mark ${PASS_SCORE}.`);
return;
}
const name = e.namedValues.Name?.[0];
const email = e.namedValues.Email?.[0];
if (!name || !email) {
Logger.log('Skipping — missing name or email on submission.');
return;
}
// 2. Clone the template. makeCopy returns a Drive file we can open as a Doc.
const copy = DriveApp.getFileById(TEMPLATE).makeCopy(`Certificate — ${name}`);
const doc = DocumentApp.openById(copy.getId());
// 3. Replace placeholders in the document body. replaceText takes a regex
// string — the curly braces here are literal, not capture groups.
const today = Utilities.formatDate(new Date(), 'GMT', CERT_DATE_FORMAT);
doc.getBody().replaceText('{{name}}', name);
doc.getBody().replaceText('{{date}}', today);
doc.saveAndClose();
// 4. Export the saved Doc as PDF and email it. getAs forces a fresh render,
// so any text we just replaced is picked up correctly.
const pdf = DriveApp.getFileById(copy.getId()).getAs('application/pdf');
GmailApp.sendEmail(email, EMAIL_SUBJECT, EMAIL_BODY, { attachments: [pdf] });
Logger.log(`Issued certificate to ${name} <${email}> for score ${score}.`);
}
How it works
- The form-submit trigger fires
onFormSubmitwith an event whosenamedValuesholds each answer keyed by question title. - The script reads
Total score, coerces it withparseInt, and bails out if the value is missing, non-numeric, or belowPASS_SCORE. Quiz auto-grading writes the total as a string, so the coercion matters. - It pulls
NameandEmailnext and bails again if either is missing — a certificate with no recipient is wasted work. DriveApp.makeCopyclones the template Doc into the script owner’s root Drive. The copy is named after the hire so you can find it later if something needs reissuing.replaceTextswaps every{{name}}and{{date}}placeholder in the document body.saveAndCloseflushes the changes so the next read sees them.getAs('application/pdf')renders the freshly-saved Doc as a PDF blob, andGmailApp.sendEmailattaches it to a short congratulations message.
Example run
A hire submits the quiz with score 8 out of 10:
| Question | Answer |
|---|---|
| Name | Theo Reyes |
| [email protected] | |
| Total score | 8 |
Within a few seconds:
- A Doc named
Certificate — Theo Reyesappears in Drive withTheo Reyesand10 Aug 2025filled into the template. - Theo receives an email titled “Your Northwind certificate” with the rendered PDF attached.
Someone scoring 5 triggers a log line — Skipping — score 5 below pass mark 7. — and no Doc, no email.
Trigger it
PDF rendering and Gmail send both need authorisation, so this requires an installable on-submit trigger.
- In the Apps Script editor, open Triggers (clock icon, left sidebar).
- Click Add trigger and pick
onFormSubmit. - Event source: From form. Event type: On form submit.
- Save and approve the authorisation prompts.
Watch out for
- Quiz total titles vary. If you used the default “Total points” or renamed
the question, change the
e.namedValues['Total score']key to match. Forms uses the visible title as the key. - Certificate Docs pile up. Every pass leaves a Doc in your Drive root. Move the copy into an issued-certificates folder after generation, or trash it once the email goes out — the PDF blob lives independently of the source.
- Placeholders are case-sensitive.
{{Name}}and{{name}}are different patterns toreplaceText. Pick one convention and keep template and script aligned. - PDF rendering is best-effort on complex layouts. If your template has embedded fonts or rotated text, render a sample by hand once before trusting the trigger — Google’s PDF export occasionally drops glyphs you did not expect.
Related
Trigger an onboarding sequence on form submit
Kick off tasks when a new Northwind hire submits their starter form.
Updated Oct 17, 2025
Build a content-submission queue
Collect Northwind guest posts or ideas for review through a Form.
Updated Oct 9, 2025
Score sentiment in open-text feedback
Rate Northwind feedback comments without manual review — using the in-Sheet sentiment function.
Updated Oct 5, 2025
Build a peer-nomination and voting system
Collect and tally Northwind nominations for awards or initiatives — one ballot, anonymous.
Updated Oct 1, 2025
Roll a form over each cycle
Archive old responses and reset for the next Northwind cycle — quarterly OKR check-ins.
Updated Sep 27, 2025