diff --git a/tap-snapshots/test/lib/commands/config.js.test.cjs b/tap-snapshots/test/lib/commands/config.js.test.cjs
index 769bbde3723e0..57956a4f5171e 100644
--- a/tap-snapshots/test/lib/commands/config.js.test.cjs
+++ b/tap-snapshots/test/lib/commands/config.js.test.cjs
@@ -71,6 +71,7 @@ exports[`test/lib/commands/config.js TAP config list --json > output matches sna
   "git-tag-version": true,
   "global": false,
   "globalconfig": "{CWD}/global/etc/npmrc",
+  "global-ignore-file": "{CWD}/global/etc/npmignore",
   "global-style": false,
   "heading": "npm",
   "https-proxy": null,
@@ -255,6 +256,7 @@ fund = true
 git = "git"
 git-tag-version = true
 global = false
+global-ignore-file = "{CWD}/global/etc/npmignore"
 global-style = false
 globalconfig = "{CWD}/global/etc/npmrc"
 heading = "npm"
diff --git a/tap-snapshots/test/lib/docs.js.test.cjs b/tap-snapshots/test/lib/docs.js.test.cjs
index fd40b3d8135e3..1c36192f7c225 100644
--- a/tap-snapshots/test/lib/docs.js.test.cjs
+++ b/tap-snapshots/test/lib/docs.js.test.cjs
@@ -865,6 +865,25 @@ folder instead of the current working directory. See
 
 
 
+#### \`global-ignore-file\`
+
+* Default: The global --prefix setting plus 'etc/npmignore'. For example,
+  '/usr/local/etc/npmignore'
+* Type: Path
+
+An additional ignore file applied during \`npm pack\` and \`npm publish\`, owned
+by the current user rather than the package. Patterns follow the same syntax
+as a package's local \`.npmignore\` file. Useful for keeping editor metadata
+(such as \`.idea/\` or \`*.iml\`) and scratch directories out of every package
+you publish, without adding them to each package's own ignore rules.
+
+The global rules apply in addition to a package's local \`.npmignore\`. When a
+package uses a \`files\` field in its \`package.json\`, an entry in \`files\` that
+contradicts a global rule (i.e., explicitly includes a path the global rule
+would exclude) still wins.
+
+
+
 #### \`globalconfig\`
 
 * Default: The global --prefix setting plus 'etc/npmrc'. For example,
@@ -2437,6 +2456,7 @@ Array [
   "git-tag-version",
   "global",
   "globalconfig",
+  "global-ignore-file",
   "global-style",
   "heading",
   "https-proxy",
@@ -2619,6 +2639,7 @@ Array [
   "git-tag-version",
   "global",
   "globalconfig",
+  "global-ignore-file",
   "global-style",
   "heading",
   "https-proxy",
@@ -2800,6 +2821,7 @@ Object {
   "gitTagVersion": true,
   "global": false,
   "globalconfig": "{CWD}/global/etc/npmrc",
+  "globalIgnoreFile": "{CWD}/global/etc/npmignore",
   "heading": "npm",
   "httpsProxy": null,
   "ifPresent": false,
diff --git a/workspaces/config/lib/definitions/definitions.js b/workspaces/config/lib/definitions/definitions.js
index 16c18f2c6ff5e..346e936c39021 100644
--- a/workspaces/config/lib/definitions/definitions.js
+++ b/workspaces/config/lib/definitions/definitions.js
@@ -935,6 +935,29 @@ const definitions = {
     `,
     flatten,
   }),
