One Form. Five Automations. Zero Manual Work.
Every school website has the same problem: content sits in someone’s head instead of on the page. Teachers don’t publish because publishing is too hard — wrong folder, wrong filename format, missing image, forgotten date.
The answer isn’t a fancier editor. It’s removing the editor entirely and replacing it with something every teacher already knows: a Google Form.
Why a Form Beats a CMS
A typical “school CMS” introduces friction at every step: log in, find the right section, remember the format, upload the image separately, hit publish, pray it didn’t break. Teachers avoid it. Content dies in inboxes.
A Google Form removes all of that. Five fields. Thirty seconds. Done.
The best editorial interface is one that doesn’t look like an editorial interface.
It’s familiar by default. Every teacher has filled in a Google Form. No training, no documentation, no onboarding session.
It works from a phone. Teacher photographs the school concert, opens the form on their way to the car, types a few keywords — the post is live before they get home.
It enforces structure. No risk of missing a date, misformatting a title, or uploading to the wrong folder. The form fields are the contract between the teacher and the system.
It’s controlled and audited. Restrict to your domain. Every submission carries a timestamp and an author. Full history, zero overhead.
The Pipeline: What Happens After “Submit”
The teacher sees a form and then sees an effect on the school website. Between those two moments, Apps Script runs a five-step pipeline — invisible, automatic, and fault-tolerant.
Google Form
↓ trigger: onFormSubmit
Apps Script
↓
┌───────────────────┬────────────────────┐
│ │ │
Gemini API Google Drive Google Calendar
(expands notes) (creates Doc) (creates event)
│ │
└───────────────────┘
↓
Web App CMS
↓
Google Sites
The teacher does one thing. The system does five.
The Form: Five Fields, No Instructions Needed
The form has two categories of fields. Content fields flow into the published document and the website. Operational fields control how Apps Script routes the submission — they never appear in the final article.
Content fields:
- Title — short answer. Example: “Family Christmas Concert.”
- Date — date picker. If the news isn’t tied to a specific event, the teacher enters the publication date.
- Type — dropdown: CONCERT, INFO, PROJECT, SPORT, RECRUITMENT. Becomes a badge on the website.
- Keywords / description — paragraph. The teacher writes the essence: “Grades 4–6 performed carols. Parents and guests attended. Venue: Youth Palace.” They don’t need to write polished prose — Gemini will expand it.
- Photos — file upload, images only, multiple files allowed.
Operational fields (add based on your school’s needs): target classes, priority, publication channels.
Step-by-Step: What the Script Does
Step 1 — Read the form response
Apps Script catches the onFormSubmit event and reads each field in order using responses[N].getResponse(). The date is formatted to YYYY-MM-DD — the format the Web App CMS expects in the filename.
Step 2 — Gemini expands the keywords
The teacher’s bullet points are sent to Gemini 2.5 Flash with a strict prompt: write 4–5 sentences, neutral informational tone, no invented facts. The original keywords are preserved at the bottom of the document as metadata — so you always see what the human wrote versus what the AI expanded.
If Gemini is unavailable (rate limit, outage), the original description becomes the article text. The pipeline never blocks on the AI step.
Step 3 — Create a structured Google Doc
A new document is created in the CMS folder with the filename YYYY-MM-DD | Title. Apps Script inserts: H1 heading, type badge (bold, coloured), Gemini text, scaled photos (max 5, max 500px wide), and the original keywords as 9pt grey metadata at the bottom. The file is shared as “anyone with link can view.”
Step 4 — Add to the school calendar
An all-day calendar event is created with the event type tag and a link to the document. Administrators, teachers, and parents can subscribe to the calendar and see every published news item automatically.
Step 5 — Invalidate the CMS cache
The script cache key used by the Web App is cleared. The next page load fetches a fresh list of documents — the new post appears without any manual refresh or republish action.
The Complete Script
Attach this to your Google Form via Script Editor (⋮ → Script editor). Store your Gemini API key in Project Settings → Script Properties as GEMINI_API_KEY.
// ===== CONFIG =====
const CONFIG = {
FOLDER_ID: "PASTE_YOUR_NEWS_FOLDER_ID",
CALENDAR_ID: "PASTE_YOUR_CALENDAR_ID",
FONT_FAMILY: "Lato",
MAX_IMAGES: 5,
MAX_IMG_WIDTH: 500,
CACHE_KEY: "NEWS_HTML"
};
// ===== TRIGGER =====
function onFormSubmit(e) {
const responses = e.response.getItemResponses();
const title = responses[0].getResponse();
const dateStr = responses[1].getResponse();
const type = responses[2].getResponse();
const keywords = responses[3].getResponse();
const fileIds = responses[4] ? responses[4].getResponse() : null;
const date = new Date(dateStr);
const isoDate = Utilities.formatDate(date, "Europe/Warsaw", "yyyy-MM-dd");
let newsText = generateNewsText(title, type, keywords);
if (!newsText) newsText = keywords;
const docUrl = createNewsDoc(title, isoDate, type, newsText, keywords, fileIds);
createCalendarEvent(title, date, type, docUrl);
clearCmsCache();
}
// ===== GEMINI =====
function generateNewsText(title, type, keywords) {
try {
const apiKey = PropertiesService.getScriptProperties()
.getProperty("GEMINI_API_KEY");
if (!apiKey) return "";
const prompt =
"You are a school website editor. Based on the teacher's notes, " +
"write a polished news post. 4-5 sentences, neutral informational " +
"tone, understandable for parents. Do not invent facts.\n\n" +
"Title: " + title + "\nEvent type: " + type +
"\nTeacher's notes: " + keywords;
const url =
"https://generativelanguage.googleapis.com" +
"/v1beta/models/gemini-2.5-flash:generateContent?key=" + apiKey;
const payload = {
contents: [{ parts: [{ text: prompt }] }],
generationConfig: {
maxOutputTokens: 1024,
thinkingConfig: { thinkingBudget: 0 }
}
};
const res = UrlFetchApp.fetch(url, {
method: "post",
contentType: "application/json",
payload: JSON.stringify(payload),
muteHttpExceptions: true
});
const json = JSON.parse(res.getContentText());
if (json.candidates?.[0]) {
return json.candidates[0].content.parts[0].text.trim();
}
} catch (err) {
Logger.log("Gemini error: " + err);
}
return "";
}
// ===== DOCUMENT =====
function createNewsDoc(title, isoDate, type, newsText, keywords, fileIds) {
const folder = DriveApp.getFolderById(CONFIG.FOLDER_ID);
const fileName = isoDate + " | " + title;
const doc = DocumentApp.create(fileName);
const body = doc.getBody();
const h1 = body.getParagraphs()[0];
h1.setHeading(DocumentApp.ParagraphHeading.HEADING1);
h1.setText(title);
h1.setFontFamily(CONFIG.FONT_FAMILY);
if (type) {
const badge = body.appendParagraph(type.toUpperCase());
badge.setBold(true).setForegroundColor("#1a73e8").setFontFamily(CONFIG.FONT_FAMILY);
}
body.appendParagraph("");
body.appendParagraph(newsText).setFontFamily(CONFIG.FONT_FAMILY);
if (fileIds?.length > 0) {
body.appendParagraph("");
const limit = Math.min(fileIds.length, CONFIG.MAX_IMAGES);
for (let i = 0; i < limit; i++) {
try {
const blob = DriveApp.getFileById(fileIds[i]).getBlob();
const image = body.appendImage(blob);
const w = image.getWidth();
if (w > CONFIG.MAX_IMG_WIDTH) {
const ratio = CONFIG.MAX_IMG_WIDTH / w;
image.setWidth(CONFIG.MAX_IMG_WIDTH);
image.setHeight(Math.round(image.getHeight() * ratio));
}
body.appendParagraph("");
} catch (err) { Logger.log(err); }
}
if (fileIds.length > CONFIG.MAX_IMAGES) {
const info = body.appendParagraph(
"Remaining photos (" + (fileIds.length - CONFIG.MAX_IMAGES) + ") available in Drive folder."
);
info.setFontSize(9).setForegroundColor("#999999").setItalic(true);
}
}
const kw = body.appendParagraph("Keywords: " + keywords);
kw.setFontSize(9).setForegroundColor("#999999").setItalic(true).setFontFamily(CONFIG.FONT_FAMILY);
const file = DriveApp.getFileById(doc.getId());
folder.addFile(file);
DriveApp.getRootFolder().removeFile(file);
file.setSharing(DriveApp.Access.ANYONE_WITH_LINK, DriveApp.Permission.VIEW);
return doc.getUrl();
}
// ===== CALENDAR =====
function createCalendarEvent(title, date, type, docUrl) {
try {
const cal = CalendarApp.getCalendarById(CONFIG.CALENDAR_ID);
if (!cal) { Logger.log("Calendar not found"); return; }
const event = cal.createAllDayEvent(
(type ? "[" + type + "] " : "") + title, date
);
event.setDescription("School news\n\nDocument:\n" + docUrl);
} catch (err) {
Logger.log("Calendar error: " + err);
}
}
// ===== CACHE =====
function clearCmsCache() {
try {
CacheService.getScriptCache().remove(CONFIG.CACHE_KEY);
} catch (err) {
Logger.log("Cache clear skipped: " + err);
}
}
Deployment Checklist
Google Form setup:
- Create a new Google Form restricted to your school domain
- Add field: Title (short answer, required)
- Add field: Date (date picker, required)
- Add field: Type (dropdown: CONCERT, INFO, PROJECT, SPORT, RECRUITMENT)
- Add field: Keywords / description (paragraph, required)
- Add field: Photos (file upload, images only, allow multiple)
Apps Script setup:
- Open Script Editor (⋮ → Script editor) and paste the full script
- Update
CONFIG.FOLDER_IDwith your CMS folder ID (from the Drive URL) - Update
CONFIG.CALENDAR_IDwith your school calendar ID - Add
GEMINI_API_KEYin Project Settings → Script Properties
Triggers and testing:
- Set trigger: clock icon → Add trigger →
onFormSubmit→ On form submit - Submit a test response and verify: Doc appears in folder, calendar event created, page updates
The Interactive Version
The widget below walks through the full pipeline step by step and lets you explore the code in context.
The Same Pattern, Different Problems
This architecture — form → Apps Script → structured output — is reusable across dozens of school workflows.
Field trip registration. Teacher submits event name, date, class, headcount. Script creates a registration document from a template, adds to the trips calendar, emails the teacher, and notifies the head.
Evaluation reports. After a survey closes, Apps Script tallies responses, sends them to Gemini for a summary, generates a PDF, and deposits it in the leadership Drive folder. What used to take an hour happens in seconds.
Weekly parent newsletter. A time-based trigger collects the last seven days of news documents, asks Gemini to write a digest, and sends a BCC email to all parent groups. Zero editorial effort for the same quality output.
Substitute cover requests. Teacher submits date, lesson, class, and notes. Script creates a cover sheet, emails the admin, and adds an absence to the shared staff calendar.
The pattern is always the same: the form collects structured input, Apps Script routes and transforms it, the output lands exactly where it needs to be. The teacher does one thing. The institution remembers everything.
A note on AI and data
The prompt sent to Gemini contains a title and keywords — never student names, ID numbers, or personal data. Keep it that way. AI helps with writing; it doesn’t process personal information. If teachers follow the simple rule — describe what happened, not who was involved — the system is GDPR-compliant by design.