express-csp-header and helmet are both npm packages designed to help Express.js applications set HTTP security headers that protect against common web vulnerabilities. helmet is a comprehensive middleware that sets multiple security-related headers by default, including Content Security Policy (CSP), XSS Protection, HSTS, and more. express-csp-header is a focused utility that only handles Content Security Policy headers, offering a programmatic way to define CSP directives without writing raw header strings.
When building web applications with Express.js, setting proper HTTP security headers is non-negotiable. Two common tools for this are express-csp-header and helmet. While both can configure Content Security Policy (CSP), they differ significantly in scope, maintenance, and philosophy. Let’s break down how they work in practice.
express-csp-header does one thing: generate and set the Content-Security-Policy header. It provides a clean API to define CSP directives using JavaScript objects instead of raw strings.
// express-csp-header: Only sets CSP
import express from 'express';
import csp from 'express-csp-header';
const app = express();
app.use(csp({
policies: {
'default-src': [csp.SELF],
'script-src': [csp.SELF, 'https://trusted.cdn.com'],
'style-src': [csp.SELF, 'https://fonts.googleapis.com']
}
}));
helmet sets multiple security headers out of the box, including CSP, but also:
X-Content-Type-OptionsX-Frame-OptionsX-XSS-ProtectionStrict-Transport-SecurityReferrer-PolicyYou can enable all defaults or pick specific ones:
// helmet: Sets many headers, including CSP
import express from 'express';
import helmet from 'helmet';
const app = express();
// Enable all recommended headers
app.use(helmet());
// Or just CSP with custom config
app.use(helmet.contentSecurityPolicy({
directives: {
'default-src': ["'self'"],
'script-src': ["'self'", 'https://trusted.cdn.com'],
'style-src': ["'self'", 'https://fonts.googleapis.com']
}
}));
Both libraries let you define CSP using JavaScript objects, but their syntax differs slightly.
express-csp-header uses constants like csp.SELF to represent 'self':
// express-csp-header
app.use(csp({
policies: {
'img-src': [csp.SELF, 'data:', 'https://images.example.com']
}
}));
// Produces: img-src 'self' data: https://images.example.com
helmet expects you to write values as strings, including quotes for special keywords:
// helmet
app.use(helmet.contentSecurityPolicy({
directives: {
'img-src': ["'self'", 'data:', 'https://images.example.com']
}
}));
// Produces: img-src 'self' data: https://images.example.com
Note: In helmet, you must include the single quotes around 'self' as a string literal. This is closer to the actual HTTP header format but can be error-prone.
As of recent checks, helmet is actively maintained, widely adopted, and aligns with current OWASP security recommendations. It’s updated regularly to reflect evolving best practices (e.g., deprecating X-XSS-Protection in favor of modern CSP).
express-csp-header, while functional, shows signs of reduced maintenance. Its GitHub repository has not seen significant updates in years, and it doesn’t handle newer CSP features as gracefully. For new projects, this raises sustainability concerns.
helmet if:Example: A SaaS dashboard serving sensitive user data.
// Recommended setup for most apps
app.use(helmet({
contentSecurityPolicy: {
directives: {
'default-src': ["'self'"],
'script-src': ["'self'", "'unsafe-inline'"], // adjust as needed
'connect-src': ["'self'", 'https://api.yourservice.com']
}
}
}));
express-csp-header only if:Example: A microservice that only serves JSON and relies on an edge gateway for other headers.
// Minimal CSP-only setup
app.use(csp({
policies: {
'default-src': [csp.NONE],
'frame-ancestors': [csp.NONE]
}
}));
Both libraries support CSP violation reporting, but implementation differs.
helmet includes report-uri and report-to in its CSP directive options:
app.use(helmet.contentSecurityPolicy({
directives: {
'default-src': ["'self'"],
'report-uri': ['/csp-violation-report-endpoint']
}
}));
express-csp-header requires you to add the report-uri as a string in the policy array:
app.use(csp({
policies: {
'default-src': [csp.SELF],
'report-uri': ['/csp-violation-report-endpoint']
}
}));
However, note that report-uri is deprecated in favor of report-to, which requires additional header configuration. helmet makes this easier by allowing full control over all directives.
For nearly all Express.js applications, helmet is the better choice. It’s secure by default, actively maintained, and reduces the chance of missing critical headers. Use express-csp-header only in niche cases where you have full control over your infrastructure and deliberately isolate CSP management — and even then, audit its compatibility carefully.
Remember: Security isn’t just about CSP. Headers like X-Content-Type-Options and Strict-Transport-Security prevent entire classes of attacks. helmet gives you those for free.
Choose express-csp-header if you need fine-grained, programmatic control over only the Content Security Policy header and prefer to manage other security headers manually or through other means. It’s suitable for projects where CSP is the primary concern and you want to avoid pulling in a broader security toolkit. However, note that this package hasn’t seen active maintenance recently, so verify compatibility with your Express version before adoption.
Choose helmet if you want a well-maintained, batteries-included solution that automatically configures a suite of essential security headers beyond just CSP — including XSS protection, frame guards, and strict transport security. It’s the standard choice for production Express applications where defense-in-depth matters, and it allows you to customize or disable individual headers as needed.
Middleware wrapper for csp-header, so for more information read its documentation.
const { expressCspHeader, INLINE, NONE, SELF } = require('express-csp-header');
app.use(expressCspHeader({
directives: {
'default-src': [SELF],
'script-src': [SELF, INLINE, 'somehost.com'],
'style-src': [SELF, 'mystyles.net'],
'img-src': ['data:', 'images.com'],
'worker-src': [NONE],
'block-all-mixed-content': true
}
}));
// express will send header "Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' somehost.com; style-src 'self' mystyles.net; img-src data: images.com; workers-src 'none'; block-all-mixed-content; report-uri https://cspreport.com/send;'
If you want to use nonce parameter you should use NONCE constant. Nonce key will be generated automatically. Also generated nonce key will be stored in req.nonce:
const { expressCspHeader, NONCE } = require('express-csp-header');
app.use(expressCspHeader({
directives: {
'script-src': [NONCE]
}
}));
// express will send header with a random nonce key "Content-Security-Policy: script-src 'nonce-pSQ9TwXOMI+HezKshnuRaw==';"
app.use((req, res) => {
console.log(req.nonce); // 'pSQ9TwXOMI+HezKshnuRaw=='
})
If you have more than one tlds you may want to have only current tld in your security policy. You can do this by replacing tld by TLD constant:
const { expressCspHeader, TLD } = require('express-csp-header');
app.use(expressCspHeader({
directives: {
'script-src': [`mystatic.${TLD}`]
}
}));
// for myhost.com it will send: "Content-Security-Policy: script-src mystatic.com;"
// for myhost.net it will send: "Content-Security-Policy: script-src mystatic.net;"
// etc
express-csp-header uses psl package to parse tld for auto-tld feature. If you have a custom tld you can specify it as an array or a regexp.
const { expressCspHeader, TLD } = require('express-csp-header');
app.use(expressCspHeader({
directives: {
'script-src': [`mystatic.${TLD}`]
},
domainOptions: {
customTlds: ['example.com']
}
}));
// for myhost.com it will send: "Content-Security-Policy: script-src mystatic.com;"
// for myhost.example.com it will send: "Content-Security-Policy: script-src mystatic.example.com;"
// etc
const { expressCspHeader } = require('express-csp-header');
app.use(expressCspHeader({
directives: {
'default-src': ["#someString#"],
'script-src': ["#someOtherString#"],
},
processCspString: (cspString, req, res) => {
// here you can process final cspString
return cspString.replaceAll('#someString#', 'https://example.com').replaceAll('#someOtherString#', 'https://example2.com');
}
}));
For more information read csp-header documentation. express-csp-header helps you manage both Content-Security-Policy and Reporting-Endpoints headers. Report-to headers is no longer recommended to use
const { expressCspHeader, INLINE, NONE, SELF } = require('express-csp-header');
app.use(expressCspHeader({
directives: {
'default-src': [SELF],
'report-to': 'csp-default'
},
reportUri: 'https://cspreport.com/send',
reportingEndpoints: [
{'csp-default': 'https://cspreport.com/send'}
]
}));
/* express will send two headers
1. Content-Security-Policy: default-src 'self'; report-to csp-default; report-uri https://cspreport.com/send;
2. Reporting-Endpoints: csp-default="https://cspreport.com/send"
*/
Read about preset in csp-header docs
To switch on Report-Only mode just specify reportOnly param:
const { expressCspHeader, SELF } = require('express-csp-header');
app.use(expressCspHeader({
directives: {
'script-src': [SELF]
},
reportOnly: true
}));
// it will send: "Content-Security-Policy-Report-Only: script-src 'self';"
const { expressCspHeader, SELF } = require('express-csp-header');
app.use(expressCspHeader({
directives: {
'script-src': [SELF]
},
reportUri: 'https://cspreport.com/send'
}));
// express will send header "Content-Security-Policy: script-src 'self'; report-uri https://cspreport.com/send;"
If you want to pass some params to the report uri just pass function instead of string:
const { expressCspHeader, SELF } = require('express-csp-header');
app.use(expressCspHeader({
directives: {
'script-src': [SELF]
},
reportUri: (req, res) => {
return `https://cspreport.com/send?time=${Number(new Date())}`;
}
}));
// express will send header "Content-Security-Policy: script-src 'self'; report-uri https://cspreport.com/send?time=1460467355592;"