CSP Generator Guide: Build a Content-Security-Policy Header
Reading a Content-Security-Policy header is a useful skill, but most teams need the opposite direction too: starting from a page and its real content, and producing a header that allows exactly that content and nothing else. That is harder than it sounds, because a modern page loads content from many places: its own origin, a font provider, an analytics service, a video host, an image CDN, and a payment widget, each of which needs to appear in the right directive. Guessing that list from memory produces a policy that either blocks legitimate content or allows far more than necessary. The disciplined approach is to build the header from evidence, directive by directive, starting strict and loosening only where testing proves a need.
The CSP Generator tool lets you enter allowed sources per directive and generates a Content-Security-Policy header. It is a builder, not a deployment tool: the output is header text for you to review and then apply in your server configuration. That boundary matters, because a generated header sitting in a tool output protects nobody, and a header applied without testing can break a page in ways that are visible to visitors before they are visible to the team. The generator's value is in structure. By filling in one directive at a time, you are forced to answer, for each content type, the question that a good policy must answer: where does this kind of content legitimately come from on this page.
Start with default-src, because it is the fallback that catches everything you forget. Setting default-src to self means that any content type without its own directive may only come from the site's own origin. That single choice makes a short policy strict by default, which is the safe direction to err in: a forgotten directive results in a blocked resource that testing reveals, rather than an allowed attacker source that nobody notices. Next, set script-src, which is the most security-critical directive, because scripts are the content type that an injection attack abuses. Begin with self, then add named hosts only for scripts the page genuinely loads, such as a named analytics host, and resist the wildcard and unsafe-inline unless you have a specific, documented reason.
The remaining directives follow the same pattern. Style-src usually needs self, plus a font or style host if the design uses one. Img-src often needs self plus an image CDN host, and sometimes data: for embedded images, which is a deliberate exception to note. Font-src needs self plus the font provider. Connect-src needs self plus any API hosts the page's scripts call. Frame-src needs the hosts of embedded frames, or none if the page embeds nothing. Object-src should be none for almost every site, because plugins are rarely needed and historically exploited. Media-src, worker-src, and manifest-src can be left to the default-src fallback unless the page uses those content types, in which case they deserve their own explicit entries.
A worked example you can follow
Take a sample blog page that loads its own scripts and styles, images from its own origin and one image CDN, fonts from one font provider, and analytics from one analytics host. Count the distinct external hosts: 3, namely the image CDN, the font provider, and the analytics host. Build the header directive by directive. Default-src self covers everything by default. Script-src self plus the analytics host covers the page's scripts. Style-src self covers its styles. Img-src self plus the image CDN covers its images. Font-src self plus the font provider covers its fonts. Connect-src self plus the analytics host covers the analytics requests. Object-src none blocks plugins. Frame-src none blocks frames. Count the directives: 8 in total, of which 2, object-src and frame-src, use the strictest value none, which is 2 divided by 8, or 25 percent of the policy at maximum strictness, and 0 directives use a wildcard or unsafe-inline.
Now test that header against a second page on the same site, a contact page that embeds a map from a map host. The map needs frame-src to allow the map host and connect-src to allow the map API host, which adds 2 new external hosts to the policy. The distinct external host count rises from 3 to 5, an increase of 2, and the header for the contact page gains those 2 entries. That is the correct outcome: different pages have different content, so they may need different policies, and a single site-wide policy must allow the union of all pages' needs, which is 5 hosts in this example. The alternative, a single policy written for the blog page and applied to the contact page, would block the map, and the breakage would be discovered by a visitor trying to find the office, not by the team.
Take a third sample, a page under development that loads a new third-party widget from a host nobody recorded. The strict header blocks the widget, and the browser reports the blocked source. The disciplined fix is to add that one named host to the one directive it needs, raising the external host count by 1, and re-generating the header. The undisciplined fix is to add a wildcard star, which raises the allowed origins for that directive from a counted list to unlimited, an increase that cannot be expressed as a number because it has no bound. That asymmetry is the point: named hosts are countable and reviewable, while wildcards are not, and a policy built from counted names stays maintainable in a way a wildcard policy never does.
Mistakes to avoid
The first mistake is starting loose, with wildcards and unsafe-inline, and planning to tighten later. Tightening later rarely happens, because every tightening risks breaking something that now depends on the looseness. Start strict and loosen on evidence instead. The second mistake is generating one policy for the whole site from a single page's content, and breaking pages with different embeds. The third mistake is forgetting default-src, which leaves content types without a directive entirely unprotected rather than falling back to a strict default. The fourth mistake is treating generated output as deployed protection. A header protects nothing until it is served, and it should be tested in report-only mode first, so breakage is observed before it affects visitors.
The CSP Checker tool reads a generated header back and flags risky values, which is a useful second pair of eyes, and the CSP Nonce and Hash Generator tool helps when inline content must be allowed safely without unsafe-inline. For background on placing CSP in a wider security review, see How to Configure HTTP Security Headers.
Checking the result before you publish it
Treat a generated header as a draft to be tested, not a finished policy. Serve it in report-only mode, browse every page type with real content, and collect the violation reports. For each blocked source, decide whether it is legitimate, and if so, add that one named host to that one directive, then regenerate and re-test. Only when the reports show no legitimate blocks should the policy switch to enforcement, and the live response should then be checked to confirm the enforced header matches the tested draft. Keep the header text and a note of each allowed host's reason together, because a policy whose reasons are recorded can be maintained, while a policy whose reasons are forgotten can only be loosened.