diff --git a/services/github/github-commit-activity.service.js b/services/github/github-commit-activity.service.js
index ca270a19cfb412b0cf2ad912a2a99e82bdbf153e..07adab1e1274aab8c4f46412a97ae5fd67990ce5 100644
--- a/services/github/github-commit-activity.service.js
+++ b/services/github/github-commit-activity.service.js
@@ -1,10 +1,15 @@
 import gql from 'graphql-tag'
 import Joi from 'joi'
+import parseLinkHeader from 'parse-link-header'
 import { InvalidResponse } from '../index.js'
 import { metric } from '../text-formatters.js'
 import { nonNegativeInteger } from '../validators.js'
 import { GithubAuthV4Service } from './github-auth-service.js'
-import { transformErrors, documentation } from './github-helpers.js'
+import {
+  transformErrors,
+  documentation,
+  httpErrorsFor,
+} from './github-helpers.js'
 
 const schema = Joi.object({
   data: Joi.object({
@@ -18,11 +23,16 @@ const schema = Joi.object({
   }).required(),
 }).required()
 
+const queryParamSchema = Joi.object({
+  authorFilter: Joi.string(),
+})
+
 export default class GitHubCommitActivity extends GithubAuthV4Service {
   static category = 'activity'
   static route = {
     base: 'github/commit-activity',
     pattern: ':interval(t|y|m|4w|w)/:user/:repo/:branch*',
+    queryParamSchema,
   }
 
   static examples = [
@@ -31,6 +41,7 @@ export default class GitHubCommitActivity extends GithubAuthV4Service {
       // Override the pattern to omit the deprecated interval "4w".
       pattern: ':interval(t|y|m|w)/:user/:repo',
       namedParams: { interval: 'm', user: 'eslint', repo: 'eslint' },
+      queryParams: { authorFilter: 'nzakas' },
       staticPreview: this.render({ interval: 'm', commitCount: 457 }),
       keywords: ['commits'],
       documentation,
@@ -45,6 +56,7 @@ export default class GitHubCommitActivity extends GithubAuthV4Service {
         repo: 'squint',
         branch: 'main',
       },
+      queryParams: { authorFilter: 'calebcartwright' },
       staticPreview: this.render({ interval: 'm', commitCount: 5 }),
       keywords: ['commits'],
       documentation,
@@ -53,9 +65,10 @@ export default class GitHubCommitActivity extends GithubAuthV4Service {
 
   static defaultBadgeData = { label: 'commit activity', color: 'blue' }
 
-  static render({ interval, commitCount }) {
+  static render({ interval, commitCount, authorFilter }) {
     // If total commits selected change label from commit activity to commits
-    const label = interval === 't' ? 'commits' : undefined
+    const label = interval === 't' ? 'commits' : 'commit activity'
+    const authorFilterLabel = authorFilter ? ` by ${authorFilter}` : ''
 
     const intervalLabel = {
       t: '',
@@ -66,7 +79,7 @@ export default class GitHubCommitActivity extends GithubAuthV4Service {
     }[interval]
 
     return {
-      label,
+      label: `${label}${authorFilterLabel}`,
       message: `${metric(commitCount)}${intervalLabel}`,
     }
   }
@@ -103,6 +116,30 @@ export default class GitHubCommitActivity extends GithubAuthV4Service {
     })
   }
 
+  async fetchAuthorFilter({
+    interval,
+    user,
+    repo,
+    branch = 'HEAD',
+    authorFilter,
+  }) {
+    const since =
+      this.constructor.getIntervalQueryStartDate({ interval }) || undefined
+
+    return this._request({
+      url: `/repos/${user}/${repo}/commits`,
+      options: {
+        searchParams: {
+          sha: branch,
+          author: authorFilter,
+          per_page: '1',
+          since,
+        },
+      },
+      httpErrors: httpErrorsFor('repo or branch not found'),
+    })
+  }
+
   static transform({ data }) {
     const {
       repository: { object: repo },
@@ -115,6 +152,16 @@ export default class GitHubCommitActivity extends GithubAuthV4Service {
     return repo.history.totalCount
   }
 
+  static transformAuthorFilter({ res }) {
+    const parsed = parseLinkHeader(res.headers.link)
+
+    if (!parsed) {
+      return 0
+    }
+
+    return parsed.last.page
+  }
+
   static getIntervalQueryStartDate({ interval }) {
     const now = new Date()
 
@@ -131,9 +178,21 @@ export default class GitHubCommitActivity extends GithubAuthV4Service {
     return now.toISOString()
   }
 
-  async handle({ interval, user, repo, branch }) {
-    const json = await this.fetch({ interval, user, repo, branch })
-    const commitCount = this.constructor.transform(json)
-    return this.constructor.render({ interval, commitCount })
+  async handle({ interval, user, repo, branch }, { authorFilter }) {
+    let commitCount
+    if (authorFilter) {
+      const authorFilterRes = await this.fetchAuthorFilter({
+        interval,
+        user,
+        repo,
+        branch,
+        authorFilter,
+      })
+      commitCount = this.constructor.transformAuthorFilter(authorFilterRes)
+    } else {
+      const json = await this.fetch({ interval, user, repo, branch })
+      commitCount = this.constructor.transform(json)
+    }
+    return this.constructor.render({ interval, commitCount, authorFilter })
   }
 }
diff --git a/services/github/github-commit-activity.tester.js b/services/github/github-commit-activity.tester.js
index bf99826fc55ae5793ca437d14ce16c2a5383fc4f..b1e95470b8f14b8635ae28fedc832f19a2eb05fb 100644
--- a/services/github/github-commit-activity.tester.js
+++ b/services/github/github-commit-activity.tester.js
@@ -12,21 +12,44 @@ const isCommitActivity = Joi.alternatives().try(
   isZeroOverTimePeriod
 )
 
+const authorFilterUser = 'jnullj'
+
 t.create('commit activity (total)').get('/t/badges/shields.json').expectBadge({
   label: 'commits',
   message: isMetric,
 })
 
+t.create('commit activity (total) by author')
+  .get(`/t/badges/shields.json?authorFilter=${authorFilterUser}`)
+  .expectBadge({
+    label: `commits by ${authorFilterUser}`,
+    message: isMetric,
+  })
+
 t.create('commit activity (1 year)').get('/y/eslint/eslint.json').expectBadge({
   label: 'commit activity',
   message: isMetricOverTimePeriod,
 })
 
+t.create('commit activity (1 year) by author')
+  .get(`/y/badges/shields.json?authorFilter=${authorFilterUser}`)
+  .expectBadge({
+    label: `commit activity by ${authorFilterUser}`,
+    message: isMetricOverTimePeriod,
+  })
+
 t.create('commit activity (1 month)').get('/m/eslint/eslint.json').expectBadge({
   label: 'commit activity',
   message: isMetricOverTimePeriod,
 })
 
+t.create('commit activity (1 month) by author')
+  .get(`/m/badges/shields.json?authorFilter=${authorFilterUser}`)
+  .expectBadge({
+    label: `commit activity by ${authorFilterUser}`,
+    message: isMetricOverTimePeriod,
+  })
+
 t.create('commit activity (4 weeks)')
   .get('/4w/eslint/eslint.json')
   .expectBadge({
@@ -34,11 +57,25 @@ t.create('commit activity (4 weeks)')
     message: isMetricOverTimePeriod,
   })
 
+t.create('commit activity (4 weeks) by author')
+  .get(`/4w/badges/shields.json?authorFilter=${authorFilterUser}`)
+  .expectBadge({
+    label: `commit activity by ${authorFilterUser}`,
+    message: isMetricOverTimePeriod,
+  })
+
 t.create('commit activity (1 week)').get('/w/eslint/eslint.json').expectBadge({
   label: 'commit activity',
   message: isCommitActivity,
 })
 
+t.create('commit activity (1 week) by author')
+  .get(`/w/badges/shields.json?authorFilter=${authorFilterUser}`)
+  .expectBadge({
+    label: `commit activity by ${authorFilterUser}`,
+    message: isCommitActivity,
+  })
+
 t.create('commit activity (custom branch)')
   .get('/y/badges/squint/main.json')
   .expectBadge({
@@ -46,6 +83,13 @@ t.create('commit activity (custom branch)')
     message: isCommitActivity,
   })
 
+t.create('commit activity (custom branch) by author')
+  .get(`/y/badges/squint/main.json?authorFilter=${authorFilterUser}`)
+  .expectBadge({
+    label: `commit activity by ${authorFilterUser}`,
+    message: isCommitActivity,
+  })
+
 t.create('commit activity (repo not found)')
   .get('/w/badges/helmets.json')
   .expectBadge({
@@ -59,3 +103,18 @@ t.create('commit activity (invalid branch)')
     label: 'commit activity',
     message: 'invalid branch',
   })
+
+// test for error handling of author filter as it uses REST and not GraphQL
+t.create('commit activity (repo not found)')
+  .get('/w/badges/helmets.json?authorFilter=zaphod')
+  .expectBadge({
+    label: 'commit activity',
+    message: 'repo or branch not found',
+  })
+
+t.create('commit activity (invalid branch)')
+  .get('/w/badges/shields/invalidBranchName.json?authorFilter=zaphod')
+  .expectBadge({
+    label: 'commit activity',
+    message: 'repo or branch not found',
+  })