---
title: "Setup — Svelte"
description: "Install the headless packages for your framework, create the engine once, and use them with server-side rendering."
framework: "Svelte"
source: "https://www.embedpdf.com/docs/headless/svelte/setup"
---

# Setup

A headless viewer needs two packages: the one for your framework, and an engine. This page
installs them, creates the engine for your whole app, and covers server-side rendering, bundlers
and fonts.

## Install

```sh
pnpm add @embedpdf/svelte@next @embedpdf/engine@next
```

The framework package holds every plugin, one import path each (`@embedpdf/svelte/search`,
`@embedpdf/svelte/annotation`). A plugin you don't import isn't in your bundle.

It's written for Svelte 5: components that take snippets, and functions that give you reactive
state for your runes.

## Create the engine once

Create one engine when your app starts, and share it with every viewer:

```ts
// pdf.ts
import { localEngine } from '@embedpdf/engine';

export const engine = localEngine();
```

Creating the engine is instant and allocates nothing. The first viewer that uses it starts it, and
PDFium boots in a Web Worker in the background.

`<Viewer>` can also own the engine. Pass a function, and the viewer creates the engine when it
mounts and destroys it when it unmounts:

**`owned-engine.svelte`**

```svelte
<script lang="ts">
  import { Viewer } from '@embedpdf/svelte/runtime';
  import { localEngine } from '@embedpdf/engine';
  import { plugins } from './pdf';
</script>

<Viewer engine={() => localEngine()} {plugins} />
```

An engine you pass in yourself is never destroyed by the viewer, so one engine can serve every
viewer in your app.

## Register the plugins

Plugins are a list you create once, next to the engine:

**`plugins.ts`**

```typescript
import { stagePlugin } from '@embedpdf/svelte/stage';
import { renderPlugin } from '@embedpdf/svelte/render';
import { interactionPlugin } from '@embedpdf/svelte/interaction';
import { selectionPlugin } from '@embedpdf/svelte/selection';

export const plugins = [stagePlugin(), renderPlugin(), interactionPlugin(), selectionPlugin()];
```

The viewer reads `engine` and `plugins` once, when it mounts, and ignores later changes to them.

Order doesn't matter, and a plugin that needs another says so when the app starts: the annotation
plugin, for example, needs the interaction plugin.

## Server-side rendering

The headless packages render nothing on the server that needs the engine: the engine only starts in
the browser. So a module-level engine is safe everywhere.

In SvelteKit, render the viewer in the browser only, for example behind `{#if browser}` with
`browser` from `$app/environment`:

**`ssr.svelte`**

```svelte
<!-- src/routes/contract/+page.svelte -->
<script lang="ts">
  import { browser } from '$app/environment';
  import DocumentViewer from '$lib/DocumentViewer.svelte';
</script>

{#if browser}
  <DocumentViewer />
{:else}
  <div style="height: 600px">Loading the viewer…</div>
{/if}
```

To leave a whole page out of server rendering, add `export const ssr = false` to its `+page.ts`.

## Bundlers and self-hosting

The engine's worker and WebAssembly ship inside the package, and your build picks them up.

Vite, and so SvelteKit, needs no configuration, and neither do webpack 5 and Rspack.

There's nothing to copy, and nothing is fetched from a CDN. The engine loads from your own site, so
it works offline and behind a firewall.

If your setup can't bundle a worker, for example with a strict Content Security Policy, host the
files yourself: [Self-hosting](https://www.embedpdf.com/docs/viewer/svelte/self-hosting) lists every option, with notes for Parcel
and plain esbuild.

## Fonts

Text the engine draws, such as a text box or a filled form field, needs a font with its characters.
For languages the built-in fonts don't cover, give the engine a fallback font:

```ts
export const engine = localEngine({
  fallbackFonts: [{ key: 'noto-cjk', familyName: 'Noto Sans CJK', url: '/fonts/NotoSansCJK.ttf' }],
});
```

[Fonts](https://www.embedpdf.com/docs/engine/documents/fonts) covers registering fonts and using them by name. Every other
engine option is in the engine's [Setup](https://www.embedpdf.com/docs/engine/setup#options).

> **The same viewer, on your server**
>
> Move a headless viewer to CloudPDF by changing one line: `localEngine()` becomes
> `cloudEngine()`. Your plugins, components and functions stay as they are.
> Files stay on your server, the first page shows without downloading the PDF, and fonts, limits and
> storage are the server's to configure.
>
> [Set up CloudPDF](https://www.cloudpdf.com/docs/headless/svelte/setup)

## Good to know

### One viewer, many documents

A viewer can hold several open documents at once, like tabs. See
[Several documents](documents/tabs).

### Changing `engine` or `plugins`

Both are read once. To use another engine or another set of plugins, mount a new `<Viewer>`, for
example inside a `{#key}` block.

> The ready-made [Viewer](https://www.embedpdf.com/docs/viewer/svelte) uses these same packages. If you start there, everything on
> these pages still applies when you move to headless.
