Local Development
After completing the Quick Start, use Vite’s development server to see frontend changes without rebuilding each time. You still open your normal Craft control panel URL; Vite serves the JavaScript and CSS and updates the page as you edit.
This setup has its own local environment setting and does not require Craft’s devMode.
1. Configure the Dev Server
In src/web/assets/cp/vite.config.ts, add this server option inside defineConfig({ ... }). Keep the existing base, plugins, and build options:
server: {
host: 'localhost',
port: 5173,
strictPort: true,
origin: 'http://localhost:5173',
cors: {
origin: 'http://my-site.test',
},
},Replace http://my-site.test with your Craft page’s origin: its scheme, hostname, and port if present, without a path. strictPort keeps Vite on the port the asset bundle expects.
If your Craft site uses HTTPS, serve Vite over HTTPS with a trusted local certificate or an HTTPS proxy too. Use that HTTPS URL in server.origin and the environment setting below. See Vite’s HTTPS and HMR connection options for your local setup. For DDEV, follow Using DDEV below.
2. Switch the Asset Bundle
Replace the quick start’s MyPluginCpAsset.php with this version, using your plugin’s namespace and alias:
<?php
namespace mynamespace\myplugin\web\assets\cp;
use craft\helpers\App;
use craft\web\AssetBundle;
use craft\web\assets\cp\CpAsset;
class MyPluginCpAsset extends AssetBundle
{
public function init(): void
{
$this->depends = [CpAsset::class];
$this->jsOptions = ['type' => 'module'];
$devServer = App::env('MY_PLUGIN_VITE_URL');
if ($devServer) {
$devServer = rtrim($devServer, '/');
$this->js = [
$devServer . '/@vite/client',
$devServer . '/src/my-plugin-cp.ts',
];
} else {
$this->sourcePath = '@mynamespace/myplugin/web/assets/cp/dist';
$this->js = ['my-plugin-cp.js'];
$this->css = ['my-plugin-cp.css'];
}
parent::init();
}
}In development, Vite handles the CSS imports in your entry, so the bundle only registers the dev-server scripts. Leave your Twig asset registration and mount element unchanged.
3. Start Vite
Add this to your Craft project’s local .env:
MY_PLUGIN_VITE_URL=http://localhost:5173From your plugin’s src/web/assets/cp folder, run:
npx viteLeave the command running and reload your Craft CP page once. Edit some text in src/App.vue and save: it should update in the browser without manually rebuilding or refreshing.
If the page is blank, check that the browser can reach the Vite URL and that cors.origin matches the Craft page. If you change the Vite URL or port, update both the config and .env.
Using DDEV
If you run Vite inside DDEV, use these settings in place of the localhost settings above. Your plugin’s files must be available inside the DDEV container. Keep the same asset bundle and entry file.
Add this entry to your Craft project’s .ddev/config.yaml. If web_extra_exposed_ports already exists, append the entry to its list:
web_extra_exposed_ports:
- name: vitecp
container_port: 5173
http_port: 5173
https_port: 5174Vite listens on port 5173 inside the container. Your browser connects through DDEV on HTTPS port 5174; use that port in the public Vite URL. DDEV handles the HTTPS certificate.
Replace the server block in your plugin’s src/web/assets/cp/vite.config.ts with:
server: {
host: '0.0.0.0',
port: 5173,
strictPort: true,
allowedHosts: ['my-site.ddev.site'],
origin: 'https://my-site.ddev.site:5174',
cors: {
origin: 'https://my-site.ddev.site',
},
},Replace my-site.ddev.site with your Craft site’s hostname. cors.origin must match the origin you use to open the Craft control panel. Set the public Vite URL in your Craft project’s local .env:
MY_PLUGIN_VITE_URL=https://my-site.ddev.site:5174From your Craft project folder, apply the DDEV configuration and open a shell inside the container:
ddev restart
ddev sshIn that shell, change to your plugin’s src/web/assets/cp folder and run:
npx viteLeave Vite running. Open https://my-site.ddev.site:5174/@vite/client in your browser: it should return JavaScript. Reload your normal Craft CP page, then edit a component to check that it updates automatically.
If these ports are already in use, choose unused ports and update the matching Vite settings and .env URL. See DDEV’s Vite setup guide for more configuration options.
What Updates Automatically?
Component and normally imported CSS changes update through hot module replacement (HMR). Some changes trigger a full-page reload instead, so do not rely on every edit preserving app state.
Refresh manually after PHP or Twig changes. Also refresh if changes to styles injected into a shadow root with ?inline do not appear. This guide does not add a PHP/Twig file watcher.
Return to Built Assets
Remove MY_PLUGIN_VITE_URL from .env (or leave it empty), stop Vite, and run:
npx vite buildReload the CP page to use the built assets again. Leave MY_PLUGIN_VITE_URL unset in production. While it is set, the bundle requires the dev server to be running.
Rebuild and Refresh Instead
If you prefer to keep the asset bundle loading dist/, leave MY_PLUGIN_VITE_URL unset and run:
npx vite build --watchVite rebuilds when source files change; refresh the Craft page after each build finishes. This option does not provide HMR or automatic page reloads.