---
title: "Hot reload"
description: "Use FrankenPHP hot reload to update PHP code, templates, and frontend assets in the browser without manual refresh, with optional DOM morphing via Idiomorph."
sidebar:
  label: "Hot reloading"
  order: 2
seo:
  title: "FrankenPHP hot reload for PHP, templates, and assets"
---
FrankenPHP includes a built-in **hot reload** feature designed to vastly improve the developer experience.

![Hot Reload](/blume-assets/content/content/assets/hot-reload.png)

This feature provides a workflow similar to **Hot Module Replacement (HMR)** in modern JavaScript tooling such as Vite or webpack.
Instead of manually refreshing the browser after every file change (PHP code, templates, JavaScript, and CSS files...),
FrankenPHP updates the page content in real-time.

Hot Reload natively works with WordPress, Laravel, Symfony, and any other PHP application or framework.

When enabled, FrankenPHP watches your current working directory for filesystem changes.
When a file is modified, it pushes a [Mercure](/docs/mercure) update to the browser.

Depending on your setup, the browser will either:

- **Morph the DOM** (preserving scroll position and input state) if [Idiomorph](https://github.com/bigskysoftware/idiomorph) is loaded.
- **Reload the page** (standard live reload) if Idiomorph is not present.

## Enabling FrankenPHP hot reload

To enable hot reloading, enable Mercure, then add the `hot_reload` sub-directive to the `php_server` directive in your `Caddyfile`.

<div class="fp-callout" data-callout="warning">
<p class="fp-callout-title">Warning</p>

This feature is intended for **development environments only**.
Do not enable `hot_reload` in production, as this feature is not secure (exposes sensitive internal details) and slows down the application.

</div>

```caddyfile
localhost

mercure {
    anonymous
}

root public/
php_server {
    hot_reload
}
```

By default, FrankenPHP will watch all files in the current working directory matching this glob pattern: `./**/*.{css,env,gif,htm,html,jpg,jpeg,js,mjs,php,png,svg,twig,webp,xml,yaml,yml}`

When hot reload is enabled, `opcache.revalidate_freq` defaults to `0`, so OPcache checks on every request whether a script changed.
Setting it in a `php.ini` file or through the `php_ini` directive takes precedence.

It's possible to set the files to watch using the glob syntax explicitly:

```caddyfile
localhost

mercure {
    anonymous
}

root public/
php_server {
    hot_reload src/**/*{.php,.js} config/**/*.yaml
}
```

Use the long form of `hot_reload` to specify the Mercure topic to use, as well as which directories or files to watch:

```caddyfile
localhost

mercure {
    anonymous
}

root public/
php_server {
    hot_reload {
        topic hot-reload-topic
        watch src/**/*.php
        watch assets/**/*.{ts,json}
        watch templates/
        watch public/css/
    }
}
```

## Client-side integration for FrankenPHP hot reload

While the server detects changes, the browser needs to subscribe to these events to update the page.
FrankenPHP exposes the Mercure Hub URL to use for subscribing to file changes via the `$_SERVER['FRANKENPHP_HOT_RELOAD']` environment variable.

A convenience JavaScript library, [frankenphp-hot-reload](https://www.npmjs.com/package/frankenphp-hot-reload), is also available to handle the client-side logic.
To use it, add the following to your main layout:

```php
<!DOCTYPE html>
<title>FrankenPHP Hot Reload</title>
<?php if (isset($_SERVER['FRANKENPHP_HOT_RELOAD'])): ?>
<meta name="frankenphp-hot-reload:url" content="<?=$_SERVER['FRANKENPHP_HOT_RELOAD']?>">
<script src="https://cdn.jsdelivr.net/npm/idiomorph/dist/idiomorph.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/frankenphp-hot-reload/+esm" type="module"></script>
<?php endif ?>
```

The library will automatically subscribe to the Mercure hub, fetch the current URL in the background when a file change is detected, and morph the DOM.
It is available as an [npm](https://www.npmjs.com/package/frankenphp-hot-reload) package and on [GitHub](https://github.com/dunglas/frankenphp-hot-reload).

Alternatively, you can implement your own client-side logic by subscribing directly to the Mercure hub using the `EventSource` native JavaScript class.

### Preserving existing DOM nodes

In rare cases, such as when using development tools [like the Symfony web debug toolbar](https://github.com/symfony/symfony/pull/62970),
you may want to preserve specific DOM nodes.
To do so, add the `data-frankenphp-hot-reload-preserve` attribute to the relevant HTML element:

```html
<div data-frankenphp-hot-reload-preserve><!-- My debug bar --></div>
```

## Hot reload with FrankenPHP worker mode

If you are running your application in [Worker Mode](/docs/worker), your application script remains in memory.
This means changes to your PHP code will not be reflected immediately, even if the browser reloads.

For the best developer experience, you should combine `hot_reload` with [the `watch` sub-directive in the `worker` directive](/docs/config#watching-for-file-changes).

- `hot_reload`: refreshes the **browser** when files change
- `worker.watch`: restarts the worker when files change

```caddyfile
# Caddyfile: combine FrankenPHP hot reload with worker watch for full dev workflow
localhost

mercure {
    anonymous
}

root public/
php_server {
    hot_reload
    worker {
        file /path/to/my_worker.php
        watch
    }
}
```

## How FrankenPHP hot reload works

1. **Watch**: FrankenPHP monitors the filesystem for modifications using [the `e-dant/watcher` library](https://github.com/e-dant/watcher) under the hood (we contributed the Go binding).
2. **Restart (Worker Mode)**: if `watch` is enabled in the worker config, the PHP worker is restarted to load the new code.
3. **Push**: a JSON payload containing the list of changed files is sent to the built-in [Mercure hub](https://mercure.rocks).
4. **Receive**: The browser, listening via the JavaScript library, receives the Mercure event.
5. **Update**:

- If **Idiomorph** is detected, it fetches the updated content and morphs the current HTML to match the new state, applying changes instantly without losing state.
- Otherwise, `window.location.reload()` is called to refresh the page.
