diff --git a/lib/config/templates/group-pr-body.hbs b/lib/config/templates/group-pr-body.hbs
index 80f03083c5bf668b79f2d3b5c19a231b932a02a6..305e433a360001ede583675972a35993dacc4e59 100644
--- a/lib/config/templates/group-pr-body.hbs
+++ b/lib/config/templates/group-pr-body.hbs
@@ -1 +1,26 @@
-This PR renovates the package group "{{groupName}}".
+This {{#if isGitHub}}Pull{{else}}Merge{{/if}} Request renovates the package group "{{groupName}}".
+
+{{#each upgrades as |upgrade|}}
+-   [{{upgrade.depName}}]({{upgrade.repositoryUrl}}): from `{{upgrade.currentVersion}}` to `{{upgrade.newVersion}}`
+{{/each}}
+
+### Commits
+
+{{#each upgrades as |upgrade|}}
+<details>
+<summary>{{upgrade.githubName}}</summary>
+
+{{#each upgrade.releases as |release|}}
+#### {{release.version}}
+{{#each release.commits as |commit|}}
+-   [`{{commit.shortSha}}`]({{commit.url}}){{commit.message}}
+{{/each}}
+{{/each}}
+
+</details>
+{{/each}}
+
+<br />
+<br />
+
+This PR has been autogenerated by [Renovate Bot](https://keylocation.sg/our-tech/renovate).
diff --git a/lib/config/templates/pr-body.hbs b/lib/config/templates/pr-body.hbs
index 7d4a3e29713bb1f5403e00741c352855bf6df6c6..c08253626ad3f055c43e2be793b9fb49e5f7ab82 100644
--- a/lib/config/templates/pr-body.hbs
+++ b/lib/config/templates/pr-body.hbs
@@ -1,3 +1,20 @@
-This Pull Request updates dependency {{depName}} from version `{{currentVersion}}` to `{{newVersion}}`
+This {{#if isGitHub}}Pull{{else}}Merge{{/if}} Request updates dependency [{{depName}}]({{repositoryUrl}}) from version `{{currentVersion}}` to `{{newVersion}}`
 
-{{changelog}}
+### Commits
+
+<details>
+<summary>{{githubName}}</summary>
+
+{{#each releases as |release|}}
+#### {{release.version}}
+{{#each release.commits as |commit|}}
+-   [`{{commit.shortSha}}`]({{commit.url}}){{commit.message}}
+{{/each}}
+{{/each}}
+
+</details>
+
+<br />
+<br />
+
+This PR has been autogenerated by [Renovate Bot](https://keylocation.sg/our-tech/renovate).
diff --git a/lib/helpers/changelog.js b/lib/helpers/changelog.js
index 8f5b889e7647ac3eb287eba5fa224adb4e4ade3f..f8ab2f1800eb76852055d323c75e9575012c673c 100644
--- a/lib/helpers/changelog.js
+++ b/lib/helpers/changelog.js
@@ -1,33 +1,41 @@
 const logger = require('winston');
 const changelog = require('changelog');
 
-module.exports = getChangeLog;
+module.exports = {
+  getChangeLogJSON,
+  getMarkdown,
+  getChangeLog,
+};
 
-// Get Changelog
-async function getChangeLog(depName, fromVersion, newVersion) {
-  logger.debug(`getChangeLog(${depName}, ${fromVersion}, ${newVersion})`);
+async function getChangeLogJSON(depName, fromVersion, newVersion) {
+  logger.debug(`getChangeLogJSON(${depName}, ${fromVersion}, ${newVersion})`);
   if (!fromVersion || fromVersion === newVersion) {
-    return '';
+    return null;
   }
   const semverString = `>${fromVersion} <=${newVersion}`;
   logger.debug(`semverString: ${semverString}`);
-  let markdownLog;
   try {
-    const log = await changelog.generate(depName, semverString);
-    // We should probably write our own markdown generation here:
-    markdownLog = changelog.markdown(log);
-  } catch (error) {
-    logger.verbose(`getChangeLog error: ${error}`);
+    return await changelog.generate(depName, semverString);
+  } catch (err) {
+    logger.verbose(`getChangeLogJSON error: ${JSON.stringify(err)}`);
+    return null;
   }
-  // Add a header if log exists
-  if (!markdownLog) {
-    logger.verbose(`No changelog for ${depName}`);
-    markdownLog = '';
-  } else {
-    markdownLog = `### Changelog\n\n${markdownLog}`;
-    // Fix up the markdown formatting of changelog
-    // This is needed for GitLab in particular
-    markdownLog = markdownLog.replace(/(.*?)\n[=]{10,}/g, '#### $1');
+}
+
+function getMarkdown(changelogJSON) {
+  if (!changelogJSON) {
+    return 'No changelog available';
   }
+  let markdownLog = changelog.markdown(changelogJSON);
+  markdownLog = `### Changelog\n\n${markdownLog}`;
+  // Fix up the markdown formatting of changelog
+  // This is needed for GitLab in particular
+  markdownLog = markdownLog.replace(/(.*?)\n[=]{10,}/g, '#### $1');
   return markdownLog;
 }
+
+// Get Changelog
+async function getChangeLog(depName, fromVersion, newVersion) {
+  const logJSON = await getChangeLogJSON(depName, fromVersion, newVersion);
+  return getMarkdown(logJSON);
+}
diff --git a/lib/worker.js b/lib/worker.js
index e086fb64ad22fb5bae8c3280428c22424b1dff77..05a46431d7f23d0c3040aaa4cfd3a81c4e1f8697 100644
--- a/lib/worker.js
+++ b/lib/worker.js
@@ -113,7 +113,6 @@ function assignDepConfigs(inputConfig, deps) {
     delete returnDep.config.enabled;
     delete returnDep.config.onboarding;
     delete returnDep.config.endpoint;
-    delete returnDep.config.platform;
     delete returnDep.config.autodiscover;
     delete returnDep.config.token;
     delete returnDep.config.githubAppId;
diff --git a/lib/workers/pr.js b/lib/workers/pr.js
index bb60693091b1e78ac9baabc47ca64c17c6368472..f905df180edc442d3519ad6cddd8a89a262e9a11 100644
--- a/lib/workers/pr.js
+++ b/lib/workers/pr.js
@@ -1,6 +1,6 @@
 const logger = require('winston');
 const handlebars = require('handlebars');
-const getChangeLog = require('../helpers/changelog');
+const changelogHelper = require('../helpers/changelog');
 
 module.exports = {
   ensurePr,
@@ -10,9 +10,9 @@ module.exports = {
 // Ensures that PR exists with matching title/body
 async function ensurePr(upgrades) {
   logger.debug(`ensurePr(${JSON.stringify(upgrades)})`);
-  const upgradeConfig = upgrades[0];
-  const config = Object.assign({}, upgradeConfig);
-  logger.debug('Ensuring PR');
+  // If there is a group, it will use the config of the first upgrade in the array
+  const config = Object.assign({}, upgrades[0]);
+  config.upgrades = upgrades;
 
   const branchName = handlebars.compile(config.branchName)(config);
   const branchStatus = await config.api.getBranchStatus(branchName);
@@ -43,23 +43,41 @@ async function ensurePr(upgrades) {
   }
 
   // Get changelog and then generate template strings
-  config.changelogs = [];
   for (const upgrade of upgrades) {
-    let log = await getChangeLog(
+    const logJSON = await changelogHelper.getChangeLogJSON(
       upgrade.depName,
       upgrade.changeLogFromVersion,
       upgrade.changeLogToVersion
     );
-    if (!(log && log.length)) {
-      log = 'No changelog available';
-    }
-    config.changelogs.push({
-      depName: upgrade.depName,
-      log,
+    // Store changelog markdown for backwards compatibility
+    config.changelog = config.changelog || changelogHelper.getMarkdown(logJSON);
+    upgrade.repositoryUrl = logJSON.project.repository;
+    upgrade.githubName = logJSON.project.github;
+    upgrade.releases = [];
+    logJSON.versions.forEach(version => {
+      const release = Object.assign({}, version);
+      release.date = version.date.toISOString().slice(0, 10);
+      release.commits = [];
+      if (release.changes) {
+        release.changes.forEach(change => {
+          const commit = Object.assign({}, change);
+          delete commit.date;
+          commit.shortSha = change.sha.slice(0, 7);
+          commit.url = `${logJSON.project.repository}/commit/${change.sha}`;
+          if (change.message) {
+            commit.message = change.message.split('\n')[0];
+          }
+          release.commits.push(commit);
+        });
+      }
+      upgrade.releases.push(release);
     });
   }
-  // Configure changelog for backwards compatibility
-  config.changelog = config.changelogs[0].log;
+
+  // Update the config object
+  Object.assign(config, upgrades[0]);
+  config.isGitHub = config.platform === 'github';
+
   const prTitle = handlebars.compile(config.prTitle)(config);
   const prBody = handlebars.compile(config.prBody)(config);
 
diff --git a/test/helpers/changelog.spec.js b/test/helpers/changelog.spec.js
index db357670581506982ce27cb3d0eebe09c278f184..4eb6d6ed3a59c1ea0211b2e35cb082fa7d478cdd 100644
--- a/test/helpers/changelog.spec.js
+++ b/test/helpers/changelog.spec.js
@@ -1,31 +1,40 @@
 const changelog = require('changelog');
-const getChangeLog = require('../../lib/helpers/changelog');
+const changelogHelper = require('../../lib/helpers/changelog');
 
 jest.mock('changelog');
 
 describe('helpers/changelog', () => {
-  describe('getChangeLog(depName, fromVersion, newVersion)', () => {
+  describe('changelogHelper.getChangeLog(depName, fromVersion, newVersion)', () => {
     it('returns empty if no fromVersion', async () => {
-      expect(await getChangeLog('renovate', null, '1.0.0')).toBe('');
+      expect(
+        await changelogHelper.getChangeLog('renovate', null, '1.0.0')
+      ).toBe('No changelog available');
     });
     it('returns empty if fromVersion equals newVersion', async () => {
-      expect(await getChangeLog('renovate', '1.0.0', '1.0.0')).toBe('');
+      expect(
+        await changelogHelper.getChangeLog('renovate', '1.0.0', '1.0.0')
+      ).toBe('No changelog available');
     });
-    it('returns empty if generated markdown is null', async () => {
-      changelog.markdown.mockReturnValueOnce(null);
-      expect(await getChangeLog('renovate', '1.0.0', '2.0.0')).toBe('');
+    it('returns empty if generated json is null', async () => {
+      changelog.generate.mockReturnValueOnce(null);
+      expect(
+        await changelogHelper.getChangeLog('renovate', '1.0.0', '2.0.0')
+      ).toBe('No changelog available');
     });
     it('returns header if generated markdown is valid', async () => {
+      changelog.generate.mockReturnValueOnce({});
       changelog.markdown.mockReturnValueOnce('dummy');
-      expect(await getChangeLog('renovate', '1.0.0', '2.0.0')).toBe(
-        '### Changelog\n\ndummy'
-      );
+      expect(
+        await changelogHelper.getChangeLog('renovate', '1.0.0', '2.0.0')
+      ).toBe('### Changelog\n\ndummy');
     });
     it('returns empty if error thrown', async () => {
-      changelog.markdown = jest.fn(() => {
+      changelog.generate = jest.fn(() => {
         throw new Error('foo');
       });
-      expect(await getChangeLog('renovate', '1.0.0', '2.0.0')).toBe('');
+      expect(
+        await changelogHelper.getChangeLog('renovate', '1.0.0', '2.0.0')
+      ).toBe('No changelog available');
     });
   });
 });
diff --git a/test/workers/pr.spec.js b/test/workers/pr.spec.js
index 752a40d3c5709ad320e28b8bdab2acd00a66fe5a..c183da0850f0f00aec0ebebaa622d5fa0b96ceaa 100644
--- a/test/workers/pr.spec.js
+++ b/test/workers/pr.spec.js
@@ -1,11 +1,33 @@
 const logger = require('winston');
 const prWorker = require('../../lib/workers/pr');
+const changelogHelper = require('../../lib/helpers/changelog');
 const defaultConfig = require('../../lib/config/defaults').getConfig();
 
 logger.remove(logger.transports.Console);
 
-const getChangeLog = jest.fn();
-getChangeLog.mockReturnValue('Mocked changelog');
+jest.mock('../../lib/helpers/changelog');
+changelogHelper.getChangeLog = jest.fn();
+changelogHelper.getChangeLog.mockReturnValue('Mocked changelog');
+changelogHelper.getChangeLogJSON = jest.fn();
+changelogHelper.getChangeLogJSON.mockReturnValue({
+  project: {
+    github: 'renovateapp/dummy',
+    repository: 'https://github.com/renovateapp/dummy',
+  },
+  versions: [
+    {
+      date: new Date('2017-01-01'),
+      version: '1.1.0',
+      changes: [
+        {
+          date: new Date('2017-01-01'),
+          sha: 'abcdefghijklmnopqrstuvwxyz',
+          message: 'foo\nbar',
+        },
+      ],
+    },
+  ],
+});
 
 describe('workers/pr', () => {
   describe('checkAutoMerge(pr, config)', () => {
@@ -66,8 +88,22 @@ describe('workers/pr', () => {
       };
       existingPr = {
         title: 'Update dependency dummy to version 1.1.0',
-        body:
-          'This Pull Request updates dependency dummy from version `1.0.0` to `1.1.0`\n\nNo changelog available',
+        body: `This Pull Request updates dependency [dummy](https://github.com/renovateapp/dummy) from version \`1.0.0\` to \`1.1.0\`
+
+### Commits
+
+<details>
+<summary>renovateapp/dummy</summary>
+
+#### 1.1.0
+-   [\`abcdefg\`](https://github.com/renovateapp/dummy/commit/abcdefghijklmnopqrstuvwxyz)foo
+
+</details>
+
+<br />
+<br />
+
+This PR has been autogenerated by [Renovate Bot](https://keylocation.sg/our-tech/renovate).`,
         displayNumber: 'Existing PR',
       };
     });
@@ -155,6 +191,7 @@ describe('workers/pr', () => {
       config.api.getBranchPr = jest.fn(() => existingPr);
       config.api.updatePr = jest.fn();
       const pr = await prWorker.ensurePr([config]);
+      expect(config.api.updatePr.mock.calls.length).toBe(0);
       expect(pr).toMatchObject(existingPr);
     });
     it('should return modified existing PR', async () => {