Why donate
API Explorer
Upgrade Guide
Creating a New Project
The /quasar.config File
Convert q/app-webpack Project
Browser Compatibility
TypeScript Support
Directory Structure
Commands List
CSS Preprocessors
Page Routing with VueRouter
Lazy Loading - Code Splitting
Handling Assets
Boot Files
Prefetch Feature
API Proxying
Handling Vite
Handling import.meta.env
State Management with Pinia
Lint and Format Code
Testing & Auditing
Developing Mobile Apps
Ajax Requests
Opening Dev Server To Public
Quasar CLI with Vite - @quasar/app-vite v3
Upgrade Guide for Quasar CLI with Vite

Important!

This guide refers to upgrading a @quasar/app-vite v2 project to @quasar/app-vite v3. For older versions, please refer to https://legacy-app.quasar.dev.

A note to App Extensions owners

You might want to release new versions of your Quasar App Extensions with support for the new @quasar/app-vite. If you are not touching the quasar.config configuration, then it will be as easy as just changing the following:

api.compatibleWith(
  '@quasar/app-vite',
  '^2.0.0' // [!code --]
  '^3.0.0-rc.1' // [!code ++]
)

WARNING

The changes to the engine behind App Extensions will only be compatible with @quasar/app-vite v3+ going forward. You will have to drop support for @quasar/app-vite v2 and @quasar/app-webpack (any version).

Removed api.engine, api.hasVite, api.hasWebpack and api.hasLint.

All api.extendX(fn, api) methods can now be async and optionally return a (Rolldown/etc) config that will be merged with the default one.

api.extendX() example

api.extendSSRWebserverConf((rolldownConf, api) => {
  // add/remove/change Quasar CLI generated Rolldown config object

  // New! Now, optionally, we can also return a config object that will
  // be merged into the default one
  return {
    output: {
      banner: '/**! My Banner */'
    }
  }
})

Many new Index API methods:


/**
 * Add/remove/change properties of SSR production generated package.json
 *
 * Can be async. Can directly modify the "pkgJson" parameter or
 * return a new one that will be merged with the default one.
 */
api.extendSSRPackageJson: (
  pkgJson: { [index in string]: any },
  api: IndexAPI
) =>
  | void
  | { [index in string]: any }
  | Promise<void | { [index in string]: any }>;

/**
 * Extend/configure the Workbox GenerateSW options
 * Specify Workbox options which will be applied on top of
 *  `pwa > extendPWAGenerateSWOptions()`.
 *
 * https://developer.chrome.com/docs/workbox/the-ways-of-workbox/
 *
 * Can be async. Can directly modify the "config" parameter or
 * return a new one that will be merged with the default one.
 */
api.extendSSRGenerateSWOptions: (
  config: GenerateSWOptions,
  api: IndexAPI
) => void | GenerateSWOptions | Promise<void | GenerateSWOptions>;

/**
 * Extend/configure the Workbox InjectManifest options
 * Specify Workbox options which will be applied on top of
 *  `pwa > extendPWAInjectManifestOptions()`.
 *
 * https://developer.chrome.com/docs/workbox/the-ways-of-workbox/
 *
 * Can be async. Can directly modify the "config" parameter or
 * return a new one that will be merged with the default one.
 */
api.extendSSRInjectManifestOptions: (
  config: InjectManifestOptions,
  api: IndexAPI
) => void | InjectManifestOptions | Promise<void | InjectManifestOptions>;

A new api.logger (available on all four scripts: Index, Install, Uninstall, Prompts) prints in the Quasar CLI’s own output style and tags every line with your extension id. See api.logger.

There’s also new wrappers that @quasar/app-vite now supplies for the Index/Prompts/Install/Uninstall scripts. IDE auto-completion, here we come.

The short form of running CLI commands provided by an App Extension has been removed:

# works, still good; the way to go!
quasar run <ext-id> <cmd> [...args]

# this will NO LONGER WORK:
quasar <ext-id> <cmd> [...args]

And the params for api.registerCommand() have changed.

We’ve also massively upgraded the dev setup for AEs. You might want to do a top to bottom read of the AE docs again, starting with AE Development Guide and spawn a new AE project folder to take advantage of all the new goodies. TS variant included!

