diff --git a/lib/config/definitions.js b/lib/config/definitions.js
index 096acc68dcfe191b9b430f857792ab8666c322a6..f443328778bbf27d438b55553c227585f0306491 100644
--- a/lib/config/definitions.js
+++ b/lib/config/definitions.js
@@ -450,6 +450,14 @@ const options = [
     stage: 'package',
     type: 'boolean',
   },
+  {
+    name: 'upgradeInRange',
+    description:
+      'Upgrade ranges to latest version even if latest version satisfies existing range',
+    stage: 'package',
+    type: 'boolean',
+    default: false,
+  },
   {
     name: 'versionStrategy',
     description:
diff --git a/lib/workers/package-file/index.js b/lib/workers/package-file/index.js
index c4da377906324760eca54dab7dcdf5a869589d72..7399a6bb6b921530bd8dcdd14958d06d4009d642 100644
--- a/lib/workers/package-file/index.js
+++ b/lib/workers/package-file/index.js
@@ -111,6 +111,7 @@ async function renovatePackageJson(input) {
     // Pin dependencies if we're pretty sure it's not a browser library
     if (
       depTypeConfig.pinVersions === null &&
+      !depTypeConfig.upgradeInRange &&
       (depType === 'devDependencies' ||
         (depType === 'dependencies' && !mightBeABrowserLibrary(config.content)))
     ) {
diff --git a/lib/workers/package/versions.js b/lib/workers/package/versions.js
index 83b5d32b4821bcdfb1df25935f04ea077fd0be5b..e1ad1f1e110569f6fd3ef519aa80c8272ad82031 100644
--- a/lib/workers/package/versions.js
+++ b/lib/workers/package/versions.js
@@ -18,12 +18,7 @@ function determineUpgrades(npmDep, config) {
   const result = {
     type: 'warning',
   };
-  const {
-    currentVersion,
-    lockedVersion,
-    pinVersions,
-    allowedVersions,
-  } = config;
+  const { lockedVersion, pinVersions, allowedVersions } = config;
   const { versions } = npmDep;
   if (!versions || Object.keys(versions).length === 0) {
     result.message = `No versions returned from registry for this package`;
@@ -32,6 +27,28 @@ function determineUpgrades(npmDep, config) {
   }
   const versionList = Object.keys(versions);
   const allUpgrades = {};
+  let { currentVersion } = config;
+  let rangeOperator;
+  if (config.upgradeInRange && semver.validRange(currentVersion)) {
+    logger.debug({ currentVersion }, 'upgradeInRange is true');
+    const parsedRange = semverUtils.parseRange(currentVersion);
+    if (parsedRange && parsedRange.length === 1) {
+      const [range] = parsedRange;
+      if (range.major && range.minor && range.patch) {
+        if (range.operator === '^' || range.operator === '~') {
+          logger.debug('Applying upgradeInRange');
+          currentVersion = `${range.major}.${range.minor}.${range.patch}`;
+          rangeOperator = range.operator;
+        } else {
+          logger.debug({ currentVersion }, 'Unsupported range type');
+        }
+      } else {
+        logger.debug({ currentVersion }, 'Range is not fully specified');
+      }
+    } else {
+      logger.debug({ currentVersion }, 'Skipping complex range');
+    }
+  }
   let changeLogFromVersion = currentVersion;
   // Check for a current range and pin it
   if (isRange(currentVersion)) {
@@ -61,7 +78,7 @@ function determineUpgrades(npmDep, config) {
       newVersionMajor: semver.major(newVersion),
     };
     changeLogFromVersion = newVersion;
-  } else if (versionList.indexOf(currentVersion) === -1) {
+  } else if (versionList.indexOf(currentVersion) === -1 && !rangeOperator) {
     logger.debug({ dependency }, 'Cannot find currentVersion');
     try {
       const rollbackVersion = semver.maxSatisfying(
@@ -163,6 +180,7 @@ function determineUpgrades(npmDep, config) {
           changeLogFromVersion,
           changeLogToVersion,
         };
+        logger.debug({ allUpgrades });
         if (type === 'major') {
           allUpgrades[upgradeKey].isMajor = true;
         } else if (type === 'minor') {
@@ -182,7 +200,14 @@ function determineUpgrades(npmDep, config) {
 
   // Return now if array is empty, or we can keep pinned version upgrades
   if (upgrades.length === 0 || config.pinVersions || !isRange(currentVersion)) {
-    return upgrades;
+    logger.debug({ upgrades });
+    return rangeOperator
+      ? upgrades.map(upgrade => ({
+          ...upgrade,
+          newVersion: `${rangeOperator}${upgrade.newVersion}`,
+          isRange: true,
+        }))
+      : upgrades;
   }
 
   logger.debug({ dependency }, 'User wanrs ranges - filtering out pins');
diff --git a/test/workers/package/__snapshots__/versions.spec.js.snap b/test/workers/package/__snapshots__/versions.spec.js.snap
index 1bd86b1f0a160f33a71853d0e50106d06084187a..97fba9d5d280d665ccdc4bb2bab98799e02bdd20 100644
--- a/test/workers/package/__snapshots__/versions.spec.js.snap
+++ b/test/workers/package/__snapshots__/versions.spec.js.snap
@@ -71,6 +71,27 @@ Array [
 ]
 `;
 
+exports[`workers/package/versions .determineUpgrades(npmDep, config) rejects complex range in-range updates 1`] = `Array []`;
+
+exports[`workers/package/versions .determineUpgrades(npmDep, config) rejects in-range unsupported operator 1`] = `Array []`;
+
+exports[`workers/package/versions .determineUpgrades(npmDep, config) rejects non-fully specified in-range updates 1`] = `Array []`;
+
+exports[`workers/package/versions .determineUpgrades(npmDep, config) rejects non-range in-range updates 1`] = `
+Array [
+  Object {
+    "changeLogFromVersion": "1.0.0",
+    "changeLogToVersion": "1.4.1",
+    "isMinor": true,
+    "newVersion": "1.4.1",
+    "newVersionMajor": 1,
+    "newVersionMinor": 4,
+    "type": "minor",
+    "unpublishable": false,
+  },
+]
+`;
+
 exports[`workers/package/versions .determineUpgrades(npmDep, config) rejects reverse ordered less than greater than 1`] = `
 Array [
   Object {
@@ -478,6 +499,22 @@ Array [
 ]
 `;
 
+exports[`workers/package/versions .determineUpgrades(npmDep, config) supports in-range updates 1`] = `
+Array [
+  Object {
+    "changeLogFromVersion": "1.0.0",
+    "changeLogToVersion": "1.4.1",
+    "isMinor": true,
+    "isRange": true,
+    "newVersion": "~1.4.1",
+    "newVersionMajor": 1,
+    "newVersionMinor": 4,
+    "type": "minor",
+    "unpublishable": false,
+  },
+]
+`;
+
 exports[`workers/package/versions .determineUpgrades(npmDep, config) supports minor and major upgrades for ranged versions 1`] = `
 Array [
   Object {
diff --git a/test/workers/package/versions.spec.js b/test/workers/package/versions.spec.js
index c883196065ec6397e2c1e6b8450979da9eec694d..e5ed164a7c7c7a2142f3d6d9a31a6bcb16f3d0b8 100644
--- a/test/workers/package/versions.spec.js
+++ b/test/workers/package/versions.spec.js
@@ -367,6 +367,35 @@ describe('workers/package/versions', () => {
       const res = versions.determineUpgrades(nextJson, config);
       expect(res).toHaveLength(0);
     });
+    it('supports in-range updates', () => {
+      config.upgradeInRange = true;
+      config.currentVersion = '~1.0.0';
+      expect(versions.determineUpgrades(qJson, config)).toMatchSnapshot();
+    });
+    it('rejects in-range unsupported operator', () => {
+      config.upgradeInRange = true;
+      config.pinVersions = false;
+      config.currentVersion = '>=1.0.0';
+      expect(versions.determineUpgrades(qJson, config)).toMatchSnapshot();
+    });
+    it('rejects non-fully specified in-range updates', () => {
+      config.upgradeInRange = true;
+      config.pinVersions = false;
+      config.currentVersion = '1.x';
+      expect(versions.determineUpgrades(qJson, config)).toMatchSnapshot();
+    });
+    it('rejects complex range in-range updates', () => {
+      config.upgradeInRange = true;
+      config.pinVersions = false;
+      config.currentVersion = '^0.9.0 || ^1.0.0';
+      expect(versions.determineUpgrades(qJson, config)).toMatchSnapshot();
+    });
+    it('rejects non-range in-range updates', () => {
+      config.upgradeInRange = true;
+      config.pinVersions = false;
+      config.currentVersion = '1.0.0';
+      expect(versions.determineUpgrades(qJson, config)).toMatchSnapshot();
+    });
   });
   describe('.isRange(input)', () => {
     it('rejects simple semver', () => {
diff --git a/website/docs/_posts/2017-10-05-configuration-options.md b/website/docs/_posts/2017-10-05-configuration-options.md
index 74ba01f3cb47280f92cba01d801f674e936b75e2..966d164b1e9491505081bb46c596b3f5218e189a 100644
--- a/website/docs/_posts/2017-10-05-configuration-options.md
+++ b/website/docs/_posts/2017-10-05-configuration-options.md
@@ -1082,6 +1082,21 @@ When schedules are in use, it generally means "no updates". However there are ca
 
 This is default true, meaning that Renovate will perform certain "desirable" updates to _existing_ PRs even when outside of schedule. If you wish to disable all updates outside of scheduled hours then set this field to false.
 
+## upgradeInRange
+
+Upgrade ranges to latest version even if latest version satisfies existing range.
+
+| name    | value   |
+| ------- | ------- |
+| type    | boolean |
+| default | false   |
+
+By default, Renovate assumes that if you are using ranges then it's because you want them to be wide/open. As such, Renovate won't deliberately "narrow" the range by increasing the semver value inside.
+
+For example, if your `package.json` specifies a value for `left-pad` of `^1.0.0` and the latest version on npmjs is `1.2.0`, then Renovate won't change anything. If instead you'd prefer to be updated to `^1.2.0` in cases like this, then set `upgradeInRange` to `true` in your Renovate config.
+
+This feature supports simple caret (`^`) and tilde (`~`) ranges only, like `^1.0.0` and `~1.0.0`. It is not compatible with `pinVersions=true`.
+
 ## versionStrategy
 
 Strategy for how to modify/update existing versions/semver. Possible values: auto, replace, or widen