Google Maps JavaScript API is one of those integrations that looks simple until CSP gets involved. Then you add one <script> tag, refresh, and your console turns into a wall of violations.
The hard part is that Google Maps does not just load one script. It pulls in additional scripts, images, styles, XHR/fetch requests, and sometimes fonts from multiple Google domains. If your policy is strict — and it should be — you need to allow the right sources without opening the door too wide.
Here’s the practical reference I wish I had the first few times I locked this down.
The short answer
For Google Maps JavaScript API, you will usually need to account for:
script-srcimg-srcstyle-srcconnect-src- sometimes
font-src
Common Google Maps domains that show up:
https://maps.googleapis.comhttps://*.googleapis.comhttps://maps.gstatic.comhttps://*.gstatic.com
If you want a copy-paste starting point, use this.
Content-Security-Policy:
default-src 'self';
script-src 'self' https://maps.googleapis.com https://maps.gstatic.com;
style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;
img-src 'self' data: https://maps.gstatic.com https://maps.googleapis.com;
connect-src 'self' https://maps.googleapis.com https://*.googleapis.com https://maps.gstatic.com;
font-src 'self' https://fonts.gstatic.com;
object-src 'none';
base-uri 'self';
frame-ancestors 'none';
That is a decent baseline, but I would not call it ideal. If you already run a nonce-based strict CSP, you should keep that model and add Maps support carefully.
A realistic CSP with Google Maps added
You gave a real CSP header from headertest.com. Here it is, adapted to support Google Maps JavaScript API while preserving the existing structure.
Content-Security-Policy:
default-src 'self' https://www.googletagmanager.com https://*.cookiebot.com https://*.google-analytics.com;
script-src 'self' 'nonce-OWFjZjE1MWItZWY3Mi00YzU5LThjZjktMmU4NTgxZTUzMzRl' 'strict-dynamic' https://www.googletagmanager.com https://*.cookiebot.com https://*.google-analytics.com https://maps.googleapis.com https://maps.gstatic.com;
style-src 'self' 'unsafe-inline' https://www.googletagmanager.com https://*.cookiebot.com https://consent.cookiebot.com https://maps.gstatic.com https://fonts.googleapis.com;
img-src 'self' data: https: https://maps.gstatic.com https://maps.googleapis.com;
font-src 'self' https://fonts.gstatic.com;
connect-src 'self' https://api.headertest.com https://tallycdn.com https://or.headertest.com wss://or.headertest.com https://*.google-analytics.com https://*.googletagmanager.com https://*.cookiebot.com https://maps.googleapis.com https://*.googleapis.com https://maps.gstatic.com;
frame-src 'self' https://consentcdn.cookiebot.com;
frame-ancestors 'none';
base-uri 'self';
form-action 'self';
object-src 'none';
A couple of opinions here:
- I’d keep
object-src 'none',base-uri 'self', andframe-ancestors 'none'exactly as they are. - I would only keep
'unsafe-inline'instyle-srcif I had verified it was actually needed. Maps often pushes people toward it, but don’t assume. - If your app already uses nonces and
'strict-dynamic', don’t throw that away just because Maps is annoying.
If you want deeper CSP directive definitions, https://csp-guide.com is a good reference.
Basic script include
Most teams start with the standard Google Maps loader.
<script
async
src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&callback=initMap">
</script>
And then:
<script nonce="{{ .CSPNonce }}">
function initMap() {
const map = new google.maps.Map(document.getElementById("map"), {
center: { lat: 40.7128, lng: -74.0060 },
zoom: 10
});
}
</script>
If you’re using a nonce-based CSP, the inline callback script needs the nonce.
Example policy:
Content-Security-Policy:
default-src 'self';
script-src 'self' 'nonce-{{RANDOM_NONCE}}' 'strict-dynamic' https://maps.googleapis.com https://maps.gstatic.com;
style-src 'self' 'unsafe-inline' https://maps.gstatic.com https://fonts.googleapis.com;
img-src 'self' data: https://maps.gstatic.com https://maps.googleapis.com;
connect-src 'self' https://maps.googleapis.com https://*.googleapis.com;
font-src 'self' https://fonts.gstatic.com;
object-src 'none';
base-uri 'self';
Better: avoid inline callback glue when possible
I generally prefer not to rely on global callback functions if I can avoid it.
<div id="map" style="height: 400px;"></div>
<script nonce="{{ .CSPNonce }}" src="/js/maps-init.js"></script>
<script
async
src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&loading=async&callback=initMap">
</script>
/js/maps-init.js:
window.initMap = function () {
const mapEl = document.getElementById("map");
new google.maps.Map(mapEl, {
center: { lat: 51.5072, lng: -0.1276 },
zoom: 12,
mapTypeControl: false,
streetViewControl: false
});
};
That still uses a global callback because the API expects it, but at least your logic lives in a normal script file.
Strict CSP setup
If you’re serious about CSP, this is closer to what I’d deploy.
Content-Security-Policy:
default-src 'self';
script-src 'self' 'nonce-{{RANDOM_NONCE}}' 'strict-dynamic' https://maps.googleapis.com;
style-src 'self' 'nonce-{{RANDOM_NONCE}}' 'unsafe-inline' https://fonts.googleapis.com;
img-src 'self' data: https://maps.gstatic.com https://maps.googleapis.com;
connect-src 'self' https://maps.googleapis.com https://*.googleapis.com;
font-src 'self' https://fonts.gstatic.com;
object-src 'none';
base-uri 'self';
frame-ancestors 'none';
form-action 'self';
Why only https://maps.googleapis.com in script-src here? Because with a nonce plus 'strict-dynamic', the trusted bootstrap script can load its dependencies. That’s the whole point of strict CSP. If you’re not familiar with that model, Google’s official CSP docs and https://csp-guide.com are worth reading.
That said, browsers vary, legacy behavior exists, and production CSP is never as clean as a blog post. If you see blocked script loads from maps.gstatic.com, allow it explicitly.
Common violations and fixes
1. Refused to load the script from maps.googleapis.com
Your script-src is missing Google Maps.
Fix:
script-src 'self' https://maps.googleapis.com;
If you use nonces:
script-src 'self' 'nonce-{{RANDOM_NONCE}}' 'strict-dynamic' https://maps.googleapis.com;
2. Refused to connect to maps.googleapis.com or another googleapis.com endpoint
Maps is making network requests your connect-src does not allow.
Fix:
connect-src 'self' https://maps.googleapis.com https://*.googleapis.com;
3. Refused to load image from maps.gstatic.com
Map tiles, marker assets, or UI images are blocked.
Fix:
img-src 'self' data: https://maps.gstatic.com https://maps.googleapis.com;
4. Refused to apply inline style
This one is common. Google Maps often injects or relies on inline styles.
You may need:
style-src 'self' 'unsafe-inline';
I don’t love that, but sometimes it’s the realistic answer. Test whether a nonce-based style-src works in your setup before defaulting to 'unsafe-inline'.
5. Refused to load font from fonts.gstatic.com
If your map UI or surrounding page uses Google fonts, allow them explicitly.
Fix:
style-src 'self' https://fonts.googleapis.com;
font-src 'self' https://fonts.gstatic.com;
Report-Only first. Always.
Don’t ship CSP changes for Maps straight to enforcement unless you enjoy breaking production on a Friday.
Use Content-Security-Policy-Report-Only first:
Content-Security-Policy-Report-Only:
default-src 'self';
script-src 'self' 'nonce-{{RANDOM_NONCE}}' 'strict-dynamic' https://maps.googleapis.com https://maps.gstatic.com;
style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;
img-src 'self' data: https://maps.gstatic.com https://maps.googleapis.com;
connect-src 'self' https://maps.googleapis.com https://*.googleapis.com;
font-src 'self' https://fonts.gstatic.com;
object-src 'none';
base-uri 'self';
Watch the console. Watch your CSP reports. Then tighten.
Minimal HTML example
Here’s a full page you can paste into a test app.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Google Maps CSP Test</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style nonce="{{CSP_NONCE}}">
#map {
width: 100%;
height: 400px;
}
</style>
</head>
<body>
<h1>Google Maps CSP Test</h1>
<div id="map"></div>
<script nonce="{{CSP_NONCE}}">
window.initMap = function () {
new google.maps.Map(document.getElementById("map"), {
center: { lat: 48.8566, lng: 2.3522 },
zoom: 11
});
};
</script>
<script
async
src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&callback=initMap">
</script>
</body>
</html>
Matching CSP:
Content-Security-Policy:
default-src 'self';
script-src 'self' 'nonce-{{CSP_NONCE}}' 'strict-dynamic' https://maps.googleapis.com;
style-src 'self' 'nonce-{{CSP_NONCE}}' 'unsafe-inline';
img-src 'self' data: https://maps.gstatic.com https://maps.googleapis.com;
connect-src 'self' https://maps.googleapis.com https://*.googleapis.com;
object-src 'none';
base-uri 'self';
My recommended starting policy
If I were adding Google Maps JavaScript API to a modern app today, I’d start here and adjust based on actual violations:
Content-Security-Policy:
default-src 'self';
script-src 'self' 'nonce-{{RANDOM_NONCE}}' 'strict-dynamic' https://maps.googleapis.com https://maps.gstatic.com;
style-src 'self' 'unsafe-inline' https://fonts.googleapis.com https://maps.gstatic.com;
img-src 'self' data: https://maps.gstatic.com https://maps.googleapis.com;
connect-src 'self' https://maps.googleapis.com https://*.googleapis.com https://maps.gstatic.com;
font-src 'self' https://fonts.gstatic.com;
object-src 'none';
base-uri 'self';
frame-ancestors 'none';
form-action 'self';
Then I’d remove anything I could prove was unnecessary.
That’s the real trick with CSP and Google Maps: start narrow, test hard, and only widen the policy for actual blocked behavior. Not guesses. Not cargo cult snippets copied from random issue threads.