Bird’s eye view on what’s new

  • ⚡ Blazing Fast Compilation: We’ve replaced esbuild with Rolldown for /src-* folders and completely redesigned the build architecture. Build steps are now parallelized across all Quasar modes, resulting in significantly faster speeds and a smaller footprint for your production distributables.

  • ⚙️ Next-Gen Environment Management: We’ve redesigned env file management from the ground up. You will no longer need to restart the dev server when making changes to these files, and you can now use them directly within your quasar.config file too!

  • 🔒 Enhanced Security & Modern Standards: We’ve migrated from process.env to the modern import.meta.env (aligning with Vite’s native model) with full TypeScript support. A new security layer ensures client-side files only use a configurable prefix for env definitions, preventing potential leaks of sensitive data.

  • 📦 Smarter Dependency Isolation: We now have a clear separation of dependencies for each Quasar mode. You can install mode-specific packages directly in their respective /src-* folders. For example, the default Electron app will no longer require dependencies to be installed in its dist folder—only what you explicitly install in /src-electron will be included.

  • 🌍 Redesigned SSR Architecture: SSR mode now features superior support for custom web servers and proper TypeScript integration. When adding SSR, the CLI will prompt you to spawn a preconfigured /src-ssr folder using Hono, Fastify, Express, or Koa (let us know what other out-of-the-box servers you’d like!).

  • 📂 New Server Assets Folder for SSR: We’ve introduced a /src-ssr/server-assets folder alongside helpful utility functions. This makes it incredibly easy to reference assets (like HTTPS certificates) across dev and production runtimes, eliminating the strict need for an Apache/Nginx wrapper. We’ve also made the serverless support a breeze.

  • 🚀 Paving the Way for SSG: This new SSR architecture lays the necessary groundwork for us to finally release Static Site Generation (SSG) mode in the future.

  • 🖥️ Revamped Electron Mode: We’ve added lots of new features to make desktop development smoother. Similar to SSR, we’ve introduced a /src-electron/electron-assets folder. Referencing files from here (or from the /public folder) is now much easier via new utility methods available in both /src-electron and /src.

  • 🛣️ Vue Router: First-class support for the filename-based routing.

  • 🚀 Smarter reloads (when absolutely needed): You’ll notice the DX on dev has improved significantly, with even smarter heuristics when changing the quasar.config file or the dotenv files.

  • 🛠️ Modernized Core: The codebase has been updated to take full advantage of Node.js v22+ features, alongside countless other small but significant improvements across all Quasar modes to boost your productivity. The CLI uses significantly less dependencies.

Start the upgrade

TIP

If you are unsure that you won’t skip by mistake any of the recommended changes, you can scaffold a new project folder with the @quasar/app-vite v3 at any time and then easily start porting your app from there.


pnpm create quasar@latest

If you’re using PNPM v11, edit your /pnpm-workspace.yaml file. No longer needing the shamefullyHoist config.

/pnpm-workspace.yaml

# https://pnpm.io/settings

allowBuilds:
  '@parcel/watcher': true
  core-js: true
  electron-winstaller: true
  esbuild: true
  lightningcss: true
  rolldown: true
  unrs-resolver: true

Also, create a pnpm-workspace.yaml file inside /src-<bex|pwa|electron|ssr> with this content:

/src-<bex|pwa|electron|ssr>/pnpm-workspace.yaml

# This file exists to force pnpm install deps here, regardless of upper workspaces
# https://pnpm.io/settings

/package.json

Edit your /package.json on the @quasar/app-vite entry:

/package.json

"devDependencies": {
  "@quasar/app-vite": "^2.0.0", // [!code --]
  "@quasar/app-vite": "^3.0.0-rc.1" // [!code ++]
}

Make sure you also have Vue Router v5+ too, which is now the minimum version required!

/package.json

"dependencies": {
  "vue-router": "^5.0.6"
}

Global search and replace

Do a global search for #q-app/wrappers and replace with #q-app.

In an effort to better align with the Vue ecosystem, Quasar CLI now injects only one alias: @/. So please do a global search and replace in your code on your import statements like below. Alternatively, you can inject the old aliases yourself (take a look below the table to find out how).

