Frontend
Automating Google Sites: What Worked, What Failed, and What Cost a Rebuild
WesLin Dev.to (EN Zone)
2 views
We needed to publish a 2,921-word article into new Google Sites from a script as part of shipping a model comparison. There isn't an API for the current version of the editor, so we had to test every programmatic input path to find what actually persists.
Most injection methods failed immediately, and one failed silently after reporting success. Here's every route we tried, the exact way each one broke, and the final model comparison on Google Sites that came out the other end.
There is no API, and that is official
If you look for a REST endpoint or an official client library, you won't find one. As Google's own deprecation notice explains:
"Sites API is deprecated and might stop working at any time. Sites API can only access classic Sites. Sites API can't access the rebuilt version of Sites that was launched on November 22, 2016."
Both the changelog and the developer guide carry that exact banner. That means classic Sites had an API, but the rebuilt version from 2016 never got one. That leaves you with the live editor in a browser tab, and whatever you can make that browser do through automation.
What we tried, and what came back
We tested seven different routes against the live editor on 2026-09-09 and 2026-09-10.
Route
What came back
document.execCommand('insertHTML', …)
"This document requires 'TrustedHTML' assignment."
new DOMParser().parseFromString(html, 'text/html')
The same Trusted Types error.
Build the DOM under trustedTypes.createPolicy(...), then Range.insertNode
Renders correctly. Autosave says saved to Drive. Gone on reload.
navigator.clipboard.write([new ClipboardItem(...)]) from an eval
Never settles. No transient activation.
Synthetic cmd+v through CDP or an extension
Nothing happens.
osascript System Events keystroke "v" using command down
"osascript is not allowed to send keystrokes."
Typing, character by character, through the extension
Works, and persists.
The third row is the one that's genuinely dangerous. When you build DOM nodes under a trusted types policy and insert them with Range.insertNode, the page renders your content cleanly.
The editor shows 18,482 characters, 11 h2 elements, 10 h3 elements, and 11 links. The autosave pill at the top turns into a checkmark and reads "all changes saved to Drive."
Then you reload the tab. The canvas clears, and you're staring at 0 characters.
Nothing in that sequence tells you it failed. The reason it disappears is that Google Sites doesn't treat the DOM as its source of truth. It keeps an internal document model in JavaScript.
When you mutate the DOM directly, that mutation doesn't update the internal model. On the next reload, the editor renders the state of its internal model, which is completely empty.
The one route that works
The only approach that updates the document model and survives a reload is sending synthetic keystrokes through a browser automation extension. It simulates a user typing characters one by one. That works, but three specific behaviors will bite you along the way.
First, synthetic typing silently drops non-ASCII characters. We had an em dash inside the string "TL;DR —", and it showed up in the editor canvas as two plain spaces.
The console threw no errors, and the extension reported a clean run. You've got to normalise every string to ASCII before you start typing.
Second, typing "- " at the start of any line triggers a retroactive list format. The editor detects the markdown shortcut and turns the entire text box into a bulleted list, including every paragraph you typed before that line.
You can't just backspace out of it. The fix is to run a select-all command and toggle the list button twice to strip the formatting.
Third, adding hyperlinks needs careful handling. You apply a link by selecting the anchor text in the live document and pressing cmd+k. That opens a link dialog with two traps.
If you press Return inside the URL input, the editor doesn't submit the form; it types the URL directly into the document and replaces your selected anchor with a bare URL. You've got to click the input field and click the apply button with synthetic click events.
On top of that, the text selector matches the first occurrence of an anchor phrase. If that phrase appears twice on the page, the script links the wrong sentence while the document looks completely normal.
Headings worked through keyboard shortcuts, but they needed explicit pacing. Pressing cmd+alt+2 and cmd+alt+3 applied real <h2> and <h3> tags to the selected line.
But the editor needed about three seconds between events. When we fired heading shortcuts back to back in a single batch, only the first one took.
A code block
Here's the terminal log showing what each programmatic input returned during our tests.
$ node -e "…" # no API to call
> execCommand('insertHTML', …) Error: This document requires 'TrustedHTML' assignment.
> new DOMParser().parseFromString Error: This document requires 'TrustedHTML' assignment.
> trustedTypes.createPolicy(...) ok
... build nodes, Range.insertNode
editor shows 18,482 chars, 11 h2, 10 h3, 11 links
autosave: "all changes saved to Drive"
reload 0 chars
> navigator.clipboard.write(...) (never settles)
> key: cmd+v (no change)
> osascript keystroke "v" Error: osascript is not allowed to send keystrokes.
> type("Disclosure: we own ...") persists
Two things about the output that cost us a rebuild
Two structural quirks forced us to tear down and rebuild our published layout.
The first quirk is how Google Sites generates <title> tags. On a subpage, the <title> tag matches the page name you assign. On the home page, the <title> tag is always the site name, no matter what you name the root page.
We published our article at the site root first, and Google Sites threw the article's title tag away. The fix was opening the page menu (the ⋮ icon) and clicking "Duplicate page."
That moved the content to a subpage and let us set the subpage name and custom path in a single dialog, so we didn't have to retype anything.
The second quirk is how the editor handles images. A text box holds text only. Images live in separate layout blocks that you position by dragging, which means an image can't sit inside the paragraph it illustrates.
Even worse, uploading an image through the editor's file input crashed the client interface with a runtime error. That crash happened on a 1.7 MB PNG and an 89 KB JPEG, so it wasn't a file size limit. That part of the pipeline still needs a human operator.
The checklist
If you're asked to automate publishing into a closed WYSIWYG editor you don't control, run these checks before writing your script:
Check whether the editor uses an internal document model instead of the live DOM.
Reload the page right after synthetic DOM mutations to confirm that autosave actually wrote data to the backend.
Test whether synthetic keystrokes drop non-ASCII characters like em dashes without logging warnings.
Check whether markdown triggers like leading dashes reformat earlier text blocks retroactively.
Test whether modal dialogs accept keyboard Enter or need explicit mouse clicks on action buttons.
Test heading shortcut pacing to make sure rapid shortcut events don't get dropped.
Test file inputs directly to see if scripted file uploads crash the client app.
Disclosure: we build Seadanse, an AI video tool, and the page we were publishing is a model comparison on a site of ours.
Read original: https://dev.to/codesugar_lin_037a57b06a4/automating-google-sites-what-worked-what-failed-and-what-cost-a-rebuild-39ji
← Previous
Can n8n Replace Your Backend for AI Workflows?
Next →
Building a Polymarket TWAP State Machine for Trading Bots
Related
I Built a Passport Photo Maker Because a Visa Photo Took Me Two Hours
Frontend
0
DEV Community
10 AI Website Builders I Tested So You Can Skip the Trial and Error
Frontend
0
DEV Community
What is the component library that you would recommend for a backend engineer?
Frontend
0
Reddit r/webdev
Advanced JSON Path Operations in WebForms Core 2.1
Frontend
2
DEV Community
Comments0
No comments yet — be the first