diff --git a/docs/design-decisions.md b/docs/design-decisions.md
index 69b2d9eb2beea879582b109be4818a4fbbde96c9..143e9712dd60475a355dac8cc6d6bf25edefcb1e 100644
--- a/docs/design-decisions.md
+++ b/docs/design-decisions.md
@@ -94,14 +94,6 @@ already closed. This allows users to close unwelcome upgrade PRs and worry about
 them being recreated every run. Typically this is most useful for major
 upgrades. This option is configurable.
 
-## Range handling
-
-`renovate` prefers pinned dependency versions, instead of maintaining ranges.
-Even if the project is using tilde ranges, why not pin them for consistency if
-you're also using `renovate` every day?
-
-This is now configurable via the `pinVersions` configuration option.
-
 ## Rebasing Unmergeable Pull Requests
 
 With the default behaviour of one branch per dependency, it's often that case
diff --git a/lib/config/definitions.js b/lib/config/definitions.js
index 7600539e494ce4c14881c4a5b154a76ad27daf9d..ba46182ad685a2dca8acc1a62f11bbca590c51a3 100644
--- a/lib/config/definitions.js
+++ b/lib/config/definitions.js
@@ -405,13 +405,6 @@ const options = [
     stage: 'package',
     type: 'boolean',
   },
