diff --git a/lib/plausible_web/plugs/tracker.ex b/lib/plausible_web/plugs/tracker.ex
index 04e2b69f7742d80777144271483b231cd73b526f..b02d5976d2b0ccb2f60cebb976c9895281d5accd 100644
--- a/lib/plausible_web/plugs/tracker.ex
+++ b/lib/plausible_web/plugs/tracker.ex
@@ -2,30 +2,41 @@ defmodule PlausibleWeb.Tracker do
   import Plug.Conn
   use Agent
 
-  @templates [
-    "plausible.js",
-    "plausible.exclusions.js",
-    "plausible.hash.js",
-    "plausible.outbound-links.js",
-    "plausible.hash.exclusions.js",
-    "plausible.hash.outbound-links.js",
-    "plausible.hash.exclusions.outbound-links.js",
-    "plausible.exclusions.outbound-links.js",
-    "p.js"
-  ]
-  @aliases %{
-    "plausible.js" => ["analytics.js"],
-    "plausible.hash.outbound-links.js" => ["plausible.outbound-links.hash.js"],
-    "plausible.hash.exclusions.js" => ["plausible.exclusions.hash.js"],
-    "plausible.exclusions.outbound-links.js" => ["plausible.outbound-links.exclusions.js"],
-    "plausible.hash.exclusions.outbound-links.js" => [
-      "plausible.exclusions.hash.outbound-links.js",
-      "plausible.exclusions.outbound-links.hash.js",
-      "plausible.hash.outbound-links.exclusions.js",
-      "plausible.outbound-links.hash.exclusions.js",
-      "plausible.outbound-links.exclusions.hash.js"
-    ]
-  }
+  base_variants = ["hash", "outbound-links", "exclusions"]
+
+  # Generates Power Set of all variants
+  variants =
+    1..Enum.count(base_variants)
+    |> Enum.map(fn x ->
+      Combination.combine(base_variants, x)
+      |> Enum.map(fn y -> Enum.sort(y) |> Enum.join(".") end)
+    end)
+    |> List.flatten()
+
+  # Formats power set into filenames
+  files_available =
+    ["plausible.js", "p.js"] ++ Enum.map(variants, fn v -> "plausible.#{v}.js" end)
+
+  # Computes permutations for every power set elements, formats them as alias filenames
+  aliases_available =
+    Enum.map(variants, fn x ->
+      variants =
+        String.split(x, ".")
+        |> Combination.permutate()
+        |> Enum.map(fn p -> Enum.join(p, ".") end)
+        |> Enum.filter(fn permutation -> permutation != x end)
+        |> Enum.map(fn v -> "plausible.#{v}.js" end)
+
+      if Enum.count(variants) > 0 do
+        {"plausible.#{x}.js", variants}
+      end
+    end)
+    |> Enum.reject(fn x -> x == nil end)
+    |> Enum.into(%{})
+    |> Map.put("plausible.js", ["analytics.js"])
+
+  @templates files_available
+  @aliases aliases_available
 
   #  1 hour
   @max_age 3600
diff --git a/mix.exs b/mix.exs
index f6e3b9aef38fc9bad545bb80601dfcbfe7bbd2fb..932ad9369c7117c6cdae89da272afc3aa22c94d1 100644
--- a/mix.exs
+++ b/mix.exs
@@ -51,6 +51,7 @@ defmodule Plausible.MixProject do
   defp deps do
     [
       {:bcrypt_elixir, "~> 2.0"},
+      {:combination, "~> 0.0.3"},
       {:cors_plug, "~> 1.5"},
       {:ecto_sql, "~> 3.0"},
       {:elixir_uuid, "~> 1.2"},
diff --git a/mix.lock b/mix.lock
index 2abdce41cb6d91cea0c6e5d3941d0c31f558ba8f..73174ccdd931967889293d702d3f05ba56109d21 100644
--- a/mix.lock
+++ b/mix.lock
@@ -14,6 +14,7 @@
   "certifi": {:hex, :certifi, "2.6.1", "dbab8e5e155a0763eea978c913ca280a6b544bfa115633fa20249c3d396d9493", [:rebar3], [], "hexpm", "524c97b4991b3849dd5c17a631223896272c6b0af446778ba4675a1dff53bb7e"},
   "clickhouse_ecto": {:git, "https://github.com/plausible/clickhouse_ecto.git", "1969f14ecef7c357b2bd8bdc3e566234269de58c", []},
   "clickhousex": {:git, "https://github.com/plausible/clickhousex", "0832dd4b1af1f0eba1d1018c231bf0d8d281f031", []},
+  "combination": {:hex, :combination, "0.0.3", "746aedca63d833293ec6e835aa1f34974868829b1486b1e1cb0685f0b2ae1f41", [:mix], [], "hexpm", "72b099f463df42ef7dc6371d250c7070b57b6c5902853f69deb894f79eda18ca"},
   "combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"},
   "comeonin": {:hex, :comeonin, "5.3.2", "5c2f893d05c56ae3f5e24c1b983c2d5dfb88c6d979c9287a76a7feb1e1d8d646", [:mix], [], "hexpm", "d0993402844c49539aeadb3fe46a3c9bd190f1ecf86b6f9ebd71957534c95f04"},
   "connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], [], "hexpm", "4a0850c9be22a43af9920a71ab17c051f5f7d45c209e40269a1938832510e4d9"},