AliasStatusDescription
@/New!Points to /src and replaces the old src alias.
app/RemovedReplace import to @/../
src/RemovedReplace import to @/
components/RemovedReplace import to @/components/
layouts/RemovedReplace import to @/layouts/
pages/RemovedReplace import to @/pages/
assets/RemovedReplace import to @/assets/. Replace ~assets/... in your .vue files in <template> section to ~@/assets/... too!
boot/RemovedReplace code using it by @/boot/
stores/RemovedReplace code using it by @/stores/

Alternative to alias changes

Should you want, you can inject the old aliases yourself and avoid the necessary changes above:

/quasar.config file

import { defineConfig } from '#q-app'

export default defineConfig(ctx => ({
  build: {
    alias: {
      src: ctx.appPaths.srcDir,
      app: ctx.appPaths.appDir,
      components: ctx.appPaths.resolve.src('components'),
      layouts: ctx.appPaths.resolve.src('layouts'),
      pages: ctx.appPaths.resolve.src('pages'),
      assets: ctx.appPaths.resolve.src('assets'),
      boot: ctx.appPaths.resolve.src('boot'),
      stores: ctx.appPaths.resolve.src('stores')
    }
  }
}))

Do a global search for process.env and replace with import.meta.env. For the Quasar supplied constants, you will need to prefix them with QUASAR_ too. Here’s a list:

process.env -> import.meta.env

// new boolean ones!
import.meta.env.QUASAR_SPA_MODE
import.meta.env.QUASAR_PWA_MODE
import.meta.env.QUASAR_SSR_MODE
import.meta.env.QUASAR_ELECTRON_MODE
import.meta.env.QUASAR_BEX_MODE
import.meta.env.QUASAR_CAPACITOR_MODE
import.meta.env.QUASAR_CORDOVA_MODE

process.env.DEV // [!code --]
process.env.PROD // [!code --]
import.meta.env.QUASAR_DEV
import.meta.env.QUASAR_PROD

// notice the DEBUGGING -> DEBUG change!
process.env.DEBUGGING // [!code --]
import.meta.env.QUASAR_DEBUG

process.env.MODE // [!code --]
process.env.TARGET // [!code --]
import.meta.env.QUASAR_MODE
import.meta.env.QUASAR_TARGET

process.env.CLIENT // [!code --]
process.env.SERVER // [!code --]
import.meta.env.QUASAR_CLIENT
import.meta.env.QUASAR_SERVER

process.env.SERVICE_WORKER_FILE // [!code --]
process.env.PWA_FALLBACK_HTML // [!code --]
process.env.PWA_SERVICE_WORKER_REGEX // [!code --]
import.meta.env.QUASAR_SERVICE_WORKER_FILE
import.meta.env.QUASAR_PWA_FALLBACK_HTML
import.meta.env.QUASAR_PWA_SERVICE_WORKER_REGEX

process.env.QUASAR_ELECTRON_PRELOAD_FOLDER // [!code --]
process.env.APP_URL // [!code --]
import.meta.env.QUASAR_ELECTRON_PRELOAD_FOLDER
import.meta.env.QUASAR_APP_URL

// removed; use ".cjs" instead: // [!code --]
process.env.QUASAR_ELECTRON_PRELOAD_EXTENSION // [!code --]

For the /index.html file, instead of relying on the previous “process.env.X”, you can now use:

<!-- old way, REPLACE! -->
<%= process.env.MY_ENV_VAR_OR_DEFINE %> // [!code --]

<!-- new way: -->
<%= importMetaEnv.MY_ENV_VAR_OR_DEFINE %> // [!code ++]
<!-- or shorthand: -->
%MY_ENV_VAR_OR_DEFINE% // [!code ++]

<% if (importMetaEnv.MY_ENV_VAR_OR_DEFINE) { %>Wow!<% } %>

Quasar mode package.json

Edit your /package.json file to remove Quasar mode specific dependencies and move them over to new /src-<mode>/package.json (create them!):

/package.json to new /src-<mode>/package.json

