Security Headers in the headers.json file Last updated: 18. Oct 2026
The headers.json file sets HTTP headers on every response from a site. For a single site, prefer the headers property in site.json, which is where new header features are added. In an app (or a package that sites use as their template), the headers you set are inherited by every site, key by key — see below.
site.json or headers.json?
For a single site, use site.json. It is the canonical place for per-site configuration and supports the same header keys and values as headers.json. If a header is set in both files, headers.json wins — it is the more specific file — and site.json only fills in the headers headers.json does not set. The site console then shows a warning, since a header with two different values is often a forgotten copy.
In an app, either file works for headers that every site on the template should get — a Content-Security-Policy, for example. Put headers.json in the template's root (./headers.json, which is #/headers.json for the sites; see the layering), or use the headers block of the template's site.json.
- A site inherits the app's headers key by key. Four layers are merged, the most specific last: the app's
site.json, the app'sheaders.json, the site's#/site.jsonand the site's#/headers.json. A site that sets one header changes only that header, and keeps the rest of the app's headers, including its CSP. - Docly does not copy the app's
site.jsoninto the site. The settings Docly writes itself (discovered image sizes) go to the site's#/images.json, so a template'ssite.jsonstays a live default, just like itsheaders.json.
Details: Put shared security headers in the template headers.json and Configure site settings and inherit them from the app.
A site's own #/headers.json (or the headers block of its site.json) overrides the template's headers it names — and only those. Set a header to null to remove it, also one Docly would otherwise send by default, and use "$inherit": false only when the site should own every header itself. Delete a test copy from a site once the template has the policy, or that site will not get later changes to those headers.
If you have an existing headers.json in a single site, it continues to work. You can migrate by moving the contents into the "headers" block of that site's site.json and deleting the headers.json file.
File format and errors
headers.json is a flat JSON object: each key is a header name and each value is a string. It can be a plain code file or a document bound to a schema — Docly reads the file's text content either way.
Header names are matched without regard to case. content-security-policy in headers.json and Content-Security-Policy in site.json are the same header, and the value in headers.json wins.
Invalid JSON is skipped, not applied. If headers.json cannot be parsed, Docly ignores that file, writes the error to the site console, and uses the other layers — site.json and, for a site on an app, the app's headers. The headers only that file sets, such as a CSP, are missing until the file is fixed, so validate the JSON before you save.
Migration example
For a single site, move the key/value pairs from #/headers.json into the headers block of #/site.json:
// Old: #/headers.json
{
"Content-Security-Policy": "default-src 'self';",
"X-Frame-Options": "SAMEORIGIN"
}
// New: #/site.json
{
"headers": {
"Content-Security-Policy": "default-src 'self';",
"X-Frame-Options": "SAMEORIGIN"
}
} For the full list of recommended security headers and their values, see the Security headers chapter in site.json config file.