Problem
Hello,
first thanks for amazing work!
I analyzed with claude codebase and there is what it found about GTM. My question is - wil be somehow GTM available ? Thanks
Analysis: SEO, meta tags, GTM, and cookies on published pages
Short discussion note. Question: how far can an author / plugin reach, and where do we have to touch the core?
Context: the published output is intentionally clean static HTML with a strict CSP.
1. What we do natively (no plugin)
The publisher emits into <head> (src/core/publisher/render.ts):
<title> (from page title / seoTitle)
<meta name="description"> (settings.metaDescription)
<link rel="icon">, lang, charset, viewport
- postType rows have
seoTitle / seoDescription fields
Plus the architecture itself = static, fast, semantic HTML with no framework runtime → the best possible baseline for technical SEO.
Missing natively: og:*, Twitter cards, <link rel="canonical">, robots meta.
2. What a plugin can handle on its own
- OG / canonical / robots meta →
frontend.assets[] with kind: "meta", placement: "head".
- Custom client-side JS (calculator, tabs,
dataLayer, self-hosted consent banner) → runtime script / script-inline (runs as 'self').
- Calls to own / allowlisted APIs →
networkAllowedHosts → connect-src.
3. Key constraint: CSP script-src is locked to 'self'
The CSP relaxation available to plugins is hardcoded and limited (server/publish/frontendInjections.ts, relaxCspForPlan):
| Directive |
What a plugin can add |
script-src |
only 'self' (+ 'unsafe-inline' for inline) — no external host |
connect-src |
'self' + networkAllowedHosts (for fetch(), not for scripts) |
style-src |
'self' 'unsafe-inline' |
img-src |
'self' data: https: |
There is no manifest field and no code path by which a plugin can add an external host (e.g. googletagmanager.com) to script-src. A remote <script src=…> declared by a plugin is additionally rejected at the manifest level (assets must be package-relative).
4. Consequence for GTM and third-party tracking
| Thing |
Plugin alone enough? |
| OG / meta / canonical |
✅ |
Custom JS / self-hosted consent / dataLayer |
✅ |
| First-party / server-side measurement (beacon to own endpoint) |
✅ |
Classic GTM (gtm.js from Google + injects vendor pixels) |
❌ |
| Remote third-party pixels (FB, GA via remote script) |
❌ |
→ A plugin alone is not enough for purely client-side GTM. It needs an external script-src host, which no plugin can add.
5. Options for enabling GTM
- Core change — add Google hosts to the base CSP (
src/core/publisher/cspPlan.ts). Works, but it is a change to the CMS core and a deliberate weakening of the security model for the whole site.
- Server-side / same-origin tagging — proxy GTM through your own domain / measure via a first-party endpoint. Stays plugin-only, but if the proxied GTM itself injects remote
<script> tags from third parties, we hit script-src again → the cleanly viable variant is mainly server-side measurement.
6. Open questions for discussion
- Do we want to support an author-facing "GTM ID / custom head scripts" field at all? It runs against the CSP philosophy.
- If yes: add a controlled
script-src host declaration to the manifest (permission-gated, visible in the install/consent UI) instead of a core hack?
- Or hold the line and officially recommend server-side tagging as the only supported path?
- Cookie consent (CMP): leave it to the author/plugin, or add a lightweight first-party consent primitive?
Sources: src/core/publisher/render.ts (head), server/publish/frontendInjections.ts (relaxCspForPlan), src/core/plugins/manifest.ts (networkAllowedHosts, frontend.assets), src/core/publisher/cspPlan.ts (base CSP).
Proposed solution
Allow GTM scripts ?
Alternatives considered
No response
Area
Plugins
Problem
Hello,
first thanks for amazing work!
I analyzed with claude codebase and there is what it found about GTM. My question is - wil be somehow GTM available ? Thanks
Analysis: SEO, meta tags, GTM, and cookies on published pages
1. What we do natively (no plugin)
The publisher emits into
<head>(src/core/publisher/render.ts):<title>(from page title /seoTitle)<meta name="description">(settings.metaDescription)<link rel="icon">,lang,charset,viewportseoTitle/seoDescriptionfieldsPlus the architecture itself = static, fast, semantic HTML with no framework runtime → the best possible baseline for technical SEO.
Missing natively:
og:*, Twitter cards,<link rel="canonical">,robotsmeta.2. What a plugin can handle on its own
frontend.assets[]withkind: "meta",placement: "head".dataLayer, self-hosted consent banner) → runtime script /script-inline(runs as'self').networkAllowedHosts→connect-src.3. Key constraint: CSP
script-srcis locked to'self'The CSP relaxation available to plugins is hardcoded and limited (
server/publish/frontendInjections.ts,relaxCspForPlan):script-src'self'(+'unsafe-inline'for inline) — no external hostconnect-src'self'+networkAllowedHosts(forfetch(), not for scripts)style-src'self' 'unsafe-inline'img-src'self' data: https:There is no manifest field and no code path by which a plugin can add an external host (e.g.
googletagmanager.com) toscript-src. A remote<script src=…>declared by a plugin is additionally rejected at the manifest level (assets must be package-relative).4. Consequence for GTM and third-party tracking
dataLayergtm.jsfrom Google + injects vendor pixels)→ A plugin alone is not enough for purely client-side GTM. It needs an external
script-srchost, which no plugin can add.5. Options for enabling GTM
src/core/publisher/cspPlan.ts). Works, but it is a change to the CMS core and a deliberate weakening of the security model for the whole site.<script>tags from third parties, we hitscript-srcagain → the cleanly viable variant is mainly server-side measurement.6. Open questions for discussion
script-srchost declaration to the manifest (permission-gated, visible in the install/consent UI) instead of a core hack?Sources:
src/core/publisher/render.ts(head),server/publish/frontendInjections.ts(relaxCspForPlan),src/core/plugins/manifest.ts(networkAllowedHosts,frontend.assets),src/core/publisher/cspPlan.ts(base CSP).Proposed solution
Allow GTM scripts ?
Alternatives considered
No response
Area
Plugins