Using a Vue app within a WordPress plugin (updated for Vite)
Written on December 3, 2025
This post is an updated version of Using a Vue app within a WordPress plugin from 2020. This newer post uses vite instead of the vue-cli-service webpack.
Creating a full-fledged Vue.js application inside a WordPress plugin takes a little bit of special setup to the two code libraries working together. Here I’ll show the way I’ve been solving this, though I’m sure it’s not the only way.
General plugin layout
I’ve structured the plugin like this:
- Main Plugin Directory
- main-plugin-file.php
- package.json
- /app
- package.json
- app.php
- index.html
- vite.config.js
- /src
- main.js
- App.vue
- …etc (all the Vue app stuff: /components, etc)
- /build
Note that there are two package.json files, one in the plugin root, and one in the vue app directory. That will become useful later if/when we want to create WordPress Blocks.
Setup
Install vite as a dependency and @vitejs/plugin-vue and vite as dev dependencies. I just installed vite and used it’s pre-configured vue setup to install a new application into the /app directory.
Your app/package.json should have:
...
"dependencies": {
"vue": "^3.5.24"
},
"devDependencies": {
"@vitejs/plugin-vue": "^6.0.1",
"vite": "^7.1.11"
...
}
...
The Scripts section
Vite doesn’t have a way to tell its dev command to write to disk rather than memory, so we need to use the build command and tweak it for our needs.
vite build --watch --sourcemap=true --minify=false
This is more-or-less a replacement for webpack-like write-dev-to-disk, with console debugging included. It just doesn’t have HMR (hot-module-reloading).
So the build section should be:
...
"scripts": {
"dev": "vite build --watch --sourcemap=true --minify=false",
"build": "vite build"
},
...
Add to the root-level package.json file some alias scripts that will run things within the app directory:
Using NPM it looks like this:
...
"scripts": {
"postinstall": "npm install --prefix ./app",
"build:app": "npm run build --prefix ./app",
"watch:app": "npm run serve --prefix ./app",
...
},
...
The –prefix flag tells npm to run as if inside the given directory, which has the effect of running the given scripts in the “app” directory’s package.json. But you can run them from the root directory, without having to fool around with changing directories. More information on npm folders.
The postinstall script tells npm to automatically install the subdirectory project after install the main project.
Using PNPM it looks like this:
...
"scripts": {
"postinstall": "pnpm -C app install",
"dev": "pnpm -C app dev",
"build": "pnpm -C app build"
},
...
The -C flag tells pnpm to run in the given directory.
vite.config.js
We need to customize a few things to get our vite playing nicely within the WordPress environment.
Inside the object that’s being fed into the defineConfig function, we’ll add the following:
filenameHashing: false
This will ensure that the compiled javascript file will always have the same name. We need because WordPress is always going to look in the same place for the file, and we don’t want to have to update our wp_enqueue_scripts call every time the file updates.
base: './'
This will force vite to use relative paths, which is what is needed an embedded WordPress plugin.
build: {
outDir: 'build'
This specifies what directory the built files should be placed in (by default vite places the output in the dist directory. Changing this is not needed, but I like to change it so I can have consistency across built Vue apps and WordPress blocks.
build: {
rollupOptions: {
output: {
entryFileNames: `assets/[name].js`,
chunkFileNames: `assets/[name].js`,
assetFileNames: `assets/[name].[ext]`
These are needed so that all the files are created without the hash.
NOTE: At this time, there is no way to use hot reloading with vite in this setup. You’ll need to actually hit cmd/ctrl + R or F5 like in the old days.So here’s the complete vite.config.js:
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// https://vite.dev/config/
/** @type {import('vite').UserConfig} */
export default defineConfig({
plugins: [vue()],
filenameHashing: false,
base: './',
build: {
outDir: 'build',
rolldownOptions: {
output: {
entryFileNames: `assets/[name].js`,
chunkFileNames: `assets/[name].js`,
assetFileNames: `assets/[name].[ext]`
}
},
},
})
index.html
Vite wants you to have an index.html as a base for its builds. You can replace the default content inside the index.html (that the vite installer placed) with:
<div id="app"></div>
<script type="module" src="./src/main.js"></script>
You won’t need to actually use this html file, but it makes vite work.
Including the app in WordPress
Now I create a php function which will load the app and it’s respective files. Then I can run that function inside a shortcode or in the block callback as desired.
function jp_do_vue_app ( $atts=[] ) {
// coming soon...
}
Note!
If you are including Lodash in your app, that might not play nicely with the Block Editor. Even though I inserted my app as a shortcode in the shortcode block (which doesn’t attempt to render anything in the admin, the lodash in my app overwrote the lodash in the block editor and I got The White Screen.
So be sure to use this at the top of your php function:
if( is_admin() )
return;
Now we’ll get to actually including the app files. First include the js file:
wp_enqueue_script( 'my-vue-app',
plugins_url( '/app/build/assets/index.js', __FILE__ ),
array(),
'1.0.0'
);
And if you have styles, enqueue those too:
wp_enqueue_style( 'my-vue-app',
plugins_url( '/app/build/assets/index.css', __FILE__),
array(),
'1.0.0'
);
Lastly, return the html that the vue app will hook onto:
return '<div id="app"></div>';
You can change the id, just make sure it matches the app given to the Vue constructor in the app/src/main.js file.
Optionally Include WordPress Data
If you are including any data from the server that should be accessible to the app, you can do that by using wp_add_inline_script() above the return statement. Something like this:
$my_js = <<<JS
window.my_vue_config = { 'placeConfigHere': true };
JS;
wp_add_inline_script('my-vue-app', $my_js, 'before' );
Be sure to include the third parameter of “before” so your variables are available to the app.
Lots of options open up here. You could also use some of the parameters from the function (or shortcode attributes), and pass those to the vue app using this method.
Of course, you can also do this after the fact by using the ajax admin url and setting up some Ajax functions to get the data.
Full php function
Here’s the full function, and I’ve just hooked it up to a shortcode for now.
function jp_do_vue_app ( $atts=[] ) {
if( is_admin() )
return;
wp_enqueue_script( 'my-vue-app',
plugins_url( '/app/build/assets/index.js', __FILE__ ),
array(),
'1.0.0'
);
wp_enqueue_style( 'my-vue-app',
plugins_url( '/app/build/assets/index.css', __FILE__),
array(),
'1.0.0'
);
return '<div id="app"></div>';
}
add_shortcode( 'jp-vue-app', 'jp_do_vue_app' );
Development & Production
To develop your app, be sure the plugin is enabled in WordPress. Then just run pnpm dev from the plugin’s root directory. Now simply insert your shortcode/block in the page, and your vue app will be there on your public page. Refresh the page manually as you make changes to your app.
When ready for production, don’t forgot to run pnpm build to get the minified production build.
Hooking the Vue app up to a WordPress Block
You can also use a Block to set attributes which will get passed to the Vue app. Create your block, with whatever attributes you want, but I didn’t try to render the actual vue app itself inside the block editor (not even a serverSideRender) because I didn’t want to try to bootstrap Vue with React already in the mix. Instead I just displayed the controls in the block area.
Let the Block’s save function return null, and in the php register_block_type() function, in the render_callback property, return the jp_do_vue_app function, passing along to it the $atts parameter directly.