diff --git a/docs/content/commands/npm-pkg.md b/docs/content/commands/npm-pkg.md
index 7ff0a4d97930f..78b13cf9e9a00 100644
--- a/docs/content/commands/npm-pkg.md
+++ b/docs/content/commands/npm-pkg.md
@@ -98,6 +98,13 @@ Returned values are always in **json** format.
     npm pkg set contributors[0].name='Foo' contributors[0].email='foo@bar.ca'
     ```
 
+    You may also append items to the end of an array using the special
+    empty bracket notation:
+
+    ```bash
+    npm pkg set contributors[].name='Foo' contributors[].name='Bar'
+    ```
+
     It's also possible to parse values as json prior to saving them to your
     `package.json` file, for example in order to set a `"private": true`
     property:
diff --git a/lib/utils/queryable.js b/lib/utils/queryable.js
index 173877e64817c..4bc76825362cc 100644
--- a/lib/utils/queryable.js
+++ b/lib/utils/queryable.js
@@ -1,14 +1,27 @@
 const util = require('util')
 const _data = Symbol('data')
 const _delete = Symbol('delete')
+const _append = Symbol('append')
 
-const sqBracketsMatcher = str => str.match(/(.+)\[([^\]]+)\](.*)$/)
+const sqBracketsMatcher = str => str.match(/(.+)\[([^\]]+)\]\.?(.*)$/)
 
-const cleanLeadingDot = str =>
-  str && str.startsWith('.') ? str.substr(1) : str
+// replaces any occurence of an empty-brackets (e.g: []) with a special
+// Symbol(append) to represent it, this is going to be useful for the setter
+// method that will push values to the end of the array when finding these
+const replaceAppendSymbols = str => {
+  const matchEmptyBracket = str.match(/^(.*)\[\]\.?(.*)$/)
+
+  if (matchEmptyBracket) {
+    const [, pre, post] = matchEmptyBracket
+    return [...replaceAppendSymbols(pre), _append, post].filter(Boolean)
+  }
+
+  return [str]
+}
 
 const parseKeys = (key) => {
   const sqBracketItems = new Set()
+  sqBracketItems.add(_append)
   const parseSqBrackets = (str) => {
     const index = sqBracketsMatcher(str)
 
@@ -21,7 +34,7 @@ const parseKeys = (key) => {
       // foo.bar[foo.bar] should split into { foo: { bar: { 'foo.bar': {} } }
       /* eslint-disable-next-line no-new-wrappers */
       const foundKey = new String(index[2])
-      const postSqBracketPortion = cleanLeadingDot(index[3])
+      const postSqBracketPortion = index[3]
 
       // we keep track of items found during this step to make sure
       // we don't try to split-separate keys that were defined within
@@ -43,7 +56,11 @@ const parseKeys = (key) => {
       ]
     }
 
-    return [str]
+    // at the end of parsing, any usage of the special empty-bracket syntax
+    // (e.g: foo.array[]) has  not yet bene parsed, here we'll take care
+    // of parsing it and adding a special symbol to represent it in
+    // the resulting list of keys
+    return replaceAppendSymbols(str)
   }
 
   const res = []
@@ -79,6 +96,14 @@ const getter = ({ data, key }) => {
   let label = ''
 
   for (const k of keys) {
+    // empty-bracket-shortcut-syntax is not supported on getter
+    if (k === _append) {
+      throw Object.assign(
+        new Error('Empty brackets are not valid syntax for retrieving values.'),
+        { code: 'EINVALIDSYNTAX' }
+      )
+    }
+
     // extra logic to take into account printing array, along with its
     // special syntax in which using a dot-sep property name after an
     // arry will expand it's results, e.g:
@@ -119,14 +144,33 @@ const setter = ({ data, key, value, force }) => {
   // ['foo', 'bar', 'baz'] -> { foo: { bar: { baz:  {} } }
   const keys = parseKeys(key)
   const setKeys = (_data, _key) => {
-    // handles array indexes, making sure the new array is created if
-    // missing and properly casting the index to a number
-    const maybeIndex = Number(_key)
-    if (!Number.isNaN(maybeIndex)) {
+    // handles array indexes, converting valid integers to numbers,
+    // note that occurences of Symbol(append) will throw,
+    // so we just ignore these for now
+    let maybeIndex = Number.NaN
+    try {
+      maybeIndex = Number(_key)
+    } catch (err) {}
+    if (!Number.isNaN(maybeIndex))
       _key = maybeIndex
-      if (!Object.keys(_data).length)
-        _data = []
-    }
+
+    // creates new array in case key is an index
+    // and the array obj is not yet defined
+    const keyIsAnArrayIndex = _key === maybeIndex || _key === _append
+    const dataHasNoItems = !Object.keys(_data).length
+    if (keyIsAnArrayIndex && dataHasNoItems)
+      _data = []
+
+    // converting from array to an object is also possible, in case the
+    // user is using force mode, we should also convert existing arrays
+    // to an empty object if the current _data is an array
+    if (force && Array.isArray(_data) && !keyIsAnArrayIndex)
+      _data = { ..._data }
+
+    // the _append key is a special key that is used to represent
+    // the empty-bracket notation, e.g: arr[] -> arr[arr.length]
+    if (_key === _append)
+      _key = _data.length
 
     // retrieves the next data object to recursively iterate on,
     // throws if trying to override a literal value or add props to an array
@@ -141,13 +185,13 @@ const setter = ({ data, key, value, force }) => {
       // appended to the resulting obj is not an array index, then it
       // should throw since we can't append arbitrary props to arrays
       const shouldNotAddPropsToArrays =
+        typeof keys[0] !== 'symbol' &&
         Array.isArray(_data[_key]) &&
         Number.isNaN(Number(keys[0]))
 
       const overrideError =
         haveContents &&
-        (shouldNotOverrideLiteralValue || shouldNotAddPropsToArrays)
-
+        shouldNotOverrideLiteralValue
       if (overrideError) {
         throw Object.assign(
           new Error(`Property ${key} already has a value in place.`),
@@ -155,6 +199,16 @@ const setter = ({ data, key, value, force }) => {
         )
       }
 
+      const addPropsToArrayError =
+        haveContents &&
+        shouldNotAddPropsToArrays
+      if (addPropsToArrayError) {
+        throw Object.assign(
+          new Error(`Can't add property ${key} to an Array.`),
+          { code: 'ENOADDPROP' }
+        )
+      }
+
       return typeof _data[_key] === 'object' ? _data[_key] || {} : {}
     }
 