+  // the global-ignore-file has its default defined outside of this module
+  'global-ignore-file': new Definition('global-ignore-file', {
+    type: path,
+    default: '',
+    defaultDescription: `
+      The global --prefix setting plus 'etc/npmignore'. For example,
+      '/usr/local/etc/npmignore'
+    `,
+    description: `
+      An additional ignore file applied during \`npm pack\` and \`npm
+      publish\`, owned by the current user rather than the package. Patterns
+      follow the same syntax as a package's local \`.npmignore\` file.
+      Useful for keeping editor metadata (such as \`.idea/\` or \`*.iml\`)
+      and scratch directories out of every package you publish, without
+      adding them to each package's own ignore rules.
+
+      The global rules apply in addition to a package's local \`.npmignore\`.
+      When a package uses a \`files\` field in its \`package.json\`, an entry
+      in \`files\` that contradicts a global rule (i.e., explicitly includes
+      a path the global rule would exclude) still wins.
+    `,
+    flatten,
+  }),
   'global-style': new Definition('global-style', {
     default: false,
     type: Boolean,
diff --git a/workspaces/config/lib/index.js b/workspaces/config/lib/index.js
index 2aae7efbefb09..4121c2a7a3840 100644
--- a/workspaces/config/lib/index.js
+++ b/workspaces/config/lib/index.js
@@ -317,6 +317,24 @@ class Config {
       configurable: true,
       enumerable: true,
     })
+
+    // like globalconfig, the global-ignore-file default is computed from
+    // the current prefix. since prefix may be overridden after defaults
+    // load (via cli, env, or userconfig), expose a getter and only freeze
+    // to a value once explicitly set.
+    Object.defineProperty(data, 'global-ignore-file', {
+      get: () => resolve(this.#get('prefix'), 'etc/npmignore'),
+      set (value) {
+        Object.defineProperty(data, 'global-ignore-file', {
+          value,
+          configurable: true,
+          writable: true,
+          enumerable: true,
+        })
+      },
+      configurable: true,
+      enumerable: true,
+    })
   }
 
   loadHome () {
diff --git a/workspaces/config/tap-snapshots/test/type-description.js.test.cjs b/workspaces/config/tap-snapshots/test/type-description.js.test.cjs
index cd782afa88dfb..8a1f464cf4b92 100644
--- a/workspaces/config/tap-snapshots/test/type-description.js.test.cjs
+++ b/workspaces/config/tap-snapshots/test/type-description.js.test.cjs
@@ -224,6 +224,9 @@ Object {
   "global": Array [
     "boolean value (true or false)",
   ],
+  "global-ignore-file": Array [
+    "valid filesystem path",
+  ],
   "global-style": Array [
     "boolean value (true or false)",
   ],
diff --git a/workspaces/config/test/definitions/definitions.js b/workspaces/config/test/definitions/definitions.js
index 71bf8bc465403..ad50d3e0111f2 100644
--- a/workspaces/config/test/definitions/definitions.js
+++ b/workspaces/config/test/definitions/definitions.js
@@ -1174,3 +1174,19 @@ t.test('dangerously-allow-all-scripts', t => {
   t.strictSame(flat, { dangerouslyAllowAllScripts: true })
   t.end()
 })
+
+t.test('global-ignore-file', t => {
+  const defs = mockDefs()
+  const def = defs['global-ignore-file']
+
+  t.ok(def, 'global-ignore-file definition is exported')
+  t.equal(def.type, require('../../lib/type-defs.js').path.type, 'is a path typed config')
+  t.equal(def.default, '', 'default value is empty (computed at load time)')
+  t.ok(/ignore/i.test(def.description), 'has a descriptive entry')
+
+  const flat = {}
+  def.flatten('global-ignore-file', { 'global-ignore-file': '/path/to/npmignore' }, flat)
+  t.strictSame(flat, { globalIgnoreFile: '/path/to/npmignore' }, 'flattens to camelCase')
+
+  t.end()
+})
diff --git a/workspaces/config/test/index.js b/workspaces/config/test/index.js
index 378db572828f7..1097243ffb194 100644
--- a/workspaces/config/test/index.js
+++ b/workspaces/config/test/index.js
@@ -2315,3 +2315,46 @@ t.test('CLI --min-release-age beats env npm_config_min_release_age', async t =>
     'CLI --min-release-age=3 overrides env npm_config_min_release_age=30'
   )
 })
+
+t.test('global-ignore-file defaults to ${prefix}/etc/npmignore', async t => {
+  const path = t.testdir()
+  const config = new Config({
+    npmPath: `${path}/npm`,
+    env: {},
+    argv: [process.execPath, __filename, '--prefix', `${path}/global`],
+    cwd: path,
+    definitions,
+    shorthands,
+    flatten,
+  })
+  await config.load()
+  t.equal(
+    config.get('global-ignore-file'),
+    resolve(`${path}/global/etc/npmignore`),
+    'computed from --prefix, mirrors globalconfig'
+  )
+  t.equal(config.flat.globalIgnoreFile, resolve(`${path}/global/etc/npmignore`), 'flattens to camelCase')
+})
+
+t.test('global-ignore-file follows an explicit override', async t => {
+  const path = t.testdir()
+  const config = new Config({
+    npmPath: `${path}/npm`,
+    env: {},
+    argv: [
+      process.execPath, __filename,
+      '--prefix', `${path}/global`,
+      '--global-ignore-file', `${path}/custom/.npmignore`,
+    ],
+    cwd: path,
+    definitions,
+    shorthands,
+    flatten,
+  })
+  await config.load()
+  t.equal(
+    config.get('global-ignore-file'),
+    resolve(`${path}/custom/.npmignore`),
+    'cli override wins over computed default'
+  )
+})
