Why donate
API Explorer
Upgrade guide
NEW!
The quasar.config file
Convert project to CLI with Vite
Browser Compatibility
Supporting TypeScript
Directory Structure
Commands List
CSS Preprocessors
Routing
Lazy Loading - Code Splitting
Handling Assets
Boot Files
Prefetch Feature
API Proxying
Handling Vite
Handling process.env
State Management with Pinia
State Management with Vuex
Linter
Testing & Auditing
Developing Mobile Apps
Ajax Requests
Opening Dev Server To Public
Quasar CLI with Vite - @quasar/app-vite
Supporting TypeScript

If you didn’t select TypeScript support when creating your project, you can still add it later. This guide will show you how to add TypeScript support to your existing JavaScript-based Quasar project.

TIP

If you selected TypeScript support when creating your project, you can skip this guide.

Installation of TypeScript Support

Install the typescript package:


$ yarn add --dev typescript

Then, create /tsconfig.json file at the root of you project with this content:

{
  "extends": "@quasar/app-vite/tsconfig-preset",
  "compilerOptions": {
    // `baseUrl` should be set to the current folder to allow Quasar TypeScript preset to manage paths on your behalf
    "baseUrl": "."
  },
  "exclude": [
    "./dist",
    "./.quasar",
    "./node_modules",
    "./src-capacitor",
    "./src-cordova",
    "./quasar.config.*.temporary.compiled*"
  ]
}

Now you can start using TypeScript into your project. Note that some IDEs might require a restart for the new setup to fully kick in.

TIP

Remember that you must change the extension of your JavaScript files to .ts to be allowed to write TypeScript code inside them. To use TypeScript in Vue files, you must update the script tag to include the lang="ts" attribute, like <script lang="ts"> or <script setup lang="ts">

WARNING

If you forget to add the tsconfig.json file, the application will break at compile time!

Linting setup

First add the needed dependencies:


$ yarn add --dev eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin
# you might also want to install the `eslint-plugin-vue` package.

Then update your ESLint configuration accordingly, like in the following example:

/.eslintrc.cjs

const { resolve } = require('node:path');

module.exports = {
  // https://eslint.org/docs/user-guide/configuring#configuration-cascading-and-hierarchy
  // This option interrupts the configuration hierarchy at this file
  // Remove this if you have an higher level ESLint config file (it usually happens into a monorepos)
  root: true,

  // https://eslint.vuejs.org/user-guide/#how-to-use-custom-parser
  // Must use parserOptions instead of "parser" to allow vue-eslint-parser to keep working
  // `parser: 'vue-eslint-parser'` is already included with any 'plugin:vue/**' config and should be omitted
  parserOptions: {
    // https://typescript-eslint.io/packages/parser/#configuration
    // Needed to make the parser take into account 'vue' files
    extraFileExtensions: ['.vue'],
    parser: require.resolve('@typescript-eslint/parser'),
    project: resolve(__dirname, './tsconfig.json'),
    tsconfigRootDir: __dirname,
  },

  env: {
    browser: true,
    es2021: true,
    node: true
  },

  // Rules order is important, please avoid shuffling them
  extends: [
    // Base ESLint recommended rules
    'eslint:recommended',

    // https://typescript-eslint.io/getting-started/legacy-eslint-setup
    // ESLint typescript rules
    'plugin:@typescript-eslint/recommended',
    // consider disabling this class of rules if linting takes too long
    'plugin:@typescript-eslint/recommended-requiring-type-checking',

    // https://eslint.vuejs.org/rules/#priority-a-essential-error-prevention
    // consider switching to `plugin:vue/strongly-recommended` or `plugin:vue/recommended` for stricter rules
    'plugin:vue/essential',

    // --- ONLY WHEN USING PRETTIER ---
    // https://github.com/prettier/eslint-config-prettier#installation
    // usage with Prettier, provided by 'eslint-config-prettier'.
    'prettier',
  ],

  plugins: [
    // required to apply rules which need type information
    '@typescript-eslint',

    // https://eslint.vuejs.org/user-guide/#why-doesn-t-it-work-on-vue-file
    // required to lint *.vue files
    'vue',
  ],

  // add your custom rules here
  rules: {
    // others rules...

    // TypeScript
    'quotes': ['warn', 'single'],
    // this rule, if on, would require explicit return type on the `render` function
    '@typescript-eslint/explicit-function-return-type': 'off',
    // in plain CommonJS modules, you can't use `import foo = require('foo')` to pass this rule, so it has to be disabled
    '@typescript-eslint/no-var-requires': 'off',
  }
}

If anything goes wrong, read the typescript-eslint guide, on which this example is based.

As a last step, update your package.json > scripts > lint script to also lint .ts files. Example:

/package.json

{
  "scripts": {
-   "lint": "eslint --ext .js,.vue .",
+   "lint": "eslint --ext .js,.ts,.vue .",
  }
}

TypeScript Declaration Files

If you chose TypeScript support when scaffolding the project, these declaration files were automatically scaffolded for you. If TypeScript support wasn’t enabled during project creation, create the following files.

