# Upgrade Guide

# 0.18.0

# Vue 3 - General

This release upgrades Vue from 2.6 to 3.2. Vue 3 in Nodewood is configured in "compatibility mode", which gives you time to convert your UI components into a form that is supported by Vue 3 (and for the Vue modules that Nodewood relies on to be converted as well).

To enable your project to work in Vue 3's compatibility mode, you must do the following:

  • In your root package.json:
    • Update eslint to "eslint": "^7.19.0".
  • In app/package.json:
    • Add the following to your dependencies:
      • "@vue/compat": "^3.2.0"
      • "@vue/compiler-sfc": "^3.2.0"
    • Update the following in your dependencies:
      • "vue": "^3.2.0",
      • "vue-router": "^4.0.10",
      • "vuex": "^4.0.2"
    • Remove the following from your dependencies:
      • vue-template-compiler
  • In your root .eslintrc.js:
    • Add the following section (or add these entries to your existing globals section):
      globals: {
        // Vue 3 compiler macros
        defineProps: 'readonly',
        defineEmits: 'readonly',
        defineExpose: 'readonly',
        withDefaults: 'readonly',
    • Add the following to the rules section:
        'vue/no-v-for-template-key': 'off', // Conflicts with vue3
        'import/no-extraneous-dependencies': 'off', // Conflicts with wood folder imports
    • Change 'plugin:vue/recommended' to 'plugin:vue/vue3-recommended'.
  • If you have added a chainWebpack entry in your app/vue.config.js, you must make sure it includes the following:
  chainWebpack: (config) => {
    config.resolve.alias.set('vue', '@vue/compat');

      .tap((options) => ({
        compilerOptions: { compatConfig: { MODE: 2 } },
  • Run yarn install.
  • Finally, update your app/ui/main.js to the following:
import app from '@wood/ui/main';

export default app;

If you have customized your app's main.js, you'll have to add those customizations back in, but this is much easier now due to new functions in #ui/configure that can be extended or overridden.

# Vue 3 - Stores

The application initialization has been changed to be a synchronous process, so store initialization must also be changed to be synchronous. You will need to change the initStores function in your ui/init.js files to look like the following:

/* eslint-disable global-require */
store.registerModule('Example', require('#features/examples/ui/stores/ExampleStore'));
// DO NOT REMOVE: Generated stores will be added above this line
/* eslint-enable */

# Vue 3 - Routes

Instead of adding routes to a router object, routes are collected into an array and passed into the router object at creation. This function is no longer asynchronous, either, as application initialization has changed to be synchronous instead. This means that the initRoutes function in your ui/init.js files will need to change to look like the following:

 * Initialize Vue routes for this feature.
 * @return {Array}
initRoutes() {
  const routes = [];

    path: '/example',
    name: 'example',
    component: () => import(/* webpackChunkName: "example" */ '#features/examples/ui/pages/ExamplePage'),

  // DO NOT REMOVE: Generated routes will be added above this line

  return routes;

# Vue 3 - Charts

vue-chartjs is not Vue 3-compatible and likely will not be updated to be so, so we have switched to vue-chart-3. It can be used mostly the same way, but you may need to consult their documentation (opens new window).

Once your charts have been converted to vue-chart-3, make sure to remove vue-chartjs from your app/package.json.

# Vue 3 - Adding PrimeVue

This release adds the PrimeVue component library (opens new window), primarily to replace vue-js-modal (which looks likely that it will not be updated to work with Vue 3). In order to enable this, you will need to create a postcss.config.js file in your project root:

module.exports = {
  plugins: [
    require('autoprefixer'), // eslint-disable-line global-require, import/no-extraneous-dependencies

# Vue 3 - Dialog component changed to PrimeVue Dialog

The Dialog component Nodewood was using (vue-js-modal) is stuck on Vue 2, and is likely not going to be updated to be compatible with Vue 3. As a result, in this release, we have changed all the Nodewood dialogues to use the PrimeVue Dialog component, and you will need to do so as well.

First, you will need to add a visible entry in your data entry for your dialog component:

data: () => ({
  visible: false,

Next, you'll need to replace your <modal> tag with a <Dialog> tag:

  header="Edit User"

The header property replaces the contents of the h2.dialog-title, and by default, all PrimeVue Dialogs come with a close button. To retain the style Nodewood dialogs had before, set closable to false. The visible property controls whether the dialog is shown or hidden, so you'll need to change your openDialog() and closeDialog() functions:

openDialog() {
  this.visible = true;
closeDialog() {
  this.visible = false;

Styling dialogs is now much easier! You can set the :style property on your Dialog component and style it directly with CSS, and use :breakpoints to add responsive styles. For a dialog that starts at 50% width, but switches to 100% width when the viewport is less than 640px, you can do the following:

  :style="{ width: '50vw' }"
  :breakpoints="{'640px': '100vw'}"
  header="Edit User"

Finally, make sure to remove vue-js-modal from your app/package.json file, to keep your bundle size small.

# Vue 3 - Toast component changed to PrimeVue Toast

vue-toasted (opens new window) has been removed, since it is only Vue 2-compatible and looks like it will not be updated for 3. Instead, it has been replaced with [PrimeVue Toasts](https://www.primefaces.org/primevue/showcase/#/toast, easily accessible through helper functions in #ui/lib/toast.js:

import errorToast from '#ui/lib/toast';

// ...

errorToast('Credit card invalid.');

The toast title and other options can be passed in as additional parameters to further customize the toast.

Once finished, make sure to remove vue-toasted from your app/package.json and run yarn install.

# Vue 3 - vue-loadable removed, replaced with vue-is-loading

vue-loadable (opens new window) has been removed, since it is only Vue 2-compatible and looks like it will not be updated for Vue 3. Instead, it has been replaced with vue-is-loading (opens new window), a Vue 3 javascript port. This is a drop-in port, and can be used as before.

If using Vue 3's Composition API (opens new window), you can use vue-is-loading's loadable function directly, like so:

    <loading-spinner v-if="$isLoading('saveUser')" />

    <button @click="submit">

<script setup>
import { ref } from 'vue';
import { loadable } from 'vue-is-loading';

const name = ref('');
const saveUser = loadable(
  (values) => store.dispatch('Users/saveUser', values),
const submit = async () => {
  await saveUser({ name });

Once finished, make sure to remove vue-loadable from your app/package.json and add "vue-is-loading": "1.0.0",, then run yarn install.

# Vue 3 - v-click-outside removed, replaced with click-outside-vue3

v-click-outside (opens new window) has been removed, since it is only Vue 2-compatible and looks like it will not be updated for Vue 3. Instead, it has been replaced with click-outside-vue3 (opens new window), a Vue 3 port. This is a drop-in port, and can be used as before.

You shouldn't need to make any changes to your code, as this directive is added in wood and shouldn't actually be referenced anywhere in app.

# Vue 3 - Final migration notes

From here, you will have to upgrade your own code from Vue 2 code to Vue 3 code (including any file generated by nodewood add). Lint messages and console warnings should indicate fairly clearly what changes you need to make (searching for the specific lint rule in question will give you examples for how to convert from "bad" code to "good" code), but the Vue docs have a more in-depth guide to migration (opens new window), if you have more advanced issues.

You may also need to update or switch libraries to Vue 3 compatible versions.

If you get stuck, feel free to email hello@nodewood.com with the details of the issue you're experiencing.

# Vue 3 - Troubleshooting

  • This dependency was not found: "vue"

Delete yarn.lock and run yarn install to get a fresh copy of the trouble libraries.

  • Syntax Error: Error: No PostCSS Config found in:

Make sure the add the postcss.config.js to your root folder as mentioned in the "Adding PrimeVue" section.

  • Miscellaneous linter errors

If you have new and persistent linter errors when working on your components or trying to build the UI, make sure to refer closely to this guide (especially old upgrade steps, if you are upgrading from anything but the most recent version) and ensure especially that all packages with eslint in them match the versions specified here.

# Styling

Some Tailwind styles have been simplified so that they behave more predictably across different browsers. A side effect of this is that your layout will completely blow up unless you modify your app/ui/public/index.html file and change the line <div id="app"></div> to <div id="app" class="w-full"></div>.

# 0.17.0


The migration in this release will create new teams and users_teams tables, and modifies the subscriptions table to be connected to the teams table instead of the users table. To do this, it creates teams and users_teams entries for every entry in users.

This process should be pretty safe and automatic! But if you've made any changes to your users table, there could be unexpected side-effects, so BACK UP YOUR DATABSE BEFORE APPLYING ANY MIGRATIONS FROM THIS RELEASE.

This goes double for any production databases you apply this to.

# New config files

Because of some upgrades made and how webpack works, you will need to create config files in app/config for all config files in wood/config or you will see "Cannot find module ./x" error messages in your browser console. These files can be effectively empty, just inheriting defaults from their "wood" counterparts, like so:

const woodConfig = require('@wood/config/teams');

 * @type {Object} Application teams config values.
module.exports = {
   * Start with default teams config.

  // Overwrite default configs here

# Updating your email configuration

The email transport configuration has been moved to its own file, so that you can import email address configuration values safely from the UI without needing to add email-sending libraries to your UI package, needlessly increasing file size.

To move your configuration, run nodewood eject config-api/email, then move the transportConfig value from your existing config/email entry to config-api/email, ensuring you also move the necessary require statements with it.

# Updating tests

The COOKIE_JWT_USER_ID_1 constant has been renamed to COOKIE_JWT_OWNER_ID_1 to reflect the fact that that it now contains an Owner role. (An COOKIE_JWT_OWNER_ID_1 constant representing a user with a member role has also been added.) You will need to use find and replace to rename this constant through your tests.

# Updating .env file

Add an entry for GENERAL_HASHID_SALT=generalHashidSalt, where generalHashidSalt should be a random series of characters that will be used to salt general-purpose Hashids created in your project. This should be different from your JWT_HASHID_SALT entry so that users cannot examine their JWTs and compare Hashids generated there and elsewhere and potentially glean information about your user IDs or other sequential values.

# ActiveUser

The Users store was renamed to the ActiveUser store for clarity. Now, instead of referencing Users.current alongside Users.role, Users.subscription, and Users.team, you reference ActiveUser.user, ActiveUser.role, ActiveUser.subscription and ActiveUser.team.

You will need to update your Vue files, so that anywhere you were referencing the Users store, you are now referencing the ActiveUser store, and anywhere you were referencing the current property of that store, you are now referencing the user property.

# UserModel

A "teams" attribute was added to UserModel. If you have extended this file, make sure to copy across the additions to the constructor() and toJSON() functions, as well as require TeamModel and RoleModel in your UserModel, as in the copy in wood.

# Configuration

Configuration values are now loaded with the getConfig() function defined in #lib/Config. This allows for easy configuration value overrides when testing. For more information, check the Configuration section of the docs. Your old ways of loading configuration values will still work, but this style is officially deprecated.

For added security, you'll need to move your transportConfig configuration value from config/email.js to config-api/email.js. This is a new folder that has protections to keep it from being loaded in the UI front-end, making it safe to store API back-end configuration secrets. When loading values from files in this folder, you'll need to use the getConfigApi() function defined in #lib/Config. This function works much like getConfig(), except with protections to ensure it cannot be called from within webpack, protecting these files from being included in your UI front-end.

# 0.16.0

# Modifying app/ui/main.js to take into account the new samples feature

Instead of polluting the default main.js with routes for sample files, those files and their routes have been moved to the samples feature in the wood library, which can be cleanly disabled by removing the feature from your app/config/app.js file. Consequently, those routes can be removed from app/ui/main.js, which can be pared down to simply:

import main from '@wood/ui/main';


You will also want to override the wood feature list in app/config/app.js, if you haven't already, and remove the samples feature, like so:

  features: {
    // (Keep your existing "app" features here!)

     * @type {Array<String>} List of enabled Nodewood features.
    wood: [
      // 'samples',

# Add eslint-plugin-promise to your eslintrc.js

The eslint-plugin-promise (opens new window) set of rules are designed to prevent you from making common mistakes with promises and async/await code. These mistakes can lead to especially hard-to-debug errors, so following these eslint rules will make a significant difference in your project.

To enable them, edit your .eslintrc.js file in your application root, and make sure the following two sections look like this:

  extends: [
    // Common

    // Vue
  plugins: [

# Updating your Webpack Dev Server, and enabling HMR

  1. Update the following line in the scripts section of your your root package.json:
    "dev-ui": "cd app && vue-cli-service serve",
  1. If you've created a custom docker-compose.yml file in app/docker, make sure to add the changes from the docker-compose.yml file in wood/docker, specifically the networks and ports section under ui, and the links section under nginx.

  2. If you've created a custom ngingx-default.conf file in app/docker, make sure to add the new location section from the docker-compose.yml file in wood/docker.

  3. If you use anything other than localhost for your local development environment (for example, if you've created a /etc/hosts entry for your Nodewood development), you'll need to put that URL in your devServer.public configuration in app/vue.config.js. For example, if you go to "https://develop.test/app" in your browser, you'll need to modify app/vue.config.js like so:

const config = require('../wood/vue.config.js');

module.exports = {

  devServer: {
    port: 9000,
    public: 'develop.test',
    stats: 'minimal',
    progress: false,
  1. Finally, you'll need to destroy and rebuild your Nginx images:
docker-compose -p PROJECT_NAME -f wood/docker/docker-compose.yml rm -sv nginx
docker-compose -p PROJECT_NAME -f wood/docker/docker-compose.yml up --build --force-recreate --no-deps nginx

Where PROJECT_NAME is the basename of the directory your project lives in. For example, if your project is in ~/projects/myproject, your PROJECT_NAME is myproject. If you have a period in your directory (for example myproject.com), the period is omitted completely for your PROJECT_NAME (myprojectcom). If you use a custom docker-compose.yml, change wood to app in the lines above.

# 0.15.0

# Error message: Cannot find module '@tailwindcss/forms'

You will see this error message during upgrade, as we are upgrading to Tailwind 2, which replaces the @tailwindcss/custom-forms plugin with the @tailwindcss/forms plugin. Since part of the Nodewood upgrade process is trying to apply your chosen Tailwind prefix to all Tailwind classes, this step will break.

Thankfully, it's a pretty easy fix:

  • Complete the Other Packages step below.
  • Run yarn install.
  • If you have set a Tailwind prefix, run nodewood tailwind:prefix and apply your prefix again.

# app/ui/main.js

All the code for this file has been moved to wood/ui/main.js so that Nodewood library updates to this file are automatically applied instead of relying on you to manually update this file. In order to take advantage of this, however, you will need to replace the content of app/ui/main.js with the following:

import main from '@wood/ui/main';
import router from '#ui/router';


  path: '/',
  name: 'home',
  component: () => import(/* webpackChunkName: "home" */ '#ui/pages/HomePage'),

  path: '/about',
  name: 'about',
  component: () => import(/* webpackChunkName: "about" */ '#ui/pages/AboutPage'),
  meta: {
    routeName: 'About',

  path: '/components',
  name: 'components',
  component: () => import(/* webpackChunkName: "components" */ '#ui/pages/ComponentsPage'),
  meta: {
    routeName: 'Components',

You can still import Vue at the beginning of this file and add custom Vue.use directives, etc, before calling main(), and if you have heavily customized this file already, you can leave your customizations in place. However, further updates to main.js may need to be manually applied to your customized file.

# Nginx

Tired of seeing all those OPTIONS logs? This release configures nginx to skip those lines when logging, resulting in cleaner log output. You'll need to rebuild your nginx Docker container to activate this new configuration:

docker-compose -p PROJECT_NAME -f wood/docker/docker-compose.yml build --no-cache nginx

Where PROJECT_NAME is the basename of the directory your project lives in. For example, if your project is in ~/projects/myproject, your PROJECT_NAME is myproject. If you have a period in your directory (for example myproject.com), the period is omitted completely for your PROJECT_NAME (myprojectcom).

# Tailwind 2

Tailwind 2 brings a lot of neat new capabilities, but it may require a little work to get working as expected. To start, you should read the official upgrade guide. (opens new window) Not all of it will be relevant, but it will give you an overview of what's changed.

Nodewood used the @tailwindcss/custom-forms plugin, which has been replaced with the @tailwindcss/forms plugin. This new plugin styles all forms cleanly by default, instead of just all the ones you added form-* classes to. You can safely go and remove all these classes, as they no longer do anything.

Of particular notice is how @apply statement order has changed (opens new window). This may mean that some classes now apply in a different order than you expect, and you'll have to make a small adjustment to that order in order to get your styles working again.

# Other Packages

Many other packages have had their versions updated as well, so you'll want to update the packages in the dependencies section of your app/package.json to the following versions:

    "@vue/cli-plugin-babel": "^4.5.11",
    "@vue/cli-plugin-eslint": "^4.5.11",
    "@vue/cli-plugin-unit-jest": "^4.5.11",
    "@vue/cli-service": "^4.5.11",
    "@vue/eslint-config-airbnb": "^5.3.0",
    "aws-sdk": "^2.839.0",
    "core-js": "^3.8.3",
    "debug": "^4.3.1",
    "enhanced-resolve": "^5.7.0",
    "eslint": "^7.19.0",
    "eslint-config-airbnb-base": "^14.2.1",
    "eslint-plugin-import": "^2.22.1",
    "eslint-plugin-jest": "^24.1.3",
    "eslint-plugin-vue": "^7.5.0",
    "hashids": "^2.2.8",
    "massive": "^6.6.5",
    "moment": "^2.29.1",
    "nodemailer": "^6.4.17",
    "nodemailer-mock": "^1.5.3",
    "pino": "^6.11.1",
    "stripe": "^8.135.0",
    "superagent": "^6.1.0",
    "supertest": "^6.1.3",
    "vue": "2.6.12",
    "vue-js-modal": "^2.0.0-rc.6",
    "vue-router": "^3.5.1",
    "vue-template-compiler": "2.6.12",
    "vuex": "^3.6.2"

You'll also want to update the packages in the devDependencies section of your package.json (in the root of your project) to the following versions:

    "nodemon": "^2.0.7",
    "eslint": "^7.19.0",
    "husky": "^5.0.9",
    "lint-staged": "^10.5.4"

In addition the following packages have been moved to the wood library or removed, and can be removed from all sections of your app/package.json file:


This will update some of the eslint rules that we uses to keep code tidy. Specifically, anywhere that you use a v-slot directive in your Vue components, you'll need to replace it with the # shorthand. Specifically, v-slot:slotname will need to change to #slotname.

# 0.13.0

This release greatly simplifies the specific inclusion of files in the app and wood folders. Instead of prefixing your includes with @wood-config/file or @app-ui/file, you will instead prefix them with @wood/config/file or @app/ui/file. This may seem superficial, but it means that we only have special cases for the wood and app folders, instead of special cases for every folder under each of them. This greatly simplifies the require logic behind the scenes, and means that you can now specifically require any file in the wood and app folders, not just ones with specifically-defined aliases.

Upgrade steps:

  1. Update _moduleAliases in package.json:
  "_moduleAliases": {
    "@app": "./app",
    "@wood": "./wood"
  1. Update moduleNameMapper in jest.config.js:
  moduleNameMapper: {
    '^@app(.*)$': '<rootDir>/app/$1',
    '^@wood(.*)$': '<rootDir>/wood/$1',
  1. Update settings.import/resolver.alias-array.map in .eslintrc.js:
        map: [
          ['#api', [
            resolve(__dirname, 'app/api'),
            resolve(__dirname, 'wood/api'),

          ['#config', [
            resolve(__dirname, 'app/config'),
            resolve(__dirname, 'wood/config'),
          ['#features', [
            resolve(__dirname, 'app/features'),
            resolve(__dirname, 'wood/features'),
          ['#lib', [
            resolve(__dirname, 'app/lib'),
            resolve(__dirname, 'wood/lib'),
          ['#ui', [
            resolve(__dirname, 'app/ui'),
            resolve(__dirname, 'wood/ui'),
          ['@app', resolve(__dirname, 'app')],
          ['@wood', resolve(__dirname, 'wood')],
  1. Update _moduleAliases in app/package.json:
  "_moduleAliases": {
    "@app": ".",
    "@wood": "../wood/"
  1. Replace all instances of @wood-root with @wood.
  2. Replace all instances of @app-root with @app.
  3. Replace all instances of @wood- with @wood/.
  4. Replace all instances of @app- with @app/.

# 0.12.0

This release is a significant change in the file layout of Nodewood. If you are upgrading from < 0.12.0 and do not perform these changes, your app will not work.

This release moves all files in api/src to api, lib/src to lib, and ui/src to ui to better match how the rest of the framework is structured. These files were originally in separate root folders, but were all moved under the wood folder a while back and should properly have had their src folder removed at that time.

But as they say, the best time to plant a tree is twenty years ago, and the second-best time is now.

Upgrade steps:

  1. Move all code from your app/api/src folder to the app/api folder.
  2. Move all code from your app/lib/src folder to the app/lib folder.
  3. Move all code from your app/ui/src folder to the app/ui folder.
  4. Update .eslintrc.js to remove the /src suffix for all entries.
  5. Update jest.config.js to change all instances of src/$1 to $1.
  6. Update package.json to remove the src suffix for all entries in _moduleAliases.
  7. Update package.json to change main to "app/api/api.js".
  8. Update package.json to change scripts.dev-api-only to "cd app/api && NODE_ENV=development nodemon --watch . --watch ../config --watch ../features --watch ../../wood -L ./api.js | npx pino-pretty"
  9. Update app/package.json to remove the src suffix for all entries in _moduleAliases.
  10. Update app/api/api.js and change the startServer line to const { startServer } = require('../../wood/api/api.js');.
  11. Update `app/
  12. Update any other custom code you have added that refers to api/src, lib/src, or ui/src.
  13. Update any other custom code you have added to the api, lib, or ui folders that access files from other folders using relative paths (i.e. if you have a sequence of ../../../, etc).

If you experience into any issues, please contact support at hello@nodewood.com.