diff --git a/priv/tracker/js/plausible.exclusions.hash.js b/priv/tracker/js/plausible.exclusions.hash.js
new file mode 100644
index 0000000000000000000000000000000000000000..efa52f734c97b5b0a35ba081e25ef3549fa8f4b8
--- /dev/null
+++ b/priv/tracker/js/plausible.exclusions.hash.js
@@ -0,0 +1 @@
+!function(i,o){"use strict";var e,l=i.location,s=i.document,t=s.querySelector('[src*="'+o+'"]'),c=t&&t.getAttribute("data-domain"),p=i.localStorage.plausible_ignore,u=t&&t.getAttribute("data-exclude").split(",");function g(e){console.warn("Ignoring Event: "+e)}function a(e,t){if(/^localhost$|^127(?:\.[0-9]+){0,2}\.[0-9]+$|^(?:0*\:)*?:?0*1$/.test(l.hostname)||"file:"===l.protocol)return g("localhost");if(!(i.phantom||i._phantom||i.__nightmare||i.navigator.webdriver)){if("true"==p)return g("localStorage flag");if(u)for(var a=0;a<u.length;a++)if("pageview"==e&&l.pathname.match(new RegExp("^"+u[a].trim().replace(/\*\*/g,".*").replace(/[^\.]\*/g,"[^\\s/]*")+"/?$")))return g("exclusion rule");var n={};n.n=e,n.u=l.href,n.d=c,n.r=s.referrer||null,n.w=i.innerWidth,t&&t.meta&&(n.m=JSON.stringify(t.meta)),t&&t.props&&(n.p=JSON.stringify(t.props)),n.h=1;var r=new XMLHttpRequest;r.open("POST",o+"/api/event",!0),r.setRequestHeader("Content-Type","text/plain"),r.send(JSON.stringify(n)),r.onreadystatechange=function(){4==r.readyState&&t&&t.callback&&t.callback()}}}function n(){e=l.pathname,a("pageview")}try{i.addEventListener("hashchange",n);var r=i.plausible&&i.plausible.q||[];i.plausible=a;for(var h=0;h<r.length;h++)a.apply(this,r[h]);"prerender"===s.visibilityState?s.addEventListener("visibilitychange",function(){e||"visible"!==s.visibilityState||n()}):n()}catch(e){console.error(e),(new Image).src=o+"/api/error?message="+encodeURIComponent(e.message)}}(window,"<%= base_url %>");
\ No newline at end of file
diff --git a/priv/tracker/js/plausible.exclusions.hash.outbound-links.js b/priv/tracker/js/plausible.exclusions.hash.outbound-links.js
new file mode 100644
index 0000000000000000000000000000000000000000..3f854ae4a34a903e5743e871fd1673072d5b5d3a
--- /dev/null
+++ b/priv/tracker/js/plausible.exclusions.hash.outbound-links.js
@@ -0,0 +1 @@
+!function(i,o){"use strict";var e,l=i.location,s=i.document,t=s.querySelector('[src*="'+o+'"]'),c=t&&t.getAttribute("data-domain"),p=i.localStorage.plausible_ignore,u=t&&t.getAttribute("data-exclude").split(",");function h(e){console.warn("Ignoring Event: "+e)}function a(e,t){if(/^localhost$|^127(?:\.[0-9]+){0,2}\.[0-9]+$|^(?:0*\:)*?:?0*1$/.test(l.hostname)||"file:"===l.protocol)return h("localhost");if(!(i.phantom||i._phantom||i.__nightmare||i.navigator.webdriver)){if("true"==p)return h("localStorage flag");if(u)for(var a=0;a<u.length;a++)if("pageview"==e&&l.pathname.match(new RegExp("^"+u[a].trim().replace(/\*\*/g,".*").replace(/[^\.]\*/g,"[^\\s/]*")+"/?$")))return h("exclusion rule");var r={};r.n=e,r.u=l.href,r.d=c,r.r=s.referrer||null,r.w=i.innerWidth,t&&t.meta&&(r.m=JSON.stringify(t.meta)),t&&t.props&&(r.p=JSON.stringify(t.props)),r.h=1;var n=new XMLHttpRequest;n.open("POST",o+"/api/event",!0),n.setRequestHeader("Content-Type","text/plain"),n.send(JSON.stringify(r)),n.onreadystatechange=function(){4==n.readyState&&t&&t.callback&&t.callback()}}}function r(){e=l.pathname,a("pageview")}function n(e){for(var t=e.target,a="auxclick"==e.type&&2==e.which,r="click"==e.type;t&&(void 0===t.tagName||"a"!=t.tagName.toLowerCase()||!t.href);)t=t.parentNode;t&&t.href&&t.host&&t.host!==l.host&&((a||r)&&plausible("Outbound Link: Click",{props:{url:t.href}}),t.target&&!t.target.match(/^_(self|parent|top)$/i)||e.ctrlKey||e.metaKey||e.shiftKey||!r||(setTimeout(function(){l.href=t.href},150),e.preventDefault()))}try{i.addEventListener("hashchange",r),s.addEventListener("click",n),s.addEventListener("auxclick",n);var f=i.plausible&&i.plausible.q||[];i.plausible=a;for(var g=0;g<f.length;g++)a.apply(this,f[g]);"prerender"===s.visibilityState?s.addEventListener("visibilitychange",function(){e||"visible"!==s.visibilityState||r()}):r()}catch(e){console.error(e),(new Image).src=o+"/api/error?message="+encodeURIComponent(e.message)}}(window,"<%= base_url %>");
\ No newline at end of file
diff --git a/tracker/compile.js b/tracker/compile.js
index e33ba321d862eaee8d333a2684ce5cb2604fa5b8..7f67804f4b12b89d102a76fbb43040f2f7623af2 100644
--- a/tracker/compile.js
+++ b/tracker/compile.js
@@ -2,6 +2,7 @@ const uglify = require("uglify-js");
 const fs = require('fs')
 const path = require('path')
 const Handlebars = require("handlebars");