/src/shims-vue.d.ts

/* eslint-disable */

/// <reference types="vite/client" />

// Mocks all files ending in `.vue` showing them as plain Vue instances
declare module '*.vue' {
  import type { DefineComponent } from 'vue';
  const component: DefineComponent<{}, {}, any>;
  export default component;
}
/src/quasar.d.ts

/* eslint-disable */

// Forces TS to apply `@quasar/app-vite` augmentations of `quasar` package
// Removing this would break `quasar/wrappers` imports as those typings are declared
//  into `@quasar/app-vite`
// As a side effect, since `@quasar/app-vite` reference `quasar` to augment it,
//  this declaration also apply `quasar` own
//  augmentations (eg. adds `$q` into Vue component context)
/// <reference types="@quasar/app-vite" />
/src/env.d.ts

/* eslint-disable */

declare namespace NodeJS {
  interface ProcessEnv {
    NODE_ENV: string;
    VUE_ROUTER_MODE: 'hash' | 'history' | 'abstract' | undefined;
    VUE_ROUTER_BASE: string | undefined;
    // Define any custom env variables you have here, if you wish
  }
}

See the following sections for the features and build modes you are using.

Pinia

If you are using Pinia, add the section below to your project. Quasar CLI provides the router property, you may need to add more global properties if you have them.

/src/stores/index.ts

import { Router } from 'vue-router';

/*
 * When adding new properties to stores, you should also
 * extend the `PiniaCustomProperties` interface.
 * @see https://pinia.vuejs.org/core-concepts/plugins.html#typing-new-store-properties
 */
declare module 'pinia' {
  export interface PiniaCustomProperties {
    readonly router: Router;
  }
}

Vuex

If you are using Vuex, add the section below to your project. Quasar CLI provides the router property, you may need to add more global properties if you have them. Adjust the state interface to suit your application.

/src/store/index.ts

import { InjectionKey } from 'vue'
import { Router } from 'vue-router'
import {
  createStore,
  Store as VuexStore,
  useStore as vuexUseStore,
} from 'vuex'

export interface StateInterface {
  // Define your own store structure, using submodules if needed
  // example: ExampleStateInterface;
  // Declared as unknown to avoid linting issue. Best to strongly type as per the line above.
  example: unknown
}

// provide typings for `this.$store`
declare module 'vue' {
  interface ComponentCustomProperties {
    $store: VuexStore<StateInterface>
  }
}

// Provide typings for `this.$router` inside Vuex stores
declare module "vuex" {
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
  export interface Store<S> {
    readonly $router: Router;
  }
}

// provide typings for `useStore` helper
export const storeKey: InjectionKey<VuexStore<StateInterface>> = Symbol('vuex-key')

export function useStore() {
  return vuexUseStore(storeKey)
}

// createStore<StateInterface>({ ... })

PWA mode

If you are using PWA mode, make the following modifications to your project, and create any files that do not exist:

/src-pwa/pwa-env.d.ts

/* eslint-disable */

declare namespace NodeJS {
  interface ProcessEnv {
    SERVICE_WORKER_FILE: string;
    PWA_FALLBACK_HTML: string;
    PWA_SERVICE_WORKER_REGEX: string;
  }
}
/src-pwa/custom-service-worker.ts

// at the top of the file
declare const self: ServiceWorkerGlobalScope &
  typeof globalThis & { skipWaiting: () => void };
/src-pwa/tsconfig.json

{
  "extends": "../tsconfig.json",
  "compilerOptions": {
    "lib": ["WebWorker", "ESNext"]
  },
  "include": ["*.ts", "*.d.ts"]
}
/src-pwa/.eslintrc.cjs

const { resolve } = require('node:path');

module.exports = {
  parserOptions: {
    project: resolve(__dirname, './tsconfig.json'),
  },

  overrides: [
    {
      files: ['custom-service-worker.ts'],

      env: {
        serviceworker: true,
      },
    },
  ],
};

Electron mode

If you are using Electron mode, add the section below to your project.

/src-electron/electron-env.d.ts

/* eslint-disable */

declare namespace NodeJS {
  interface ProcessEnv {
    QUASAR_PUBLIC_FOLDER: string;
    QUASAR_ELECTRON_PRELOAD_FOLDER: string;
    QUASAR_ELECTRON_PRELOAD_EXTENSION: string;
    APP_URL: string;
  }
}

BEX mode

If you are using BEX mode, add the section below to your project. You may need to adjust it to your needs depending on the events you are using. The key is the event name, the value is a tuple where the first element is the input and the second is the output type.

/src-bex/background.ts

declare module '@quasar/app-vite' {
  interface BexEventMap {
    /* eslint-disable @typescript-eslint/no-explicit-any */
    log: [{ message: string; data?: any[] }, never];
    getTime: [never, number];

    'storage.get': [{ key: string | null }, any];
    'storage.set': [{ key: string; value: any }, any];
    'storage.remove': [{ key: string }, any];
    /* eslint-enable @typescript-eslint/no-explicit-any */
  }
}