diff --git a/packages/@vue/cli/__tests__/Generator.spec.js b/packages/@vue/cli/__tests__/Generator.spec.js
index 263aa4a792..405a56d161 100644
--- a/packages/@vue/cli/__tests__/Generator.spec.js
+++ b/packages/@vue/cli/__tests__/Generator.spec.js
@@ -256,7 +256,7 @@ test('api: extendPackage merge dependencies', async () => {
 })
 
 test('api: warn invalid dep range', async () => {
-  new Generator('/', { plugins: [
+  const generator = new Generator('/', { plugins: [
     {
       id: 'test1',
       apply: api => {
@@ -269,6 +269,8 @@ test('api: warn invalid dep range', async () => {
     }
   ] })
 
+  await generator.generate()
+
   expect(logs.warn.some(([msg]) => {
     return (
       msg.match(/invalid version range for dependency "foo"/) &&
@@ -278,7 +280,7 @@ test('api: warn invalid dep range', async () => {
 })
 
 test('api: extendPackage dependencies conflict', async () => {
-  new Generator('/', { plugins: [
+  const generator = new Generator('/', { plugins: [
     {
       id: 'test1',
       apply: api => {
@@ -301,6 +303,8 @@ test('api: extendPackage dependencies conflict', async () => {
     }
   ] })
 
+  await generator.generate()
+
   expect(logs.warn.some(([msg]) => {
     return (
       msg.match(/conflicting versions for project dependency "foo"/) &&
@@ -312,7 +316,7 @@ test('api: extendPackage dependencies conflict', async () => {
 })
 
 test('api: extendPackage merge warn nonstrictly semver deps', async () => {
-  new Generator('/', { plugins: [
+  const generator = new Generator('/', { plugins: [
     {
       id: 'test3',
       apply: api => {
@@ -335,6 +339,8 @@ test('api: extendPackage merge warn nonstrictly semver deps', async () => {
     }
   ] })
 
+  await generator.generate()
+
   expect(logs.warn.some(([msg]) => {
     return (
       msg.match(/conflicting versions for project dependency "bar"/) &&
@@ -436,10 +442,10 @@ test('api: hasPlugin', () => {
   ] })
 })
 
-test('api: onCreateComplete', () => {
+test('api: onCreateComplete', async () => {
   const fn = () => {}
   const cbs = []
-  new Generator('/', {
+  const generator = new Generator('/', {
     plugins: [
       {
         id: 'test',
@@ -467,6 +473,9 @@ test('api: afterInvoke', () => {
     ],
     afterInvokeCbs: cbs
   })
+
+  await generator.generate()
+
   expect(cbs).toContain(fn)
 })
 
diff --git a/packages/@vue/cli/__tests__/mock-preset-with-async-generator/generator/index.js b/packages/@vue/cli/__tests__/mock-preset-with-async-generator/generator/index.js
new file mode 100644
index 0000000000..22ba464be7
--- /dev/null
+++ b/packages/@vue/cli/__tests__/mock-preset-with-async-generator/generator/index.js
@@ -0,0 +1,17 @@
+const sleep = n => new Promise(resolve => setTimeout(resolve, n))
+
+module.exports = async (api, options) => {
+  api.render('./template', options)
+
+  // add asynchronous code test
+  await sleep(1000)
+
+  api.extendPackage({
+    scripts: {
+      testasync: 'this is the test'
+    },
+    devDependencies: {
+      'vue-cli-plugin-async-generator': 'v0.0.1'
+    }
+  })
+}
diff --git a/packages/@vue/cli/__tests__/mock-preset-with-async-generator/generator/template/test.js b/packages/@vue/cli/__tests__/mock-preset-with-async-generator/generator/template/test.js
new file mode 100644
index 0000000000..12db7fb421
--- /dev/null
+++ b/packages/@vue/cli/__tests__/mock-preset-with-async-generator/generator/template/test.js
@@ -0,0 +1 @@
+<%= ok %>
diff --git a/packages/@vue/cli/__tests__/mock-preset-with-async-generator/preset.json b/packages/@vue/cli/__tests__/mock-preset-with-async-generator/preset.json
new file mode 100644
index 0000000000..63ec289edc
--- /dev/null
+++ b/packages/@vue/cli/__tests__/mock-preset-with-async-generator/preset.json
@@ -0,0 +1,5 @@
+{
+  "plugins": {
+    "@vue/cli-plugin-babel": {}
+  }
+}
diff --git a/packages/@vue/cli/__tests__/mock-preset-with-async-generator/prompts.js b/packages/@vue/cli/__tests__/mock-preset-with-async-generator/prompts.js
new file mode 100644
index 0000000000..26971b45f2
--- /dev/null
+++ b/packages/@vue/cli/__tests__/mock-preset-with-async-generator/prompts.js
@@ -0,0 +1,5 @@
+module.exports = [{
+  type: 'confirm',
+  name: 'ok',
+  message: 'Are you ok?'
+}]
diff --git a/packages/@vue/cli/__tests__/preset.spec.js b/packages/@vue/cli/__tests__/preset.spec.js
index 7afab7b1f9..5329976922 100644
--- a/packages/@vue/cli/__tests__/preset.spec.js
+++ b/packages/@vue/cli/__tests__/preset.spec.js
@@ -56,3 +56,32 @@ test('should recognize generator/index.js in a local preset directory', async ()
   const pkg = require(path.resolve(cwd, name, 'package.json'))
   expect(pkg.devDependencies).toHaveProperty('@vue/cli-plugin-babel')
 })
+
+test('should recognize generator/index.js in a local preset directory by async generatory', async () => {
+  const cwd = path.resolve(__dirname, '../../../test')
+  const name = 'test-preset-template-async-generator'
+
+  expectPrompts([{
+    message: 'Are you ok',
+    confirm: true
+  }])
+
+  await create(
+    name,
+    {
+      force: true,
+      git: false,
+      cwd,
+      preset: path.resolve(__dirname, './mock-preset-with-async-generator')
+    }
+  )
+
+  const testFile = await fs.readFile(path.resolve(cwd, name, 'test.js'), 'utf-8')
+  expect(testFile).toBe('true\n')
+
+  const pkg = require(path.resolve(cwd, name, 'package.json'))
+  expect(pkg.devDependencies).toHaveProperty('@vue/cli-plugin-babel')
+  expect(pkg.devDependencies).toHaveProperty('vue-cli-plugin-async-generator')
+  expect(pkg.scripts).toHaveProperty('testasync')
+})
+
diff --git a/packages/@vue/cli/lib/Generator.js b/packages/@vue/cli/lib/Generator.js
index b99dcebd28..c449fba02b 100644
--- a/packages/@vue/cli/lib/Generator.js
+++ b/packages/@vue/cli/lib/Generator.js
@@ -83,7 +83,7 @@ module.exports = class Generator {
     this.pm = new PackageManager({ context })
     this.imports = {}
     this.rootOptions = {}
-    this.afterInvokeCbs = []
+    this.afterInvokeCbs = afterInvokeCbs
     this.afterAnyInvokeCbs = afterAnyInvokeCbs
     this.configTransforms = {}
     this.defaultConfigTransforms = defaultConfigTransforms
@@ -98,10 +98,8 @@ module.exports = class Generator {
     // exit messages
     this.exitLogs = []
 
-    const pluginIds = plugins.map(p => p.id)
-
     // load all the other plugins
-    this.allPlugins = Object.keys(this.pkg.dependencies || {})
+    this.allPluginIds = Object.keys(this.pkg.dependencies || {})
       .concat(Object.keys(this.pkg.devDependencies || {}))
       .filter(isPlugin)
 
@@ -110,43 +108,55 @@ module.exports = class Generator {
       ? cliService.options
       : inferRootOptions(pkg)
 
+    this.rootOptions = rootOptions
+  }
+
+  async initPlugins () {
+    const { rootOptions, invoking } = this
+    const pluginIds = this.plugins.map(p => p.id)
+
     // apply hooks from all plugins
-    this.allPlugins.forEach(id => {
+    for (const id of this.allPluginIds) {
       const api = new GeneratorAPI(id, this, {}, rootOptions)
-      const pluginGenerator = loadModule(`${id}/generator`, context)
+      const pluginGenerator = loadModule(`${id}/generator`, this.context)
 
       if (pluginGenerator && pluginGenerator.hooks) {
-        pluginGenerator.hooks(api, {}, rootOptions, pluginIds)
+        await pluginGenerator.hooks(api, {}, rootOptions, pluginIds)
       }
-    })
+    }
 
     // We are doing save/load to make the hook order deterministic
     // save "any" hooks
     const afterAnyInvokeCbsFromPlugins = this.afterAnyInvokeCbs
 
     // reset hooks
-    this.afterInvokeCbs = afterInvokeCbs
     this.afterAnyInvokeCbs = []
     this.postProcessFilesCbs = []
 
     // apply generators from plugins
-    plugins.forEach(({ id, apply, options }) => {
+    for (const plugin of this.plugins) {
+      const { id, apply, options } = plugin
       const api = new GeneratorAPI(id, this, options, rootOptions)
-      apply(api, options, rootOptions, invoking)
+      await apply(api, options, rootOptions, invoking)
 
       if (apply.hooks) {
-        apply.hooks(api, options, rootOptions, pluginIds)
+        // while we execute the entire `hooks` function,
+        // only the `afterInvoke` hook is respected
+        // because `afterAnyHooks` is already determined by the `allPluginIds` loop aboe
+        await apply.hooks(api, options, rootOptions, pluginIds)
       }
-    })
 
-    // load "any" hooks
-    this.afterAnyInvokeCbs = afterAnyInvokeCbsFromPlugins
+      // restore "any" hooks
+      this.afterAnyInvokeCbs = afterAnyInvokeCbsFromPlugins
+    }
   }
 
   async generate ({
     extractConfigFiles = false,
     checkExisting = false
   } = {}) {
+    await this.initPlugins()
+
     // save the file system before applying plugin for comparison
     const initialFiles = Object.assign({}, this.files)
     // extract configs from package.json into dedicated files.
@@ -284,7 +294,7 @@ module.exports = class Generator {
   hasPlugin (_id, _version) {
     return [
       ...this.plugins.map(p => p.id),
-      ...this.allPlugins
+      ...this.allPluginIds
     ].some(id => {
       if (!matchesPluginId(_id, id)) {
         return false