diff --git a/services/spiget/spiget-base.js b/services/spiget/spiget-base.js
new file mode 100644
index 0000000000000000000000000000000000000000..be2885939a12c43febaceb8b270317d0dedc951d
--- /dev/null
+++ b/services/spiget/spiget-base.js
@@ -0,0 +1,46 @@
+'use strict'
+
+const Joi = require('joi')
+const BaseJsonService = require('../base-json')
+
+const resourceSchema = Joi.object({
+  downloads: Joi.number().required(),
+  file: Joi.object({
+    size: Joi.number().required(),
+    sizeUnit: Joi.string().required(),
+  }).required(),
+  testedVersions: Joi.array(),
+  rating: Joi.object({
+    count: Joi.number().required(),
+    average: Joi.number().required(),
+  }).required(),
+}).required()
+
+const documentation = `
+<p>You can find your resource ID in the url for your resource page.</p>
+<p>Example: <code>https://www.spigotmc.org/resources/essentialsx.9089/</code> - Here the Resource ID is 9089.</p>`
+
+const keywords = ['spigot', 'spigotmc']
+
+class BaseSpigetService extends BaseJsonService {
+  async fetch({
+    resourceid,
+    schema = resourceSchema,
+    url = `https://api.spiget.org/v2/resources/${resourceid}`,
+  }) {
+    return this._requestJson({
+      schema,
+      url,
+    })
+  }
+
+  static get defaultBadgeData() {
+    return { label: 'spiget' }
+  }
+
+  static get category() {
+    return 'other'
+  }
+}
+
+module.exports = { keywords, documentation, BaseSpigetService }
diff --git a/services/spiget/spiget-download-size.service.js b/services/spiget/spiget-download-size.service.js
new file mode 100644
index 0000000000000000000000000000000000000000..91fb611e6befdfaf0144ef8bdc731a2bc9a67fb0
--- /dev/null
+++ b/services/spiget/spiget-download-size.service.js
@@ -0,0 +1,47 @@
+'use strict'
+
+const { BaseSpigetService, documentation, keywords } = require('./spiget-base')
+
+module.exports = class SpigetDownloadSize extends BaseSpigetService {
+  static get route() {
+    return {
+      base: 'spiget/download-size',
+      pattern: ':resourceid',
+    }
+  }
+
+  static get defaultBadgeData() {
+    return {
+      label: 'size',
+      color: 'blue',
+    }
+  }
+
+  async handle({ resourceid }) {
+    const { file } = await this.fetch({ resourceid })
+    return this.constructor.render({ size: file.size, unit: file.sizeUnit })
+  }
+
+  static render({ size, unit }) {
+    return {
+      message: `${size} ${unit}`,
+    }
+  }
+
+  static get category() {
+    return 'size'
+  }
+  static get examples() {
+    return [
+      {
+        title: 'Spiget Download Size',
+        namedParams: {
+          resourceid: '9089',
+        },
+        staticPreview: this.render({ size: 2.5, unit: 'MB' }),
+        documentation,
+        keywords,
+      },
+    ]
+  }
+}
diff --git a/services/spiget/spiget-download-size.tester.js b/services/spiget/spiget-download-size.tester.js
new file mode 100644
index 0000000000000000000000000000000000000000..d40c256f7d91472b865240e888ca378268a2848e
--- /dev/null
+++ b/services/spiget/spiget-download-size.tester.js
@@ -0,0 +1,17 @@
+'use strict'
+
+const Joi = require('joi')
+const { isFileSize } = require('../test-validators')
+
+const t = (module.exports = require('../create-service-tester')())
+
+t.create('EssentialsX (id 9089)')
+  .get('/9089.json')
+  .expectJSONTypes(Joi.object().keys({ name: 'size', value: isFileSize }))
+
+t.create('Invalid Resource (id 1)')
+  .get('/1.json')
+  .expectJSON({
+    name: 'size',
+    value: 'not found',
+  })
diff --git a/services/spiget/spiget-downloads.service.js b/services/spiget/spiget-downloads.service.js
new file mode 100644
index 0000000000000000000000000000000000000000..31580e6661f2c116503620d41a5f0562521373b7
--- /dev/null
+++ b/services/spiget/spiget-downloads.service.js
@@ -0,0 +1,50 @@
+'use strict'
+
+const { BaseSpigetService, documentation, keywords } = require('./spiget-base')
+const { metric } = require('../../lib/text-formatters')
+const { downloadCount } = require('../../lib/color-formatters')
+
+module.exports = class SpigetDownloads extends BaseSpigetService {
+  static get route() {
+    return {
+      base: 'spiget/downloads',
+      pattern: ':resourceid',
+    }
+  }
+
+  static get defaultBadgeData() {
+    return {
+      label: 'downloads',
+    }
+  }
+
+  async handle({ resourceid }) {
+    const { downloads } = await this.fetch({ resourceid })
+    return this.constructor.render({ downloads })
+  }
+
+  static render({ downloads }) {
+    return {
+      message: metric(downloads),
+      color: downloadCount(downloads),
+    }
+  }
+
+  static get category() {
+    return 'downloads'
+  }
+
+  static get examples() {
+    return [
+      {
+        title: 'Spiget Downloads',
+        namedParams: {
+          resourceid: '9089',
+        },
+        staticPreview: this.render({ downloads: 560891 }),
+        documentation,
+        keywords,
+      },
+    ]
+  }
+}
diff --git a/services/spiget/spiget-downloads.tester.js b/services/spiget/spiget-downloads.tester.js
new file mode 100644
index 0000000000000000000000000000000000000000..761da8c7e74b55e5234bce7a621c34d98b2a36c2
--- /dev/null
+++ b/services/spiget/spiget-downloads.tester.js
@@ -0,0 +1,22 @@
+'use strict'
+
+const Joi = require('joi')
+const { isMetric } = require('../test-validators')
+
+const t = (module.exports = require('../create-service-tester')())
+
+t.create('EssentialsX (id 9089)')
+  .get('/9089.json')
+  .expectJSONTypes(
+    Joi.object().keys({
+      name: 'downloads',
+      value: isMetric,
+    })
+  )
+
+t.create('Invalid Resource (id 1)')
+  .get('/1.json')
+  .expectJSON({
+    name: 'downloads',
+    value: 'not found',
+  })
diff --git a/services/spiget/spiget-latest-version.service.js b/services/spiget/spiget-latest-version.service.js
new file mode 100644
index 0000000000000000000000000000000000000000..79a43b54263e9f103982bd0df352fc8cfab1fed6
--- /dev/null
+++ b/services/spiget/spiget-latest-version.service.js
@@ -0,0 +1,53 @@
+'use strict'
+
+const { BaseSpigetService, documentation, keywords } = require('./spiget-base')
+
+const { renderVersionBadge } = require('../../lib/version')
+
+const Joi = require('joi')
+const versionSchema = Joi.object({
+  downloads: Joi.number().required(),
+  name: Joi.string().required(),
+}).required()
+
+module.exports = class SpigetLatestVersion extends BaseSpigetService {
+  static get route() {
+    return {
+      base: 'spiget/version',
+      pattern: ':resourceid',
+    }
+  }
+
+  static get defaultBadgeData() {
+    return {
+      label: 'version',
+      color: 'blue',
+    }
+  }
+
+  async handle({ resourceid }) {
+    const { name } = await this.fetch({
+      resourceid,
+      schema: versionSchema,
+      url: `https://api.spiget.org/v2/resources/${resourceid}/versions/latest`,
+    })
+    return renderVersionBadge({ version: name })
+  }
+
+  static get category() {
+    return 'version'
+  }
+  static get examples() {
+    return [
+      {
+        title: 'Spiget Version',
+        namedParams: {
+          resourceid: '9089',
+        },
+        staticPreview: renderVersionBadge({ version: 2.1 }),
+        documentation,
+        keywords,
+      },
+    ]
+  }
+}
diff --git a/services/spiget/spiget-latest-version.tester.js b/services/spiget/spiget-latest-version.tester.js
new file mode 100644
index 0000000000000000000000000000000000000000..f484295632749eb5845f3abfe27ea73f1e2585a3
--- /dev/null
+++ b/services/spiget/spiget-latest-version.tester.js
@@ -0,0 +1,24 @@
+'use strict'
+
+const Joi = require('joi')
+const { withRegex } = require('../test-validators')
+
+const t = (module.exports = require('../create-service-tester')())
+
+// Note that Spigot versions can be anything (including just a string), so we'll make sure it's not returning 'not found'
+
+t.create('EssentialsX (id 9089)')
+  .get('/9089.json')
+  .expectJSONTypes(
+    Joi.object().keys({
+      name: 'version',
+      value: withRegex(/^(?!not found$)/),
+    })
+  )
+
+t.create('Invalid Resource (id 1)')
+  .get('/1.json')
+  .expectJSON({
+    name: 'version',
+    value: 'not found',
+  })
diff --git a/services/spiget/spiget-rating.service.js b/services/spiget/spiget-rating.service.js
new file mode 100644
index 0000000000000000000000000000000000000000..625e5d010e4a2da87fc86dc592040a63bb166f2c
--- /dev/null
+++ b/services/spiget/spiget-rating.service.js
@@ -0,0 +1,71 @@
+'use strict'
+
+const { BaseSpigetService, documentation, keywords } = require('./spiget-base')
+
+const { starRating, metric } = require('../../lib/text-formatters')
+const { floorCount } = require('../../lib/color-formatters')
+
+module.exports = class SpigetRatings extends BaseSpigetService {
+  static get route() {
+    return {
+      base: 'spiget',
+      pattern: ':format(rating|stars)/:resourceid',
+    }
+  }
+
+  async handle({ format, resourceid }) {
+    const { rating } = await this.fetch({ resourceid })
+    return this.constructor.render({
+      format,
+      total: rating.count,
+      average: rating.average,
+    })
+  }
+
+  static render({ format, total, average }) {
+    const message =
+      format === 'stars'
+        ? starRating(average)
+        : `${average}/5 (${metric(total)})`
+    return {
+      message,
+      color: floorCount(average, 2, 3, 4),
+    }
+  }
+
+  static get category() {
+    return 'rating'
+  }
+
+  static get defaultBadgeData() {
+    return { label: 'rating' }
+  }
+
+  static get examples() {
+    return [
+      {
+        title: 'Spiget Stars',
+        pattern: 'stars/:resourceid',
+        namedParams: {
+          resourceid: '9089',
+        },
+        staticPreview: this.render({
+          format: 'stars',
+          total: 325,
+          average: 4.5,
+        }),
+        documentation,
+      },
+      {
+        title: 'Spiget Rating',
+        pattern: 'rating/:resourceid',
+        namedParams: {
+          resourceid: '9089',
+        },
+        staticPreview: this.render({ total: 325, average: 4.5 }),
+        documentation,
+        keywords,
+      },
+    ]
+  }
+}
diff --git a/services/spiget/spiget-rating.tester.js b/services/spiget/spiget-rating.tester.js
new file mode 100644
index 0000000000000000000000000000000000000000..46263caa588f6ca012cd1dd730b4240e84af7e5e
--- /dev/null
+++ b/services/spiget/spiget-rating.tester.js
@@ -0,0 +1,39 @@
+'use strict'
+
+const Joi = require('joi')
+
+const { isStarRating, withRegex } = require('../test-validators')
+
+const t = (module.exports = require('../create-service-tester')())
+
+t.create('Stars - EssentialsX (id 9089)')
+  .get('/stars/9089.json')
+  .expectJSONTypes(
+    Joi.object().keys({
+      name: 'rating',
+      value: isStarRating,
+    })
+  )
+
+t.create('Stars - Invalid Resource (id 1)')
+  .get('/stars/1.json')
+  .expectJSON({
+    name: 'rating',
+    value: 'not found',
+  })
+
+t.create('Rating - EssentialsX (id 9089)')
+  .get('/rating/9089.json')
+  .expectJSONTypes(
+    Joi.object().keys({
+      name: 'rating',
+      value: withRegex(/^(\d*\.\d+)(\/5 \()(\d+)(\))$/),
+    })
+  )
+
+t.create('Rating - Invalid Resource (id 1)')
+  .get('/rating/1.json')
+  .expectJSON({
+    name: 'rating',
+    value: 'not found',
+  })
diff --git a/services/spiget/spiget-tested-versions.service.js b/services/spiget/spiget-tested-versions.service.js
new file mode 100644
index 0000000000000000000000000000000000000000..c647d4bf87677bbe260e1beadf8b16e716532a00
--- /dev/null
+++ b/services/spiget/spiget-tested-versions.service.js
@@ -0,0 +1,57 @@
+'use strict'
+
+const { BaseSpigetService, documentation, keywords } = require('./spiget-base')
+
+module.exports = class SpigetTestedVersions extends BaseSpigetService {
+  static get route() {
+    return {
+      base: 'spiget/tested-versions',
+      pattern: ':resourceid',
+    }
+  }
+
+  static get defaultBadgeData() {
+    return {
+      label: 'tested versions',
+      color: 'blue',
+    }
+  }
+
+  async handle({ resourceid }) {
+    const { testedVersions } = await this.fetch({ resourceid })
+    const { versions } = this.transform({ testedVersions })
+    return this.constructor.render({ versions })
+  }
+
+  transform({ testedVersions }) {
+    const earliest = testedVersions[0]
+    const latest = testedVersions.slice(-1)[0]
+    let versions = ''
+    if (earliest === latest) {
+      versions = earliest
+    } else {
+      versions = `${earliest}-${latest}`
+    }
+    return { versions }
+  }
+
+  static render({ versions }) {
+    return {
+      message: versions,
+    }
+  }
+
+  static get examples() {
+    return [
+      {
+        title: 'Spiget Tested Versions',
+        namedParams: {
+          resourceid: '9089',
+        },
+        staticPreview: this.render({ versions: '1.7-1.13' }),
+        documentation,
+        keywords,
+      },
+    ]
+  }
+}
diff --git a/services/spiget/spiget-tested-versions.tester.js b/services/spiget/spiget-tested-versions.tester.js
new file mode 100644
index 0000000000000000000000000000000000000000..696bc2d53fc010963e82dde5265ea00d925140f6
--- /dev/null
+++ b/services/spiget/spiget-tested-versions.tester.js
@@ -0,0 +1,71 @@
+'use strict'
+
+const Joi = require('joi')
+
+const t = (module.exports = require('../create-service-tester')())
+
+const { withRegex } = require('../test-validators')
+
+const multipleVersions = withRegex(/^([+]?\d*\.\d+)(-)([+]?\d*\.\d+)$/)
+
+t.create('EssentialsX - multiple versions supported - (id 9089)')
+  .get('/9089.json')
+  .expectJSONTypes(
+    Joi.object().keys({
+      name: 'tested versions',
+      value: multipleVersions,
+    })
+  )
+
+t.create('Invalid Resource (id 1)')
+  .get('/1.json')
+  .expectJSON({
+    name: 'tested versions',
+    value: 'not found',
+  })
+
+t.create('Nock - single version supported')
+  .get('/1.json')
+  .intercept(nock =>
+    nock('https://api.spiget.org/v2/resources/')
+      .get('/1')
+      .reply(200, {
+        downloads: 1,
+        file: {
+          size: 1,
+          sizeUnit: '1',
+        },
+        testedVersions: ['1.13'],
+        rating: {
+          count: 1,
+          average: 1,
+        },
+      })
+  )
+  .expectJSON({
+    name: 'tested versions',
+    value: '1.13',
+  })
+
+t.create('Nock - multiple versions supported')
+  .get('/1.json')
+  .intercept(nock =>
+    nock('https://api.spiget.org/v2/resources/')
+      .get('/1')
+      .reply(200, {
+        downloads: 1,
+        file: {
+          size: 1,
+          sizeUnit: '1',
+        },
+        testedVersions: ['1.10', '1.11', '1.12', '1.13'],
+        rating: {
+          count: 1,
+          average: 1,
+        },
+      })
+  )
+  .expectJSON({
+    name: 'tested versions',
+    value: '1.10-1.13',
+  })