+const g = require("generatorics");
 
 function relPath(segment) {
   return path.join(__dirname, segment)
@@ -15,13 +16,14 @@ function compilefile(input, output, templateVars = {}) {
   fs.writeFileSync(output, result.code)
 }
 
+const base_variants = ["hash", "outbound-links", "exclusions"]
+const variants = [...g.clone.powerSet(base_variants)].filter(a => a.length > 0).map(a => a.sort());
+
 compilefile(relPath('src/plausible.js'), relPath('../priv/tracker/js/plausible.js'))
-compilefile(relPath('src/plausible.js'), relPath('../priv/tracker/js/plausible.exclusions.js'), {exclusionMode: true})
-compilefile(relPath('src/plausible.js'), relPath('../priv/tracker/js/plausible.hash.js'), {hashMode: true})
-compilefile(relPath('src/plausible.js'), relPath('../priv/tracker/js/plausible.hash.exclusions.js'), {hashMode: true, exclusionMode: true})
-compilefile(relPath('src/plausible.js'), relPath('../priv/tracker/js/plausible.outbound-links.js'), {outboundLinks: true})
-compilefile(relPath('src/plausible.js'), relPath('../priv/tracker/js/plausible.exclusions.outbound-links.js'), {outboundLinks: true, exclusionMode: true})
-compilefile(relPath('src/plausible.js'), relPath('../priv/tracker/js/plausible.hash.outbound-links.js'), {hashMode: true, outboundLinks: true})
-compilefile(relPath('src/plausible.js'), relPath('../priv/tracker/js/plausible.hash.exclusions.outbound-links.js'), {hashMode: true, outboundLinks: true, exclusionMode: true})
+compilefile(relPath('src/plausible.js'), relPath('../priv/tracker/js/analytics.js'))
 compilefile(relPath('src/p.js'), relPath('../priv/tracker/js/p.js'))
-fs.copyFileSync(relPath('../priv/tracker/js/plausible.js'), relPath('../priv/tracker/js/analytics.js'))
+
+variants.map(variant => {
+  const options = variant.map(variant => variant.replace('-', '_')).reduce((acc, curr) => (acc[curr] = true, acc), {})
+  compilefile(relPath('src/plausible.js'), relPath(`../priv/tracker/js/plausible.${variant.join('.')}.js`), options)
+})
diff --git a/tracker/package-lock.json b/tracker/package-lock.json
index cb50639fb8af402e63bd4cc7c65f435bbbf7ff11..f75c6ff886caf3bc2930ee947085d5fac368f511 100644
--- a/tracker/package-lock.json
+++ b/tracker/package-lock.json
@@ -6,6 +6,7 @@
     "": {
       "license": "MIT",
       "dependencies": {
+        "generatorics": "^1.1.0",
         "handlebars": "^4.7.7",
         "uglify-js": "^3.9.4"
       }
@@ -15,6 +16,14 @@
       "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
       "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="
     },
+    "node_modules/generatorics": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/generatorics/-/generatorics-1.1.0.tgz",
+      "integrity": "sha1-aVBgu42IuQmzAXGlyz1CR2hmETg=",
+      "engines": {
+        "node": ">=6.0.0"
+      }
+    },
     "node_modules/handlebars": {
       "version": "4.7.7",
       "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz",
@@ -79,6 +88,11 @@
       "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
       "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="
     },