diff --git a/test/lib/pkg.js b/test/lib/pkg.js
index 42eb7c0cc5e9c..688df6859054a 100644
--- a/test/lib/pkg.js
+++ b/test/lib/pkg.js
@@ -291,6 +291,38 @@ t.test('set single field', t => {
   })
 })
 
+t.test('push to array syntax', t => {
+  const json = {
+    name: 'foo',
+    version: '1.1.1',
+    keywords: [
+      'foo',
+    ],
+  }
+  npm.localPrefix = t.testdir({
+    'package.json': JSON.stringify(json),
+  })
+
+  pkg.exec(['set', 'keywords[]=bar', 'keywords[]=baz'], err => {
+    if (err)
+      throw err
+
+    t.strictSame(
+      readPackageJson(),
+      {
+        ...json,
+        keywords: [
+          'foo',
+          'bar',
+          'baz',
+        ],
+      },
+      'should append to arrays using empty bracket syntax'
+    )
+    t.end()
+  })
+})
+
 t.test('set multiple fields', t => {
   const json = {
     name: 'foo',
diff --git a/test/lib/utils/queryable.js b/test/lib/utils/queryable.js
index 2e66eeeb9e080..6e244b2e8e861 100644
--- a/test/lib/utils/queryable.js
+++ b/test/lib/utils/queryable.js
@@ -130,6 +130,14 @@ t.test('query', async t => {
     q.query('missing[bar]'),
     undefined,
     'should return undefined also')
+  t.throws(() => q.query('lorem.dolor[]'),
+    { code: 'EINVALIDSYNTAX' },
+    'should throw if using empty brackets notation'
+  )
+  t.throws(() => q.query('lorem.dolor[].sit[0]'),
+    { code: 'EINVALIDSYNTAX' },
+    'should throw if using nested empty brackets notation'
+  )
 
   const qq = new Queryable({
     foo: {
@@ -597,11 +605,213 @@ t.test('set arrays', async t => {
       'b',
     ],
   })
+
+  qqq.set('arr[]', 'c')
+  t.strictSame(
+    qqq.toJSON(),
+    {
+      arr: [
+        'a',
+        'b',
+        'c',
+      ],
+    },
+    'should be able to append to array using empty bracket notation'
+  )
+
+  qqq.set('arr[].foo', 'foo')
+  t.strictSame(
+    qqq.toJSON(),
+    {
+      arr: [
+        'a',
+        'b',
+        'c',
+        {
+          foo: 'foo',
+        },
+      ],
+    },
+    'should be able to append objects to array using empty bracket notation'
+  )
+
+  qqq.set('arr[].bar.name', 'BAR')
+  t.strictSame(
+    qqq.toJSON(),
+    {
+      arr: [
+        'a',
+        'b',
+        'c',
+        {
+          foo: 'foo',
+        },
+        {
+          bar: {
+            name: 'BAR',
+          },
+        },
+      ],
+    },
+    'should be able to append more objects to array using empty brackets'
+  )
+
+  qqq.set('foo.bar.baz[].lorem.ipsum', 'something')
+  t.strictSame(
+    qqq.toJSON(),
+    {
+      arr: [
+        'a',
+        'b',
+        'c',
+        {
+          foo: 'foo',
+        },
+        {
+          bar: {
+            name: 'BAR',
+          },
+        },
+      ],
+      foo: {
+        bar: {
+          baz: [
+            {
+              lorem: {
+                ipsum: 'something',
+              },
+            },
+          ],
+        },
+      },
+    },
+    'should be able to append to array using empty brackets in nested objs'
+  )
+
+  qqq.set('foo.bar.baz[].lorem.array[]', 'new item')
+  t.strictSame(
+    qqq.toJSON(),
+    {
+      arr: [
+        'a',
+        'b',
+        'c',
+        {
+          foo: 'foo',
+        },
+        {
+          bar: {
+            name: 'BAR',
+          },
+        },
+      ],
+      foo: {
+        bar: {
+          baz: [
+            {
+              lorem: {
+                ipsum: 'something',
+              },
+            },
+            {
+              lorem: {
+                array: [
+                  'new item',
+                ],
+              },
+            },
+          ],
+        },
+      },
+    },
+    'should be able to append to array using empty brackets in nested objs'
+  )
+
+  const qqqq = new Queryable({
+    arr: [
+      'a',
+      'b',
+    ],
+  })
   t.throws(
-    () => qqq.set('arr.foo', 'foo'),
-    { code: 'EOVERRIDEVALUE' },
+    () => qqqq.set('arr.foo', 'foo'),
+    { code: 'ENOADDPROP' },
     'should throw an override error'
   )
+
+  qqqq.set('arr.foo', 'foo', { force: true })
+  t.strictSame(
+    qqqq.toJSON(),
+    {
+      arr: {
+        0: 'a',
+        1: 'b',
+        foo: 'foo',
+      },
+    },
+    'should be able to override arrays with objects when using force=true'
+  )
+
+  qqqq.set('bar[]', 'item', { force: true })
+  t.strictSame(
+    qqqq.toJSON(),
+    {
+      arr: {
+        0: 'a',
+        1: 'b',
+        foo: 'foo',
+      },
+      bar: [
+        'item',
+      ],
+    },
+    'should be able to create new array with item when using force=true'
+  )
+
+  qqqq.set('bar[]', 'something else', { force: true })
+  t.strictSame(
+    qqqq.toJSON(),
+    {
+      arr: {
+        0: 'a',
+        1: 'b',
+        foo: 'foo',
+      },
+      bar: [
+        'item',
+        'something else',
+      ],
+    },
+    'should be able to append items to arrays when using force=true'
+  )
+
+  const qqqqq = new Queryable({
+    arr: [
+      null,
+    ],
+  })
+  qqqqq.set('arr[]', 'b')
+  t.strictSame(
+    qqqqq.toJSON(),
+    {
+      arr: [
+        null,
+        'b',
+      ],
+    },
+    'should be able to append items with empty items'
+  )
+  qqqqq.set('arr[0]', 'a')
+  t.strictSame(
+    qqqqq.toJSON(),
+    {
+      arr: [
+        'a',
+        'b',
+      ],
+    },
+    'should be able to replace empty items in an array'
+  )
 })
 
 t.test('delete values', async t => {