diff --git a/services/wikiapiary/wikiapiary-installs.service.js b/services/wikiapiary/wikiapiary-installs.service.js
new file mode 100644
index 0000000000000..3aa11e110da2a
--- /dev/null
+++ b/services/wikiapiary/wikiapiary-installs.service.js
@@ -0,0 +1,110 @@
+'use strict'
+
+const Joi = require('joi')
+const { metric } = require('../text-formatters')
+const { BaseJsonService, NotFound } = require('..')
+
+const documentation = `
+
+ The name of an extension is case-sensitive excluding the first character.
+
+
+ For example, in the case of ParserFunctions
, the following are
+ valid:
+
+ ParserFunctions
+ parserFunctions
+
+
+ However, the following are invalid:
+
+ parserfunctions
+ Parserfunctions
+ pARSERfUNCTIONS
+
+
+`
+
+const schema = Joi.object({
+ query: Joi.object({
+ results: Joi.alternatives([
+ Joi.object()
+ .required()
+ .pattern(/^\w+:.+$/, {
+ printouts: Joi.object({
+ 'Has website count': Joi.array()
+ .required()
+ .items(Joi.number().required()),
+ }).required(),
+ }),
+ Joi.array().required(),
+ ]).required(),
+ }).required(),
+}).required()
+
+/**
+ * This badge displays the total installations of a MediaWiki extensions, skins,
+ * etc via Wikiapiary.
+ *
+ * {@link https://www.mediawiki.org/wiki/Manual:Extensions MediaWiki Extensions Manual}
+ */
+module.exports = class WikiapiaryInstalls extends BaseJsonService {
+ static category = 'downloads'
+ static route = {
+ base: 'wikiapiary',
+ pattern: ':variant(extension|skin|farm|generator|host)/installs/:name',
+ }
+
+ static examples = [
+ {
+ title: 'Wikiapiary installs',
+ namedParams: { variant: 'extension', name: 'ParserFunctions' },
+ staticPreview: this.render({ usage: 11170 }),
+ documentation,
+ keywords: ['mediawiki'],
+ },
+ ]
+
+ static defaultBadgeData = { label: 'installs', color: 'informational' }
+
+ static render({ usage }) {
+ return { message: metric(usage) }
+ }
+
+ static validate({ results }) {
+ if (Array.isArray(results))
+ throw new NotFound({ prettyMessage: 'not found' })
+ }
+
+ async fetch({ variant, name }) {
+ return this._requestJson({
+ schema,
+ url: `https://wikiapiary.com/w/api.php`,
+ options: {
+ qs: {
+ action: 'ask',
+ query: `[[${variant}:${name}]]|?Has_website_count`,
+ format: 'json',
+ },
+ },
+ })
+ }
+
+ async handle({ variant, name }) {
+ const response = await this.fetch({ variant, name })
+ const { results } = response.query
+
+ this.constructor.validate({ results })
+
+ const keyLowerCase = `${variant}:${name.toLowerCase()}`
+ const resultKey = Object.keys(results).find(
+ key => keyLowerCase === key.toLowerCase()
+ )
+
+ if (resultKey === undefined)
+ throw new NotFound({ prettyMessage: 'not found' })
+
+ const [usage] = results[resultKey].printouts['Has website count']
+ return this.constructor.render({ usage })
+ }
+}
diff --git a/services/wikiapiary/wikiapiary-installs.tester.js b/services/wikiapiary/wikiapiary-installs.tester.js
new file mode 100644
index 0000000000000..fee38c87645f1
--- /dev/null
+++ b/services/wikiapiary/wikiapiary-installs.tester.js
@@ -0,0 +1,44 @@
+'use strict'
+
+const t = (module.exports = require('../tester').createServiceTester())
+const { isMetric } = require('../test-validators')
+
+t.create('Extension')
+ .get('/extension/installs/ParserFunctions.json')
+ .expectBadge({ label: 'installs', message: isMetric })
+
+t.create('Skins')
+ .get('/skin/installs/Vector.json')
+ .expectBadge({ label: 'installs', message: isMetric })
+
+t.create('Extension Not Found')
+ .get('/extension/installs/FakeExtensionThatDoesNotExist.json')
+ .expectBadge({ label: 'installs', message: 'not found' })
+
+t.create('Name Lowercase')
+ .get('/extension/installs/parserfunctions.json')
+ .expectBadge({ label: 'installs', message: 'not found' })
+
+t.create('Name Title Case')
+ .get('/extension/installs/parserFunctions.json')
+ .expectBadge({ label: 'installs', message: isMetric })
+
+t.create('Malformed API Response')
+ .get('/extension/installs/ParserFunctions.json')
+ .intercept(nock =>
+ nock('https://wikiapiary.com')
+ .get('/w/api.php')
+ .query({
+ action: 'ask',
+ query: '[[extension:ParserFunctions]]|?Has_website_count',
+ format: 'json',
+ })
+ .reply(200, {
+ query: {
+ results: {
+ 'Extension:Malformed': { printouts: { 'Has website count': [0] } },
+ },
+ },
+ })
+ )
+ .expectBadge({ label: 'installs', message: 'not found' })