CSP (Content-Security Policy) Build

In order for Alpine to execute JavaScript expressions from HTML attributes like x-on:click="console.log()", it needs to use utilities that violate the "unsafe-eval" Content Security Policy that some applications enforce for security purposes.

Under the hood, Alpine doesn't actually use eval() itself because it's slow and problematic. Instead it uses Function declarations, which are much better, but still violate "unsafe-eval".

Alpine offers an alternate build that doesn't violate "unsafe-eval" and supports most of Alpine's inline expression syntax.

Installation

You can use this build by either including it from a <script> tag or installing it via NPM:

Via CDN

You can include this build's CDN as a <script> tag just like you would normally with standard Alpine build:

<!-- Alpine's CSP-friendly Core -->
<script defer src="https://cdn.jsdelivr.net/npm/@alpinejs/[email protected]/dist/cdn.min.js"></script>

Via NPM

You can alternatively install this build from NPM for use inside your bundle like so:

npm install @alpinejs/csp

Then initialize it from your bundle:

import Alpine from '@alpinejs/csp'
 
window.Alpine = Alpine
 
Alpine.start()

Basic Example

Here's a working counter component using Alpine's CSP build. Notice how most expressions work exactly like regular Alpine:

<html>
<head>
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'nonce-a23gbfz9e'">
<script defer nonce="a23gbfz9e" src="https://cdn.jsdelivr.net/npm/@alpinejs/[email protected]/dist/cdn.min.js"></script>
</head>
<body>
<div x-data="{ count: 0, message: 'Hello' }">
<button x-on:click="count++">Increment</button>
<button x-on:click="count = 0">Reset</button>
 
<span x-text="count"></span>
<span x-text="message + ' World'"></span>
<span x-show="count > 5">Count is greater than 5!</span>
</div>
</body>
</html>

What's Supported

The CSP build supports most JavaScript expressions you'd want to use in Alpine:

Object and Array Literals

<!-- ✅ These work -->
<div x-data="{ user: { name: 'John', age: 30 }, items: [1, 2, 3] }">
<span x-text="user.name"></span>
<span x-text="items[0]"></span>
</div>

Basic Operations

<!-- ✅ These work -->
<div x-data="{ count: 5, name: 'Alpine' }">
<span x-text="count + 10"></span>
<span x-text="count > 3"></span>
<span x-text="count === 5 ? 'Yes' : 'No'"></span>
<span x-text="'Hello ' + name"></span>
<div x-show="!loading && count > 0"></div>
</div>

Assignments and Updates

<!-- ✅ These work -->
<div x-data="{ count: 0, user: { name: '' } }">
<button x-on:click="count++">Increment</button>
<button x-on:click="count = 0">Reset</button>
<input x-model="user.name">
</div>

Method Calls

<!-- ✅ These work -->
<div x-data="{ items: ['a', 'b'], getMessage: () => 'Hello' }">
<span x-text="getMessage()"></span>
<button x-on:click="items.push('c')">Add Item</button>
</div>

Global Variables and Functions

<!-- ✅ These work -->
<div x-data="{ count: 42 }">
<button x-on:click="console.log('Count is:', count)">Log Count</button>
<span x-text="Math.max(count, 100)"></span>
<span x-text="parseInt('123') + count"></span>
<span x-text="JSON.stringify({ value: count })"></span>
</div>

What's Not Supported

Some advanced JavaScript features aren't supported:

<!-- ❌ These don't work -->
<div x-data>
<!-- Arrow functions -->
<button x-on:click="() => console.log('hi')">Bad</button>
 
<!-- Destructuring -->
<div x-text="{ name } = user">Bad</div>
 
<!-- Template literals -->
<div x-text="`Hello ${name}`">Bad</div>
 
<!-- Spread operator -->
<div x-data="{ ...defaults }">Bad</div>
</div>

When to Extract Logic

While the CSP build supports simple inline expressions, you'll want to extract complex logic into dedicated functions or Alpine.data() components for better organization:

<!-- Instead of this -->
<div x-data="{ users: [] }" x-show="users.filter(u => u.active && u.role === 'admin').length > 0">
<!-- Do this -->
<div x-data="userManager" x-show="hasActiveAdmins">
 
<script nonce="...">
Alpine.data('userManager', () => ({
users: [],
 
get hasActiveAdmins() {
return this.users.filter(u => u.active && u.role === 'admin').length > 0
}
}))
</script>

This approach makes your code more readable, testable, and maintainable, especially for complex applications.

CSP Headers

Here's an example CSP header that works with Alpine's CSP build:

Content-Security-Policy: default-src 'self'; script-src 'nonce-[random]' 'strict-dynamic';

The key is removing 'unsafe-eval' from your script-src directive while still allowing your nonce-based scripts to run.

Code highlighting provided by Torchlight