+    "generatorics": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/generatorics/-/generatorics-1.1.0.tgz",
+      "integrity": "sha1-aVBgu42IuQmzAXGlyz1CR2hmETg="
+    },
     "handlebars": {
       "version": "4.7.7",
       "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz",
diff --git a/tracker/package.json b/tracker/package.json
index 46c3a9c601631d7667a04d2bea3d114413a809a9..88ce3cb434d036f26e394db91217496ddd0e6c82 100644
--- a/tracker/package.json
+++ b/tracker/package.json
@@ -4,6 +4,7 @@
   },
   "license": "MIT",
   "dependencies": {
+    "generatorics": "^1.1.0",
     "handlebars": "^4.7.7",
     "uglify-js": "^3.9.4"
   }
diff --git a/tracker/src/plausible.js b/tracker/src/plausible.js
index aff85ede580c674d98f6ecefec370e96e3017b39..ffb6fa64430965b9f00fb45c7d7b1850a714a860 100644
--- a/tracker/src/plausible.js
+++ b/tracker/src/plausible.js
@@ -7,7 +7,7 @@
   var scriptEl = document.querySelector('[src*="' + plausibleHost +'"]')
   var domain = scriptEl && scriptEl.getAttribute('data-domain')
   var plausible_ignore = window.localStorage.plausible_ignore;
-  {{#if exclusionMode}}
+  {{#if exclusions}}
   var excludedPaths = scriptEl && scriptEl.getAttribute('data-exclude').split(',');
   {{/if}}
   var lastPage;
@@ -20,7 +20,7 @@
     if (/^localhost$|^127(?:\.[0-9]+){0,2}\.[0-9]+$|^(?:0*\:)*?:?0*1$/.test(location.hostname) || location.protocol === 'file:') return warn('localhost');
     if (window.phantom || window._phantom || window.__nightmare || window.navigator.webdriver || window.Cypress) return;
     if (plausible_ignore=="true") return warn('localStorage flag')
-    {{#if exclusionMode}}
+    {{#if exclusions}}
     if (excludedPaths)
       for (var i = 0; i < excludedPaths.length; i++)
         if (eventName == "pageview" && location.pathname.match(new RegExp('^' + excludedPaths[i].trim().replace(/\*\*/g, '.*').replace(/([^\.])\*/g, '$1[^\\s\/]*') + '\/?$')))
@@ -39,7 +39,7 @@
     if (options && options.props) {
       payload.p = JSON.stringify(options.props)
     }
-    {{#if hashMode}}
+    {{#if hash}}
     payload.h = 1
     {{/if}}
 
@@ -57,7 +57,7 @@
   }
 
   function page() {
-    {{#unless hashMode}}
+    {{#unless hash}}
     if (lastPage === location.pathname) return;
     {{/unless}}
     lastPage = location.pathname
@@ -70,7 +70,7 @@
     }
   }
 
-  {{#if outboundLinks}}
+  {{#if outbound_links}}
   function handleOutbound(event) {
     var link = event.target;
     var middle = event.type == "auxclick" && event.which == 2;
@@ -102,7 +102,7 @@
   {{/if}}
 
   try {
-    {{#if hashMode}}
+    {{#if hash}}
     window.addEventListener('hashchange', page)
     {{else}}
     var his = window.history
@@ -116,7 +116,7 @@
     }
 
     {{/if}}
-    {{#if outboundLinks}}
+    {{#if outbound_links}}
     registerOutboundLinkEvents()
     {{/if}}