-  {
-    name: 'pinVersions',
-    description: 'Convert ranged versions to pinned versions',
-    stage: 'package',
-    type: 'boolean',
-    default: false,
-  },
   {
     name: 'separateMajorReleases',
     description:
@@ -456,20 +449,12 @@ const options = [
     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:
-      'Strategy for how to modify/update existing versions/semver. Possible values: auto, replace, or widen',
+    name: 'rangeStrategy',
+    description: 'Policy for how to modify/update existing ranges.',
     stage: 'package',
     type: 'string',
     default: 'auto',
+    allowedValues: ['auto', 'pin', 'bump', 'replace', 'widen'],
     cli: false,
     env: false,
   },
diff --git a/lib/config/migration.js b/lib/config/migration.js
index 2a384f960b1634131f619c527a677104f22e0dd4..75955ea27a35492f427fab31c3dd398c300cf95a 100644
--- a/lib/config/migration.js
+++ b/lib/config/migration.js
@@ -91,6 +91,26 @@ function migrateConfig(config) {
         delete depTypePackageRule.packageRules;
         migratedConfig.packageRules.push(depTypePackageRule);
         delete migratedConfig[key];
+      } else if (key === 'pinVersions') {
+        isMigrated = true;
+        delete migratedConfig.pinVersions;
+        if (val === true) {
+          migratedConfig.rangeStrategy = 'pin';
+        } else if (val === false) {
+          migratedConfig.rangeStrategy = 'replace';
+        }
+      } else if (key === 'upgradeInRange') {
+        isMigrated = true;
+        delete migratedConfig.upgradeInRange;
+        if (val === true) {
+          migratedConfig.rangeStrategy = 'bump';
+        }
+      } else if (key === 'versionStrategy') {
+        isMigrated = true;
+        delete migratedConfig.versionStrategy;
+        if (val === 'widen') {
+          migratedConfig.rangeStrategy = 'widen';
+        }
       } else if (key === 'semanticPrefix') {
         isMigrated = true;
         delete migratedConfig.semanticPrefix;
diff --git a/lib/manager/npm/package.js b/lib/manager/npm/package.js
index 2ceb48f93f5e6bc9eeecda85927c0ce77a1d8ed5..92173142b12cf01648fd449f123c80012e9a1b45 100644
--- a/lib/manager/npm/package.js
+++ b/lib/manager/npm/package.js
@@ -2,11 +2,46 @@ const npmApi = require('../../datasource/npm');
 const versions = require('./versions');
 const { isValidSemver } = require('../../util/semver');
 const nodeManager = require('../_helpers/node/package');
+const { parseRange } = require('../../util/semver');
 
 module.exports = {
+  getRangeStrategy,
   getPackageUpdates,
 };
 
+function getRangeStrategy(config) {
+  const {
+    depType,
+    depName,
+    packageJsonType,
+    currentVersion,
+    rangeStrategy,
+  } = config;
+  if (rangeStrategy !== 'auto') {
+    return rangeStrategy;
+  }
+  if (depType === 'devDependencies') {
+    // Always pin devDependencies
+    logger.debug({ depName }, 'Pinning devDependency');
+    return 'pin';
+  }
+  if (depType === 'dependencies' && packageJsonType === 'app') {
+    // Pin dependencies if we're pretty sure it's not a browser library
+    logger.debug('Pinning app dependency');
+    return 'pin';
+  }
+  if (depType === 'peerDependencies') {
+    // Widen peer dependencies
+    logger.debug('Widening peer dependencies');
+    return 'widen';
+  }
+  const semverParsed = parseRange(currentVersion);
+  if (semverParsed.length > 1) {
+    return 'widen';
+  }
+  return 'replace';
+}
+
 async function getPackageUpdates(config) {
   logger.trace({ config }, `npm.getPackageUpdates()`);
   const { depType, depName, currentVersion } = config;
@@ -34,13 +69,17 @@ async function getPackageUpdates(config) {
     logger.debug(results[0].message);
     return results;
   }
+  const rangeStrategy = getRangeStrategy(config);
   npmApi.setNpmrc(
     config.npmrc,
     config.global ? config.global.exposeEnv : false
   );
   const npmDep = await npmApi.getDependency(depName);
   if (npmDep) {
-    results = await versions.determineUpgrades(npmDep, config);
+    results = await versions.determineUpgrades(npmDep, {
+      ...config,
+      rangeStrategy,
+    });
     if (results.length > 0) {
       logger.info(
         { dependency: depName },
diff --git a/lib/manager/npm/versions.js b/lib/manager/npm/versions.js
index a6c32a4b20bc60d3f12726a5b8fda33467ed6a85..7e17b7648ce3e240bd47983348446aff7250a063 100644
--- a/lib/manager/npm/versions.js
+++ b/lib/manager/npm/versions.js
@@ -29,7 +29,7 @@ function determineUpgrades(npmDep, config) {
   const result = {
     type: 'warning',
   };
-  const { lockedVersion, pinVersions, allowedVersions } = config;
+  const { lockedVersion, rangeStrategy, allowedVersions } = config;
   const { versions } = npmDep;
   if (!versions || Object.keys(versions).length === 0) {
     result.message = `No versions returned from registry for this package`;
@@ -54,17 +54,16 @@ function determineUpgrades(npmDep, config) {
       isPastLatest(npmDep, version) === false // if the version is less than or equal to latest
   );
   let rangeOperator;
-  if (config.upgradeInRange && isRange(currentVersion)) {
-    logger.debug({ currentVersion }, 'upgradeInRange is true');
+  if (config.rangeStrategy === 'bump' && isRange(currentVersion)) {
+    logger.debug({ currentVersion }, 'bumping current range');
     const parsedRange = 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');
+          logger.debug('Applying in-range bump');
           currentVersion = `${range.major}.${range.minor}.${range.patch}`;
           currentVersion += range.release ? `-${range.release}` : '';
-          logger.debug({ currentVersion }, 'upgradeInRange currentVersion');
           rangeOperator = range.operator;
         } else {
           logger.debug({ currentVersion }, 'Unsupported range type');
@@ -80,7 +79,11 @@ function determineUpgrades(npmDep, config) {
   // Check for a current range and pin it
   if (isRange(currentVersion)) {
     let newVersion;
-    if (pinVersions && lockedVersion && isPinnedVersion(lockedVersion)) {
+    if (
+      rangeStrategy === 'pin' &&
+      lockedVersion &&
+      isPinnedVersion(lockedVersion)
+    ) {
       newVersion = lockedVersion;
     } else {
       // Pin ranges to their maximum satisfying version
@@ -219,7 +222,11 @@ 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)) {
+  if (
+    upgrades.length === 0 ||
+    config.rangeStrategy === 'pin' ||
+    !isRange(currentVersion)
+  ) {
     return rangeOperator
       ? upgrades.map(upgrade => ({
           ...upgrade,
@@ -268,10 +275,10 @@ function determineUpgrades(npmDep, config) {
     .map(upgrade => ({ ...upgrade, ...{ isRange: true } }))
     .map(upgrade => {
       const { major, minor } = parseVersion(upgrade.newVersion);
-      const canReplace = config.versionStrategy !== 'widen';
-      const forceReplace = config.versionStrategy === 'replace';
-      const canWiden = config.versionStrategy !== 'replace';
-      const forceWiden = config.versionStrategy === 'widen';
+      const canReplace = config.rangeStrategy !== 'widen';
+      const forceReplace = config.rangeStrategy === 'replace';
+      const canWiden = config.rangeStrategy !== 'replace';
+      const forceWiden = config.rangeStrategy === 'widen';
       if (
         lastSemver.operator === '~' &&
         canReplace &&
diff --git a/lib/manager/travis/package.js b/lib/manager/travis/package.js
index 340970594da767bd6b6fc03353c059b18b393067..4ecc30c3183d557d437c3225ac96edbf41edec62 100644
--- a/lib/manager/travis/package.js
+++ b/lib/manager/travis/package.js
@@ -34,7 +34,10 @@ async function getPackageUpdates(config) {
     .sort() // sort combined array
     .reverse() // we want to order latest to oldest
     .map(version => `${version}`); // convert to strings
-  if (config.pinVersions || isPinnedVersion(config.currentVersion[0])) {
+  if (
+    config.rangeStrategy === 'pin' ||
+    isPinnedVersion(config.currentVersion[0])
+  ) {
     const releases = await getRepoReleases('nodejs/node');
     newVersion = newVersion.map(version =>
       maxSatisfyingVersion(releases, version).replace(/^v/, '')
diff --git a/lib/workers/repository/process/fetch.js b/lib/workers/repository/process/fetch.js
index f7d7e69cf04b30c489094b37da37ad55a0948501..33276a4716e4e1e1cebad6f4e45a719ffc0fc3a3 100644
--- a/lib/workers/repository/process/fetch.js
+++ b/lib/workers/repository/process/fetch.js
@@ -12,7 +12,7 @@ module.exports = {
 async function fetchDepUpdates(packageFileConfig, dep) {
   /* eslint-disable no-param-reassign */
   const { manager, packageFile } = packageFileConfig;
-  const { depType, depName, currentVersion } = dep;
+  const { depName, currentVersion } = dep;
   let depConfig = mergeChildConfig(packageFileConfig, dep);
   depConfig = applyPackageRules(depConfig);
   dep.updates = [];
@@ -25,25 +25,13 @@ async function fetchDepUpdates(packageFileConfig, dep) {
   ) {
     logger.debug(
       { depName: dep.depName },
-      'Dependency is ignored as part of monorepo'
+      'Dependency is ignored due to being internal'
     );
     dep.skipReason = 'internal-package';
   } else if (depConfig.enabled === false) {
     logger.debug({ depName: dep.depName }, 'Dependency is disabled');
     dep.skipReason = 'disabled';
   } else {
-    if (depConfig.pinVersions === null && !depConfig.upgradeInRange) {
-      if (depType === 'devDependencies') {
-        // Always pin devDependencies
-        logger.debug({ depName }, 'Pinning devDependency');
-        depConfig.pinVersions = true;
-      }
-      if (depType === 'dependencies' && depConfig.packageJsonType === 'app') {
-        // Pin dependencies if we're pretty sure it's not a browser library
-        logger.debug('Pinning app dependency');
-        depConfig.pinVersions = true;
-      }
-    }
     dep.updates = await getPackageUpdates(manager, depConfig);
     logger.debug({
       packageFile,
diff --git a/test/_fixtures/config/file-with-repo-presets.js b/test/_fixtures/config/file-with-repo-presets.js
index 823610546c73589d3296c48c0143ec41233d8a09..d6ce9aae8678cab344d0a7c22b118b82783d9dc7 100644
--- a/test/_fixtures/config/file-with-repo-presets.js
+++ b/test/_fixtures/config/file-with-repo-presets.js
@@ -1,13 +1,13 @@
 module.exports = {
   logLevel: 'error',
   extends: [':prHourlyLimit1', ':automergePatch'],
-  upgradeInRange: true,
+  automerge: true,
   separatePatchReleases: true,
   repositories: [
     'bar/baz',
     {
       repository: 'foo/bar',
-      upgradeInRange: false,
+      automerge: false,
     },
     {
       repository: 'renovateapp/renovate',
diff --git a/test/config/__snapshots__/index.spec.js.snap b/test/config/__snapshots__/index.spec.js.snap
index 09afdc585a3eb95e1a5603db2e47afe05f0d7878..b3d948e7696c36745ad3fece7e42d70e8c4b0cf0 100644
--- a/test/config/__snapshots__/index.spec.js.snap
+++ b/test/config/__snapshots__/index.spec.js.snap
@@ -2,8 +2,8 @@
 
 exports[`config/index .parseConfigs(env, defaultArgv) resolves all presets: foo/bar 1`] = `
 Object {
+  "automerge": false,
   "repository": "foo/bar",
-  "upgradeInRange": false,
 }
 `;
 
@@ -180,7 +180,6 @@ Object {
     "unpublishSafe": false,
   },
   "pinDigests": true,
-  "pinVersions": false,
   "pip_requirements": Object {
     "enabled": false,
     "fileMatch": Array [
@@ -197,6 +196,7 @@ Object {
   "prTitle": null,
   "privateKey": null,
   "python": Object {},
+  "rangeStrategy": "auto",
   "rebaseStalePrs": null,
   "recreateClosed": false,
   "renovateFork": false,
@@ -224,8 +224,6 @@ Object {
   "unstablePattern": null,
   "updateLockFiles": true,
   "updateNotScheduled": true,
-  "upgradeInRange": false,
-  "versionStrategy": "auto",
   "yarnrc": null,
 }
 `;
@@ -245,7 +243,7 @@ Object {
   "description": Array [
     "Use version pinning (maintain a single version only and not semver ranges)",
   ],
-  "pinVersions": true,
+  "rangeStrategy": "pin",
   "repository": "renovateapp/renovate",
 }
 `;
diff --git a/test/config/__snapshots__/migration.spec.js.snap b/test/config/__snapshots__/migration.spec.js.snap
index 93d4d03a839f67861bd721496c4344a6e9fb04f9..6025070470a46ed8b2c401546e0fad015e3b3663 100644
--- a/test/config/__snapshots__/migration.spec.js.snap
+++ b/test/config/__snapshots__/migration.spec.js.snap
@@ -77,6 +77,12 @@ Object {
         "ang",
       ],
     },
+    Object {
+      "depTypeList": Array [
+        "peerDependencies",
+      ],
+      "rangeStrategy": "widen",
+    },
     Object {
       "depTypeList": Array [
         "devDependencies",
@@ -107,6 +113,7 @@ Object {
     "automerge": true,
   },
   "prTitle": "{{#if semanticCommitType}}{{semanticCommitType}}{{#if semanticCommitScope}}({{semanticCommitScope}}){{/if}}: {{/if}}some pr title",
+  "rangeStrategy": "bump",
   "schedule": "on the first day of the month",
   "semanticCommitScope": "deps",
   "semanticCommitType": "fix",
@@ -129,7 +136,7 @@ Object {
       "paths": Array [
         "package.json",
       ],
-      "pinVersions": true,
+      "rangeStrategy": "pin",
     },
     Object {
       "depTypeList": Array [
@@ -138,7 +145,7 @@ Object {
       "paths": Array [
         "package.json",
       ],
-      "pinVersions": true,
+      "rangeStrategy": "pin",
     },
   ],
 }
@@ -171,13 +178,13 @@ Object {
       "paths": Array [
         "backend/package.json",
       ],
-      "pinVersions": false,
+      "rangeStrategy": "replace",
     },
     Object {
       "paths": Array [
         "frontend/package.json",
       ],
-      "pinVersions": true,
+      "rangeStrategy": "pin",
     },
     Object {
       "depTypeList": Array [
@@ -186,7 +193,7 @@ Object {
       "paths": Array [
         "other/package.json",
       ],
-      "pinVersions": true,
+      "rangeStrategy": "pin",
     },
     Object {
       "depTypeList": Array [
@@ -195,7 +202,7 @@ Object {
       "paths": Array [
         "other/package.json",
       ],
-      "pinVersions": true,
+      "rangeStrategy": "pin",
     },
   ],
 }
diff --git a/test/config/__snapshots__/presets.spec.js.snap b/test/config/__snapshots__/presets.spec.js.snap
index af43c227c445dee82e3f217c5a42ba966cf5911f..f02db8cdfb5088bbdb90865fe20e8ee649855454 100644
--- a/test/config/__snapshots__/presets.spec.js.snap
+++ b/test/config/__snapshots__/presets.spec.js.snap
@@ -76,7 +76,7 @@ Object {
   "description": Array [
     "Use version pinning (maintain a single version only and not semver ranges)",
   ],
-  "pinVersions": true,
+  "rangeStrategy": "pin",
 }
 `;
 
@@ -287,13 +287,13 @@ Object {
       "packagePatterns": Array [
         "*",
       ],
-      "pinVersions": false,
+      "rangeStrategy": "replace",
     },
     Object {
       "depTypeList": Array [
         "devDependencies",
       ],
-      "pinVersions": true,
+      "rangeStrategy": "pin",
     },
   ],
   "prCreation": "immediate",
@@ -670,6 +670,6 @@ Object {
     "Use version pinning (maintain a single version only and not semver ranges)",
   ],
   "foo": 1,
-  "pinVersions": true,
+  "rangeStrategy": "pin",
 }
 `;
diff --git a/test/config/index.spec.js b/test/config/index.spec.js
index af69477e66fa15269c960fec44456529e8d10a1f..43d15fca825d599505fbd1b8ea047e8694c59715 100644
--- a/test/config/index.spec.js
+++ b/test/config/index.spec.js
@@ -199,7 +199,7 @@ describe('config/index', () => {
       expect(get.mock.calls.length).toBe(0);
     });
     it('resolves all presets', async () => {
-      defaultArgv.push('--pr-hourly-limit=10', '--upgrade-in-range=false');
+      defaultArgv.push('--pr-hourly-limit=10', '--automerge=false');
       const env = {
         GITHUB_TOKEN: 'abc',
         RENOVATE_CONFIG_FILE: require.resolve(
@@ -218,7 +218,7 @@ describe('config/index', () => {
       expect(actual.minor.automerge).toBeUndefined();
       expect(actual.major.automerge).toBeUndefined();
       expect(actual.prHourlyLimit).toBe(10);
-      expect(actual.upgradeInRange).toBe(false);
+      expect(actual.automerge).toBe(false);
       actual.repositories.forEach(repo => {
         if (typeof repo === 'object') {
           expect(repo).toMatchSnapshot(repo.repository);
@@ -233,7 +233,7 @@ describe('config/index', () => {
       const parentConfig = { ...defaultConfig };
       const childConfig = {
         foo: 'bar',
-        pinVersions: false,
+        rangeStrategy: 'replace',
         lockFileMaintenance: {
           schedule: ['on monday'],
         },
@@ -241,7 +241,7 @@ describe('config/index', () => {
       const configParser = require('../../lib/config/index.js');
       const config = configParser.mergeChildConfig(parentConfig, childConfig);
       expect(config.foo).toEqual('bar');
-      expect(config.pinVersions).toBe(false);
+      expect(config.rangeStrategy).toEqual('replace');
       expect(config.lockFileMaintenance.schedule).toEqual(['on monday']);
       expect(config.lockFileMaintenance).toMatchSnapshot();
     });
diff --git a/test/config/migration.spec.js b/test/config/migration.spec.js
index 9249b03aeb3202fb5a6a9100f2f6032160501c9d..49c483c99ffe0cfa7947033908bd077df491c541 100644
--- a/test/config/migration.spec.js
+++ b/test/config/migration.spec.js
@@ -13,6 +13,7 @@ describe('config/migration', () => {
         automergeMajor: false,
         automergeMinor: true,
         automergePatch: true,
+        upgradeInRange: true,
         baseBranch: 'next',
         ignoreNodeModules: true,
         node: {
@@ -30,6 +31,9 @@ describe('config/migration', () => {
             extends: ['foo'],
           },
         ],
+        peerDependencies: {
+          versionStrategy: 'widen',
+        },
         packageRules: [
           {
             packagePatterns: '^(@angular|typescript)',
@@ -81,7 +85,7 @@ describe('config/migration', () => {
       expect(isMigrated).toBe(true);
       expect(migratedConfig.depTypes).not.toBeDefined();
       expect(migratedConfig.automerge).toEqual(false);
-      expect(migratedConfig.packageRules).toHaveLength(6);
+      expect(migratedConfig.packageRules).toHaveLength(7);
     });
     it('migrates before and after schedules', () => {
       const config = {
@@ -283,8 +287,8 @@ describe('config/migration', () => {
       expect(migratedConfig.includePaths).toHaveLength(4);
       expect(migratedConfig.packageFiles).toBeUndefined();
       expect(migratedConfig.packageRules).toHaveLength(4);
-      expect(migratedConfig.packageRules[0].pinVersions).toBe(false);
-      expect(migratedConfig.packageRules[1].pinVersions).toBe(true);
+      expect(migratedConfig.packageRules[0].rangeStrategy).toBe('replace');
+      expect(migratedConfig.packageRules[1].rangeStrategy).toBe('pin');
     });
     it('it migrates more packageFiles', () => {
       const config = {
diff --git a/test/config/presets.spec.js b/test/config/presets.spec.js
index f417924f6cad2f848b80a0a5a1db0f0c6bb40d3e..e4b8727840d63f365e3b2ec04195b82fcec0b361 100644
--- a/test/config/presets.spec.js
+++ b/test/config/presets.spec.js
@@ -107,7 +107,7 @@ describe('config/presets', () => {
       config.extends = [':pinVersions'];
       const res = await presets.resolveConfigPresets(config);
       expect(res).toMatchSnapshot();
-      expect(res.pinVersions).toBe(true);
+      expect(res.rangeStrategy).toEqual('pin');
     });
     it('throws if valid and invalid', async () => {
       config.foo = 1;
diff --git a/test/manager/npm/package.spec.js b/test/manager/npm/package.spec.js
index 5bfc66071f0d36fc687255dc4cd0fdd61e1b2120..80241a5b685e6a4c0282c5deb359c30df97fc5da 100644
--- a/test/manager/npm/package.spec.js
+++ b/test/manager/npm/package.spec.js
@@ -8,6 +8,40 @@ jest.mock('../../../lib/manager/_helpers/node/package');
 npmApi.getDependency = jest.fn();
 
 describe('lib/manager/npm/package', () => {
+  describe('getRangeStrategy', () => {
+    it('returns same if not auto', () => {
+      const config = { rangeStrategy: 'widen' };
+      expect(npm.getRangeStrategy(config)).toEqual('widen');
+    });
+    it('pins devDependencies', () => {
+      const config = { rangeStrategy: 'auto', depType: 'devDependencies' };
+      expect(npm.getRangeStrategy(config)).toEqual('pin');
+    });
+    it('pins app dependencies', () => {
+      const config = {
+        rangeStrategy: 'auto',
+        depType: 'dependencies',
+        packageJsonType: 'app',
+      };
+      expect(npm.getRangeStrategy(config)).toEqual('pin');
+    });
+    it('widens peerDependencies', () => {
+      const config = { rangeStrategy: 'auto', depType: 'peerDependencies' };
+      expect(npm.getRangeStrategy(config)).toEqual('widen');
+    });
+    it('widens complex ranges', () => {
+      const config = {
+        rangeStrategy: 'auto',
+        depType: 'dependencies',
+        currentVersion: '^1.6.0 || ^2.0.0',
+      };
+      expect(npm.getRangeStrategy(config)).toEqual('widen');
+    });
+    it('defaults to replace', () => {
+      const config = { rangeStrategy: 'auto', depType: 'dependencies' };
+      expect(npm.getRangeStrategy(config)).toEqual('replace');
+    });
+  });
   describe('getPackageUpdates', () => {
     let config;
     beforeEach(() => {
diff --git a/test/manager/npm/versions.spec.js b/test/manager/npm/versions.spec.js
index 1e67a13bef463d225dec589bc237115072707a58..16948c573d2b8068e4daf59ab62e9b76ef7d55a6 100644
--- a/test/manager/npm/versions.spec.js
+++ b/test/manager/npm/versions.spec.js
@@ -10,7 +10,7 @@ let config;
 describe('manager/npm/versions', () => {
   beforeEach(() => {
     config = { ...require('../../../lib/config/defaults').getConfig() };
-    config.pinVersions = true;
+    config.rangeStrategy = 'pin';
   });
 
   describe('.determineUpgrades(npmDep, config)', () => {
@@ -120,27 +120,23 @@ describe('manager/npm/versions', () => {
       expect(versions.determineUpgrades(qJson, config)).toMatchSnapshot();
     });
     it('widens minor ranged versions if configured', () => {
-      config.pinVersions = false;
       config.currentVersion = '~1.3.0';
-      config.versionStrategy = 'widen';
+      config.rangeStrategy = 'widen';
       expect(versions.determineUpgrades(qJson, config)).toMatchSnapshot();
     });
     it('replaces minor complex ranged versions if configured', () => {
-      config.pinVersions = false;
       config.currentVersion = '~1.2.0 || ~1.3.0';
-      config.versionStrategy = 'replace';
+      config.rangeStrategy = 'replace';
       expect(versions.determineUpgrades(qJson, config)).toMatchSnapshot();
     });
     it('widens major ranged versions if configured', () => {
-      config.pinVersions = false;
       config.currentVersion = '^2.0.0';
-      config.versionStrategy = 'widen';
+      config.rangeStrategy = 'widen';
       expect(versions.determineUpgrades(webpackJson, config)).toMatchSnapshot();
     });
     it('replaces major complex ranged versions if configured', () => {
-      config.pinVersions = false;
       config.currentVersion = '^1.0.0 || ^2.0.0';
-      config.versionStrategy = 'replace';
+      config.rangeStrategy = 'replace';
       expect(versions.determineUpgrades(webpackJson, config)).toMatchSnapshot();
     });
     it('pins minor ranged versions', () => {
@@ -153,7 +149,7 @@ describe('manager/npm/versions', () => {
       expect(versions.determineUpgrades(qJson, config)).toMatchSnapshot();
     });
     it('ignores minor ranged versions when not pinning', () => {
-      config.pinVersions = false;
+      config.rangeStrategy = 'replace';
       config.currentVersion = '^1.0.0';
       expect(versions.determineUpgrades(qJson, config)).toHaveLength(0);
     });
@@ -166,115 +162,115 @@ describe('manager/npm/versions', () => {
       expect(versions.determineUpgrades(qJson, config)).toMatchSnapshot();
     });
     it('upgrades tilde ranges without pinning', () => {
-      config.pinVersions = false;
+      config.rangeStrategy = 'replace';
       config.currentVersion = '~1.3.0';
       expect(versions.determineUpgrades(qJson, config)).toMatchSnapshot();
     });
     it('upgrades .x major ranges without pinning', () => {
-      config.pinVersions = false;
+      config.rangeStrategy = 'replace';
       config.currentVersion = '0.x';
       expect(versions.determineUpgrades(qJson, config)).toMatchSnapshot();
     });
     it('upgrades .x minor ranges without pinning', () => {
-      config.pinVersions = false;
+      config.rangeStrategy = 'replace';
       config.currentVersion = '1.3.x';
       expect(versions.determineUpgrades(qJson, config)).toMatchSnapshot();
     });
     it('upgrades shorthand major ranges without pinning', () => {
-      config.pinVersions = false;
+      config.rangeStrategy = 'replace';
       config.currentVersion = '0';
       expect(versions.determineUpgrades(qJson, config)).toMatchSnapshot();
     });
     it('upgrades shorthand minor ranges without pinning', () => {
-      config.pinVersions = false;
+      config.rangeStrategy = 'replace';
       config.currentVersion = '1.3';
       expect(versions.determineUpgrades(qJson, config)).toMatchSnapshot();
     });
     it('upgrades multiple tilde ranges without pinning', () => {
-      config.pinVersions = false;
+      config.rangeStrategy = 'replace';
       config.currentVersion = '~0.7.0';
       expect(versions.determineUpgrades(qJson, config)).toMatchSnapshot();
     });
     it('upgrades multiple caret ranges without pinning', () => {
-      config.pinVersions = false;
+      config.rangeStrategy = 'replace';
       config.currentVersion = '^0.7.0';
       expect(versions.determineUpgrades(qJson, config)).toMatchSnapshot();
     });
     it('supports complex ranges', () => {
-      config.pinVersions = false;
+      config.rangeStrategy = 'widen';
       config.currentVersion = '^0.7.0 || ^0.8.0';
       const res = versions.determineUpgrades(qJson, config);
       expect(res).toHaveLength(2);
       expect(res[0]).toMatchSnapshot();
     });
     it('supports complex major ranges', () => {
-      config.pinVersions = false;
+      config.rangeStrategy = 'widen';
       config.currentVersion = '^1.0.0 || ^2.0.0';
       const res = versions.determineUpgrades(webpackJson, config);
       expect(res).toMatchSnapshot();
     });
     it('supports complex major hyphen ranges', () => {
-      config.pinVersions = false;
+      config.rangeStrategy = 'widen';
       config.currentVersion = '1.x - 2.x';
       const res = versions.determineUpgrades(webpackJson, config);
       expect(res).toMatchSnapshot();
     });
     it('widens .x OR ranges', () => {
-      config.pinVersions = false;
+      config.rangeStrategy = 'widen';
       config.currentVersion = '1.x || 2.x';
       const res = versions.determineUpgrades(webpackJson, config);
       expect(res).toMatchSnapshot();
     });
     it('widens stanndalone major OR ranges', () => {
-      config.pinVersions = false;
+      config.rangeStrategy = 'widen';
       config.currentVersion = '1 || 2';
       const res = versions.determineUpgrades(webpackJson, config);
       expect(res).toMatchSnapshot();
     });
     it('supports complex tilde ranges', () => {
-      config.pinVersions = false;
+      config.rangeStrategy = 'widen';
       config.currentVersion = '~1.2.0 || ~1.3.0';
       const res = versions.determineUpgrades(qJson, config);
       expect(res).toMatchSnapshot();
     });
     it('returns nothing for greater than ranges', () => {
-      config.pinVersions = false;
+      config.rangeStrategy = 'replace';
       config.currentVersion = '>= 0.7.0';
       expect(versions.determineUpgrades(qJson, config)).toHaveLength(0);
     });
     it('upgrades less than equal ranges without pinning', () => {
-      config.pinVersions = false;
+      config.rangeStrategy = 'replace';
       config.currentVersion = '<= 0.7.2';
       expect(versions.determineUpgrades(qJson, config)).toMatchSnapshot();
     });
     it('upgrades less than ranges without pinning', () => {
-      config.pinVersions = false;
+      config.rangeStrategy = 'replace';
       config.currentVersion = '< 0.7.2';
       expect(versions.determineUpgrades(qJson, config)).toMatchSnapshot();
     });
     it('upgrades major less than equal ranges', () => {
-      config.pinVersions = false;
+      config.rangeStrategy = 'replace';
       config.currentVersion = '<= 1.0.0';
       const res = versions.determineUpgrades(qJson, config);
       expect(res).toMatchSnapshot();
       expect(res[0].newVersion).toEqual('<= 2.0.0');
     });
     it('upgrades major less than ranges without pinning', () => {
-      config.pinVersions = false;
+      config.rangeStrategy = 'replace';
       config.currentVersion = '< 1.0.0';
       const res = versions.determineUpgrades(qJson, config);
       expect(res).toMatchSnapshot();
       expect(res[0].newVersion).toEqual('< 2.0.0');
     });
     it('upgrades major greater than less than ranges without pinning', () => {
-      config.pinVersions = false;
+      config.rangeStrategy = 'replace';
       config.currentVersion = '>= 0.5.0 < 1.0.0';
       const res = versions.determineUpgrades(qJson, config);
       expect(res).toMatchSnapshot();
       expect(res[0].newVersion).toEqual('>= 0.5.0 < 2.0.0');
     });
     it('upgrades minor greater than less than ranges without pinning', () => {
-      config.pinVersions = false;
+      config.rangeStrategy = 'replace';
       config.currentVersion = '>= 0.5.0 <0.8';
       const res = versions.determineUpgrades(qJson, config);
       expect(res).toMatchSnapshot();
@@ -282,7 +278,7 @@ describe('manager/npm/versions', () => {
       expect(res[1].newVersion).toEqual('>= 0.5.0 <1.5');
     });
     it('upgrades minor greater than less than equals ranges without pinning', () => {
-      config.pinVersions = false;
+      config.rangeStrategy = 'replace';
       config.currentVersion = '>= 0.5.0 <= 0.8.0';
       const res = versions.determineUpgrades(qJson, config);
       expect(res).toMatchSnapshot();
@@ -290,7 +286,7 @@ describe('manager/npm/versions', () => {
       expect(res[1].newVersion).toEqual('>= 0.5.0 <= 1.5.0');
     });
     it('rejects reverse ordered less than greater than', () => {
-      config.pinVersions = false;
+      config.rangeStrategy = 'replace';
       config.currentVersion = '<= 0.8.0 >= 0.5.0';
       const res = versions.determineUpgrades(qJson, config);
       expect(res).toMatchSnapshot();
@@ -335,12 +331,12 @@ describe('manager/npm/versions', () => {
       ).toMatchSnapshot();
     });
     it('should treat zero zero tilde ranges as 0.0.x', () => {
-      config.pinVersions = false;
+      config.rangeStrategy = 'replace';
       config.currentVersion = '~0.0.34';
       expect(versions.determineUpgrades(helmetJson, config)).toEqual([]);
     });
     it('should treat zero zero caret ranges as pinned', () => {
-      config.pinVersions = false;
+      config.rangeStrategy = 'replace';
       config.currentVersion = '^0.0.34';
       expect(versions.determineUpgrades(helmetJson, config)).toMatchSnapshot();
     });
@@ -363,36 +359,32 @@ describe('manager/npm/versions', () => {
     });
     it('does not jump  major unstable', () => {
       config.currentVersion = '^4.4.0-canary.3';
-      config.pinVersions = false;
+      config.rangeStrategy = 'replace';
       const res = versions.determineUpgrades(nextJson, config);
       expect(res).toHaveLength(0);
     });
     it('supports in-range updates', () => {
-      config.upgradeInRange = true;
+      config.rangeStrategy = 'bump';
       config.currentVersion = '~1.0.0';
       expect(versions.determineUpgrades(qJson, config)).toMatchSnapshot();
     });
     it('rejects in-range unsupported operator', () => {
-      config.upgradeInRange = true;
-      config.pinVersions = false;
+      config.rangeStrategy = 'bump';
       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.rangeStrategy = 'bump';
       config.currentVersion = '1.x';
       expect(versions.determineUpgrades(qJson, config)).toMatchSnapshot();
     });
     it('rejects complex range in-range updates', () => {
-      config.upgradeInRange = true;
-      config.pinVersions = false;
+      config.rangeStrategy = 'bump';
       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.rangeStrategy = 'bump';
       config.currentVersion = '1.0.0';
       expect(versions.determineUpgrades(qJson, config)).toMatchSnapshot();
     });
diff --git a/test/workers/repository/process/fetch.spec.js b/test/workers/repository/process/fetch.spec.js
index 2f5e6a91fb58533c223578d97c3351bca7183a12..4bb48f9248f1a0733c5fb1d562114b89f58eecd3 100644
--- a/test/workers/repository/process/fetch.spec.js
+++ b/test/workers/repository/process/fetch.spec.js
@@ -51,7 +51,7 @@ describe('workers/repository/process/fetch', () => {
       expect(packageFiles.npm[0].deps[2].updates).toHaveLength(0);
     });
     it('fetches updates', async () => {
-      config.pinVersions = null;
+      config.rangeStrategy = 'auto';
       const packageFiles = {
         npm: [
           {
diff --git a/website/docs/configuration-options.md b/website/docs/configuration-options.md
index c8cd70e4b4b7856a1f6d81dc11bee16e166fcecc..aa33e40d177b23a712ffa92470918d8a696ccc9f 100644
--- a/website/docs/configuration-options.md
+++ b/website/docs/configuration-options.md
@@ -415,11 +415,11 @@ Use this field if you want to have one or more exact name matches in your packag
 ```
   "packageRules": [{
     "packageNames": ["angular"],
-    "pinVersions": true
+    "rangeStrategy": "pin"
   }]
 ```
 
-The above will enable `pinVersions` for the package `angular`.
+The above will enable set `rangeStrategy` to `pin` only for the package `angular`.
 
 ### packagePatterns
 
@@ -428,11 +428,11 @@ Use this field if you want to have one or more package names patterns in your pa
 ```
   "packageRules": [{
     "packageNames": ["^angular"],
-    "pinVersions": false
+    "rangeStrategy": "replace"
   }]
 ```
 
-The above will enable `pinVersions` for any package starting with `angular`.
+The above will set `rangeStrategy` to `replace` for any package starting with `angular`.
 
 ## patch
 
@@ -448,10 +448,6 @@ Add to this object if you wish to define rules that apply only to PRs that pin d
 
 By default, Renovate will add sha256 digests to Docker source images so that they are then "immutable". Set this to false to continue using only tags to identify source images.
 
-## pinVersions
-
-This is a very important feature to consider, because not every repository's requirements are the same. The default value within the tool itself is false, which means no existing ranges are pinned. However if you are using the suggested preset `"config:base"`, then it changes the default of pinVersions to `null`, which means Renovate attempts to autodetect what's best for the project. In such cases `devDependencies` in `package.json` will alway be pinned, but `dependencies` will only be pinned if the package is `private` or has no `main` entry defined - both indicators that it is not intended to be published and consumed by other packages. To override the `"config:base"` setting, add the preset `":preserveSemverRanges"` to your `extends` array.
-
 ## pip_requirements
 
 ## prBody
@@ -494,6 +490,32 @@ The PR title is important for some of Renovate's matching algorithms (e.g. deter
 
 ## python
 
+## rangeStrategy
+
+Behaviour:
+
+* `auto` = Renovate decides (this will be done on a manager-by-manager basis)
+* `pin` = convert ranges to exact versions, e.g. `^1.0.0` -> `1.1.0`
+* `bump` = e.g. bump the range even if the new version satisifies the existing range, e.g. `^1.0.0` -> `^1.1.0`
+* `replace` = Replace the range with a newer one if the new version falls outside it, e.g. `^1.0.0` -> `^2.0.0`
+* `widen` = Widen the range with newer one, e.g. `^1.0.0` -> `^1.0.0 || ^2.0.0`
+
+Renovate's "auto" strategy works like this for npm:
+
+1.  Always pin `devDependencies`
+2.  Pin `dependencies` if we detect that it's an app and not a library
+3.  Widen `peerDependencies`
+4.  If an existing range already ends with an "or" operator - e.g. `"^1.0.0 || ^2.0.0"` - then Renovate will widen it, e.g. making it into `"^1.0.0 || ^2.0.0 || ^3.0.0"`.
+5.  Otherwise, replace the range. e.g. `"^2.0.0"` would be replaced by `"^3.0.0"`
+
+**bump**
+
+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" any 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 because `1.2.0` satisfies the range. If instead you'd prefer to be updated to `^1.2.0` in cases like this, then set `rangeStrategy` to `bump` 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`.
+
 ## rebaseStalePrs
 
 This field is defaulted to `null` because it has a potential to create a lot of noise and additional builds to your repository. If you enable it to true, it means each Renovate branch will be updated whenever the base branch has changed. If enabled, this also means that whenever a Renovate PR is merged (whether by automerge or manually via GitHub web) then any other existing Renovate PRs will then need to get rebased and retested.
@@ -629,25 +651,4 @@ 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
-
-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
-
-npm-only.
-
-Renovate's "auto" strategy for updating versions is like this:
-
-1.  If the existing version already ends with an "or" operator - e.g. `"^1.0.0 || ^2.0.0"` - then Renovate will widen it, e.g. making it into `"^1.0.0 || ^2.0.0 || ^3.0.0"`.
-2.  Otherwise, replace it. e.g. `"^2.0.0"` would be replaced by `"^3.0.0"`
-
-You can override logic either way, by setting it to `replace` or `widen`. e.g. if the currentVersion is `"^1.0.0 || ^2.0.0"` but you configure `versionStrategy=replace` then the result will be `"^3.0.0"`.
-
-Or for example if you configure all `peerDependencies` with `versionStrategy=widen` and have `"react": "^15.0.0"` as current version then it will be updated to `"react": "^15.0.0 || ^16.0.0"`.
-
 ## yarnrc