diff --git a/lib/audit.js b/lib/audit.js
index 9df2698589278..61fc726f4bfd3 100644
--- a/lib/audit.js
+++ b/lib/audit.js
@@ -2,9 +2,9 @@ const Arborist = require('@npmcli/arborist')
 const auditReport = require('npm-audit-report')
 const reifyFinish = require('./utils/reify-finish.js')
 const auditError = require('./utils/audit-error.js')
-const BaseCommand = require('./base-command.js')
+const ReifyCmd = require('./utils/reify-cmd.js')
 
-class Audit extends BaseCommand {
+class Audit extends ReifyCmd {
   /* istanbul ignore next - see test/lib/load-all-commands.js */
   static get description () {
     return 'Run a security audit'
@@ -24,6 +24,7 @@ class Audit extends BaseCommand {
       'json',
       'package-lock-only',
       'production',
+      ...super.params,
     ]
   }
 
@@ -57,7 +58,9 @@ class Audit extends BaseCommand {
       audit: true,
       path: this.npm.prefix,
       reporter,
+      workspaces: this.workspaces,
     }
+
     const arb = new Arborist(opts)
     const fix = args[0] === 'fix'
     await arb.audit({ fix })
diff --git a/lib/base-command.js b/lib/base-command.js
index 322fd8963a203..e1efcff5832b3 100644
--- a/lib/base-command.js
+++ b/lib/base-command.js
@@ -6,6 +6,7 @@ class BaseCommand {
   constructor (npm) {
     this.wrapWidth = 80
     this.npm = npm
+    this.workspaces = null
   }
 
   get name () {
diff --git a/lib/ci.js b/lib/ci.js
index 9ae31950ef102..8d8945fe7c091 100644
--- a/lib/ci.js
+++ b/lib/ci.js
@@ -17,9 +17,9 @@ const removeNodeModules = async where => {
   await Promise.all(entries.map(f => rimraf(`${path}/${f}`, rimrafOpts)))
   process.emit('timeEnd', 'npm-ci:rm')
 }
-const BaseCommand = require('./base-command.js')
+const ReifyCmd = require('./utils/reify-cmd.js')
 
-class CI extends BaseCommand {
+class CI extends ReifyCmd {
   /* istanbul ignore next - see test/lib/load-all-commands.js */
   static get description () {
     return 'Install a project with a clean slate'
@@ -47,6 +47,7 @@ class CI extends BaseCommand {
       path: where,
       log: this.npm.log,
       save: false, // npm ci should never modify the lockfile or package.json
+      workspaces: this.workspaces,
     }
 
     const arb = new Arborist(opts)
diff --git a/lib/dedupe.js b/lib/dedupe.js
index 9649025739c60..1b1fd5dfef931 100644
--- a/lib/dedupe.js
+++ b/lib/dedupe.js
@@ -2,9 +2,9 @@
 const Arborist = require('@npmcli/arborist')
 const reifyFinish = require('./utils/reify-finish.js')
 
-const BaseCommand = require('./base-command.js')
+const ReifyCmd = require('./utils/reify-cmd.js')
 
-class Dedupe extends BaseCommand {
+class Dedupe extends ReifyCmd {
   /* istanbul ignore next - see test/lib/load-all-commands.js */
   static get description () {
     return 'Reduce duplication in the package tree'
@@ -33,6 +33,7 @@ class Dedupe extends BaseCommand {
       log: this.npm.log,
       path: where,
       dryRun,
+      workspaces: this.workspaces,
     }
     const arb = new Arborist(opts)
     await arb.dedupe(opts)
diff --git a/lib/install.js b/lib/install.js
index a023015ed823a..36541e99fcd65 100644
--- a/lib/install.js
+++ b/lib/install.js
@@ -9,8 +9,8 @@ const { resolve, join } = require('path')
 const Arborist = require('@npmcli/arborist')
 const runScript = require('@npmcli/run-script')
 
-const BaseCommand = require('./base-command.js')
-class Install extends BaseCommand {
+const ReifyCmd = require('./utils/reify-cmd.js')
+class Install extends ReifyCmd {
   /* istanbul ignore next - see test/lib/load-all-commands.js */
   static get description () {
     return 'Install a package'
@@ -26,6 +26,7 @@ class Install extends BaseCommand {
     return [
       'save',
       'save-exact',
+      ...super.params,
     ]
   }
 
@@ -132,6 +133,7 @@ class Install extends BaseCommand {
       auditLevel: null,
       path: where,
       add: args,
+      workspaces: this.workspaces,
     }
     const arb = new Arborist(opts)
     await arb.reify(opts)
diff --git a/lib/prune.js b/lib/prune.js
index 5c4a549d4d7ad..57419a4d9fa9a 100644
--- a/lib/prune.js
+++ b/lib/prune.js
@@ -2,8 +2,8 @@
 const Arborist = require('@npmcli/arborist')
 const reifyFinish = require('./utils/reify-finish.js')
 
-const BaseCommand = require('./base-command.js')
-class Prune extends BaseCommand {
+const ReifyCmd = require('./utils/reify-cmd.js')
+class Prune extends ReifyCmd {
   /* istanbul ignore next - see test/lib/load-all-commands.js */
   static get description () {
     return 'Remove extraneous packages'
@@ -16,7 +16,7 @@ class Prune extends BaseCommand {
 
   /* istanbul ignore next - see test/lib/load-all-commands.js */
   static get params () {
-    return ['production']
+    return ['production', ...super.params]
   }
 
   /* istanbul ignore next - see test/lib/load-all-commands.js */
@@ -34,6 +34,7 @@ class Prune extends BaseCommand {
       ...this.npm.flatOptions,
       path: where,
       log: this.npm.log,
+      workspaces: this.workspaces,
     }
     const arb = new Arborist(opts)
     await arb.prune(opts)
diff --git a/lib/rebuild.js b/lib/rebuild.js
index 5910ab3d172dc..db5a23fa5f13d 100644
--- a/lib/rebuild.js
+++ b/lib/rebuild.js
@@ -4,6 +4,8 @@ const npa = require('npm-package-arg')
 const semver = require('semver')
 const completion = require('./utils/completion/installed-deep.js')
 
+// TODO: make Arborist.rebuild() understand the workspaces option
+// and then extend ReifyCmd instead
 const BaseCommand = require('./base-command.js')
 class Rebuild extends BaseCommand {
   /* istanbul ignore next - see test/lib/load-all-commands.js */
@@ -36,6 +38,8 @@ class Rebuild extends BaseCommand {
     const arb = new Arborist({
       ...this.npm.flatOptions,
       path: where,
+      // TODO when extending ReifyCmd
+      // workspaces: this.workspaces,
     })
 
     if (args.length) {
diff --git a/lib/uninstall.js b/lib/uninstall.js
index 79a4420d89f39..23825ef06022a 100644
--- a/lib/uninstall.js
+++ b/lib/uninstall.js
@@ -5,8 +5,8 @@ const rpj = require('read-package-json-fast')
 const reifyFinish = require('./utils/reify-finish.js')
 const completion = require('./utils/completion/installed-shallow.js')
 
-const BaseCommand = require('./base-command.js')
-class Uninstall extends BaseCommand {
+const ReifyCmd = require('./utils/reify-cmd.js')
+class Uninstall extends ReifyCmd {
   static get description () {
     return 'Remove a package'
   }
@@ -18,7 +18,7 @@ class Uninstall extends BaseCommand {
 
   /* istanbul ignore next - see test/lib/load-all-commands.js */
   static get params () {
-    return ['save']
+    return ['save', ...super.params]
   }
 
   /* istanbul ignore next - see test/lib/load-all-commands.js */
@@ -66,7 +66,7 @@ class Uninstall extends BaseCommand {
       path,
       log: this.npm.log,
       rm: args,
-
+      workspaces: this.workspaces,
     }
     const arb = new Arborist(opts)
     await arb.reify(opts)
diff --git a/lib/update.js b/lib/update.js
index f8cb12d267d8a..cef09a85e85f0 100644
--- a/lib/update.js
+++ b/lib/update.js
@@ -6,8 +6,8 @@ const log = require('npmlog')
 const reifyFinish = require('./utils/reify-finish.js')
 const completion = require('./utils/completion/installed-deep.js')
 
-const BaseCommand = require('./base-command.js')
-class Update extends BaseCommand {
+const ReifyCmd = require('./utils/reify-cmd.js')
+class Update extends ReifyCmd {
   /* istanbul ignore next - see test/lib/load-all-commands.js */
   static get description () {
     return 'Update packages'
@@ -20,7 +20,7 @@ class Update extends BaseCommand {
 
   /* istanbul ignore next - see test/lib/load-all-commands.js */
   static get params () {
-    return ['global']
+    return ['global', ...super.params]
   }
 
   /* istanbul ignore next - see test/lib/load-all-commands.js */
@@ -53,6 +53,7 @@ class Update extends BaseCommand {
       ...this.npm.flatOptions,
       log: this.npm.log,
       path: where,
+      workspaces: this.workspaces,
     })
 
     await arb.reify({ update })
diff --git a/lib/utils/reify-cmd.js b/lib/utils/reify-cmd.js
new file mode 100644
index 0000000000000..66118e4c4d0e2
--- /dev/null
+++ b/lib/utils/reify-cmd.js
@@ -0,0 +1,24 @@
+// This is the base for all commands whose execWorkspaces just gets
+// a list of workspace names and passes it on to new Arborist() to
+// be able to run a filtered Arborist.reify() at some point.
+
+const BaseCommand = require('../base-command.js')
+const getWorkspaces = require('../workspaces/get-workspaces.js')
+class ReifyCmd extends BaseCommand {
+  static get params () {
+    return [
+      'workspaces',
+      'workspace',
+    ]
+  }
+
+  execWorkspaces (args, filters, cb) {
+    getWorkspaces(filters, { path: this.npm.localPrefix })
+      .then(workspaces => {
+        this.workspaces = [...workspaces.keys()]
+        this.exec(args, cb)
+      })
+  }
+}
+
+module.exports = ReifyCmd
diff --git a/test/lib/audit.js b/test/lib/audit.js
index bb6f06debc51f..80c34ccb9e511 100644
--- a/test/lib/audit.js
+++ b/test/lib/audit.js
@@ -191,3 +191,5 @@ t.test('completion', t => {
 
   t.end()
 })
+
+t.test('workspaces')
diff --git a/test/lib/ci.js b/test/lib/ci.js
index b60375c289842..e7cc09fd4bfc6 100644
--- a/test/lib/ci.js
+++ b/test/lib/ci.js
@@ -242,3 +242,5 @@ t.test('should remove existing node_modules before installing', (t) => {
       throw er
   })
 })
+
+t.test('workspaces')
diff --git a/test/lib/dedupe.js b/test/lib/dedupe.js
index 801e3c96de3cf..14bbb404c88e4 100644
--- a/test/lib/dedupe.js
+++ b/test/lib/dedupe.js
@@ -62,3 +62,5 @@ t.test('should remove dupes using Arborist - no arguments', (t) => {
     t.end()
   })
 })
+
+t.test('workspaces')
diff --git a/test/lib/install.js b/test/lib/install.js
index b7929bddafdba..df22cdbe68868 100644
--- a/test/lib/install.js
+++ b/test/lib/install.js
@@ -218,3 +218,5 @@ t.test('completion', async t => {
   t.notOk(res)
   t.end()
 })
+
+t.test('workspaces')
diff --git a/test/lib/prune.js b/test/lib/prune.js
index 87bb1370f3a19..01bfaece2a95c 100644
--- a/test/lib/prune.js
+++ b/test/lib/prune.js
@@ -26,3 +26,5 @@ t.test('should prune using Arborist', (t) => {
     t.end()
   })
 })
+
+t.test('workspaces')
diff --git a/test/lib/rebuild.js b/test/lib/rebuild.js
index e686b6a32ce53..4a932b5877acd 100644
--- a/test/lib/rebuild.js
+++ b/test/lib/rebuild.js
@@ -256,3 +256,5 @@ t.test('global prefix', t => {
     t.end()
   })
 })
+
+t.test('workspaces')
diff --git a/test/lib/uninstall.js b/test/lib/uninstall.js
index 8cf35bd428d3b..65865d24b106a 100644
--- a/test/lib/uninstall.js
+++ b/test/lib/uninstall.js
@@ -249,3 +249,5 @@ t.test('unknown error reading from localPrefix package.json', t => {
     t.end()
   })
 })
+
+t.test('workspaces')
diff --git a/test/lib/update.js b/test/lib/update.js
index 5396397d520cf..ad2250956c0ae 100644
--- a/test/lib/update.js
+++ b/test/lib/update.js
@@ -37,7 +37,12 @@ t.test('no args', t => {
     constructor (args) {
       t.same(
         args,
-        { ...npm.flatOptions, path: npm.prefix, log: noop },
+        {
+          ...npm.flatOptions,
+          path: npm.prefix,
+          log: noop,
+          workspaces: null,
+        },
         'should call arborist contructor with expected args'
       )
     }
@@ -71,7 +76,12 @@ t.test('with args', t => {
     constructor (args) {
       t.same(
         args,
-        { ...npm.flatOptions, path: npm.prefix, log: noop },
+        {
+          ...npm.flatOptions,
+          path: npm.prefix,
+          log: noop,
+          workspaces: null,
+        },
         'should call arborist contructor with expected args'
       )
     }
@@ -139,7 +149,7 @@ t.test('update --global', t => {
       const { path, ...opts } = args
       t.same(
         opts,
-        { ...npm.flatOptions, log: noop },
+        { ...npm.flatOptions, log: noop, workspaces: undefined },
         'should call arborist contructor with expected options'
       )
 
@@ -164,3 +174,5 @@ t.test('update --global', t => {
       throw err
   })
 })
+
+t.test('workspaces')
diff --git a/test/lib/utils/reify-cmd.js b/test/lib/utils/reify-cmd.js
new file mode 100644
index 0000000000000..b8537d38099ab
--- /dev/null
+++ b/test/lib/utils/reify-cmd.js
@@ -0,0 +1,7 @@
+const t = require('tap')
+const ReifyCmd = require('../../../lib/utils/reify-cmd.js')
+// Just a dummy thing here so eslint will stop yelling, but it'll still
+// log a todo.
+t.todo('ReifyCmd basic tests', { todo: true }, async t => {
+  console.log(ReifyCmd)
+})