diff --git a/lib/modules/versioning/api.ts b/lib/modules/versioning/api.ts
index 2fb4afb5d9479d5803ee76b967894e85f08c1a80..764529bf40c69fd49cbd681e0394e85d931e549b 100644
--- a/lib/modules/versioning/api.ts
+++ b/lib/modules/versioning/api.ts
@@ -32,6 +32,7 @@ import * as regex from './regex';
 import * as rez from './rez';
 import * as rpm from './rpm';
 import * as ruby from './ruby';
+import * as sameMajor from './same-major';
 import * as semver from './semver';
 import * as semverCoerced from './semver-coerced';
 import * as swift from './swift';
@@ -76,6 +77,7 @@ api.set(regex.id, regex.api);
 api.set(rez.id, rez.api);
 api.set(rpm.id, rpm.api);
 api.set(ruby.id, ruby.api);
+api.set(sameMajor.id, sameMajor.api);
 api.set(semver.id, semver.api);
 api.set(semverCoerced.id, semverCoerced.api);
 api.set(swift.id, swift.api);
diff --git a/lib/modules/versioning/same-major/index.spec.ts b/lib/modules/versioning/same-major/index.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b84791e5d33eabdcc82b884c26da032f84e0510b
--- /dev/null
+++ b/lib/modules/versioning/same-major/index.spec.ts
@@ -0,0 +1,70 @@
+import sameMajor from '.';
+
+describe('modules/versioning/same-major/index', () => {
+  describe('.isGreaterThan(version, other)', () => {
+    it('should return true', () => {
+      expect(sameMajor.isGreaterThan('4.0.0', '3.0.0')).toBeTrue(); // greater
+    });
+
+    it('should return false', () => {
+      expect(sameMajor.isGreaterThan('2.0.2', '3.1.0')).toBeFalse(); // less
+      expect(sameMajor.isGreaterThan('3.1.0', '3.0.0')).toBeFalse(); // same major -> equal
+      expect(sameMajor.isGreaterThan('3.0.0', '3.0.0')).toBeFalse(); // equal
+      expect(sameMajor.isGreaterThan('a', '3.0.0')).toBeFalse(); // invalid versions
+    });
+  });
+
+  describe('.matches(version, range)', () => {
+    it('should return true when version has same major', () => {
+      expect(sameMajor.matches('1.0.1', '1.0.0')).toBeTrue();
+      expect(sameMajor.matches('1.0.0', '1.0.0')).toBeTrue();
+    });
+
+    it('should return false when version has different major', () => {
+      expect(sameMajor.matches('2.0.1', '1.0.0')).toBeFalse();
+    });
+
+    it('should return false when version is out of range', () => {
+      expect(sameMajor.matches('1.2.3', '1.2.4')).toBeFalse();
+      expect(sameMajor.matches('2.0.0', '1.2.4')).toBeFalse();
+      expect(sameMajor.matches('3.2.4', '1.2.4')).toBeFalse();
+    });
+
+    it('should return false when version is invalid', () => {
+      expect(sameMajor.matches('1.0.0', 'xxx')).toBeFalse();
+    });
+  });
+
+  describe('.getSatisfyingVersion(versions, range)', () => {
+    it('should return max satisfying version in range', () => {
+      expect(
+        sameMajor.getSatisfyingVersion(
+          ['1.0.0', '1.0.4', '1.3.0', '2.0.0'],
+          '1.0.3',
+        ),
+      ).toBe('1.3.0');
+    });
+  });
+
+  describe('.minSatisfyingVersion(versions, range)', () => {
+    it('should return min satisfying version in range', () => {
+      expect(
+        sameMajor.minSatisfyingVersion(
+          ['1.0.0', '1.0.4', '1.3.0', '2.0.0'],
+          '1.0.3',
+        ),
+      ).toBe('1.0.4');
+    });
+  });
+
+  describe('.isLessThanRange(version, range)', () => {
+    it('should return true', () => {
+      expect(sameMajor.isLessThanRange?.('2.0.2', '3.0.0')).toBeTrue();
+    });
+
+    it('should return false', () => {
+      expect(sameMajor.isLessThanRange?.('4.0.0', '3.0.0')).toBeFalse();
+      expect(sameMajor.isLessThanRange?.('3.1.0', '3.0.0')).toBeFalse();
+    });
+  });
+});
diff --git a/lib/modules/versioning/same-major/index.ts b/lib/modules/versioning/same-major/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..95d287f6b3d5cd3b3ea068c34ab93aad59f89f1c
--- /dev/null
+++ b/lib/modules/versioning/same-major/index.ts
@@ -0,0 +1,72 @@
+import { logger } from '../../../logger';
+import { api as semverCoerced } from '../semver-coerced';
+import type { VersioningApi } from '../types';
+
+export const id = 'same-major';
+export const displayName = 'Same Major Versioning';
+export const urls = [];
+export const supportsRanges = false;
+
+/**
+ *
+ * Converts input to range if it's a version. eg. X.Y.Z -> '>=X.Y.Z <X+1'
+ * If the input is already a range, it returns the input.
+ */
+function massageVersion(input: string): string {
+  // istanbul ignore if: same-major versioning should not be used with ranges as it defeats the purpose
+  if (!semverCoerced.isSingleVersion(input)) {
+    logger.warn(
+      { version: input },
+      'Same major versioning expects a single version but got a range. Please switch to a different versioning as this may lead to unexpected behaviour.',
+    );
+    return input;
+  }
+
+  // we are sure to get a major because of the isSingleVersion check
+  const major = semverCoerced.getMajor(input)!;
+  return `>=${input} <${major + 1}`;
+}
+
+// for same major versioning one version is greater than the other if its major is greater
+function isGreaterThan(version: string, other: string): boolean {
+  const versionMajor = semverCoerced.getMajor(version)!;
+  const otherMajor = semverCoerced.getMajor(other)!;
+
+  if (!versionMajor || !otherMajor) {
+    return false;
+  }
+
+  return versionMajor > otherMajor;
+}
+
+function matches(version: string, range: string): boolean {
+  return semverCoerced.matches(version, massageVersion(range));
+}
+
+function getSatisfyingVersion(
+  versions: string[],
+  range: string,
+): string | null {
+  return semverCoerced.getSatisfyingVersion(versions, massageVersion(range));
+}
+
+function minSatisfyingVersion(
+  versions: string[],
+  range: string,
+): string | null {
+  return semverCoerced.minSatisfyingVersion(versions, massageVersion(range));
+}
+
+function isLessThanRange(version: string, range: string): boolean {
+  return semverCoerced.isLessThanRange!(version, massageVersion(range));
+}
+
+export const api: VersioningApi = {
+  ...semverCoerced,
+  matches,
+  getSatisfyingVersion,
+  minSatisfyingVersion,
+  isLessThanRange,
+  isGreaterThan,
+};
+export default api;
diff --git a/lib/modules/versioning/same-major/readme.md b/lib/modules/versioning/same-major/readme.md
new file mode 100644
index 0000000000000000000000000000000000000000..5a95fe4634d917865bd1e02075983f6e26be1ee3
--- /dev/null
+++ b/lib/modules/versioning/same-major/readme.md
@@ -0,0 +1,27 @@
+The 'Same Major' versioning is designed to handle the case where a version needs to treated as a "greate than or equal to" constraint.
+Specifically, the case where the version say, `X.Y.Z` signifies a range of compatibility from greater than or equal to `X.Y.Z` to less than `X+1`.
+
+This process uses Semver-Coerced versioning beneath the surface, single versions (e.g., `X.Y.Z`) are converted to a range like `X+1` and then passed to the corresponding semver-coerced method.
+
+This method is handy when managing dependencies like dotnet-sdk's rollForward settings.
+Let's say a project uses dotnet-sdk version `3.1.0`.
+It needs to be compatible with any version in the `3.x.x` range but _not_ with versions in the next major version, like `4.x.x`.
+
+For example:
+
+```json
+{
+  "sdk": {
+    "version": "6.0.300",
+    "rollForward": "major"
+  }
+}
+```
+
+The roll-forward policy to use when selecting an SDK version, either as a fallback when a specific SDK version is missing or as a directive to use a higher version. In this case with `major` it means that select the latest version with the same major.
+ie. `>= 6.0.300 < 7.0.0`
+
+For such cases, the users would not want Renovate to create an update PR for any version within the range `>= 6.0.300 < 7.0.0` as it would not change the behaviour on their end, since it is handled by the manager already.
+
+Note:
+You should create a discussion before using this versioning as this is an experimental support and might have some edge cases unhandled.
diff --git a/lib/util/package-rules/current-version.spec.ts b/lib/util/package-rules/current-version.spec.ts
index 07a4ba1dfc922995f524b771615785ca4f3a21ba..c4fce08fb497f646c0e55021ba7bea069b9acbfd 100644
--- a/lib/util/package-rules/current-version.spec.ts
+++ b/lib/util/package-rules/current-version.spec.ts
@@ -116,5 +116,31 @@ describe('util/package-rules/current-version', () => {
       );
       expect(result).toBeFalse();
     });
+
+    it('return true for same-major verisioning if version lies in expected range', () => {
+      const result = matcher.matches(
+        {
+          versioning: 'same-major',
+          currentValue: '6.0.300',
+        },
+        {
+          matchCurrentVersion: '6.0.400',
+        },
+      );
+      expect(result).toBeTrue();
+    });
+
+    it('return false for same-major verisioning if version lies outside of expected range', () => {
+      const result = matcher.matches(
+        {
+          versioning: 'same-major',
+          currentValue: '6.0.300',
+        },
+        {
+          matchCurrentVersion: '6.0.100',
+        },
+      );
+      expect(result).toBeFalse();
+    });
   });
 });