diff --git a/lib/platform/gitlab/__snapshots__/index.spec.ts.snap b/lib/platform/gitlab/__snapshots__/index.spec.ts.snap
index 4ddc39db5ccda508e08a7d6766575430d948c3da..732b9c57b0043c773c2be2bd602873b5a714c273 100644
--- a/lib/platform/gitlab/__snapshots__/index.spec.ts.snap
+++ b/lib/platform/gitlab/__snapshots__/index.spec.ts.snap
@@ -141,8 +141,6 @@ Array [
 ]
 `;
 
-exports[`platform/gitlab addReviewers(issueNo, reviewers) should add the given reviewers to the PR 1`] = `Array []`;
-
 exports[`platform/gitlab createPr(branchName, title, body) auto-accepts the MR when requested 1`] = `
 Array [
   Object {
@@ -968,23 +966,16 @@ Array [
 
 exports[`platform/gitlab getBranchPr(branchName) should return the PR object 1`] = `
 Object {
-  "additions": 1,
-  "base": Object {
-    "sha": "1234",
-  },
   "body": undefined,
-  "commits": 1,
-  "deletions": 1,
   "displayNumber": "Merge Request #91",
   "hasAssignees": false,
   "hasReviewers": false,
-  "iid": 91,
+  "labels": undefined,
   "number": 91,
+  "sha": undefined,
   "sourceBranch": "some-branch",
-  "source_branch": "some-branch",
   "state": "open",
   "targetBranch": "master",
-  "target_branch": "master",
   "title": "some change",
 }
 `;
@@ -1040,24 +1031,17 @@ Array [
 
 exports[`platform/gitlab getBranchPr(branchName) should strip deprecated draft prefix from title 1`] = `
 Object {
-  "additions": 1,
-  "base": Object {
-    "sha": "1234",
-  },
   "body": undefined,
-  "commits": 1,
-  "deletions": 1,
   "displayNumber": "Merge Request #91",
   "hasAssignees": false,
   "hasReviewers": false,
-  "iid": 91,
   "isDraft": true,
+  "labels": undefined,
   "number": 91,
+  "sha": undefined,
   "sourceBranch": "some-branch",
-  "source_branch": "some-branch",
   "state": "open",
   "targetBranch": "master",
-  "target_branch": "master",
   "title": "some change",
 }
 `;
@@ -1113,24 +1097,17 @@ Array [
 
 exports[`platform/gitlab getBranchPr(branchName) should strip draft prefix from title 1`] = `
 Object {
-  "additions": 1,
-  "base": Object {
-    "sha": "1234",
-  },
   "body": undefined,
-  "commits": 1,
-  "deletions": 1,
   "displayNumber": "Merge Request #91",
   "hasAssignees": false,
   "hasReviewers": false,
-  "iid": 91,
   "isDraft": true,
+  "labels": undefined,
   "number": 91,
+  "sha": undefined,
   "sourceBranch": "some-branch",
-  "source_branch": "some-branch",
   "state": "open",
   "targetBranch": "master",
-  "target_branch": "master",
   "title": "some change",
 }
 `;
@@ -1609,22 +1586,17 @@ exports[`platform/gitlab getPr(prNo) removes deprecated draft prefix from return
 Object {
   "body": "a merge request",
   "canMerge": false,
-  "description": "a merge request",
   "displayNumber": "Merge Request #12345",
-  "diverged_commits_count": 5,
   "hasAssignees": false,
   "hasReviewers": false,
-  "id": 1,
-  "iid": 12345,
   "isConflicted": true,
   "isDraft": true,
-  "merge_status": "cannot_be_merged",
+  "labels": undefined,
   "number": 12345,
+  "sha": undefined,
   "sourceBranch": "some-branch",
-  "source_branch": "some-branch",
   "state": "merged",
   "targetBranch": "master",
-  "target_branch": "master",
   "title": "do something",
 }
 `;
@@ -1649,22 +1621,17 @@ exports[`platform/gitlab getPr(prNo) removes draft prefix from returned title 1`
 Object {
   "body": "a merge request",
   "canMerge": false,
-  "description": "a merge request",
   "displayNumber": "Merge Request #12345",
-  "diverged_commits_count": 5,
   "hasAssignees": false,
   "hasReviewers": false,
-  "id": 1,
-  "iid": 12345,
   "isConflicted": true,
   "isDraft": true,
-  "merge_status": "cannot_be_merged",
+  "labels": undefined,
   "number": 12345,
+  "sha": undefined,
   "sourceBranch": "some-branch",
-  "source_branch": "some-branch",
   "state": "merged",
   "targetBranch": "master",
-  "target_branch": "master",
   "title": "do something",
 }
 `;
@@ -1689,21 +1656,16 @@ exports[`platform/gitlab getPr(prNo) returns the PR 1`] = `
 Object {
   "body": "a merge request",
   "canMerge": false,
-  "description": "a merge request",
   "displayNumber": "Merge Request #12345",
-  "diverged_commits_count": 5,
   "hasAssignees": false,
   "hasReviewers": false,
-  "id": 1,
-  "iid": 12345,
   "isConflicted": true,
-  "merge_status": "cannot_be_merged",
+  "labels": undefined,
   "number": 12345,
+  "sha": undefined,
   "sourceBranch": "some-branch",
-  "source_branch": "some-branch",
   "state": "merged",
   "targetBranch": "master",
-  "target_branch": "master",
   "title": "do something",
 }
 `;
@@ -1728,21 +1690,16 @@ exports[`platform/gitlab getPr(prNo) returns the PR with nonexisting branch 1`]
 Object {
   "body": "a merge request",
   "canMerge": false,
-  "description": "a merge request",
   "displayNumber": "Merge Request #12345",
-  "diverged_commits_count": 2,
   "hasAssignees": true,
   "hasReviewers": false,
-  "id": 1,
-  "iid": 12345,
   "isConflicted": true,
-  "merge_status": "cannot_be_merged",
+  "labels": undefined,
   "number": 12345,
+  "sha": undefined,
   "sourceBranch": "some-branch",
-  "source_branch": "some-branch",
   "state": "open",
   "targetBranch": "master",
-  "target_branch": "master",
   "title": "do something",
 }
 `;
@@ -1767,19 +1724,15 @@ exports[`platform/gitlab getPr(prNo) returns the mergeable PR 1`] = `
 Object {
   "body": "a merge request",
   "canMerge": true,
-  "description": "a merge request",
   "displayNumber": "Merge Request #12345",
-  "diverged_commits_count": 5,
   "hasAssignees": true,
   "hasReviewers": false,
-  "id": 1,
-  "iid": 12345,
+  "labels": undefined,
   "number": 12345,
+  "sha": undefined,
   "sourceBranch": "some-branch",
-  "source_branch": "some-branch",
   "state": "open",
   "targetBranch": "master",
-  "target_branch": "master",
   "title": "do something",
 }
 `;
diff --git a/lib/platform/gitlab/http.ts b/lib/platform/gitlab/http.ts
new file mode 100644
index 0000000000000000000000000000000000000000..51a836de629ab27f2f9b5526a2f9de4bd97fb50b
--- /dev/null
+++ b/lib/platform/gitlab/http.ts
@@ -0,0 +1,9 @@
+import { GitlabHttp } from '../../util/http/gitlab';
+
+export const gitlabApi = new GitlabHttp();
+
+export async function getUserID(username: string): Promise<number> {
+  return (
+    await gitlabApi.getJson<{ id: number }[]>(`users?username=${username}`)
+  ).body[0].id;
+}
diff --git a/lib/platform/gitlab/index.spec.ts b/lib/platform/gitlab/index.spec.ts
index 7bf98bc4aa5a90870b99c090ba7ce067bd01ce34..9831c459db8dd3b1df8271772d19eeffd55540e0 100644
--- a/lib/platform/gitlab/index.spec.ts
+++ b/lib/platform/gitlab/index.spec.ts
@@ -9,6 +9,7 @@ import {
   REPOSITORY_EMPTY,
   REPOSITORY_MIRRORED,
 } from '../../constants/error-messages';
+import { logger as _logger } from '../../logger';
 import { BranchStatus, PrState } from '../../types';
 import * as _git from '../../util/git';
 import * as _hostRules from '../../util/host-rules';
@@ -19,11 +20,15 @@ describe('platform/gitlab', () => {
   let gitlab: Platform;
   let hostRules: jest.Mocked<typeof _hostRules>;
   let git: jest.Mocked<typeof _git>;
+  let logger: jest.Mocked<typeof _logger>;
+
   beforeEach(async () => {
     // reset module
     jest.resetModules();
     jest.resetAllMocks();
     gitlab = await import('.');
+    jest.mock('../../logger');
+    logger = (await import('../../logger')).logger as never;
     jest.mock('../../util/host-rules');
     jest.mock('delay');
     hostRules = require('../../util/host-rules');
@@ -44,6 +49,25 @@ describe('platform/gitlab', () => {
     httpMock.reset();
   });
 
+  async function initFakePlatform(version: string) {
+    httpMock
+      .scope(gitlabApiHost)
+      .get('/api/v4/user')
+      .reply(200, {
+        email: 'a@b.com',
+        name: 'Renovate Bot',
+      })
+      .get('/api/v4/version')
+      .reply(200, {
+        version: `${version}-ee`,
+      });
+
+    await gitlab.initPlatform({
+      token: 'some-token',
+      endpoint: undefined,
+    });
+  }
+
   describe('initPlatform()', () => {
     it(`should throw if no token`, async () => {
       await expect(gitlab.initPlatform({} as any)).rejects.toThrow();
@@ -830,14 +854,115 @@ describe('platform/gitlab', () => {
       expect(httpMock.getTrace()).toMatchSnapshot();
     });
   });
-  describe('addReviewers(issueNo, reviewers)', () => {
-    it('should add the given reviewers to the PR', async () => {
-      // no-op
-      httpMock.scope(gitlabApiHost);
-      await gitlab.addReviewers(42, ['someuser', 'someotheruser']);
-      expect(httpMock.getTrace()).toMatchSnapshot();
+
+  describe('addReviewers(iid, reviewers)', () => {
+    describe('13.8.0', () => {
+      it('should not be supported in too low version', async () => {
+        await initFakePlatform('13.8.0');
+        await gitlab.addReviewers(42, ['someuser', 'foo', 'someotheruser']);
+        expect(logger.warn).toHaveBeenCalledWith(
+          { version: '13.8.0' },
+          'Adding reviewers is only available in GitLab 13.9 and onwards'
+        );
+      });
+    });
+
+    describe('13.9.0', () => {
+      beforeEach(async () => {
+        await initFakePlatform('13.9.0');
+      });
+
+      const existingReviewers = [
+        { id: 1, username: 'foo' },
+        { id: 2, username: 'bar' },
+      ];
+
+      it('should fail to get existing reviewers', async () => {
+        const scope = httpMock
+          .scope(gitlabApiHost)
+          .get(
+            '/api/v4/projects/undefined/merge_requests/42?include_diverged_commits_count=1'
+          )
+          .reply(404);
+
+        await gitlab.addReviewers(42, ['someuser', 'foo', 'someotheruser']);
+        expect(scope.isDone()).toBeTrue();
+      });
+
+      it('should fail to get user IDs', async () => {
+        const scope = httpMock
+          .scope(gitlabApiHost)
+          .get(
+            '/api/v4/projects/undefined/merge_requests/42?include_diverged_commits_count=1'
+          )
+          .reply(200, { reviewers: existingReviewers })
+          .get('/api/v4/users?username=someuser')
+          .reply(200, [{ id: 10 }])
+          .get('/api/v4/users?username=someotheruser')
+          .reply(404);
+
+        await gitlab.addReviewers(42, ['someuser', 'foo', 'someotheruser']);
+        expect(scope.isDone()).toBeTrue();
+      });
+
+      it('should fail to add reviewers to the MR', async () => {
+        const scope = httpMock
+          .scope(gitlabApiHost)
+          .get(
+            '/api/v4/projects/undefined/merge_requests/42?include_diverged_commits_count=1'
+          )
+          .reply(200, { reviewers: existingReviewers })
+          .get('/api/v4/users?username=someuser')
+          .reply(200, [{ id: 10 }])
+          .get('/api/v4/users?username=someotheruser')
+          .reply(200, [{ id: 15 }])
+          .put('/api/v4/projects/undefined/merge_requests/42', {
+            reviewer_ids: [1, 2, 10, 15],
+          })
+          .reply(404);
+
+        await gitlab.addReviewers(42, ['someuser', 'foo', 'someotheruser']);
+        expect(scope.isDone()).toBeTrue();
+      });
+
+      it('should add the given reviewers to the MR', async () => {
+        const scope = httpMock
+          .scope(gitlabApiHost)
+          .get(
+            '/api/v4/projects/undefined/merge_requests/42?include_diverged_commits_count=1'
+          )
+          .reply(200, { reviewers: existingReviewers })
+          .get('/api/v4/users?username=someuser')
+          .reply(200, [{ id: 10 }])
+          .get('/api/v4/users?username=someotheruser')
+          .reply(200, [{ id: 15 }])
+          .put('/api/v4/projects/undefined/merge_requests/42', {
+            reviewer_ids: [1, 2, 10, 15],
+          })
+          .reply(200);
+
+        await gitlab.addReviewers(42, ['someuser', 'foo', 'someotheruser']);
+        expect(scope.isDone()).toBeTrue();
+      });
+
+      it('should only add reviewers if necessary', async () => {
+        const scope = httpMock
+          .scope(gitlabApiHost)
+          .get(
+            '/api/v4/projects/undefined/merge_requests/42?include_diverged_commits_count=1'
+          )
+          .reply(200, { reviewers: existingReviewers })
+          .get('/api/v4/users?username=someuser')
+          .reply(200, [{ id: 1 }])
+          .get('/api/v4/users?username=someotheruser')
+          .reply(200, [{ id: 2 }]);
+
+        await gitlab.addReviewers(42, ['someuser', 'foo', 'someotheruser']);
+        expect(scope.isDone()).toBeTrue();
+      });
     });
   });
+
   describe('ensureComment', () => {
     it('add comment if not found', async () => {
       const scope = await initRepo();
diff --git a/lib/platform/gitlab/index.ts b/lib/platform/gitlab/index.ts
index 09f806d9993d0efed6b652ebb500f9cb788774ea..02976cf76a110f9bbf5db523af24cf36cf729f9f 100755
--- a/lib/platform/gitlab/index.ts
+++ b/lib/platform/gitlab/index.ts
@@ -1,7 +1,8 @@
 import URL from 'url';
 import is from '@sindresorhus/is';
 import delay from 'delay';
-import semver from 'semver';
+import pAll from 'p-all';
+import { lt } from 'semver';
 import {
   PLATFORM_AUTHENTICATION_ERROR,
   REPOSITORY_ACCESS_FORBIDDEN,
@@ -19,7 +20,7 @@ import { BranchStatus, PrState, VulnerabilityAlert } from '../../types';
 import * as git from '../../util/git';
 import * as hostRules from '../../util/host-rules';
 import { HttpResponse } from '../../util/http';
-import { GitlabHttp, setBaseUrl } from '../../util/http/gitlab';
+import { setBaseUrl } from '../../util/http/gitlab';
 import { sanitize } from '../../util/sanitize';
 import { ensureTrailingSlash, getQueryString } from '../../util/url';
 import type {
@@ -39,15 +40,16 @@ import type {
   UpdatePrConfig,
 } from '../types';
 import { smartTruncate } from '../utils/pr-body';
+import { getUserID, gitlabApi } from './http';
+import { getMR, updateMR } from './merge-request';
 import type {
+  GitLabMergeRequest,
   GitlabComment,
   GitlabIssue,
   MergeMethod,
   RepoResponse,
 } from './types';
 
-const gitlabApi = new GitlabHttp();
-
 let config: {
   repository: string;
   localDir: string;
@@ -63,6 +65,7 @@ let config: {
 const defaults = {
   hostType: PLATFORM_TYPE_GITLAB,
   endpoint: 'https://gitlab.com/api/v4/',
+  version: '0.0.0',
 };
 
 const DRAFT_PREFIX = 'Draft: ';
@@ -100,6 +103,7 @@ export async function initPlatform({
       await gitlabApi.getJson<{ version: string }>('version', { token })
     ).body.version.split('-')[0];
     logger.debug('GitLab version is: ' + gitlabVersion);
+    defaults.version = gitlabVersion;
   } catch (err) {
     logger.debug(
       { err },
@@ -107,7 +111,7 @@ export async function initPlatform({
     );
     throw new Error('Init: Authentication failure');
   }
-  draftPrefix = semver.lt(gitlabVersion, '13.2.0')
+  draftPrefix = lt(gitlabVersion, '13.2.0')
     ? DRAFT_PREFIX_DEPRECATED
     : DRAFT_PREFIX;
   const platformConfig: PlatformResult = {
@@ -518,33 +522,24 @@ export async function createPr({
 
 export async function getPr(iid: number): Promise<Pr> {
   logger.debug(`getPr(${iid})`);
-  const url = `projects/${config.repository}/merge_requests/${iid}?include_diverged_commits_count=1`;
-  const pr = (
-    await gitlabApi.getJson<
-      Pr & {
-        iid: number;
-        source_branch: string;
-        target_branch: string;
-        description: string;
-        diverged_commits_count: number;
-        merge_status: string;
-        assignee?: { id?: number };
-        assignees?: { id?: number }[];
-      }
-    >(url)
-  ).body;
+  const mr = await getMR(config.repository, iid);
+
   // Harmonize fields with GitHub
-  pr.sourceBranch = pr.source_branch;
-  pr.targetBranch = pr.target_branch;
-  pr.number = pr.iid;
-  pr.displayNumber = `Merge Request #${pr.iid}`;
-  pr.body = pr.description;
-  pr.state = pr.state === 'opened' ? PrState.Open : pr.state;
-  pr.hasAssignees = !!(pr.assignee?.id || pr.assignees?.[0]?.id);
-  delete pr.assignee;
-  delete pr.assignees;
-  pr.hasReviewers = false;
-  if (pr.merge_status === 'cannot_be_merged') {
+  const pr: Pr = {
+    sourceBranch: mr.source_branch,
+    targetBranch: mr.target_branch,
+    number: mr.iid,
+    displayNumber: `Merge Request #${mr.iid}`,
+    body: mr.description,
+    state: mr.state === 'opened' ? PrState.Open : mr.state,
+    hasAssignees: !!(mr.assignee?.id || mr.assignees?.[0]?.id),
+    hasReviewers: !!mr.reviewers?.length,
+    title: mr.title,
+    labels: mr.labels,
+    sha: mr.sha,
+  };
+
+  if (mr.merge_status === 'cannot_be_merged') {
     logger.debug('pr cannot be merged');
     pr.canMerge = false;
     pr.isConflicted = true;
@@ -847,22 +842,14 @@ export async function addAssignees(
 ): Promise<void> {
   logger.debug(`Adding assignees '${assignees.join(', ')}' to #${iid}`);
   try {
-    let assigneeId = (
-      await gitlabApi.getJson<{ id: number }[]>(
-        `users?username=${assignees[0]}`
-      )
-    ).body[0].id;
+    let assigneeId = await getUserID(assignees[0]);
     let url = `projects/${config.repository}/merge_requests/${iid}?assignee_id=${assigneeId}`;
     await gitlabApi.putJson(url);
     try {
       if (assignees.length > 1) {
         url = `projects/${config.repository}/merge_requests/${iid}?assignee_ids[]=${assigneeId}`;
         for (let i = 1; i < assignees.length; i += 1) {
-          assigneeId = (
-            await gitlabApi.getJson<{ id: number }[]>(
-              `users?username=${assignees[i]}`
-            )
-          ).body[0].id;
+          assigneeId = await getUserID(assignees[i]);
           url += `&assignee_ids[]=${assigneeId}`;
         }
         await gitlabApi.putJson(url);
@@ -876,10 +863,54 @@ export async function addAssignees(
   }
 }
 
-export function addReviewers(iid: number, reviewers: string[]): Promise<void> {
-  logger.debug(`addReviewers('${iid}, [${reviewers.join(', ')}])`);
-  logger.warn('Unimplemented in GitLab: approvals');
-  return Promise.resolve();
+export async function addReviewers(
+  iid: number,
+  reviewers: string[]
+): Promise<void> {
+  logger.debug(`Adding reviewers '${reviewers.join(', ')}' to #${iid}`);
+
+  if (lt(defaults.version, '13.9.0')) {
+    logger.warn(
+      { version: defaults.version },
+      'Adding reviewers is only available in GitLab 13.9 and onwards'
+    );
+    return;
+  }
+
+  let mr: GitLabMergeRequest;
+  try {
+    mr = await getMR(config.repository, iid);
+  } catch (err) {
+    logger.warn({ err }, 'Failed to get existing reviewers');
+    return;
+  }
+
+  mr.reviewers = mr.reviewers ?? [];
+  const existingReviewers = mr.reviewers.map((r) => r.username);
+  const existingReviewerIDs = mr.reviewers.map((r) => r.id);
+
+  // Figure out which reviewers (of the ones we want to add) are not already on the MR as a reviewer
+  const newReviewers = reviewers.filter((r) => !existingReviewers.includes(r));
+
+  // Gather the IDs for all the reviewers we want to add
+  let newReviewerIDs: number[];
+  try {
+    newReviewerIDs = await pAll(
+      newReviewers.map((r) => () => getUserID(r)),
+      { concurrency: 5 }
+    );
+  } catch (err) {
+    logger.warn({ err }, 'Failed to get IDs of the new reviewers');
+    return;
+  }
+
+  try {
+    await updateMR(config.repository, iid, {
+      reviewer_ids: [...existingReviewerIDs, ...newReviewerIDs],
+    });
+  } catch (err) {
+    logger.warn({ err }, 'Failed to add reviewers');
+  }
 }
 
 export async function deleteLabel(
diff --git a/lib/platform/gitlab/merge-request.ts b/lib/platform/gitlab/merge-request.ts
new file mode 100644
index 0000000000000000000000000000000000000000..497294f275059d221aef02e2faebc150d36301e2
--- /dev/null
+++ b/lib/platform/gitlab/merge-request.ts
@@ -0,0 +1,26 @@
+import { logger } from '../../logger';
+import { gitlabApi } from './http';
+import type { GitLabMergeRequest, UpdateMergeRequest } from './types';
+
+export async function getMR(
+  repository: string,
+  iid: number
+): Promise<GitLabMergeRequest> {
+  logger.debug(`getMR(${iid})`);
+
+  const url = `projects/${repository}/merge_requests/${iid}?include_diverged_commits_count=1`;
+  return (await gitlabApi.getJson<GitLabMergeRequest>(url)).body;
+}
+
+export async function updateMR(
+  repository: string,
+  iid: number,
+  data: UpdateMergeRequest
+): Promise<void> {
+  logger.debug(`updateMR(${iid})`);
+
+  const url = `projects/${repository}/merge_requests/${iid}`;
+  await gitlabApi.putJson(url, {
+    body: data,
+  });
+}
diff --git a/lib/platform/gitlab/types.ts b/lib/platform/gitlab/types.ts
index fda51a9ffcf971f962233184b70dad01c6c7992e..607b3e973ea0c1d93e48ae6571f666c6a6b73186 100644
--- a/lib/platform/gitlab/types.ts
+++ b/lib/platform/gitlab/types.ts
@@ -8,9 +8,38 @@ export interface GitlabComment {
   id: number;
 }
 
+export interface GitLabUser {
+  id: number;
+  username: string;
+}
+
+export interface GitLabMergeRequest {
+  iid: number;
+  title: string;
+  state: string;
+  source_branch: string;
+  target_branch: string;
+  description: string;
+  diverged_commits_count: number;
+  merge_status: string;
+  assignee?: GitLabUser;
+  assignees?: GitLabUser[];
+  reviewers?: GitLabUser[];
+  labels: string[];
+  sha: string;
+}
+
+export interface UpdateMergeRequest {
+  target_branch?: string;
+  title?: string;
+  assignee_id?: number;
+  assignee_ids?: number[];
+  reviewer_ids?: number[];
+}
+
 export type MergeMethod = 'merge' | 'rebase_merge' | 'ff';
 
-export type RepoResponse = {
+export interface RepoResponse {
   archived: boolean;
   mirror: boolean;
   default_branch: string;
@@ -21,4 +50,4 @@ export type RepoResponse = {
   merge_requests_access_level: 'disabled' | 'private' | 'enabled';
   merge_method: MergeMethod;
   path_with_namespace: string;
-};
+}