---
title: "Self-hosting & air-gapped deployments — Angular"
description: "Every URL EmbedPDF can request, the setting that controls each, and how to run it with no request leaving your origin."
integration: "Angular"
source: "https://www.embedpdf.com/docs/viewer/angular/self-hosting"
---

# Self-hosting & air-gapped deployments

EmbedPDF ships with the product. Nothing it needs at runtime comes from a
public CDN unless you point it there: the PDF engine's WebAssembly binary, its
worker, and the built-in stamp library all travel inside your build or inside
the artifact you serve. This page is the complete inventory — every URL a
deployment can touch and the setting that controls it — so an air-gapped or
strict-CSP deployment is a checklist, not an investigation.

Every engine setting on this page goes in the viewer's `engine` config, such as
`engine: { assetsUrl: '/vendor/embedpdf/' }`. In a headless app, the same
settings are `localEngine()` options.

## The inventory

| What                                   | Where it comes from by default                                                                                                                                                      | Setting                                                   |
| -------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------- |
| `embedpdf.wasm` (the engine, \~6.4 MB) | Emitted by your bundler as an asset of your build, streamed from your origin. Under Angular, a lazy JavaScript chunk of your build instead.                                         | `engine.assetsUrl`, `engine.wasmUrl`, `engine.wasmBinary` |
| The engine worker                      | A file of your build, next to the WebAssembly, emitted by your bundler. From a CDN or with the engine's portable build (Angular, plain esbuild), a `blob:` URL instead (see below). | `engine.worker`                                           |
| The image-encoder worker               | The same, with a main-thread fallback.                                                                                                                                              | `engine.encoderWorker`                                    |
| Fonts                                  | None are fetched unless you configure them.                                                                                                                                         | `fonts`, `engine.fallbackFonts`                           |
| The standard stamps                    | A lazy JavaScript chunk of your build (`@embedpdf/default-stamps`).                                                                                                                 | `plugins.stamp.standard`                                  |
| Your documents                         | Whatever you pass as `src` or open yourself.                                                                                                                                        | —                                                         |

Verify it yourself: open the browser's network panel, filter out your own
origin, and load a document. The list should be empty. We keep it that way
with a nightly matrix that builds a minimal app under every major bundler and
fails on any request that leaves the app's origin.

## The engine's WebAssembly

With no `engine` settings, the viewer is right in almost every toolchain.
Your bundler sees `new URL('./embedpdf.wasm', import.meta.url)` inside
`@embedpdf/engine-runtime-wasm32`, copies the file into your build output, and
the worker streams and compiles it from your origin. Verified out of the box:
Vite, webpack 5, Rspack, Parcel 2, and Next.js with either webpack or Turbopack.

**Angular** resolves packages with the `es2020` export condition, and
`@embedpdf/engine` routes that condition to its portable build: the same engine, with the binary carried as a lazy chunk of your build instead
of an asset. Nothing to configure. It costs the same bytes over the wire and a
short inflate at boot, and it does not stream-compile. If you want the streamed
asset instead, copy the file with one `angular.json` entry and say where it is:

```json
"assets": [
  { "glob": "embedpdf.wasm", "input": "node_modules/@embedpdf/engine-runtime-wasm32/lib", "output": "/embedpdf" }
]
```

```js
{ engine: { assetsUrl: '/embedpdf/' } }
```

Under pnpm, add `@embedpdf/engine-runtime-wasm32` to your own dependencies so
that path exists; npm and yarn hoist it for you.

**Plain esbuild** emits neither the asset nor the engine's workers, and has no
condition to route on. Use the engine's portable build, as Angular does: the
WebAssembly comes as a lazy chunk of your build, and both workers start from
`blob:` URLs, so the policy needs `worker-src blob:` (see below).

Add `@embedpdf/engine` to your dependencies and give the viewer that engine:

```js
import { localEngine } from '@embedpdf/engine/portable';

const config = { engine: () => localEngine() };
```

**Explicit sources** win over every default and never fall back to anything:

```js
{ engine: { assetsUrl: '/vendor/embedpdf/' } } // a directory you serve; embedpdf.wasm is appended
{ engine: { wasmUrl: '/vendor/embedpdf/engine.wasm' } } // the exact file
{ engine: { wasmBinary: bytes } } // bytes you already have, so no request at all
```

If the file is missing at boot, the engine fails with a message that names
these options. It never tries another location on its own.

## Workers and Content-Security-Policy

The engine's worker and the image-encoder worker are files of your build, next to
the WebAssembly, so `worker-src 'self'` covers them. To serve them from somewhere
else, copy `workers/embedpdf-worker.js`, `workers/encoder-worker.js` and
`embedpdf.wasm` into one directory you serve and say where the worker is; it
finds the WebAssembly as its own sibling:

```js
{
  engine: {
    worker: '/vendor/embedpdf/embedpdf-worker.js',
    encoderWorker: '/vendor/embedpdf/encoder-worker.js',
  },
}
```

`encoderWorker: false` encodes images on the main thread instead. With the engine's portable
build (Angular, plain esbuild), both workers start from `blob:` URLs unless you set these two; the
WebAssembly still comes from your build, so the two worker files are all you copy.
WebAssembly itself needs `script-src 'wasm-unsafe-eval'`. The whole policy is
on [Security & CSP](concepts/security).

## The standard stamps

The stamps panel's standard stamps are a lazy chunk of your build, one per
language. To serve a copy of `@embedpdf/default-stamps` yourself, or to ship
none:

```js
{ plugins: { stamp: { standard: 'https://files.example.com/stamps/{locale}/stamps.pdf' } } }
{ plugins: { stamp: { standard: false } } }
```

A URL you give is used exactly as given. Libraries your users create are kept
in their browser's IndexedDB, never sent anywhere.

## The CDN snippet

The snippet, `dist/embedpdf.js`, is the one artifact designed to be loaded
from another origin. Its folder is the unit of delivery: the entry, its
`chunks/`, and `embedpdf.wasm`, `embedpdf-worker.js` and `encoder-worker.js` beside it. Every sibling is referenced by a
path relative to the folder, so serving that folder from jsDelivr, from your
own CDN, or from an intranet server is the same operation, and no URL in the
code names any of them. Copy the folder, never the file alone.

A browser starts a Web Worker only from its own origin, so a page that loads
the folder from another one starts the engine's worker from a `blob:` URL. A
strict policy for a page that loads it from `https://cdn.example`:

```
script-src 'self' https://cdn.example 'wasm-unsafe-eval';
worker-src blob:;
connect-src 'self' https://cdn.example;
```

`connect-src` covers the wasm fetch from the folder; add the origins your
documents come from.

## Toolchain notes

- **Parcel 2** resolves our packages only with `packageExports` enabled in
  your project's `package.json`: `"@parcel/resolver-default": { "packageExports": true }`.
- **Turbopack** and **webpack** in Next.js emit the wasm under `_next/static/media`; no configuration.

> The guarantee behind this page is enforced, not promised: a lint fails the build on any public-CDN
> URL literal in the viewer or engine source, and the nightly bundler matrix fails on any request
> that leaves the app's origin.