// create /src-bex/package.json: // [!code ++]
{
  "name": "quasar-bex-app",
  "version": "1.0.0",
  "description": "Quasar BEX Folder",
  "private": true,
  "type": "module",
  "devDependencies": {
    "@types/chrome": "^0.1.40" // for TS only
  }
}

Notable /quasar.config file changes

Edit your /quasar.config file. These are just the important changes that you need to be aware of:

/quasar.config file

build: {
  rawDefine: {}, // [!code --]
  define: {}, // values need to be JSON.stringify()

  env: {}, // [!code --]
  defineEnv: {}, // or long form "define" with 'import.meta.env.' prefix in key

  // change to "true" if needed; defaults to "false" now!
  vueOptionsAPI,

  // removed; deferring to Vite's default // [!code --]
  polyfillModulePreload, // [!code --]

  // new! Vue Router v5+ filename-based routing
  filenameBasedRouting: boolean | VueRouterVitePluginOptions,

  // NOT async, but it can now also return a new config
  // that will be merged with the default one
  extendTsConfig: (tsConfig: TSConfig) => void | TSConfig,

  // removed; add your preferred analyzer yourself; // [!code --]
  // example available below // [!code --]
  analyze, // [!code --]
},

sourceFiles: {
  // defaults to: 'src-pwa/register-sw' now!
  // change file name or set to your current one:
  pwaRegisterServiceWorker: 'src-pwa/register-service-worker',

  // defaults to 'src-pwa/custom-sw' now!
  // change file name or set to your current one:
  pwaServiceWorker: 'src-pwa/custom-service-worker',
},

cordova: {
  // no longer available; only modern build system
  noIosLegacyBuildFlag: true,  // [!code --]
},

ssr: {
  extendPackageJson (pkgJson) {}, // [!code --]
  // can now be async and optionally return object to be merged with default one
  extendSSRPackageJson (pkgJson) {},

  extendSSRWebserverConf (esbuildConf) {}, // [!code --]
  // can now be async and optionally return object to be merged with default one
  extendSSRWebserverConf (rolldownConf) {},

  pwaExtendGenerateSWOptions (conf) {}, // [!code --]
  pwaExtendInjectManifestOptions (conf) {}, // [!code --]
  // can now be async and optionally return object to be merged with default one
  extendSSRGenerateSWOptions (conf) {},
  // can now be async and optionally return object to be merged with default one
  extendSSRInjectManifestOptions (conf) {},
},

pwa: {
  // new! NOT async, but it can now also return a new config
  // that will be merged with the default one
  extendPWASwTsConfig: (tsConfig: TSConfig) => void | TSConfig,

  extendManifestJson (json) {}, // [!code --]
  // can now be async and optionally return object to be merged with default one
  extendPWAManifestJson (json) {},

  injectPwaMetaTags: boolean, // [!code --]
  injectPWAMetaTags: boolean,

  extendGenerateSWOptions (conf) {}, // [!code --]
  extendInjectManifestOptions (conf) {}, // [!code --]
  // can now be async and optionally return object to be merged with default one
  extendPWAGenerateSWOptions (conf) {},
  // can now be async and optionally return object to be merged with default one
  extendPWAInjectManifestOptions (conf) {},

  extendPWACustomSWConf (esbuildConf) {}, // [!code --]
  // can now be async and optionally return object to be merged with default one
  extendPWACustomSWConf (rolldownConf) {},
},

electron: {
  extendPackageJson (pkgJson) {}, // [!code --]
  // can now be async and optionally return object to be merged with default one
  extendElectronPackageJson (pkgJson) {},

  extendElectronMainConf (esbuildConf) {}, // [!code --]
  extendElectronPreloadConf (esbuildConf) {}, // [!code --]
  // can now be async and optionally return object to be merged with default one
  extendElectronMainConf (rolldownConf) {},
  // can now be async and optionally return object to be merged with default one
  extendElectronPreloadConf (rolldownConf) {},
},

bex: {
  extendBexScriptsConf (esbuildConf) {}, // [!code --]
  // can now be async and optionally return object to be merged with default one
  extendBexScriptsConf (rolldownConf) {},
}

Since build.analyze has been removed, here is how to manually do it now:

Alternatives for build.analyze

