diff --git a/lib/badge-data.js b/lib/badge-data.js
index f2931dc8e60c60615c5e830f17a0939ab8997968..96647f97df3adf1c620239708478b8c19960bb2c 100644
--- a/lib/badge-data.js
+++ b/lib/badge-data.js
@@ -1,9 +1,6 @@
 'use strict'
 
-const { toSvgColor } = require('../gh-badges/lib/color')
-const logos = require('./load-logos')()
-const simpleIcons = require('./load-simple-icons')()
-const { svg2base64, isDataUri } = require('./logo-helper')
+const { makeLogo } = require('./logos')
 
 function toArray(val) {
   if (val === undefined) {
@@ -15,20 +12,6 @@ function toArray(val) {
   }
 }
 
-function prependPrefix(s, prefix) {
-  if (s === undefined) {
-    return undefined
-  }
-
-  s = `${s}`
-
-  if (s.startsWith(prefix)) {
-    return s
-  } else {
-    return prefix + s
-  }
-}
-
 function makeLabel(defaultLabel, overrides) {
   return `${
     overrides.label === undefined
@@ -37,51 +20,6 @@ function makeLabel(defaultLabel, overrides) {
   }`
 }
 
-function getShieldsIcon(icon = '', color = '') {
-  icon = typeof icon === 'string' ? icon.toLowerCase() : ''
-  if (!logos[icon]) {
-    return undefined
-  }
-  color = toSvgColor(color)
-  return color
-    ? logos[icon].svg.replace(/fill="(.+?)"/g, `fill="${color}"`)
-    : logos[icon].base64
-}
-
-function getSimpleIcon(icon = '', color) {
-  icon = typeof icon === 'string' ? icon.toLowerCase().replace(/ /g, '-') : ''
-  if (!simpleIcons[icon]) {
-    return undefined
-  }
-  color = toSvgColor(color)
-  return color
-    ? simpleIcons[icon].svg.replace('<svg', `<svg fill="${color}"`)
-    : simpleIcons[icon].base64
-}
-
-function makeLogo(defaultNamedLogo, overrides) {
-  if (overrides.logo === undefined) {
-    return svg2base64(
-      getShieldsIcon(defaultNamedLogo, overrides.logoColor) ||
-        getSimpleIcon(defaultNamedLogo, overrides.logoColor)
-    )
-  }
-
-  // +'s are replaced with spaces when used in query params, this returns them to +'s, then removes remaining whitespace - #1546
-  const maybeDataUri = prependPrefix(overrides.logo, 'data:')
-    .replace(/ /g, '+')
-    .replace(/\s/g, '')
-
-  if (isDataUri(maybeDataUri)) {
-    return maybeDataUri
-  } else {
-    return svg2base64(
-      getShieldsIcon(overrides.logo, overrides.logoColor) ||
-        getSimpleIcon(overrides.logo, overrides.logoColor)
-    )
-  }
-}
-
 // Generate the initial badge data. Pass the URL query parameters, which
 // override the default label.
 //
@@ -120,8 +58,6 @@ function makeBadgeData(defaultLabel, overrides) {
 
 module.exports = {
   toArray,
-  prependPrefix,
   makeLabel,
-  makeLogo,
   makeBadgeData,
 }
diff --git a/lib/badge-data.spec.js b/lib/badge-data.spec.js
index 783368055225b36137d7f3883860d28755da254c..70d478ded967cc9609941e1da2198717181ebe91 100644
--- a/lib/badge-data.spec.js
+++ b/lib/badge-data.spec.js
@@ -1,23 +1,9 @@
 'use strict'
 
-const { expect } = require('chai')
-const { test, given, forCases } = require('sazerac')
-const {
-  prependPrefix,
-  makeLabel,
-  makeLogo,
-  makeBadgeData,
-} = require('./badge-data')
+const { test, given } = require('sazerac')
+const { makeLabel, makeBadgeData } = require('./badge-data')
 
 describe('Badge data helpers', function() {
-  test(prependPrefix, () => {
-    given('data:image/svg+xml;base64,PHN2ZyB4bWxu', 'data:').expect(
-      'data:image/svg+xml;base64,PHN2ZyB4bWxu'
-    )
-    given('foobar', 'data:').expect('data:foobar')
-    given(undefined, 'data:').expect(undefined)
-  })
-
   test(makeLabel, () => {
     given('my badge', {}).expect('my badge')
     given('My bAdge', {}).expect('my badge')
@@ -28,22 +14,6 @@ describe('Badge data helpers', function() {
     given('my badge', { label: '' }).expect('')
   })
 
-  test(makeLogo, () => {
-    forCases([
-      given('npm', { logo: 'image/svg+xml;base64,PHN2ZyB4bWxu' }),
-      given('npm', { logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu' }),
-      given('npm', { logo: 'data:image/svg xml;base64,PHN2ZyB4bWxu' }),
-      given('npm', { logo: 'data:image/svg+xml;base64,PHN2ZyB\n4bWxu' }),
-    ]).expect('data:image/svg+xml;base64,PHN2ZyB4bWxu')
-    forCases([given('npm', { logo: '' }), given(undefined, {})]).expect(
-      undefined
-    )
-    given('npm', {}).assert(
-      'should not be empty',
-      v => expect(v).not.to.be.empty
-    )
-  })
-
   test(makeBadgeData, () => {
     given('my badge', {
       label: 'no, my badge',
diff --git a/lib/load-logos.js b/lib/load-logos.js
index f812de8fc33273427b10bf2fb2649a8676829e8b..756793a536cb89d5ed6c57bff5229db1228c748b 100644
--- a/lib/load-logos.js
+++ b/lib/load-logos.js
@@ -2,7 +2,7 @@
 
 const fs = require('fs')
 const path = require('path')
-const { svg2base64 } = require('./logo-helper')
+const { svg2base64 } = require('./svg-helpers')
 
 function loadLogos() {
   // Cache svg logos from disk in base64 string
diff --git a/lib/load-simple-icons.js b/lib/load-simple-icons.js
index 02729d42c92dac276cd3deffcfded225f750c2cc..6e8a4e8725ca21f283788cfceaa4f195628b7a9f 100644
--- a/lib/load-simple-icons.js
+++ b/lib/load-simple-icons.js
@@ -1,7 +1,7 @@
 'use strict'
 
 const simpleIcons = require('simple-icons')
-const { svg2base64 } = require('./logo-helper')
+const { svg2base64 } = require('./svg-helpers')
 
 function loadSimpleIcons() {
   Object.keys(simpleIcons).forEach(key => {
diff --git a/lib/logo-helper.js b/lib/logo-helper.js
deleted file mode 100644
index b31f15814ad94e9fbcad3b8c964f519d048db303..0000000000000000000000000000000000000000
--- a/lib/logo-helper.js
+++ /dev/null
@@ -1,20 +0,0 @@
-'use strict'
-
-function isDataUri(s) {
-  return s !== undefined && /^(data:)([^;]+);([^,]+),(.+)$/.test(s)
-}
-
-function svg2base64(svg) {
-  if (typeof svg !== 'string') {
-    return undefined
-  }
-  // Check if logo is already base64
-  return isDataUri(svg)
-    ? svg
-    : `data:image/svg+xml;base64,${Buffer.from(svg).toString('base64')}`
-}
-
-module.exports = {
-  svg2base64,
-  isDataUri,
-}
diff --git a/lib/logo-helper.spec.js b/lib/logo-helper.spec.js
deleted file mode 100644
index 2acdad7b36082768ec948419dba17dacb1531bdc..0000000000000000000000000000000000000000
--- a/lib/logo-helper.spec.js
+++ /dev/null
@@ -1,21 +0,0 @@
-'use strict'
-
-const { test, given, forCases } = require('sazerac')
-const { svg2base64, isDataUri } = require('./logo-helper')
-
-describe('Logo helpers', function() {
-  test(svg2base64, () => {
-    given('data:image/svg+xml;base64,PHN2ZyB4bWxu').expect(
-      'data:image/svg+xml;base64,PHN2ZyB4bWxu'
-    )
-    given('<svg xmlns="http://www.w3.org/2000/svg"/>').expect(
-      'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciLz4='
-    )
-    given(undefined).expect(undefined)
-  })
-
-  test(isDataUri, () => {
-    given('data:image/svg+xml;base64,PHN2ZyB4bWxu').expect(true)
-    forCases([given('data:foobar'), given('foobar')]).expect(false)
-  })
-})
diff --git a/lib/logos.js b/lib/logos.js
new file mode 100644
index 0000000000000000000000000000000000000000..50795b0501587563b4fdcb156d0668e6d38102fe
--- /dev/null
+++ b/lib/logos.js
@@ -0,0 +1,100 @@
+'use strict'
+
+const { toSvgColor } = require('../gh-badges/lib/color')
+const coalesce = require('./coalesce')
+const { svg2base64 } = require('./svg-helpers')
+
+const logos = require('./load-logos')()
+const simpleIcons = require('./load-simple-icons')()
+
+function prependPrefix(s, prefix) {
+  if (s === undefined) {
+    return undefined
+  }
+
+  s = `${s}`
+
+  if (s.startsWith(prefix)) {
+    return s
+  } else {
+    return prefix + s
+  }
+}
+
+function isDataUrl(s) {
+  return s !== undefined && /^(data:)([^;]+);([^,]+),(.+)$/.test(s)
+}
+
+// +'s are replaced with spaces when used in query params, this returns them
+// to +'s, then removes remaining whitespace.
+// https://github.com/badges/shields/pull/1546
+function decodeDataUrlFromQueryParam(value) {
+  if (typeof value !== 'string') {
+    return undefined
+  }
+  const maybeDataUrl = prependPrefix(value, 'data:')
+    .replace(/ /g, '+')
+    .replace(/\s/g, '')
+  return isDataUrl(maybeDataUrl) ? maybeDataUrl : undefined
+}
+
+function getShieldsIcon({ name, color }) {
+  const key = name.toLowerCase()
+
+  if (!(key in logos)) {
+    return undefined
+  }
+
+  const { svg, base64 } = logos[key]
+  const svgColor = toSvgColor(color)
+  if (svgColor) {
+    return svg2base64(svg.replace(/fill="(.+?)"/g, `fill="${svgColor}"`))
+  } else {
+    return base64
+  }
+}
+
+function getSimpleIcon({ name, color }) {
+  const key = name.toLowerCase().replace(/ /g, '-')
+
+  if (!(key in simpleIcons)) {
+    return undefined
+  }
+
+  const { svg, base64 } = simpleIcons[key]
+  const svgColor = toSvgColor(color)
+  if (svgColor) {
+    return svg2base64(svg.replace('<svg', `<svg fill="${svgColor}"`))
+  } else {
+    return base64
+  }
+}
+
+function prepareNamedLogo({ name, color }) {
+  if (typeof name !== 'string') {
+    return undefined
+  }
+
+  return getShieldsIcon({ name, color }) || getSimpleIcon({ name, color })
+}
+
+function makeLogo(defaultNamedLogo, overrides) {
+  const maybeDataUrl = decodeDataUrlFromQueryParam(overrides.logo)
+  if (maybeDataUrl) {
+    return maybeDataUrl
+  } else {
+    return prepareNamedLogo({
+      name: coalesce(overrides.logo, defaultNamedLogo),
+      color: overrides.logoColor,
+    })
+  }
+}
+
+module.exports = {
+  prependPrefix,
+  isDataUrl,
+  decodeDataUrlFromQueryParam,
+  prepareNamedLogo,
+  getShieldsIcon,
+  makeLogo,
+}
diff --git a/lib/logos.spec.js b/lib/logos.spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..6f9f22d219677e291f4516d4276a169ac93156aa
--- /dev/null
+++ b/lib/logos.spec.js
@@ -0,0 +1,50 @@
+'use strict'
+
+const { test, given, forCases } = require('sazerac')
+const {
+  prependPrefix,
+  isDataUrl,
+  prepareNamedLogo,
+  makeLogo,
+} = require('./logos')
+
+describe('Logo helpers', function() {
+  test(prependPrefix, () => {
+    given('data:image/svg+xml;base64,PHN2ZyB4bWxu', 'data:').expect(
+      'data:image/svg+xml;base64,PHN2ZyB4bWxu'
+    )
+    given('foobar', 'data:').expect('data:foobar')
+    given(undefined, 'data:').expect(undefined)
+  })
+
+  test(isDataUrl, () => {
+    given('data:image/svg+xml;base64,PHN2ZyB4bWxu').expect(true)
+    forCases([given('data:foobar'), given('foobar')]).expect(false)
+  })
+
+  test(prepareNamedLogo, () => {
+    // These two strings are identical except for characters 159-165 which
+    // differ.
+    given({ name: 'github' }).expect(
+      'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0MCIgaGVpZ2h0PSI0MCIgdmlld0JveD0iMTIgMTIgNDAgNDAiPgo8cGF0aCBmaWxsPSIjMzMzMzMzIiBkPSJNMzIsMTMuNGMtMTAuNSwwLTE5LDguNS0xOSwxOWMwLDguNCw1LjUsMTUuNSwxMywxOGMxLDAuMiwxLjMtMC40LDEuMy0wLjljMC0wLjUsMC0xLjcsMC0zLjIgYy01LjMsMS4xLTYuNC0yLjYtNi40LTIuNkMyMCw0MS42LDE4LjgsNDEsMTguOCw0MWMtMS43LTEuMiwwLjEtMS4xLDAuMS0xLjFjMS45LDAuMSwyLjksMiwyLjksMmMxLjcsMi45LDQuNSwyLjEsNS41LDEuNiBjMC4yLTEuMiwwLjctMi4xLDEuMi0yLjZjLTQuMi0wLjUtOC43LTIuMS04LjctOS40YzAtMi4xLDAuNy0zLjcsMi01LjFjLTAuMi0wLjUtMC44LTIuNCwwLjItNWMwLDAsMS42LTAuNSw1LjIsMiBjMS41LTAuNCwzLjEtMC43LDQuOC0wLjdjMS42LDAsMy4zLDAuMiw0LjcsMC43YzMuNi0yLjQsNS4yLTIsNS4yLTJjMSwyLjYsMC40LDQuNiwwLjIsNWMxLjIsMS4zLDIsMywyLDUuMWMwLDcuMy00LjUsOC45LTguNyw5LjQgYzAuNywwLjYsMS4zLDEuNywxLjMsMy41YzAsMi42LDAsNC42LDAsNS4yYzAsMC41LDAuNCwxLjEsMS4zLDAuOWM3LjUtMi42LDEzLTkuNywxMy0xOC4xQzUxLDIxLjksNDIuNSwxMy40LDMyLDEzLjR6Ii8+Cjwvc3ZnPgo='
+    )
+    given({ name: 'github', color: 'blue' }).expect(
+      'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0MCIgaGVpZ2h0PSI0MCIgdmlld0JveD0iMTIgMTIgNDAgNDAiPgo8cGF0aCBmaWxsPSIjMDA3ZWM2IiBkPSJNMzIsMTMuNGMtMTAuNSwwLTE5LDguNS0xOSwxOWMwLDguNCw1LjUsMTUuNSwxMywxOGMxLDAuMiwxLjMtMC40LDEuMy0wLjljMC0wLjUsMC0xLjcsMC0zLjIgYy01LjMsMS4xLTYuNC0yLjYtNi40LTIuNkMyMCw0MS42LDE4LjgsNDEsMTguOCw0MWMtMS43LTEuMiwwLjEtMS4xLDAuMS0xLjFjMS45LDAuMSwyLjksMiwyLjksMmMxLjcsMi45LDQuNSwyLjEsNS41LDEuNiBjMC4yLTEuMiwwLjctMi4xLDEuMi0yLjZjLTQuMi0wLjUtOC43LTIuMS04LjctOS40YzAtMi4xLDAuNy0zLjcsMi01LjFjLTAuMi0wLjUtMC44LTIuNCwwLjItNWMwLDAsMS42LTAuNSw1LjIsMiBjMS41LTAuNCwzLjEtMC43LDQuOC0wLjdjMS42LDAsMy4zLDAuMiw0LjcsMC43YzMuNi0yLjQsNS4yLTIsNS4yLTJjMSwyLjYsMC40LDQuNiwwLjIsNWMxLjIsMS4zLDIsMywyLDUuMWMwLDcuMy00LjUsOC45LTguNyw5LjQgYzAuNywwLjYsMS4zLDEuNywxLjMsMy41YzAsMi42LDAsNC42LDAsNS4yYzAsMC41LDAuNCwxLjEsMS4zLDAuOWM3LjUtMi42LDEzLTkuNywxMy0xOC4xQzUxLDIxLjksNDIuNSwxMy40LDMyLDEzLjR6Ii8+Cjwvc3ZnPgo='
+    )
+  })
+
+  test(makeLogo, () => {
+    forCases([
+      given('npm', { logo: 'image/svg+xml;base64,PHN2ZyB4bWxu' }),
+      given('npm', { logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu' }),
+      given('npm', { logo: 'data:image/svg xml;base64,PHN2ZyB4bWxu' }),
+      given('npm', { logo: 'data:image/svg+xml;base64,PHN2ZyB\n4bWxu' }),
+    ]).expect('data:image/svg+xml;base64,PHN2ZyB4bWxu')
+    forCases([given('npm', { logo: '' }), given(undefined, {})]).expect(
+      undefined
+    )
+    given('github', {}).expect(
+      'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0MCIgaGVpZ2h0PSI0MCIgdmlld0JveD0iMTIgMTIgNDAgNDAiPgo8cGF0aCBmaWxsPSIjMzMzMzMzIiBkPSJNMzIsMTMuNGMtMTAuNSwwLTE5LDguNS0xOSwxOWMwLDguNCw1LjUsMTUuNSwxMywxOGMxLDAuMiwxLjMtMC40LDEuMy0wLjljMC0wLjUsMC0xLjcsMC0zLjIgYy01LjMsMS4xLTYuNC0yLjYtNi40LTIuNkMyMCw0MS42LDE4LjgsNDEsMTguOCw0MWMtMS43LTEuMiwwLjEtMS4xLDAuMS0xLjFjMS45LDAuMSwyLjksMiwyLjksMmMxLjcsMi45LDQuNSwyLjEsNS41LDEuNiBjMC4yLTEuMiwwLjctMi4xLDEuMi0yLjZjLTQuMi0wLjUtOC43LTIuMS04LjctOS40YzAtMi4xLDAuNy0zLjcsMi01LjFjLTAuMi0wLjUtMC44LTIuNCwwLjItNWMwLDAsMS42LTAuNSw1LjIsMiBjMS41LTAuNCwzLjEtMC43LDQuOC0wLjdjMS42LDAsMy4zLDAuMiw0LjcsMC43YzMuNi0yLjQsNS4yLTIsNS4yLTJjMSwyLjYsMC40LDQuNiwwLjIsNWMxLjIsMS4zLDIsMywyLDUuMWMwLDcuMy00LjUsOC45LTguNyw5LjQgYzAuNywwLjYsMS4zLDEuNywxLjMsMy41YzAsMi42LDAsNC42LDAsNS4yYzAsMC41LDAuNCwxLjEsMS4zLDAuOWM3LjUtMi42LDEzLTkuNywxMy0xOC4xQzUxLDIxLjksNDIuNSwxMy40LDMyLDEzLjR6Ii8+Cjwvc3ZnPgo='
+    )
+  })
+})
diff --git a/lib/svg-helpers.js b/lib/svg-helpers.js
new file mode 100644
index 0000000000000000000000000000000000000000..7011c156aed7245f54217acd7a73c9d976ccac08
--- /dev/null
+++ b/lib/svg-helpers.js
@@ -0,0 +1,7 @@
+'use strict'
+
+function svg2base64(svg) {
+  return `data:image/svg+xml;base64,${Buffer.from(svg).toString('base64')}`
+}
+
+module.exports = { svg2base64 }
diff --git a/lib/svg-helpers.spec.js b/lib/svg-helpers.spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..517794bc474f6485538f54fbba449fdd824a5381
--- /dev/null
+++ b/lib/svg-helpers.spec.js
@@ -0,0 +1,12 @@
+'use strict'
+
+const { test, given } = require('sazerac')
+const { svg2base64 } = require('./svg-helpers')
+
+describe('SVG helpers', function() {
+  test(svg2base64, () => {
+    given('<svg xmlns="http://www.w3.org/2000/svg"/>').expect(
+      'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciLz4='
+    )
+  })
+})
diff --git a/services/base.js b/services/base.js
index 665aac07faaf8b4ca65e34cf6ab2de9743fdde08..664cd307cc5864a2d97369a376ff4a666a7a05ff 100644
--- a/services/base.js
+++ b/services/base.js
@@ -14,7 +14,12 @@ const {
 const coalesce = require('../lib/coalesce')
 const validate = require('../lib/validate')
 const { checkErrorResponse } = require('../lib/error-helper')
-const { makeLogo, toArray } = require('../lib/badge-data')
+const { toArray } = require('../lib/badge-data')
+const { svg2base64 } = require('../lib/svg-helpers')
+const {
+  decodeDataUrlFromQueryParam,
+  prepareNamedLogo,
+} = require('../lib/logos')
 const trace = require('./trace')
 const { validateExample, transformExample } = require('./transform-example')
 const { assertValidCategory } = require('./categories')
@@ -24,7 +29,7 @@ const defaultBadgeDataSchema = Joi.object({
   label: Joi.string(),
   color: Joi.string(),
   labelColor: Joi.string(),
-  logo: Joi.string(),
+  namedLogo: Joi.string(),
 }).required()
 
 const serviceDataSchema = Joi.object({
@@ -38,10 +43,30 @@ const serviceDataSchema = Joi.object({
   // Generally services should not use these options, which are provided to
   // support the Endpoint badge.
   labelColor: Joi.string(),
+  namedLogo: Joi.string(),
+  logoSvg: Joi.string(),
+  logoColor: Joi.forbidden(),
+  logoWidth: Joi.forbidden(),
+  logoPosition: Joi.forbidden(),
   cacheLengthSeconds: Joi.number()
     .integer()
     .min(0),
-}).required()
+})
+  .oxor('namedLogo', 'logoSvg')
+  .when(
+    Joi.alternatives().try(
+      Joi.object({ namedLogo: Joi.string().required() }).unknown(),
+      Joi.object({ logoSvg: Joi.string().required() }).unknown()
+    ),
+    {
+      then: Joi.object({
+        logoColor: Joi.string(),
+        logoWidth: Joi.number(),
+        logoPosition: Joi.number(),
+      }),
+    }
+  )
+  .required()
 
 class BaseService {
   constructor({ sendAndCacheRequest }, { handleInternalErrors }) {
@@ -327,24 +352,56 @@ class BaseService {
   }
 
   // Translate modern badge data to the legacy schema understood by the badge
-  // maker.
+  // maker. Allow the user to override the label, color, logo, etc. through
+  // the query string. Provide support for most badge options via
+  // `serviceData` so the Endpoint badge can specify logos and colors, though
+  // allow that the user's logo or color to take precedence. A notable
+  // exception is the case of errors. When the service specifies that an error
+  // has occurred, the user's requested color does not override the error color.
+  //
+  // Logos are resolved in this manner:
+  //
+  // 1. When `?logo=` contains the name of one of the Shields logos, or contains
+  //    base64-encoded SVG, that logo is used. In the case of a named logo, when
+  //    a `&logoColor=` is specified, that color is used. Otherwise the default
+  //    color is used. `logoColor` will not be applied to a custom
+  //    (base64-encoded) logo; if a custom color is desired the logo should be
+  //    recolored prior to making the request. The appearance of the logo can be
+  //    customized using `logoWidth`, and in the case of the popout badge,
+  //    `logoPosition`. When `?logo=` is specified, any logo-related parameters
+  //    specified dynamically by the service, or by default in the service, are
+  //    ignored.
+  // 2. The second precedence is the dynamic logo returned by a service. This is
+  //    used only by the Endpoint badge. The `logoColor` can be overridden by the
+  //    query string.
+  // 3. In the case of the `social` style only, the last precedence is the
+  //    service's default logo. The `logoColor` can be overridden by the query
+  //    string.
   static _makeBadgeData(overrides, serviceData) {
     const {
       style,
       label: overrideLabel,
-      logo: overrideLogo,
       logoColor: overrideLogoColor,
-      logoWidth: overrideLogoWidth,
       link: overrideLink,
     } = overrides
     // Scoutcamp converts numeric query params to numbers. Convert them back.
-    let { colorB: overrideColor, colorA: overrideLabelColor } = overrides
+    let {
+      colorB: overrideColor,
+      colorA: overrideLabelColor,
+      logoWidth: overrideLogoWidth,
+      logoPosition: overrideLogoPosition,
+    } = overrides
     if (typeof overrideColor === 'number') {
       overrideColor = `${overrideColor}`
     }
     if (typeof overrideLabelColor === 'number') {
       overrideLabelColor = `${overrideLabelColor}`
     }
+    overrideLogoWidth = +overrideLogoWidth || undefined
+    overrideLogoPosition = +overrideLogoPosition || undefined
+    // `?logo=` could be a named logo or encoded svg. Split up these cases.
+    const overrideLogoSvgBase64 = decodeDataUrlFromQueryParam(overrides.logo)
+    const overrideNamedLogo = overrideLogoSvgBase64 ? undefined : overrides.logo
 
     const {
       isError,
@@ -352,19 +409,41 @@ class BaseService {
       message: serviceMessage,
       color: serviceColor,
       labelColor: serviceLabelColor,
+      logoSvg: serviceLogoSvg,
+      namedLogo: serviceNamedLogo,
+      logoColor: serviceLogoColor,
+      logoWidth: serviceLogoWidth,
+      logoPosition: serviceLogoPosition,
       link: serviceLink,
       cacheLengthSeconds: serviceCacheLengthSeconds,
     } = serviceData
+    const serviceLogoSvgBase64 = serviceLogoSvg
+      ? svg2base64(serviceLogoSvg)
+      : undefined
 
     const {
       color: defaultColor,
-      logo: defaultLogo,
+      namedLogo: defaultNamedLogo,
       label: defaultLabel,
       labelColor: defaultLabelColor,
     } = this.defaultBadgeData
     const defaultCacheLengthSeconds = this._cacheLength
 
-    const badgeData = {
+    const namedLogoSvgBase64 = prepareNamedLogo({
+      name: coalesce(
+        overrideNamedLogo,
+        serviceNamedLogo,
+        style === 'social' ? defaultNamedLogo : undefined
+      ),
+      color: coalesce(
+        overrideLogoColor,
+        // If the logo has been overridden it does not make sense to inherit
+        // the color.
+        overrideNamedLogo ? undefined : serviceLogoColor
+      ),
+    })
+
+    return {
       text: [
         // Use `coalesce()` to support empty labels and messages, as in the
         // static badge.
@@ -385,19 +464,27 @@ class BaseService {
         defaultLabelColor
       ),
       template: style,
-      logo: makeLogo(style === 'social' ? defaultLogo : undefined, {
-        logo: overrideLogo,
-        logoColor: overrideLogoColor,
-      }),
-      logoWidth: +overrideLogoWidth,
+      logo: coalesce(
+        overrideLogoSvgBase64,
+        serviceLogoSvgBase64,
+        namedLogoSvgBase64
+      ),
+      logoWidth: coalesce(
+        overrideLogoWidth,
+        // If the logo has been overridden it does not make sense to inherit
+        // the width or position.
+        overrideNamedLogo ? undefined : serviceLogoWidth
+      ),
+      logoPosition: coalesce(
+        overrideLogoPosition,
+        overrideNamedLogo ? undefined : serviceLogoPosition
+      ),
       links: toArray(overrideLink || serviceLink),
       cacheLengthSeconds: coalesce(
         serviceCacheLengthSeconds,
         defaultCacheLengthSeconds
       ),
     }
-
-    return badgeData
   }
 
   static register({ camp, handleRequest, githubApiProvider }, serviceConfig) {
diff --git a/services/base.spec.js b/services/base.spec.js
index ddb31184dd68068426ab369e54b487ff50619c73..506d51442d9e18ece7af07614d0817e36b3ee369 100644
--- a/services/base.spec.js
+++ b/services/base.spec.js
@@ -4,6 +4,7 @@ const Joi = require('joi')
 const { expect } = require('chai')
 const { test, given, forCases } = require('sazerac')
 const sinon = require('sinon')
+const { getShieldsIcon } = require('../lib/logos')
 const trace = require('./trace')
 
 const {
@@ -33,7 +34,7 @@ class DummyService extends BaseService {
   }
 
   static get defaultBadgeData() {
-    return { label: 'cat' }
+    return { label: 'cat', namedLogo: 'appveyor' }
   }
 
   static get examples() {
@@ -414,23 +415,65 @@ describe('BaseService', function() {
       })
 
       it('overrides the logo', function() {
-        const expLogo =
-          'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0MCIgaGVpZ2h0PSI0MCIgdmlld0JveD0iMTIgMTIgNDAgNDAiPgo8cGF0aCBmaWxsPSIjMzMzMzMzIiBkPSJNMzIsMTMuNGMtMTAuNSwwLTE5LDguNS0xOSwxOWMwLDguNCw1LjUsMTUuNSwxMywxOGMxLDAuMiwxLjMtMC40LDEuMy0wLjljMC0wLjUsMC0xLjcsMC0zLjIgYy01LjMsMS4xLTYuNC0yLjYtNi40LTIuNkMyMCw0MS42LDE4LjgsNDEsMTguOCw0MWMtMS43LTEuMiwwLjEtMS4xLDAuMS0xLjFjMS45LDAuMSwyLjksMiwyLjksMmMxLjcsMi45LDQuNSwyLjEsNS41LDEuNiBjMC4yLTEuMiwwLjctMi4xLDEuMi0yLjZjLTQuMi0wLjUtOC43LTIuMS04LjctOS40YzAtMi4xLDAuNy0zLjcsMi01LjFjLTAuMi0wLjUtMC44LTIuNCwwLjItNWMwLDAsMS42LTAuNSw1LjIsMiBjMS41LTAuNCwzLjEtMC43LDQuOC0wLjdjMS42LDAsMy4zLDAuMiw0LjcsMC43YzMuNi0yLjQsNS4yLTIsNS4yLTJjMSwyLjYsMC40LDQuNiwwLjIsNWMxLjIsMS4zLDIsMywyLDUuMWMwLDcuMy00LjUsOC45LTguNyw5LjQgYzAuNywwLjYsMS4zLDEuNywxLjMsMy41YzAsMi42LDAsNC42LDAsNS4yYzAsMC41LDAuNCwxLjEsMS4zLDAuOWM3LjUtMi42LDEzLTkuNywxMy0xOC4xQzUxLDIxLjksNDIuNSwxMy40LDMyLDEzLjR6Ii8+Cjwvc3ZnPgo='
         const badgeData = DummyService._makeBadgeData(
-          { logo: 'github', style: 'social' },
-          {}
+          { logo: 'github' },
+          { namedLogo: 'appveyor' }
         )
-        expect(badgeData.logo).to.equal(expLogo)
+        // .not.be.empty for confidence that nothing has changed with `getShieldsIcon()`.
+        expect(badgeData.logo).to.equal(getShieldsIcon({ name: 'github' })).and
+          .not.be.empty
       })
 
-      it('overrides the logo with color', function() {
-        const expLogo =
-          'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0MCIgaGVpZ2h0PSI0MCIgdmlld0JveD0iMTIgMTIgNDAgNDAiPgo8cGF0aCBmaWxsPSIjMDA3ZWM2IiBkPSJNMzIsMTMuNGMtMTAuNSwwLTE5LDguNS0xOSwxOWMwLDguNCw1LjUsMTUuNSwxMywxOGMxLDAuMiwxLjMtMC40LDEuMy0wLjljMC0wLjUsMC0xLjcsMC0zLjIgYy01LjMsMS4xLTYuNC0yLjYtNi40LTIuNkMyMCw0MS42LDE4LjgsNDEsMTguOCw0MWMtMS43LTEuMiwwLjEtMS4xLDAuMS0xLjFjMS45LDAuMSwyLjksMiwyLjksMmMxLjcsMi45LDQuNSwyLjEsNS41LDEuNiBjMC4yLTEuMiwwLjctMi4xLDEuMi0yLjZjLTQuMi0wLjUtOC43LTIuMS04LjctOS40YzAtMi4xLDAuNy0zLjcsMi01LjFjLTAuMi0wLjUtMC44LTIuNCwwLjItNWMwLDAsMS42LTAuNSw1LjIsMiBjMS41LTAuNCwzLjEtMC43LDQuOC0wLjdjMS42LDAsMy4zLDAuMiw0LjcsMC43YzMuNi0yLjQsNS4yLTIsNS4yLTJjMSwyLjYsMC40LDQuNiwwLjIsNWMxLjIsMS4zLDIsMywyLDUuMWMwLDcuMy00LjUsOC45LTguNyw5LjQgYzAuNywwLjYsMS4zLDEuNywxLjMsMy41YzAsMi42LDAsNC42LDAsNS4yYzAsMC41LDAuNCwxLjEsMS4zLDAuOWM3LjUtMi42LDEzLTkuNywxMy0xOC4xQzUxLDIxLjksNDIuNSwxMy40LDMyLDEzLjR6Ii8+Cjwvc3ZnPgo='
+      it('overrides the logo with a color', function() {
         const badgeData = DummyService._makeBadgeData(
           { logo: 'github', logoColor: 'blue' },
-          {}
+          { namedLogo: 'appveyor' }
         )
-        expect(badgeData.logo).to.equal(expLogo)
+        expect(badgeData.logo).to.equal(
+          getShieldsIcon({ name: 'github', color: 'blue' })
+        ).and.not.be.empty
+      })
+
+      it("when the logo is overridden, it ignores the service's logo color, position, and width", function() {
+        const badgeData = DummyService._makeBadgeData(
+          { logo: 'github' },
+          {
+            namedLogo: 'appveyor',
+            logoColor: 'red',
+            logoPosition: -3,
+            logoWidth: 100,
+          }
+        )
+        expect(badgeData.logo).to.equal(getShieldsIcon({ name: 'github' })).and
+          .not.be.empty
+      })
+
+      it("overrides the service logo's color", function() {
+        const badgeData = DummyService._makeBadgeData(
+          { logoColor: 'blue' },
+          { namedLogo: 'github', logoColor: 'red' }
+        )
+        expect(badgeData.logo).to.equal(
+          getShieldsIcon({ name: 'github', color: 'blue' })
+        ).and.not.be.empty
+      })
+
+      it('overrides the logo with custom svg', function() {
+        const logoSvg = 'data:image/svg+xml;base64,PHN2ZyB4bWxu'
+        const badgeData = DummyService._makeBadgeData(
+          { logo: logoSvg },
+          { namedLogo: 'appveyor' }
+        )
+        expect(badgeData.logo).to.equal(logoSvg)
+      })
+
+      it('ignores the color when custom svg is provided', function() {
+        const logoSvg = 'data:image/svg+xml;base64,PHN2ZyB4bWxu'
+        const badgeData = DummyService._makeBadgeData(
+          { logo: logoSvg, logoColor: 'brightgreen' },
+          { namedLogo: 'appveyor' }
+        )
+        expect(badgeData.logo).to.equal(logoSvg)
       })
 
       it('overrides the logoWidth', function() {
@@ -438,6 +481,11 @@ describe('BaseService', function() {
         expect(badgeData.logoWidth).to.equal(20)
       })
 
+      it('overrides the logoPosition', function() {
+        const badgeData = DummyService._makeBadgeData({ logoPosition: -10 }, {})
+        expect(badgeData.logoPosition).to.equal(-10)
+      })
+
       it('overrides the links', function() {
         const badgeData = DummyService._makeBadgeData(
           { link: 'https://circleci.com/gh/badges/daily-tests' },
@@ -491,6 +539,42 @@ describe('BaseService', function() {
         expect(badgeData.color).to.equal('red')
       })
 
+      it('applies the named logo', function() {
+        const badgeData = DummyService._makeBadgeData(
+          {},
+          { namedLogo: 'github' }
+        )
+        // .not.be.empty for confidence that nothing has changed with `getShieldsIcon()`.
+        expect(badgeData.logo).to.equal(getShieldsIcon({ name: 'github' })).and
+          .not.to.be.empty
+      })
+
+      it('applies the named logo with color', function() {
+        const badgeData = DummyService._makeBadgeData(
+          {},
+          { namedLogo: 'github', logoColor: 'blue' }
+        )
+        expect(badgeData.logo).to.equal(
+          getShieldsIcon({ name: 'github', color: 'blue' })
+        ).and.not.to.be.empty
+      })
+
+      it('applies the logo width', function() {
+        const badgeData = DummyService._makeBadgeData(
+          {},
+          { namedLogo: 'github', logoWidth: 275 }
+        )
+        expect(badgeData.logoWidth).to.equal(275)
+      })
+
+      it('applies the logo position', function() {
+        const badgeData = DummyService._makeBadgeData(
+          {},
+          { namedLogo: 'github', logoPosition: -10 }
+        )
+        expect(badgeData.logoPosition).to.equal(-10)
+      })
+
       it('applies the service label color', function() {
         const badgeData = DummyService._makeBadgeData({}, { labelColor: 'red' })
         expect(badgeData.labelColor).to.equal('red')
@@ -512,6 +596,17 @@ describe('BaseService', function() {
         const badgeData = DummyService._makeBadgeData({}, {})
         expect(badgeData.labelColor).to.be.undefined
       })
+
+      it('when not a social badge, ignores the default named logo', function() {
+        const badgeData = DummyService._makeBadgeData({}, {})
+        expect(badgeData.logo).to.be.undefined
+      })
+
+      it('when a social badge, uses the default named logo', function() {
+        const badgeData = DummyService._makeBadgeData({ style: 'social' }, {})
+        expect(badgeData.logo).to.equal(getShieldsIcon({ name: 'appveyor' }))
+          .and.not.be.empty
+      })
     })
   })
 
@@ -556,7 +651,8 @@ describe('BaseService', function() {
         color: 'lightgrey',
         template: undefined,
         logo: undefined,
-        logoWidth: NaN,
+        logoWidth: undefined,
+        logoPosition: undefined,
         links: [],
         labelColor: undefined,
         cacheLengthSeconds: undefined,
diff --git a/services/github/github-pull-request-check-state.service.js b/services/github/github-pull-request-check-state.service.js
index 3c2dbba6b0d876452ab199f163b1b4ccfe3bc8ce..7b089bfe73b75c4048245d3fb0e50833acf5e843 100644
--- a/services/github/github-pull-request-check-state.service.js
+++ b/services/github/github-pull-request-check-state.service.js
@@ -67,7 +67,7 @@ module.exports = class GithubPullRequestCheckState extends GithubAuthService {
   static get defaultBadgeData() {
     return {
       label: 'checks',
-      logo: 'github',
+      namedLogo: 'github',
     }
   }