Custom HTML tags are code deployment
The tag manager was sold to the business as a way to avoid waiting for developers. That is exactly what it does.
A Custom HTML tag is unreviewed JavaScript running on every page of a production site, deployed by whoever holds publish rights on the container. Nothing about the marketing framing changes that. Google provides two mechanisms that fix it: custom templates, which run sandboxed with declared permissions, and a page-level policy API that lets the site itself refuse what a container asks to do. Most organisations use neither.
State the situation without the euphemism
A Custom HTML tag is a text field that accepts arbitrary JavaScript and runs it in the context of your site, on the pages a trigger matches, for every visitor. It has the same capabilities as any other script on the page: reading the document, reading cookies, reading form fields, sending anything anywhere.
The person who can do that is anyone with publish rights on the container, which in most organisations is a longer list than the list of people with commit rights on the website. That asymmetry is the whole problem in one sentence.
Nothing here is a criticism of the tool. The capability is intentional, and there are jobs it is the right answer for. The criticism is of treating the deployment path as a marketing task because the interface looks like a marketing tool.
The test I use in a review is simple. If this JavaScript arrived as a pull request against the website, would it be merged without a second reader? If not, it should not reach production through the container either.
The two code paths, side by side
| A change to the website | A Custom HTML tag | |
|---|---|---|
| Second reader | Enforced by the repository | None by default |
| Automated tests | Run before merge | None |
| Rollback unit | One commit | The whole container version |
| Content security policy | Applies and is tested | Often needs it relaxed |
| Audit trail | Commit, author, message | Version name, if somebody wrote one |
| Who can ship | The engineering team | Anyone with publish rights |
Every row in that table is a control that exists on one side and not the other, for the same production surface, carrying the same risk. Written out like that, the situation is hard to defend, which is why it is rarely written out.
The rollback row is the practical one during an incident. Reverting a Custom HTML tag means publishing an older container version, and that older version also reverts everything else that shipped with it, which is one reason small container versions published often matter more than they sound.
The content security policy row is the one that turns into an argument between teams. A container full of inline script pushes an organisation toward relaxing directives it spent effort putting in place, and that argument is easier to have before the tags exist. The mechanics are in running GTM behind a content security policy.
Custom templates are the supported answer
Google's own position is not subtle. A custom template packages the same functionality with constraints, and the constraints are the point.
What the sandbox blocks
Template code runs in sandboxed JavaScript, which Google describes as a simplified subset based on ECMAScript 5.1 that is not executed in the standard global execution environment, so the window object and its properties are not available. Reaching outside the sandbox happens through a require function, and every capability it grants is a declared permission.
Permissions are the reviewable artefact
That permission list is the reviewable artefact. Google documents the full set, including access to global variables, local storage, cookies by name and the referrer URL, and notes that permissions are automatically detected from the sandboxed code as edits are made and validated again when the code is compiled.
So a reviewer does not have to read the code to know what it can do. They read a list. That is the difference between a review that happens in five minutes and one that gets skipped, and skipped reviews are the actual failure mode in every organisation I have seen this in.
The page can refuse, and almost nobody uses it
There is a second mechanism that engineering teams tend to like immediately once they hear it exists, because it puts the control on their side of the boundary.
The site can install policies that the container has to satisfy. Google documents that policies are implemented on the web page, and when a container runs on the page they are applied to Tag Manager's custom template definitions to control how certain features can be used, through a gtag policy call.
How a policy is registered
The mechanics are workable. A policy is registered against a permission identifier such as inject_script or send_pixel, or against the keyword all to intercept every check, and the policy function receives the container id, the permission being requested and the relevant data. A policy rejects a request by returning false or throwing, and thrown strings or errors appear in the errors section of the debug pane in preview mode.
An allowlist enforced by the site
One documented example allows a script injection only when the URL exactly matches an approved host and throws otherwise. That is an allowlist for third-party scripts, enforced by the site, over the head of whoever is editing the container. If you have ever wanted a hard boundary rather than a policy document, that is it.
The two things Google tells you not to do
Both are in the data layer documentation and both are worth quoting in an internal standard, because they are the cases where a Custom HTML tag does not merely carry risk, it produces incorrect behaviour.
Do not hand-roll Google tags
The first: do not use Custom HTML tags to deploy code based on the Google tag, and use Tag Manager's native templates for Google Ads, Analytics and Floodlight instead. Hand-rolling a measurement snippet inside a container is how a site ends up with two implementations of the same tag disagreeing about what fired.
Do not set consent from a tag
The second: do not use Custom HTML tags to configure consent settings, because consent settings need to be applied before the relevant tags fire, so you must apply the Tag Manager consent APIs to configure default and update behaviour. The container-level detail is in wiring consent signals through a container.
Both are ordering problems
Both rules have the same underlying cause, which is message ordering, and both produce failures that look like intermittent bad luck rather than a defect. That is the worst kind of bug to inherit.
A review process sized for a small team
You do not need a change board. You need four rules that fit on one page and an owner who applies them.
In an organisation that already has a privacy function, this is not a process you get to design from scratch. The reviewer exists, the escalation is mandatory rather than advisory, and a third-party script on a page that collects personal information will be refused outright. That is the normal starting position for the professional services firms and associations in downtown Ottawa and Centretown, and writing the tracking plan for that audience first is considerably cheaper than retrofitting one after a refusal.
The four rules
One: any new Custom HTML tag requires a named reviewer who is not the author, and the reviewer's name goes in the version notes. Two: any tag that reads a form field, a cookie or a URL parameter is escalated, because those are the three sources that carry personal data and that is how it ends up somewhere nobody intended.
Three: any tag loading a third-party script names the vendor, the purpose and who approved the vendor, which is the same due diligence question asked of any other martech supplier. Four: anything that survives three months as a Custom HTML tag becomes a custom template or a site change, with a date attached.
Audit what is already there
Then audit what is already there. Inventory every Custom HTML tag, find the author, and delete the ones nobody will claim. A container's documentation and its behaviour are rarely the same thing, and establishing what actually fires is the first step of a GA4 and Tag Manager engagement for exactly this reason.