// pnpm/yarn/npm/bun add -D rollup-plugin-visualizer
// ...and yes, rollup-* as rollup plugins are compatible with Rolldown

import { defineConfig } from '#q-app'

export default defineConfig(ctx => {
  return {
    build: {
      vitePlugins: [
        ctx.prod
          ? [
              "rollup-plugin-visualizer",
              {
                open: true,
                filename: ctx.appPaths.resolve.cache("stats.html")
              },
              { client: true }
            ]
          : null
      ]
    }
  }
})

The ctx object now includes a logger that prints in the Quasar CLI’s own output style. See Logging via ctx.

TypeScript changes

The only .d.ts file that you need will be in the root of your project folder:

/env.d.ts

/**
 * Add types (that are not auto-magically added by Quasar CLI already)
 * for your custom variables to avoid TypeScript errors, like dynamic
 * process.env variables or definitions in dotenv files configured ONLY
 * for the /quasar.config file itself.
 *
 * @example
 * interface ImportMetaEnv {
 *   readonly MY_VAR: string;
 *   readonly MY_OTHER_VAR: string;
 * }
 */
interface ImportMetaEnv {}

You were previously using the following, which needs to be removed from all your .d.ts files. It would be a good idea to just have the /env.d.ts file defined above.

*env.d.ts

/**
 * REMOVE this! No longer needed.
 * Delete the entire block:
 */
declare namespace NodeJS {
  interface ProcessEnv {
    NODE_ENV: string
    VUE_ROUTER_MODE: 'hash' | 'history' | 'abstract' | undefined
    VUE_ROUTER_BASE: string | undefined
    // ...along with any other previously Quasar needed defines
  }
}

You can read about Handling import.meta.env. Highly recommended as it will show you all the new goodies.

Install new deps

Then pnpm/yarn/npm/bun install in the root folder and run quasar prepare. Restart your IDE to make sure the new dependencies have been correctly picked up.

Make sure to update your /quasar.config file with the newest specs in order to satisfy the types. Check all following sections.

Notable breaking changes

  • All imports from #q-app/wrappers need to be replaced with #q-app. Do a global search and replace.
  • Switched from process.env to the modern import.meta.env. Link
  • /quasar.config > build > vueOptionsAPI is now false by default
  • /quasar.config > build > polyfillModulePreload (removed and now defaulting to Vite’s own config for this)
  • /index.html -> new HTML Constant Replacement. <% if (process.env.X) %> will no longer work.
  • New dotenv files support, including for the /quasar.config file itself. By default, Quasar CLI will only look for .env and .env.local, but you can add other files as well to support the ones with prod/dev/mode suffixes.
  • /quasar.config > env is now used by the new dotenv support. Dropped build > rawDefine as well. Use the new build > define & defineEnv instead. Link
  • Boot files & preFetch > redirect() usage has changed. Need to return immediately after calling it. No longer supporting throwing an error (or returning a Promise) with the { url } syntax. Directly use redirect() instead.
  • The /quasar.config file can come with .js or .ts extensions only. Dropped support for .cjs, .mjs, .cts and .mts.
  • All Quasar Modes now need to install their specific dependencies directly under their /src-<mode> folder.
  • Replaced esbuild tool with Rolldown for all the specific Quasar mode files (under their /src-<mode> folders). This also has impact over all the /quasar.config extendX() methods, as they will now receive a Rolldown config object.
  • All /quasar.config extendX() methods can now be async and optionally return a (Rolldown/etc) config that will be merged with the default one.
  • Dropped support for Capacitor v4 and below
  • Dropped support for @electron/packager v18 and below
  • Cordova with iOS now uses the modern build system. The noIosLegacyBuildFlag has been removed.
  • The “quasar dev -m cordova” command now opens up the corresponding IDE instead (just like Capacitor mode)
  • The “quasar dev/build -m bex” command now defaults to “chrome” target, so the -t|--target option can be ommitted.

Electron mode changes

We are introducing quasarRuntime. More info.

Move your /src-electron/icons to /src-electron/electron-assets/icons (create the new electron-assets folder).

Preload script

/src-electron/electron-preload (Optional!)

/**
 * Only one preload script should contain this
 */

