diff --git a/lib/versioning/composer/index.spec.ts b/lib/versioning/composer/index.spec.ts
index 95e746584f8e9fed0cbdc5abfece20ad2790ed37..62848c38bec2fa3d746971d2d71aeea8813d9263 100644
--- a/lib/versioning/composer/index.spec.ts
+++ b/lib/versioning/composer/index.spec.ts
@@ -12,6 +12,11 @@ describe('semver.equals(a, b)', () => {
   it('should pad really short version', () => {
     expect(semver.equals('v1.0.0', '1')).toBe(true);
   });
+  it('should translate stability modifier', () => {
+    expect(semver.equals('1.0@alpha3', '1.0.0-alpha.3')).toBe(true);
+    expect(semver.equals('1.0@beta', '1.0.0-beta')).toBe(true);
+    expect(semver.equals('1.0@rc2', '1.0.0-rc.2')).toBe(true);
+  });
 });
 describe('semver.isGreaterThan(a, b)', () => {
   it('should pad short version', () => {
@@ -49,6 +54,11 @@ describe('semver.isValid(input)', () => {
     expect(semver.isValid('^1.2.3')).toBeTruthy();
     expect(semver.isValid('>1.2.3')).toBeTruthy();
   });
+  it('should support ranges with stability modifiers', () => {
+    expect(semver.isValid('~1.2.3@beta1')).toBeTruthy();
+    expect(semver.isValid('^1.2.3@alpha')).toBeTruthy();
+    expect(semver.isValid('>1.2.3@rc2')).toBeTruthy();
+  });
 });
 describe('semver.isVersion(input)', () => {
   it('should support simple semver', () => {
@@ -88,6 +98,22 @@ describe('semver.maxSatisfyingVersion()', () => {
       )
     ).toBe('0.5.0');
   });
+  it('handles prereleases', () => {
+    expect(
+      semver.maxSatisfyingVersion(
+        [
+          '0.4.0',
+          '0.5.0',
+          '4.0.0-beta1',
+          '4.0.0-beta2',
+          '4.2.0-beta1',
+          '4.2.0-beta2',
+          '5.0.0',
+        ],
+        '~4@beta'
+      )
+    ).toBe('4.0.0-beta2');
+  });
 });
 describe('semver.minSatisfyingVersion()', () => {
   it('handles massaged tilde', () => {
@@ -110,6 +136,22 @@ describe('semver.minSatisfyingVersion()', () => {
       )
     ).toBe('0.4.0');
   });
+  it('handles prereleases', () => {
+    expect(
+      semver.minSatisfyingVersion(
+        [
+          '0.4.0',
+          '0.5.0',
+          '4.0.0-beta1',
+          '4.0.0',
+          '4.2.0-beta1',
+          '4.2.0-beta2',
+          '5.0.0',
+        ],
+        '~4@beta'
+      )
+    ).toBe('4.0.0-beta1');
+  });
 });
 describe('semver.matches()', () => {
   it('handles massaged tilde', () => {
@@ -286,6 +328,26 @@ describe('semver.getNewValue()', () => {
       })
     ).toEqual('^v1.1');
   });
+  it('bumps short caret with stability modifiers', () => {
+    expect(
+      semver.getNewValue({
+        currentValue: '^v1.0@beta',
+        rangeStrategy: 'bump',
+        fromVersion: '1.0.0-beta3',
+        toVersion: '1.0.0-beta5',
+      })
+    ).toEqual('^v1.0.0@beta5');
+  });
+  it('replaces short caret with stability modifiers', () => {
+    expect(
+      semver.getNewValue({
+        currentValue: '^v1.0@beta',
+        rangeStrategy: 'replace',
+        fromVersion: '1.0.0-beta3',
+        toVersion: '2.0.0-beta5',
+      })
+    ).toEqual('^v2.0.0@beta5');
+  });
   it('handles differing lengths', () => {
     expect(
       semver.getNewValue({
diff --git a/lib/versioning/composer/index.ts b/lib/versioning/composer/index.ts
index c66b70561ab55d91d5b2f135d0b0853fd63443f1..e58003e2c991f5db10e91c3e2c5a4d5c54a205ec 100644
--- a/lib/versioning/composer/index.ts
+++ b/lib/versioning/composer/index.ts
@@ -14,32 +14,66 @@ export const urls = [
 export const supportsRanges = true;
 export const supportedRangeStrategies = ['bump', 'extend', 'pin', 'replace'];
 
+function getVersionParts(input: string): [string, string] {
+  const versionParts = input.split('-');
+  if (versionParts.length === 1) {
+    return [input, ''];
+  }
+
+  return [versionParts[0], '-' + versionParts[1]];
+}
+
 function padZeroes(input: string): string {
-  const sections = input.split('.');
+  const [output, stability] = getVersionParts(input);
+
+  const sections = output.split('.');
   while (sections.length < 3) {
     sections.push('0');
   }
-  return sections.join('.');
+  return sections.join('.') + stability;
 }
 
-function removeLeadingV(input: string): string {
-  return input.replace(/^v/i, '');
+function convertStabilitiyModifier(input: string): string {
+  // Handle stability modifiers.
+  const versionParts = input.split('@');
+  if (versionParts.length === 1) {
+    return input;
+  }
+
+  // 1.0@beta2 to 1.0-beta.2
+  const stability = versionParts[1].replace(
+    /(?:^|\s)(beta|alpha|rc)([1-9][0-9]*)(?: |$)/gi,
+    '$1.$2'
+  );
+
+  // If there is a stability part, npm semver expects the version
+  // to be full
+  return padZeroes(versionParts[0]) + '-' + stability;
+}
+
+function normalizeVersion(input: string): string {
+  let output = input;
+  output = output.replace(/(^|>|>=|\^|~)v/i, '$1');
+  return convertStabilitiyModifier(output);
 }
 
 function composer2npm(input: string): string {
-  const cleanInput = removeLeadingV(input);
+  const cleanInput = normalizeVersion(input);
   if (npm.isVersion(cleanInput)) {
     return cleanInput;
   }
   if (npm.isVersion(padZeroes(cleanInput))) {
     return padZeroes(cleanInput);
   }
-  let output = cleanInput;
+  const [versionId, stability] = getVersionParts(cleanInput);
+  let output = versionId;
+
   // ~4 to ^4 and ~4.1 to ^4.1
   output = output.replace(/(?:^|\s)~([1-9][0-9]*(?:\.[0-9]*)?)(?: |$)/g, '^$1');
   // ~0.4 to >=0.4 <1
   output = output.replace(/(?:^|\s)~(0\.[1-9][0-9]*)(?: |$)/g, '>=$1 <1');
-  return output;
+
+  return output + stability;
 }
 
 const equals = (a: string, b: string): boolean =>
@@ -116,15 +150,15 @@ function getNewValue({
       newValue = `${operator}${toMajor}.${toMinor}`;
     }
   } else if (
-    npm.isVersion(padZeroes(toVersion)) &&
-    npm.isValid(currentValue) &&
-    composer2npm(currentValue) === removeLeadingV(currentValue)
+    npm.isVersion(padZeroes(normalizeVersion(toVersion))) &&
+    npm.isValid(normalizeVersion(currentValue)) &&
+    composer2npm(currentValue) === normalizeVersion(currentValue)
   ) {
     newValue = npm.getNewValue({
-      currentValue,
+      currentValue: normalizeVersion(currentValue),
       rangeStrategy,
-      fromVersion,
-      toVersion: padZeroes(toVersion),
+      fromVersion: normalizeVersion(fromVersion),
+      toVersion: padZeroes(normalizeVersion(toVersion)),
     });
   }
   if (currentValue.includes(' || ')) {
@@ -151,7 +185,7 @@ function getNewValue({
   if (currentValue.split('.')[0].includes('v')) {
     newValue = newValue.replace(/([0-9])/, 'v$1');
   }
-  return newValue;
+  return newValue.replace('-', '@');
 }
 
 function sortVersions(a: string, b: string): number {