import { contextBridge } from 'electron'
import { quasarRuntime } from '#q-app/electron/preload'

/**
 * Can be used in the renderer process through `window.quasarRuntime`
 */
contextBridge.exposeInMainWorld('quasarRuntime', quasarRuntime)

Main script

And you might also want to update your /src-electron/electron-main script:

/src-electron/electron-main

import { fileURLToPath } from 'url' // [!code --]
const currentDir = fileURLToPath(new URL('.', import.meta.url)) // [!code --]

import {
  registerQuasarRuntime,
  resolveElectronAssetsPath
} from "#q-app/electron/main"

let mainWindow: BrowserWindow | undefined; // [!code --]
async function createWindow() { // [!code --]
  mainWindow = new BrowserWindow({ // [!code --]
    icon: path.resolve(currentDir, 'icons/icon.png'), // tray icon // [!code --]
    webPreferences: { // [!code --]
      preload: path.resolve( // [!code --]
        currentDir, // [!code --]
        path.join(process.env.QUASAR_ELECTRON_PRELOAD_FOLDER, 'electron-preload' + process.env.QUASAR_ELECTRON_PRELOAD_EXTENSION) // [!code --]
      ), // [!code --]
    }, // [!code --]
async function createWindow() {
  const mainWindow = new BrowserWindow({
    icon: resolveElectronAssetsPath("icons/icon.png"), // linux
    webPreferences: {
      preload: path.join(import.meta.dirname, "electron-preload.cjs")
    },

   if (process.env.DEV) { // [!code --]
     await mainWindow.loadURL(process.env.APP_URL); // [!code --]
   } // [!code --]
   if (import.meta.env.QUASAR_DEV) {
     await mainWindow.loadURL(import.meta.env.QUASAR_APP_URL);
   }

   mainWindow.on('closed', () => { // [!code --]
     mainWindow = undefined; // [!code --]
   }); // [!code --]
}

void app.whenReady().then(createWindow); // [!code --]
app.on('window-all-closed', () => { // [!code --]
  if (platform !== 'darwin') { // [!code --]
    app.quit(); // [!code --]
  } // [!code --]
}); // [!code --]
app.on('activate', () => { // [!code --]
  if (mainWindow === undefined) { // [!code --]
    void createWindow(); // [!code --]
  } // [!code --]
}); // [!code --]
void app.whenReady().then(async () => {
  await registerQuasarRuntime();
  void createWindow();
  app.on("activate", () => {
    if (BrowserWindow.getAllWindows().length === 0) {
      void createWindow();
    }
  });
});
app.on("window-all-closed", () => {
  if (platform !== "darwin") {
    app.quit();
  }
});

Other Electron Mentions

SSR mode changes

We’ve added way better support for non-Express.js webservers and highly improved typings. When the SSR mode is added to a project, the Quasar CLI will ask what webserver you would like to use. You can pick from Hono/Express.js/Fastify/Koa.

Instead of diffing here, you might want to check the next pages (even if you still want to stay with Express.js):

  • Installing SSR Dependencies
  • Webserver: check out examples with Hono/Express/Fastify/Koa: SSR Webserver; or remove and add SSR mode again.
  • Middlewares: check out examples with Hono/Express/Fastify/Koa: SSR Middleware; or remove and add SSR mode again.
  • Check out the SSR Handling of 404 and 500 Errors page.
  • If using a serverless architecture, then check out the new Serverless section in SSR Webserver page.
  • You might also want to use thew new /src-ssr/server-assets folder (create it). This is copied as-is to dist and can be used in dev too, through resolve.serverAssets() or folders.serverAssets.
  • One more thing to note, for SSR middlewares: serve.error() has been changed to serve.devError() (with new params).

PWA mode changes

/src-pwa/sw/ subfolder for the service worker

The service worker and the PWA-specific tsconfig.json (for TypeScript projects) now lives inside /src-pwa/sw/. The main-thread file register-sw.{js,ts} stays at the /src-pwa/ root.

Background: TypeScript does NOT pick up nested tsconfig.json files when running tsc/vue-tsc from the project root, so the previous flat layout (src-pwa/custom-sw.ts + sibling tsconfig.json) produced false errors like Property 'skipWaiting' does not exist on type 'ServiceWorkerGlobalScope'. Also, you could not use DOM types (e.g., location.reload()) inside the register service worker script when it works perfectly in runtime. Quasar’s generated .quasar/tsconfig.json now auto-excludes /src-pwa/sw/ so root tsc/vue-tsc skip it, it is checked separately, and the nested tsconfig handles SW typing in the IDE as before.

Migration steps:

  1. Create /src-pwa/sw/.

  2. Move /src-pwa/custom-sw.{js,ts} -> /src-pwa/sw/custom-sw.{js,ts}.

  3. TS only: move /src-pwa/tsconfig.json -> /src-pwa/sw/tsconfig.json then replace the contents with a thin pointer to the Quasar-generated SW config:

    /src-pwa/sw/tsconfig.json

    {
      "extends": "../../.quasar/tsconfig.pwa-sw.json"
    }

    The generated `.quasar/tsconfig.pwa-sw.json` handles the WebWorker lib swap and the scoped include/exclude. If you had custom settings in your old `src-pwa/tsconfig.json`, you can use `quasar.config file > pwa > extendPWASwTsConfig` to customize the generated one. See [PWA with TypeScript](/quasar-cli-vite/developing-pwa/pwa-with-typescript) for more information.

  4. Update your ESLint config glob:

    {
      files: ['src-pwa/custom-service-worker.ts'], // [!code --]
      files: ['src-pwa/sw/**/*.ts'], // [!code ++]
      languageOptions: {
        globals: {
          ...globals.serviceworker
        }
      }
    }

  5. If you set sourceFiles.pwaServiceWorker explicitly in quasar.config, update it:

    sourceFiles: {
      pwaServiceWorker: 'src-pwa/custom-service-worker', // [!code --]
      pwaServiceWorker: 'src-pwa/sw/custom-sw', // [!code ++]
    }

    If you don't set it, the new default kicks in automatically.

  6. (Optional) TypeScript + ESLint only: to type-check the SW during dev/build, add a typescript entry to your vite-plugin-checker options (alongside vueTsc: true):

/quasar.config.ts

vitePlugins: [
  [
    'vite-plugin-checker',
    {
      vueTsc: true,
      typescript: { // [!code ++]
        tsconfigPath: './src-pwa/sw/tsconfig.json' // [!code ++]
      } // [!code ++]
      // ...
    },
    { server: false }
  ]
]
  1. (Optional) TypeScript: add a package.json script to check both root and SW types:

    /package.json

    "scripts": {
     "typecheck": "vue-tsc --noEmit && tsc --project src-pwa/sw/tsconfig.json --noEmit", // [!code ++]
      // ...
    }

Capacitor mode changes

capacitor.config in js/ts form

The new @quasar/app-vite adds support for capacitor.config.js and capacitor.config.ts files, and drops support for capacitor.config.json. The .js and .ts variants are much more flexible and do not have the git noise of the .json one, which was being rewritten on every “quasar dev” / “quasar build” with relevant fields. You must migrate to capacitor.config.ts (for TypeScript projects) or capacitor.config.js (for JS projects) before upgrading, more details below.

The “quasar mode add capacitor” command now scaffolds capacitor.config.js for JS projects, or a capacitor.config.ts for TypeScript projects. See Configuring Capacitor for more information. Config files use the new defineCapacitorConfig helper from “@quasar/app-vite/capacitor”:


const { defineCapacitorConfig } = require('@quasar/app-vite/capacitor')

module.exports = defineCapacitorConfig({
  appId: 'org.example.app',
  appName: 'My App'
})

The helper defaults webDir to 'www', injects server.url (and server.cleartext: true on Android) in dev mode, and types your input against CapacitorConfig from “@capacitor/cli”. Your own values always win, and the source file isn’t mutated. Inside the config, import.meta.env.QUASAR_DEV, QUASAR_TARGET, QUASAR_APP_URL, and your own .env / build.env values are available. Read Configuring Capacitor for more information.

To migrate from “.json”, replace the file with a defineCapacitorConfig({...}) call carrying the same fields. webDir can be dropped:

Migrating from capacitor.config.json

{ // [!code --]
  "appId": "org.example.app", // [!code --]
  "appName": "My App", // [!code --]
  "webDir": "www" // [!code --]
} // [!code --]
const { defineCapacitorConfig } = require('@quasar/app-vite/capacitor');

module.exports = defineCapacitorConfig({
  appId: 'org.example.app',
  appName: 'My App'
});

Removed: quasar.config > capacitor.{appName, version, description}

Three fields under quasar.config > capacitor are gone. None of them did what they appeared to.

The version and description were never read by the Capacitor CLI, neither from capacitor.config.* nor from src-capacitor/package.json. iOS and Android take their versions from android/app/build.gradle (versionName / versionCode) and ios/App/App/Info.plist (CFBundleShortVersionString / CFBundleVersion). Edit those directly when bumping for a store release. See Publishing to Store.

The appName had some effect, but it was limited. Capacitor writes it into Info.plist’s “CFBundleDisplayName” (iOS) and “strings.xml” > app_name (Android), but only at “cap add” time. The “cap sync” and “cap copy” commands don’t re-run that step, so a quasar.config file field suggested a live setting it wasn’t. It’s now captured via a prompt during “quasar mode add capacitor”, written into the scaffolded capacitor.config.*, and applied to the native projects when you add the platform. Later renames happen by editing Info.plist and strings.xml directly, or by removing and re-adding the platform.

If you were setting any of these, remove them:

/quasar.config file

capacitor: {
  appName: 'My App', // [!code --]
  version: '1.2.0', // [!code --]
  description: 'My great app' // [!code --]
  // hideSplashscreen, capacitorCliPreparationParams remain
}

src-capacitor/package.json no longer rewritten

Quasar used to overwrite “name”, “version”, “description”, and “author” in src-capacitor/package.json on every “quasar dev/build” command. Capacitor’s CLI doesn’t read most of that, so the rewrites were churn for no benefit (and noise in git). New projects scaffold a static quasar-capacitor-app / 1.0.0 template. Existing projects can update theirs to match, or leave it alone. Quasar won’t touch it either way.

Other considerations

Switching to Oxlint and Oxfmt

You may also want to switch your linting and formatting to oxlint and oxfmt. In our opinion, this is the future anyway. At some point in the near future, Quasar’s project scaffolding package will only offer this for linting.

As of writing these lines, the support for .vue files is not yet fully ready, but you will still be able to enjoy it a lot.

More info

Filename-based routing with Vue Router v5+

We now have first-class support for Vue Router’s filename-based routing. You might want to give it a try.

Upgrade to @quasar/extras v2

Optionally (but highly recommended) also upgrade to the new @quasar/extras v2: Release notes.

New CLI command options

For all commands: --no-color

By default, all CLI commands output colored text in the terminal (when not running in a CI environment). Should you wish to avoid this, use the --no-color when you run any of the CLI commands.

For build command: --no-summary

Should you want your build to skip printing the build summary (and thus being slightly faster) after building your app:

quasar build --no-summary

Running AE commands

The short form of running CLI commands provided by an App Extension has been removed:

# works, still good; the way to go!
quasar run <ext-id> <cmd> [...args]

# this will NO LONGER WORK:
quasar <ext-id> <cmd> [...args]

CSP (Content Security Policy)

You may want to add a CSP meta tag in your /index.html. This is especially useful for Electron mode where a warning about the lack of one is displayed, but it’s a good security measure for all Quasar Modes too:

<!doctype html>
<html>
  <head>
    <!-- add to the head -->
    <meta
      http-equiv="Content-Security-Policy"
      content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline';<% if (ctx.dev) { %> connect-src 'self' ws://localhost:*; worker-src 'self' blob:;<% } %>"
    />
  </head>
</html>

TIP

This works great with Oxlint and Oxfmt. However, the above might need a bit of tweaking when using ESLint and vite-plugin-checker.

Final Note

A quick favor to ask: Please consider supporting our efforts! If you use Quasar at work, drop a message to your management about sponsoring us at https://donate.quasar.dev/. We rely on your support to make massive updates like this possible!

And don’t forget to enjoy your new modern setup! That’s it! 🚀