diff --git a/.github/workflows/release-develop.yml b/.github/workflows/release-develop.yml
index ce41fcc3e6..cf0d6f848c 100644
--- a/.github/workflows/release-develop.yml
+++ b/.github/workflows/release-develop.yml
@@ -7,6 +7,7 @@ on:
 
 env:
   POSTHOG_TOKEN: ${{ secrets.POSTHOG_TOKEN }}
+  INTERCOM_TOKEN: ${{ secrets.INTERCOM_TOKEN }}
   POSTHOG_URL: ${{ secrets.POSTHOG_URL }}
   SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
       
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index aaee3923ef..7b38a70eb7 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -4,9 +4,16 @@ on:
  push:
     branches:
       - master
+ workflow_dispatch:
+    inputs:
+      release_self_host:
+        description: 'Release to self hosters? (Y/N)'     
+        required: true
+        default: 'N'
 
 env:
   POSTHOG_TOKEN: ${{ secrets.POSTHOG_TOKEN }}
+  INTERCOM_TOKEN: ${{ secrets.INTERCOM_TOKEN }}
   POSTHOG_URL: ${{ secrets.POSTHOG_URL }}
   SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
 
@@ -47,7 +54,19 @@ jobs:
         uses: "WyriHaximus/github-action-get-previous-tag@v1"
 
       - name: Build/release Docker images
-        run: |
+        if: ${{ github.event.inputs.release_self_host != 'Y' }}
+        run: | 
+          docker login -u $DOCKER_USER -p $DOCKER_PASSWORD
+          yarn build
+          yarn build:docker
+        env:
+          DOCKER_USER: ${{ secrets.DOCKER_USERNAME }}
+          DOCKER_PASSWORD: ${{ secrets.DOCKER_API_KEY }}
+          BUDIBASE_RELEASE_VERSION: ${{ steps.previoustag.outputs.tag }}
+
+      - name: Build/release Docker images (Self Host)
+        if: ${{ github.event.inputs.release_self_host == 'Y' }}
+        run: | 
           docker login -u $DOCKER_USER -p $DOCKER_PASSWORD
           yarn build
           yarn build:docker
diff --git a/lerna.json b/lerna.json
index 19b2d150eb..e2e4d027b5 100644
--- a/lerna.json
+++ b/lerna.json
@@ -1,5 +1,5 @@
 {
-  "version": "0.9.125-alpha.19",
+  "version": "0.9.140-alpha.5",
   "npmClient": "yarn",
   "packages": [
     "packages/*"
diff --git a/package.json b/package.json
index f87c3715aa..3df577ca58 100644
--- a/package.json
+++ b/package.json
@@ -42,7 +42,8 @@
     "lint:fix": "yarn run lint:fix:ts && yarn run lint:fix:prettier && yarn run lint:fix:eslint",
     "test:e2e": "lerna run cy:test",
     "test:e2e:ci": "lerna run cy:ci",
-    "build:docker": "lerna run build:docker && cd hosting/scripts/linux/ && ./release-to-docker-hub.sh $BUDIBASE_RELEASE_VERSION release && cd -",
+    "build:docker": "lerna run build:docker && cd hosting/scripts/linux/ && ./release-to-docker-hub.sh $BUDIBASE_RELEASE_VERSION && cd -",
+    "build:docker:production": "lerna run build:docker && cd hosting/scripts/linux/ && ./release-to-docker-hub.sh $BUDIBASE_RELEASE_VERSION release && cd -",
     "build:docker:develop": "node scripts/pinVersions && lerna run build:docker && cd hosting/scripts/linux/ && ./release-to-docker-hub.sh develop && cd -",
     "release:helm": "./scripts/release_helm_chart.sh",
     "multi:enable": "lerna run multi:enable",
diff --git a/packages/auth/package.json b/packages/auth/package.json
index df23c89661..8c1f38d64c 100644
--- a/packages/auth/package.json
+++ b/packages/auth/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@budibase/auth",
-  "version": "0.9.125-alpha.19",
+  "version": "0.9.140-alpha.5",
   "description": "Authentication middlewares for budibase builder and apps",
   "main": "src/index.js",
   "author": "Budibase",
diff --git a/packages/auth/src/environment.js b/packages/auth/src/environment.js
index 51f24c2c4a..bae5c65a1b 100644
--- a/packages/auth/src/environment.js
+++ b/packages/auth/src/environment.js
@@ -16,6 +16,7 @@ module.exports = {
   REDIS_PASSWORD: process.env.REDIS_PASSWORD,
   MINIO_ACCESS_KEY: process.env.MINIO_ACCESS_KEY,
   MINIO_SECRET_KEY: process.env.MINIO_SECRET_KEY,
+  AWS_REGION: process.env.AWS_REGION,
   MINIO_URL: process.env.MINIO_URL,
   INTERNAL_API_KEY: process.env.INTERNAL_API_KEY,
   MULTI_TENANCY: process.env.MULTI_TENANCY,
diff --git a/packages/auth/src/index.js b/packages/auth/src/index.js
index 569456ea10..4aa2c8ab96 100644
--- a/packages/auth/src/index.js
+++ b/packages/auth/src/index.js
@@ -12,6 +12,7 @@ const {
   auditLog,
   tenancy,
   appTenancy,
+  authError,
 } = require("./middleware")
 const { setDB } = require("./db")
 const userCache = require("./cache/user")
@@ -60,6 +61,7 @@ module.exports = {
     buildTenancyMiddleware: tenancy,
     buildAppTenancyMiddleware: appTenancy,
     auditLog,
+    authError,
   },
   cache: {
     user: userCache,
diff --git a/packages/auth/src/middleware/index.js b/packages/auth/src/middleware/index.js
index 059f20af8b..cf8676a2bc 100644
--- a/packages/auth/src/middleware/index.js
+++ b/packages/auth/src/middleware/index.js
@@ -2,6 +2,7 @@ const jwt = require("./passport/jwt")
 const local = require("./passport/local")
 const google = require("./passport/google")
 const oidc = require("./passport/oidc")
+const { authError } = require("./passport/utils")
 const authenticated = require("./authenticated")
 const auditLog = require("./auditLog")
 const tenancy = require("./tenancy")
@@ -16,4 +17,5 @@ module.exports = {
   auditLog,
   tenancy,
   appTenancy,
+  authError,
 }
diff --git a/packages/auth/src/middleware/passport/google.js b/packages/auth/src/middleware/passport/google.js
index 07d6816c0b..cb93844c31 100644
--- a/packages/auth/src/middleware/passport/google.js
+++ b/packages/auth/src/middleware/passport/google.js
@@ -27,7 +27,11 @@ async function authenticate(accessToken, refreshToken, profile, done) {
  * from couchDB rather than environment variables, using this factory is necessary for dynamically configuring passport.
  * @returns Dynamically configured Passport Google Strategy
  */
-exports.strategyFactory = async function (config, callbackUrl) {
+exports.strategyFactory = async function (
+  config,
+  callbackUrl,
+  verify = authenticate
+) {
   try {
     const { clientID, clientSecret } = config
 
@@ -43,7 +47,7 @@ exports.strategyFactory = async function (config, callbackUrl) {
         clientSecret: config.clientSecret,
         callbackURL: callbackUrl,
       },
-      authenticate
+      verify
     )
   } catch (err) {
     console.error(err)
diff --git a/packages/auth/src/middleware/passport/tests/third-party-common.spec.js b/packages/auth/src/middleware/passport/tests/third-party-common.spec.js
index ff38a01fbb..1ace65ba40 100644
--- a/packages/auth/src/middleware/passport/tests/third-party-common.spec.js
+++ b/packages/auth/src/middleware/passport/tests/third-party-common.spec.js
@@ -104,7 +104,7 @@ describe("third party common", () => {
           _id: id,
           email: email,
         }
-        const response = await db.post(dbUser)
+        const response = await db.put(dbUser)
         dbUser._rev = response.rev
       }
 
diff --git a/packages/auth/src/middleware/passport/third-party-common.js b/packages/auth/src/middleware/passport/third-party-common.js
index 7c03944232..c25aa3e0b0 100644
--- a/packages/auth/src/middleware/passport/third-party-common.js
+++ b/packages/auth/src/middleware/passport/third-party-common.js
@@ -71,7 +71,7 @@ exports.authenticateThirdParty = async function (
   dbUser = await syncUser(dbUser, thirdPartyUser)
 
   // create or sync the user
-  const response = await db.post(dbUser)
+  const response = await db.put(dbUser)
   dbUser._rev = response.rev
 
   // authenticate
diff --git a/packages/auth/src/objectStore/index.js b/packages/auth/src/objectStore/index.js
index 81bdd06b62..9f271ad80e 100644
--- a/packages/auth/src/objectStore/index.js
+++ b/packages/auth/src/objectStore/index.js
@@ -73,6 +73,7 @@ exports.ObjectStore = bucket => {
   AWS.config.update({
     accessKeyId: env.MINIO_ACCESS_KEY,
     secretAccessKey: env.MINIO_SECRET_KEY,
+    region: env.AWS_REGION,
   })
   const config = {
     s3ForcePathStyle: true,
diff --git a/packages/auth/src/security/sessions.js b/packages/auth/src/security/sessions.js
index 328f74c794..83ca9d9bcd 100644
--- a/packages/auth/src/security/sessions.js
+++ b/packages/auth/src/security/sessions.js
@@ -30,6 +30,10 @@ exports.invalidateSessions = async (userId, sessionId = null) => {
     sessions.push({ key: makeSessionID(userId, sessionId) })
   } else {
     sessions = await getSessionsForUser(userId)
+    sessions.forEach(
+      session =>
+        (session.key = makeSessionID(session.userId, session.sessionId))
+    )
   }
   const client = await redis.getSessionClient()
   const promises = []
diff --git a/packages/auth/yarn.lock b/packages/auth/yarn.lock
index b6be8ad1e8..35f892669a 100644
--- a/packages/auth/yarn.lock
+++ b/packages/auth/yarn.lock
@@ -4470,9 +4470,9 @@ tmp@^0.0.33:
     os-tmpdir "~1.0.2"
 
 tmpl@1.0.x:
-  version "1.0.4"
-  resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.4.tgz#23640dd7b42d00433911140820e5cf440e521dd1"
-  integrity sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE=
+  version "1.0.5"
+  resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc"
+  integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==
 
 to-fast-properties@^2.0.0:
   version "2.0.0"
diff --git a/packages/bbui/package.json b/packages/bbui/package.json
index 41d8633fad..9ddedfd28f 100644
--- a/packages/bbui/package.json
+++ b/packages/bbui/package.json
@@ -1,7 +1,7 @@
 {
   "name": "@budibase/bbui",
   "description": "A UI solution used in the different Budibase projects.",
-  "version": "0.9.125-alpha.19",
+  "version": "0.9.140-alpha.5",
   "license": "AGPL-3.0",
   "svelte": "src/index.js",
   "module": "dist/bbui.es.js",
diff --git a/packages/builder/package.json b/packages/builder/package.json
index 9a06cf4eef..01dfc08f2c 100644
--- a/packages/builder/package.json
+++ b/packages/builder/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@budibase/builder",
-  "version": "0.9.125-alpha.19",
+  "version": "0.9.140-alpha.5",
   "license": "AGPL-3.0",
   "private": true,
   "scripts": {
@@ -65,10 +65,10 @@
     }
   },
   "dependencies": {
-    "@budibase/bbui": "^0.9.125-alpha.19",
-    "@budibase/client": "^0.9.125-alpha.19",
+    "@budibase/bbui": "^0.9.140-alpha.5",
+    "@budibase/client": "^0.9.140-alpha.5",
     "@budibase/colorpicker": "1.1.2",
-    "@budibase/string-templates": "^0.9.125-alpha.19",
+    "@budibase/string-templates": "^0.9.140-alpha.5",
     "@sentry/browser": "5.19.1",
     "@spectrum-css/page": "^3.0.1",
     "@spectrum-css/vars": "^3.0.1",
diff --git a/packages/builder/src/App.svelte b/packages/builder/src/App.svelte
index 0624690b27..60051ea043 100644
--- a/packages/builder/src/App.svelte
+++ b/packages/builder/src/App.svelte
@@ -1,16 +1,10 @@
 <script>
-  import { onMount } from "svelte"
   import { Router } from "@roxi/routify"
   import { routes } from "../.routify/routes"
-  import { initialise } from "builderStore"
   import { NotificationDisplay } from "@budibase/bbui"
   import { parse, stringify } from "qs"
   import HelpIcon from "components/common/HelpIcon.svelte"
 
-  onMount(async () => {
-    await initialise()
-  })
-
   const queryHandler = { parse, stringify }
 </script>
 
diff --git a/packages/builder/src/analytics.js b/packages/builder/src/analytics.js
deleted file mode 100644
index 5b130a8e6b..0000000000
--- a/packages/builder/src/analytics.js
+++ /dev/null
@@ -1,139 +0,0 @@
-import * as Sentry from "@sentry/browser"
-import posthog from "posthog-js"
-import api from "builderStore/api"
-
-let analyticsEnabled
-const posthogConfigured = process.env.POSTHOG_TOKEN && process.env.POSTHOG_URL
-const sentryConfigured = process.env.SENTRY_DSN
-
-const FEEDBACK_SUBMITTED_KEY = "budibase:feedback_submitted"
-const APP_FIRST_STARTED_KEY = "budibase:first_run"
-const feedbackHours = 12
-
-async function activate() {
-  if (analyticsEnabled === undefined) {
-    // only the server knows the true NODE_ENV
-    // this was an issue as NODE_ENV = 'cypress' on the server,
-    // but 'production' on the client
-    const response = await api.get("/api/analytics")
-    analyticsEnabled = (await response.json()).enabled === true
-  }
-  if (!analyticsEnabled) return
-  if (sentryConfigured) Sentry.init({ dsn: process.env.SENTRY_DSN })
-  if (posthogConfigured) {
-    posthog.init(process.env.POSTHOG_TOKEN, {
-      autocapture: false,
-      capture_pageview: false,
-      api_host: process.env.POSTHOG_URL,
-    })
-    posthog.set_config({ persistence: "cookie" })
-  }
-}
-
-function identify(id) {
-  if (!analyticsEnabled || !id) return
-  if (posthogConfigured) posthog.identify(id)
-  if (sentryConfigured)
-    Sentry.configureScope(scope => {
-      scope.setUser({ id: id })
-    })
-}
-
-async function identifyByApiKey(apiKey) {
-  if (!analyticsEnabled) return true
-  try {
-    const response = await fetch(
-      `https://03gaine137.execute-api.eu-west-1.amazonaws.com/prod/account/id?api_key=${apiKey.trim()}`
-    )
-    if (response.status === 200) {
-      const id = await response.json()
-
-      await api.put("/api/keys/userId", { value: id })
-      identify(id)
-      return true
-    }
-
-    return false
-  } catch (error) {
-    console.log(error)
-  }
-}
-
-function captureException(err) {
-  if (!analyticsEnabled) return
-  Sentry.captureException(err)
-  captureEvent("Error", { error: err.message ? err.message : err })
-}
-
-function captureEvent(eventName, props = {}) {
-  if (!analyticsEnabled || !process.env.POSTHOG_TOKEN) return
-  props.sourceApp = "builder"
-  posthog.capture(eventName, props)
-}
-
-if (!localStorage.getItem(APP_FIRST_STARTED_KEY)) {
-  localStorage.setItem(APP_FIRST_STARTED_KEY, Date.now())
-}
-
-const isFeedbackTimeElapsed = sinceDateStr => {
-  const sinceDate = parseFloat(sinceDateStr)
-  const feedbackMilliseconds = feedbackHours * 60 * 60 * 1000
-  return Date.now() > sinceDate + feedbackMilliseconds
-}
-
-function submitFeedback(values) {
-  if (!analyticsEnabled || !process.env.POSTHOG_TOKEN) return
-  localStorage.setItem(FEEDBACK_SUBMITTED_KEY, Date.now())
-
-  const prefixedValues = Object.entries(values).reduce((obj, [key, value]) => {
-    obj[`feedback_${key}`] = value
-    return obj
-  }, {})
-
-  posthog.capture("Feedback Submitted", prefixedValues)
-}
-
-function requestFeedbackOnDeploy() {
-  if (!analyticsEnabled || !process.env.POSTHOG_TOKEN) return false
-  const lastSubmittedStr = localStorage.getItem(FEEDBACK_SUBMITTED_KEY)
-  if (!lastSubmittedStr) return true
-  return isFeedbackTimeElapsed(lastSubmittedStr)
-}
-
-function highlightFeedbackIcon() {
-  if (!analyticsEnabled || !process.env.POSTHOG_TOKEN) return false
-  const lastSubmittedStr = localStorage.getItem(FEEDBACK_SUBMITTED_KEY)
-  if (lastSubmittedStr) return isFeedbackTimeElapsed(lastSubmittedStr)
-  const firstRunStr = localStorage.getItem(APP_FIRST_STARTED_KEY)
-  if (!firstRunStr) return false
-  return isFeedbackTimeElapsed(firstRunStr)
-}
-
-// Opt In/Out
-const ifAnalyticsEnabled = func => () => {
-  if (analyticsEnabled && process.env.POSTHOG_TOKEN) {
-    return func()
-  }
-}
-const disabled = () => posthog.has_opted_out_capturing()
-const optIn = () => posthog.opt_in_capturing()
-const optOut = () => posthog.opt_out_capturing()
-
-export default {
-  activate,
-  identify,
-  identifyByApiKey,
-  captureException,
-  captureEvent,
-  requestFeedbackOnDeploy,
-  submitFeedback,
-  highlightFeedbackIcon,
-  disabled: () => {
-    if (analyticsEnabled == null) {
-      return true
-    }
-    return ifAnalyticsEnabled(disabled)
-  },
-  optIn: ifAnalyticsEnabled(optIn),
-  optOut: ifAnalyticsEnabled(optOut),
-}
diff --git a/packages/builder/src/analytics/IntercomClient.js b/packages/builder/src/analytics/IntercomClient.js
new file mode 100644
index 0000000000..8cc7e35bbf
--- /dev/null
+++ b/packages/builder/src/analytics/IntercomClient.js
@@ -0,0 +1,94 @@
+export default class IntercomClient {
+  constructor(token) {
+    this.token = token
+  }
+
+  /**
+   * Instantiate intercom using their provided script.
+   */
+  init() {
+    if (!this.token) return
+
+    const token = this.token
+
+    var w = window
+    var ic = w.Intercom
+    if (typeof ic === "function") {
+      ic("reattach_activator")
+      ic("update", w.intercomSettings)
+    } else {
+      var d = document
+      var i = function () {
+        i.c(arguments)
+      }
+      i.q = []
+      i.c = function (args) {
+        i.q.push(args)
+      }
+      w.Intercom = i
+      var l = function () {
+        var s = d.createElement("script")
+        s.type = "text/javascript"
+        s.async = true
+        s.src = "https://widget.intercom.io/widget/" + token
+        var x = d.getElementsByTagName("script")[0]
+        x.parentNode.insertBefore(s, x)
+      }
+      if (document.readyState === "complete") {
+        l()
+      } else if (w.attachEvent) {
+        w.attachEvent("onload", l)
+      } else {
+        w.addEventListener("load", l, false)
+      }
+
+      this.initialised = true
+    }
+  }
+
+  /**
+   * Show the intercom chat bubble.
+   * @param {Object} user - user to identify
+   * @returns Intercom global object
+   */
+  show(user = {}) {
+    if (!this.initialised) return
+
+    return window.Intercom("boot", {
+      app_id: this.token,
+      ...user,
+    })
+  }
+
+  /**
+   * Update intercom user details and messages.
+   * @returns Intercom global object
+   */
+  update() {
+    if (!this.initialised) return
+
+    return window.Intercom("update")
+  }
+
+  /**
+   * Capture analytics events and send them to intercom.
+   * @param {String} event - event identifier
+   * @param {Object} props - properties for the event
+   * @returns Intercom global object
+   */
+  captureEvent(event, props = {}) {
+    if (!this.initialised) return
+
+    return window.Intercom("trackEvent", event, props)
+  }
+
+  /**
+   * Disassociate the user from the current session.
+   * @returns Intercom global object
+   */
+  logout() {
+    if (!this.initialised) return
+
+    return window.Intercom("shutdown")
+  }
+}
diff --git a/packages/builder/src/analytics/PosthogClient.js b/packages/builder/src/analytics/PosthogClient.js
new file mode 100644
index 0000000000..0a1fde42ea
--- /dev/null
+++ b/packages/builder/src/analytics/PosthogClient.js
@@ -0,0 +1,80 @@
+import posthog from "posthog-js"
+import { Events } from "./constants"
+
+export default class PosthogClient {
+  constructor(token, url) {
+    this.token = token
+    this.url = url
+  }
+
+  init() {
+    if (!this.token || !this.url) return
+
+    posthog.init(this.token, {
+      autocapture: false,
+      capture_pageview: false,
+      api_host: this.url,
+    })
+    posthog.set_config({ persistence: "cookie" })
+
+    this.initialised = true
+  }
+
+  /**
+   * Set the posthog context to the current user
+   * @param {String} id - unique user id
+   */
+  identify(id) {
+    if (!this.initialised) return
+
+    posthog.identify(id)
+  }
+
+  /**
+   * Update user metadata associated with current user in posthog
+   * @param {Object} meta - user fields
+   */
+  updateUser(meta) {
+    if (!this.initialised) return
+
+    posthog.people.set(meta)
+  }
+
+  /**
+   * Capture analytics events and send them to posthog.
+   * @param {String} event - event identifier
+   * @param {Object} props - properties for the event
+   */
+  captureEvent(eventName, props) {
+    if (!this.initialised) return
+
+    props.sourceApp = "builder"
+    posthog.capture(eventName, props)
+  }
+
+  /**
+   * Submit NPS feedback to posthog.
+   * @param {Object} values - NPS Values
+   */
+  npsFeedback(values) {
+    if (!this.initialised) return
+
+    localStorage.setItem(Events.NPS.SUBMITTED, Date.now())
+
+    const prefixedFeedback = {}
+    for (let key in values) {
+      prefixedFeedback[`feedback_${key}`] = values[key]
+    }
+
+    posthog.capture(Events.NPS.SUBMITTED, prefixedFeedback)
+  }
+
+  /**
+   * Reset posthog user back to initial state on logout.
+   */
+  logout() {
+    if (!this.initialised) return
+
+    posthog.reset()
+  }
+}
diff --git a/packages/builder/src/analytics/SentryClient.js b/packages/builder/src/analytics/SentryClient.js
new file mode 100644
index 0000000000..2a1f8732e3
--- /dev/null
+++ b/packages/builder/src/analytics/SentryClient.js
@@ -0,0 +1,37 @@
+import * as Sentry from "@sentry/browser"
+
+export default class SentryClient {
+  constructor(dsn) {
+    this.dsn = dsn
+  }
+
+  init() {
+    if (this.dsn) {
+      Sentry.init({ dsn: this.dsn })
+
+      this.initalised = true
+    }
+  }
+
+  /**
+   * Capture an exception and send it to sentry.
+   * @param {Error} err - JS error object
+   */
+  captureException(err) {
+    if (!this.initalised) return
+
+    Sentry.captureException(err)
+  }
+
+  /**
+   * Identify user in sentry.
+   * @param {String} id - Unique user id
+   */
+  identify(id) {
+    if (!this.initalised) return
+
+    Sentry.configureScope(scope => {
+      scope.setUser({ id })
+    })
+  }
+}
diff --git a/packages/builder/src/analytics/constants.js b/packages/builder/src/analytics/constants.js
new file mode 100644
index 0000000000..d38b7bba4f
--- /dev/null
+++ b/packages/builder/src/analytics/constants.js
@@ -0,0 +1,49 @@
+export const Events = {
+  BUILDER: {
+    STARTED: "Builder Started",
+  },
+  COMPONENT: {
+    CREATED: "Added Component",
+  },
+  DATASOURCE: {
+    CREATED: "Datasource Created",
+    UPDATED: "Datasource Updated",
+  },
+  TABLE: {
+    CREATED: "Table Created",
+  },
+  VIEW: {
+    CREATED: "View Created",
+    ADDED_FILTER: "Added View Filter",
+    ADDED_CALCULATE: "Added View Calculate",
+  },
+  SCREEN: {
+    CREATED: "Screen Created",
+  },
+  AUTOMATION: {
+    CREATED: "Automation Created",
+    SAVED: "Automation Saved",
+    BLOCK_ADDED: "Added Automation Block",
+  },
+  NPS: {
+    SUBMITTED: "budibase:feedback_submitted",
+  },
+  APP: {
+    CREATED: "budibase:app_created",
+    PUBLISHED: "budibase:app_published",
+    UNPUBLISHED: "budibase:app_unpublished",
+  },
+  ANALYTICS: {
+    OPT_IN: "budibase:analytics_opt_in",
+    OPT_OUT: "budibase:analytics_opt_out",
+  },
+  USER: {
+    INVITE: "budibase:portal_user_invite",
+  },
+  SMTP: {
+    SAVED: "budibase:smtp_saved",
+  },
+  SSO: {
+    SAVED: "budibase:sso_saved",
+  },
+}
diff --git a/packages/builder/src/analytics/index.js b/packages/builder/src/analytics/index.js
new file mode 100644
index 0000000000..b79ab67e0c
--- /dev/null
+++ b/packages/builder/src/analytics/index.js
@@ -0,0 +1,79 @@
+import api from "builderStore/api"
+import PosthogClient from "./PosthogClient"
+import IntercomClient from "./IntercomClient"
+import SentryClient from "./SentryClient"
+import { Events } from "./constants"
+import { auth } from "stores/portal"
+import { get } from "svelte/store"
+
+const posthog = new PosthogClient(
+  process.env.POSTHOG_TOKEN,
+  process.env.POSTHOG_URL
+)
+const sentry = new SentryClient(process.env.SENTRY_DSN)
+const intercom = new IntercomClient(process.env.INTERCOM_TOKEN)
+
+class AnalyticsHub {
+  constructor() {
+    this.clients = [posthog, sentry, intercom]
+  }
+
+  async activate() {
+    // Setting the analytics env var off in the backend overrides org/tenant settings
+    const analyticsStatus = await api.get("/api/analytics")
+    const json = await analyticsStatus.json()
+
+    // Multitenancy disabled on the backend
+    if (!json.enabled) return
+
+    const tenantId = get(auth).tenantId
+
+    if (tenantId) {
+      const res = await api.get(
+        `/api/global/configs/public?tenantId=${tenantId}`
+      )
+      const orgJson = await res.json()
+
+      // analytics opted out for the tenant
+      if (orgJson.config?.analytics === false) return
+    }
+
+    this.clients.forEach(client => client.init())
+    this.enabled = true
+  }
+
+  identify(id, metadata) {
+    posthog.identify(id)
+    if (metadata) {
+      posthog.updateUser(metadata)
+    }
+    sentry.identify(id)
+  }
+
+  captureException(err) {
+    sentry.captureException(err)
+  }
+
+  captureEvent(eventName, props = {}) {
+    posthog.captureEvent(eventName, props)
+    intercom.captureEvent(eventName, props)
+  }
+
+  showChat(user) {
+    intercom.show(user)
+  }
+
+  submitFeedback(values) {
+    posthog.npsFeedback(values)
+  }
+
+  async logout() {
+    posthog.logout()
+    intercom.logout()
+  }
+}
+
+const analytics = new AnalyticsHub()
+
+export { Events }
+export default analytics
diff --git a/packages/builder/src/builderStore/dataBinding.js b/packages/builder/src/builderStore/dataBinding.js
index 903512d0fb..d3af6799f3 100644
--- a/packages/builder/src/builderStore/dataBinding.js
+++ b/packages/builder/src/builderStore/dataBinding.js
@@ -443,7 +443,10 @@ function bindingReplacement(bindableProperties, textWithBindings, convertTo) {
     for (let from of convertFromProps) {
       if (shouldReplaceBinding(newBoundValue, from, convertTo)) {
         const binding = bindableProperties.find(el => el[convertFrom] === from)
-        newBoundValue = newBoundValue.replace(from, binding[convertTo])
+        newBoundValue = newBoundValue.replace(
+          new RegExp(from, "gi"),
+          binding[convertTo]
+        )
       }
     }
     result = result.replace(boundValue, newBoundValue)
diff --git a/packages/builder/src/builderStore/index.js b/packages/builder/src/builderStore/index.js
index 6fecda84c0..f32dedd47e 100644
--- a/packages/builder/src/builderStore/index.js
+++ b/packages/builder/src/builderStore/index.js
@@ -3,7 +3,6 @@ import { getAutomationStore } from "./store/automation"
 import { getHostingStore } from "./store/hosting"
 import { getThemeStore } from "./store/theme"
 import { derived, writable } from "svelte/store"
-import analytics from "analytics"
 import { FrontendTypes, LAYOUT_NAMES } from "../constants"
 import { findComponent } from "./storeUtils"
 
@@ -55,13 +54,4 @@ export const mainLayout = derived(store, $store => {
 
 export const selectedAccessRole = writable("BASIC")
 
-export const initialise = async () => {
-  try {
-    await analytics.activate()
-    analytics.captureEvent("Builder Started")
-  } catch (err) {
-    console.log(err)
-  }
-}
-
 export const screenSearchString = writable(null)
diff --git a/packages/builder/src/builderStore/store/automation/index.js b/packages/builder/src/builderStore/store/automation/index.js
index e60553070b..0a47970d28 100644
--- a/packages/builder/src/builderStore/store/automation/index.js
+++ b/packages/builder/src/builderStore/store/automation/index.js
@@ -2,7 +2,7 @@ import { writable } from "svelte/store"
 import api from "../../api"
 import Automation from "./Automation"
 import { cloneDeep } from "lodash/fp"
-import analytics from "analytics"
+import analytics, { Events } from "analytics"
 
 const automationActions = store => ({
   fetch: async () => {
@@ -110,7 +110,7 @@ const automationActions = store => ({
       state.selectedBlock = newBlock
       return state
     })
-    analytics.captureEvent("Added Automation Block", {
+    analytics.captureEvent(Events.AUTOMATION.BLOCK_ADDED, {
       name: block.name,
     })
   },
diff --git a/packages/builder/src/builderStore/store/frontend.js b/packages/builder/src/builderStore/store/frontend.js
index 603fa88b09..b7c42003da 100644
--- a/packages/builder/src/builderStore/store/frontend.js
+++ b/packages/builder/src/builderStore/store/frontend.js
@@ -19,7 +19,7 @@ import {
 import { fetchComponentLibDefinitions } from "../loadComponentLibraries"
 import api from "../api"
 import { FrontendTypes } from "constants"
-import analytics from "analytics"
+import analytics, { Events } from "analytics"
 import {
   findComponentType,
   findComponentParent,
@@ -443,7 +443,7 @@ export const getFrontendStore = () => {
         })
 
         // Log event
-        analytics.captureEvent("Added Component", {
+        analytics.captureEvent(Events.COMPONENT.CREATED, {
           name: componentInstance._component,
         })
 
diff --git a/packages/builder/src/components/automation/AutomationBuilder/FlowChart/ActionModal.svelte b/packages/builder/src/components/automation/AutomationBuilder/FlowChart/ActionModal.svelte
index 9af78df1b6..b822973b62 100644
--- a/packages/builder/src/components/automation/AutomationBuilder/FlowChart/ActionModal.svelte
+++ b/packages/builder/src/components/automation/AutomationBuilder/FlowChart/ActionModal.svelte
@@ -123,7 +123,7 @@
     padding: var(--spectrum-alias-item-padding-s);
     background: var(--spectrum-alias-background-color-secondary);
     transition: 0.3s all;
-    border: solid #3b3d3c;
+    border: solid var(--spectrum-alias-border-color);
     border-radius: 5px;
     box-sizing: border-box;
     border-width: 2px;
diff --git a/packages/builder/src/components/automation/AutomationBuilder/FlowChart/FlowChart.svelte b/packages/builder/src/components/automation/AutomationBuilder/FlowChart/FlowChart.svelte
index 92cc6e7cee..c05a103fac 100644
--- a/packages/builder/src/components/automation/AutomationBuilder/FlowChart/FlowChart.svelte
+++ b/packages/builder/src/components/automation/AutomationBuilder/FlowChart/FlowChart.svelte
@@ -1,9 +1,8 @@
 <script>
   import { automationStore } from "builderStore"
-
+  import ConfirmDialog from "components/common/ConfirmDialog.svelte"
   import FlowItem from "./FlowItem.svelte"
   import TestDataModal from "./TestDataModal.svelte"
-
   import { flip } from "svelte/animate"
   import { fade, fly } from "svelte/transition"
   import {
@@ -13,13 +12,12 @@
     notifications,
     Modal,
   } from "@budibase/bbui"
-  import { database } from "stores/backend"
 
   export let automation
   export let onSelect
   let testDataModal
   let blocks
-  $: instanceId = $database._id
+  let confirmDeleteDialog
 
   $: {
     blocks = []
@@ -35,6 +33,7 @@
     await automationStore.actions.delete(
       $automationStore.selectedAutomation?.automation
     )
+    notifications.success("Automation deleted.")
   }
 
   async function testAutomation() {
@@ -63,8 +62,14 @@
           style="display:flex;
           color: var(--spectrum-global-color-gray-400);"
         >
-          <span on:click={() => deleteAutomation()} class="iconPadding">
-            <Icon name="DeleteOutline" />
+          <span class="iconPadding">
+            <div class="icon">
+              <Icon
+                on:click={confirmDeleteDialog.show}
+                hoverable
+                name="DeleteOutline"
+              />
+            </div>
           </span>
           <ActionButton
             on:click={() => {
@@ -92,6 +97,17 @@
       </div>
     {/each}
   </div>
+  <ConfirmDialog
+    bind:this={confirmDeleteDialog}
+    okText="Delete Automation"
+    onOk={deleteAutomation}
+    title="Confirm Deletion"
+  >
+    Are you sure you wish to delete the automation
+    <i>{automation.name}?</i>
+    This action cannot be undone.
+  </ConfirmDialog>
+
   <Modal bind:this={testDataModal} width="30%">
     <TestDataModal {testAutomation} />
   </Modal>
@@ -139,7 +155,7 @@
     justify-content: space-between;
   }
 
-  .iconPadding {
+  .icon {
     cursor: pointer;
     display: flex;
     padding-right: var(--spacing-m);
diff --git a/packages/builder/src/components/automation/AutomationPanel/CreateAutomationModal.svelte b/packages/builder/src/components/automation/AutomationPanel/CreateAutomationModal.svelte
index 6580cd0b87..f3273aa5ec 100644
--- a/packages/builder/src/components/automation/AutomationPanel/CreateAutomationModal.svelte
+++ b/packages/builder/src/components/automation/AutomationPanel/CreateAutomationModal.svelte
@@ -4,7 +4,7 @@
   import { automationStore } from "builderStore"
   import { notifications } from "@budibase/bbui"
   import { Input, ModalContent, Layout, Body, Icon } from "@budibase/bbui"
-  import analytics from "analytics"
+  import analytics, { Events } from "analytics"
 
   let name
   let selectedTrigger
@@ -36,7 +36,7 @@
     notifications.success(`Automation ${name} created.`)
 
     $goto(`./${$automationStore.selectedAutomation.automation._id}`)
-    analytics.captureEvent("Automation Created", { name })
+    analytics.captureEvent(Events.AUTOMATION.CREATED, { name })
   }
   $: triggers = Object.entries($automationStore.blockDefinitions.TRIGGER)
 
@@ -102,7 +102,7 @@
     padding: var(--spectrum-alias-item-padding-s);
     background: var(--spectrum-alias-background-color-secondary);
     transition: 0.3s all;
-    border: solid #3b3d3c;
+    border: solid var(--spectrum-alias-border-color);
     border-radius: 5px;
     box-sizing: border-box;
     border-width: 2px;
diff --git a/packages/builder/src/components/automation/AutomationPanel/UpdateAutomationModal.svelte b/packages/builder/src/components/automation/AutomationPanel/UpdateAutomationModal.svelte
index 29966ec372..64197c3a77 100644
--- a/packages/builder/src/components/automation/AutomationPanel/UpdateAutomationModal.svelte
+++ b/packages/builder/src/components/automation/AutomationPanel/UpdateAutomationModal.svelte
@@ -2,7 +2,7 @@
   import { automationStore } from "builderStore"
   import { notifications } from "@budibase/bbui"
   import { Icon, Input, ModalContent, Modal } from "@budibase/bbui"
-  import analytics from "analytics"
+  import analytics, { Events } from "analytics"
 
   let name
   let error = ""
@@ -26,7 +26,7 @@
     }
     await automationStore.actions.save(updatedAutomation)
     notifications.success(`Automation ${name} updated successfully.`)
-    analytics.captureEvent("Automation Saved", { name })
+    analytics.captureEvent(Events.AUTOMATION.SAVED, { name })
     hide()
   }
 
diff --git a/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte b/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte
index ea3c60932c..adc22e5daf 100644
--- a/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte
+++ b/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte
@@ -20,7 +20,6 @@
   import QueryParamSelector from "./QueryParamSelector.svelte"
   import CronBuilder from "./CronBuilder.svelte"
   import Editor from "components/integration/QueryEditor.svelte"
-  import { database } from "stores/backend"
   import { debounce } from "lodash"
   import ModalBindableInput from "components/common/bindings/ModalBindableInput.svelte"
   import FilterDrawer from "components/design/PropertiesPanel/PropertyControls/FilterEditor/FilterDrawer.svelte"
@@ -35,13 +34,11 @@
   let drawer
   let tempFilters = lookForFilters(schemaProperties) || []
   let fillWidth = true
-
   $: stepId = block.stepId
   $: bindings = getAvailableBindings(
     block || $automationStore.selectedBlock,
     $automationStore.selectedAutomation?.automation?.definition
   )
-  $: instanceId = $database._id
 
   $: inputData = testData ? testData : block.inputs
   $: tableId = inputData ? inputData.tableId : null
@@ -210,7 +207,7 @@
       {:else if value.customType === "webhookUrl"}
         <WebhookDisplay value={inputData[key]} />
       {:else if value.customType === "triggerSchema"}
-        <SchemaSetup on:change={e => onChange(e, key)} value={value[key]} />
+        <SchemaSetup on:change={e => onChange(e, key)} value={inputData[key]} />
       {:else if value.customType === "code"}
         <CodeEditorModal>
           <pre>{JSON.stringify(bindings, null, 2)}</pre>
diff --git a/packages/builder/src/components/automation/SetupPanel/RowSelector.svelte b/packages/builder/src/components/automation/SetupPanel/RowSelector.svelte
index 3f390e0a4f..1d54c86b4a 100644
--- a/packages/builder/src/components/automation/SetupPanel/RowSelector.svelte
+++ b/packages/builder/src/components/automation/SetupPanel/RowSelector.svelte
@@ -1,6 +1,6 @@
 <script>
   import { tables } from "stores/backend"
-  import { Select } from "@budibase/bbui"
+  import { Select, Toggle, DatePicker, Multiselect } from "@budibase/bbui"
   import DrawerBindableInput from "../../common/bindings/DrawerBindableInput.svelte"
   import AutomationBindingPanel from "../../common/bindings/ServerBindingPanel.svelte"
   import { createEventDispatcher } from "svelte"
@@ -44,13 +44,31 @@
   <div class="schema-fields">
     {#each schemaFields as [field, schema]}
       {#if !schema.autocolumn}
-        {#if schemaHasOptions(schema)}
+        {#if schemaHasOptions(schema) && schema.type !== "array"}
           <Select
             on:change={e => onChange(e, field)}
             label={field}
             value={value[field]}
             options={schema.constraints.inclusion}
           />
+        {:else if schema.type === "datetime"}
+          <DatePicker
+            label={field}
+            value={value[field]}
+            on:change={e => onChange(e, field)}
+          />
+        {:else if schema.type === "boolean"}
+          <Toggle
+            text={field}
+            value={value[field]}
+            on:change={e => onChange(e, field)}
+          />
+        {:else if schema.type === "array"}
+          <Multiselect
+            bind:value={value[field]}
+            label={field}
+            options={schema.constraints.inclusion}
+          />
         {:else if schema.type === "string" || schema.type === "number"}
           {#if $automationStore.selectedAutomation.automation.testData}
             <ModalBindableInput
diff --git a/packages/builder/src/components/automation/SetupPanel/SchemaSetup.svelte b/packages/builder/src/components/automation/SetupPanel/SchemaSetup.svelte
index 54f5b90164..730de6270a 100644
--- a/packages/builder/src/components/automation/SetupPanel/SchemaSetup.svelte
+++ b/packages/builder/src/components/automation/SetupPanel/SchemaSetup.svelte
@@ -5,10 +5,14 @@
   const dispatch = createEventDispatcher()
 
   export let value = {}
-  $: fieldsArray = Object.entries(value).map(([name, type]) => ({
-    name,
-    type,
-  }))
+
+  $: fieldsArray = value
+    ? Object.entries(value).map(([name, type]) => ({
+        name,
+        type,
+      }))
+    : []
+
   const typeOptions = [
     {
       label: "Text",
@@ -73,7 +77,7 @@
       <Select
         value={field.type}
         on:change={e => {
-          value[field.name] = e.target.value
+          value[field.name] = e.detail
           dispatch("change", value)
         }}
         options={typeOptions}
@@ -88,9 +92,7 @@
 
 <style>
   .root {
-    position: relative;
     max-width: 100%;
-    overflow-x: auto;
     /* so we can show the "+" button beside the "fields" label*/
     top: -26px;
   }
@@ -110,7 +112,6 @@
     /*grid-template-rows: auto auto;
     grid-template-columns: auto;*/
     position: relative;
-    overflow: hidden;
   }
 
   .field :global(select) {
diff --git a/packages/builder/src/components/backend/DataTable/modals/CalculateModal.svelte b/packages/builder/src/components/backend/DataTable/modals/CalculateModal.svelte
index 660a822898..50d44eca88 100644
--- a/packages/builder/src/components/backend/DataTable/modals/CalculateModal.svelte
+++ b/packages/builder/src/components/backend/DataTable/modals/CalculateModal.svelte
@@ -1,7 +1,7 @@
 <script>
   import { Select, Label, notifications, ModalContent } from "@budibase/bbui"
   import { tables, views } from "stores/backend"
-  import analytics from "analytics"
+  import analytics, { Events } from "analytics"
   import { FIELDS } from "constants/backend"
 
   const CALCULATIONS = [
@@ -40,7 +40,7 @@
   function saveView() {
     views.save(view)
     notifications.success(`View ${view.name} saved.`)
-    analytics.captureEvent("Added View Calculate", { field: view.field })
+    analytics.captureEvent(Events.VIEW.ADDED_CALCULATE, { field: view.field })
   }
 </script>
 
diff --git a/packages/builder/src/components/backend/DataTable/modals/CreateViewModal.svelte b/packages/builder/src/components/backend/DataTable/modals/CreateViewModal.svelte
index 61777c0b7e..2f6ec51233 100644
--- a/packages/builder/src/components/backend/DataTable/modals/CreateViewModal.svelte
+++ b/packages/builder/src/components/backend/DataTable/modals/CreateViewModal.svelte
@@ -3,7 +3,7 @@
   import { goto } from "@roxi/routify"
   import { views as viewsStore } from "stores/backend"
   import { tables } from "stores/backend"
-  import analytics from "analytics"
+  import analytics, { Events } from "analytics"
 
   let name
   let field
@@ -21,7 +21,7 @@
       field,
     })
     notifications.success(`View ${name} created`)
-    analytics.captureEvent("View Created", { name })
+    analytics.captureEvent(Events.VIEW.CREATED, { name })
     $goto(`../../view/${name}`)
   }
 </script>
diff --git a/packages/builder/src/components/backend/DataTable/modals/FilterModal.svelte b/packages/builder/src/components/backend/DataTable/modals/FilterModal.svelte
index 170bb75142..9c6f4956b0 100644
--- a/packages/builder/src/components/backend/DataTable/modals/FilterModal.svelte
+++ b/packages/builder/src/components/backend/DataTable/modals/FilterModal.svelte
@@ -11,7 +11,7 @@
     Icon,
   } from "@budibase/bbui"
   import { tables, views } from "stores/backend"
-  import analytics from "analytics"
+  import analytics, { Events } from "analytics"
 
   const CONDITIONS = [
     {
@@ -65,7 +65,7 @@
   function saveView() {
     views.save(view)
     notifications.success(`View ${view.name} saved.`)
-    analytics.captureEvent("Added View Filter", {
+    analytics.captureEvent(Events.VIEW.ADDED_FILTER, {
       filters: JSON.stringify(view.filters),
     })
   }
diff --git a/packages/builder/src/components/backend/DatasourceNavigator/modals/CreateDatasourceModal.svelte b/packages/builder/src/components/backend/DatasourceNavigator/modals/CreateDatasourceModal.svelte
index 9cdd893230..e7affb30c4 100644
--- a/packages/builder/src/components/backend/DatasourceNavigator/modals/CreateDatasourceModal.svelte
+++ b/packages/builder/src/components/backend/DatasourceNavigator/modals/CreateDatasourceModal.svelte
@@ -5,7 +5,7 @@
   import { Input, Label, ModalContent, Modal, Context } from "@budibase/bbui"
   import TableIntegrationMenu from "../TableIntegrationMenu/index.svelte"
   import CreateTableModal from "components/backend/TableNavigator/modals/CreateTableModal.svelte"
-  import analytics from "analytics"
+  import analytics, { Events } from "analytics"
   import { getContext } from "svelte"
 
   const modalContext = getContext(Context.Modal)
@@ -45,7 +45,7 @@
       plus,
     })
     notifications.success(`Datasource ${name} created successfully.`)
-    analytics.captureEvent("Datasource Created", { name, type })
+    analytics.captureEvent(Events.DATASOURCE.CREATED, { name, type })
 
     // Navigate to new datasource
     $goto(`./datasource/${response._id}`)
diff --git a/packages/builder/src/components/backend/DatasourceNavigator/modals/UpdateDatasourceModal.svelte b/packages/builder/src/components/backend/DatasourceNavigator/modals/UpdateDatasourceModal.svelte
index f93af59a38..28625aa86e 100644
--- a/packages/builder/src/components/backend/DatasourceNavigator/modals/UpdateDatasourceModal.svelte
+++ b/packages/builder/src/components/backend/DatasourceNavigator/modals/UpdateDatasourceModal.svelte
@@ -2,7 +2,7 @@
   import { datasources } from "stores/backend"
   import { notifications } from "@budibase/bbui"
   import { Input, ModalContent, Modal } from "@budibase/bbui"
-  import analytics from "analytics"
+  import analytics, { Events } from "analytics"
 
   let error = ""
   let modal
@@ -35,7 +35,7 @@
     }
     await datasources.save(updatedDatasource)
     notifications.success(`Datasource ${name} updated successfully.`)
-    analytics.captureEvent("Datasource Updated", updatedDatasource)
+    analytics.captureEvent(Events.DATASOURCE.UPDATED, updatedDatasource)
     hide()
   }
 </script>
diff --git a/packages/builder/src/components/backend/TableNavigator/modals/CreateTableModal.svelte b/packages/builder/src/components/backend/TableNavigator/modals/CreateTableModal.svelte
index b59e5cda5e..dd8876be27 100644
--- a/packages/builder/src/components/backend/TableNavigator/modals/CreateTableModal.svelte
+++ b/packages/builder/src/components/backend/TableNavigator/modals/CreateTableModal.svelte
@@ -12,7 +12,7 @@
     Layout,
   } from "@budibase/bbui"
   import TableDataImport from "../TableDataImport.svelte"
-  import analytics from "analytics"
+  import analytics, { Events } from "analytics"
   import screenTemplates from "builderStore/store/screenTemplates"
   import { buildAutoColumn, getAutoColumnInformation } from "builderStore/utils"
   import { NEW_ROW_TEMPLATE } from "builderStore/store/screenTemplates/newRowScreen"
@@ -67,7 +67,7 @@
     // Create table
     const table = await tables.save(newTable)
     notifications.success(`Table ${name} created successfully.`)
-    analytics.captureEvent("Table Created", { name })
+    analytics.captureEvent(Events.TABLE.CREATED, { name })
 
     // Create auto screens
     if (createAutoscreens) {
diff --git a/packages/builder/src/components/deploy/DeployModal.svelte b/packages/builder/src/components/deploy/DeployModal.svelte
index 4daa16c7c4..3dcf0c27b1 100644
--- a/packages/builder/src/components/deploy/DeployModal.svelte
+++ b/packages/builder/src/components/deploy/DeployModal.svelte
@@ -2,7 +2,8 @@
   import { onMount, onDestroy } from "svelte"
   import { Button, Modal, notifications, ModalContent } from "@budibase/bbui"
   import api from "builderStore/api"
-  import analytics from "analytics"
+  import analytics, { Events } from "analytics"
+  import { store } from "builderStore"
 
   const DeploymentStatus = {
     SUCCESS: "SUCCESS",
@@ -23,6 +24,9 @@
       if (response.status !== 200) {
         throw new Error(`status ${response.status}`)
       } else {
+        analytics.captureEvent(Events.APP.PUBLISHED, {
+          appId: $store.appId,
+        })
         notifications.success(`Application published successfully`)
       }
     } catch (err) {
diff --git a/packages/builder/src/components/design/NavigationPanel/NewScreenModal.svelte b/packages/builder/src/components/design/NavigationPanel/NewScreenModal.svelte
index ed0c764956..e02f9d87e5 100644
--- a/packages/builder/src/components/design/NavigationPanel/NewScreenModal.svelte
+++ b/packages/builder/src/components/design/NavigationPanel/NewScreenModal.svelte
@@ -4,7 +4,7 @@
   import { roles } from "stores/backend"
   import { Input, Select, ModalContent, Toggle } from "@budibase/bbui"
   import getTemplates from "builderStore/store/screenTemplates"
-  import analytics from "analytics"
+  import analytics, { Events } from "analytics"
 
   const CONTAINER = "@budibase/standard-components/container"
 
@@ -66,7 +66,7 @@
 
     if (templateIndex !== undefined) {
       const template = templates[templateIndex]
-      analytics.captureEvent("Screen Created", {
+      analytics.captureEvent(Events.SCREEN.CREATED, {
         template: template.id || template.name,
       })
     }
diff --git a/packages/builder/src/components/start/CreateAppModal.svelte b/packages/builder/src/components/start/CreateAppModal.svelte
index 4310d3322e..9ce9d746d7 100644
--- a/packages/builder/src/components/start/CreateAppModal.svelte
+++ b/packages/builder/src/components/start/CreateAppModal.svelte
@@ -12,7 +12,7 @@
   import { admin } from "stores/portal"
   import { string, mixed, object } from "yup"
   import api, { get, post } from "builderStore/api"
-  import analytics from "analytics"
+  import analytics, { Events } from "analytics"
   import { onMount } from "svelte"
   import { capitalise } from "helpers"
   import { goto } from "@roxi/routify"
@@ -98,9 +98,9 @@
         throw new Error(appJson.message)
       }
 
-      analytics.captureEvent("App Created", {
+      analytics.captureEvent(Events.APP.CREATED, {
         name: $values.name,
-        appId: appJson._id,
+        appId: appJson.instance._id,
         template,
       })
 
diff --git a/packages/builder/src/pages/builder/auth/login.svelte b/packages/builder/src/pages/builder/auth/login.svelte
index 783e5a4903..f9f2b34578 100644
--- a/packages/builder/src/pages/builder/auth/login.svelte
+++ b/packages/builder/src/pages/builder/auth/login.svelte
@@ -29,6 +29,7 @@
         username,
         password,
       })
+
       if ($auth?.user?.forceResetPassword) {
         $goto("./reset")
       } else {
diff --git a/packages/builder/src/pages/builder/portal/apps/index.svelte b/packages/builder/src/pages/builder/portal/apps/index.svelte
index a18ec6a8bd..d84b327e90 100644
--- a/packages/builder/src/pages/builder/portal/apps/index.svelte
+++ b/packages/builder/src/pages/builder/portal/apps/index.svelte
@@ -15,8 +15,7 @@
   } from "@budibase/bbui"
   import CreateAppModal from "components/start/CreateAppModal.svelte"
   import UpdateAppModal from "components/start/UpdateAppModal.svelte"
-  import api, { del } from "builderStore/api"
-  import analytics from "analytics"
+  import { del } from "builderStore/api"
   import { onMount } from "svelte"
   import { apps, auth, admin } from "stores/portal"
   import download from "downloadjs"
@@ -66,14 +65,6 @@
     }
   }
 
-  const checkKeys = async () => {
-    const response = await api.get(`/api/keys/`)
-    const keys = await response.json()
-    if (keys.userId) {
-      analytics.identify(keys.userId)
-    }
-  }
-
   const initiateAppCreation = () => {
     creationModal.show()
     creatingApp = true
@@ -188,7 +179,6 @@
   }
 
   onMount(async () => {
-    checkKeys()
     await apps.load()
     loaded = true
   })
diff --git a/packages/builder/src/pages/builder/portal/manage/auth/index.svelte b/packages/builder/src/pages/builder/portal/manage/auth/index.svelte
index 48d9da18f9..c2445e14ae 100644
--- a/packages/builder/src/pages/builder/portal/manage/auth/index.svelte
+++ b/packages/builder/src/pages/builder/portal/manage/auth/index.svelte
@@ -23,6 +23,7 @@
   import api from "builderStore/api"
   import { organisation, auth, admin } from "stores/portal"
   import { uuid } from "builderStore/uuid"
+  import analytics, { Events } from "analytics"
 
   $: tenantId = $auth.tenantId
   $: multiTenancyEnabled = $admin.multiTenancy
@@ -209,6 +210,7 @@
             providers[res.type]._id = res._id
           })
           notifications.success(`Settings saved.`)
+          analytics.captureEvent(Events.SSO.SAVED)
         })
         .catch(err => {
           notifications.error(`Failed to update auth settings. ${err}`)
diff --git a/packages/builder/src/pages/builder/portal/manage/email/index.svelte b/packages/builder/src/pages/builder/portal/manage/email/index.svelte
index 76d98ed545..5a78623b81 100644
--- a/packages/builder/src/pages/builder/portal/manage/email/index.svelte
+++ b/packages/builder/src/pages/builder/portal/manage/email/index.svelte
@@ -16,6 +16,7 @@
   import { email } from "stores/portal"
   import api from "builderStore/api"
   import { cloneDeep } from "lodash/fp"
+  import analytics, { Events } from "analytics"
 
   const ConfigTypes = {
     SMTP: "smtp",
@@ -69,6 +70,7 @@
       smtpConfig._rev = json._rev
       smtpConfig._id = json._id
       notifications.success(`Settings saved.`)
+      analytics.captureEvent(Events.SMTP.SAVED)
     }
   }
 
diff --git a/packages/builder/src/pages/builder/portal/manage/users/_components/AddUserModal.svelte b/packages/builder/src/pages/builder/portal/manage/users/_components/AddUserModal.svelte
index 9504f73b68..25a69af1c8 100644
--- a/packages/builder/src/pages/builder/portal/manage/users/_components/AddUserModal.svelte
+++ b/packages/builder/src/pages/builder/portal/manage/users/_components/AddUserModal.svelte
@@ -10,6 +10,7 @@
   } from "@budibase/bbui"
   import { createValidationStore, emailValidator } from "helpers/validation"
   import { users } from "stores/portal"
+  import analytics, { Events } from "analytics"
 
   export let disabled
 
@@ -25,6 +26,7 @@
       notifications.error(res.message)
     } else {
       notifications.success(res.message)
+      analytics.captureEvent(Events.USER.INVITE, { type: selected })
     }
   }
 </script>
diff --git a/packages/builder/src/pages/builder/portal/settings/organisation.svelte b/packages/builder/src/pages/builder/portal/settings/organisation.svelte
index be8b60e6e7..79eaebb28b 100644
--- a/packages/builder/src/pages/builder/portal/settings/organisation.svelte
+++ b/packages/builder/src/pages/builder/portal/settings/organisation.svelte
@@ -25,7 +25,7 @@
   }
 
   const values = writable({
-    analytics: !analytics.disabled(),
+    analytics: analytics.enabled,
     company: $organisation.company,
     platformUrl: $organisation.platformUrl,
     logo: $organisation.logoUrl
@@ -48,13 +48,6 @@
   async function saveConfig() {
     loading = true
 
-    // Set analytics preference
-    if ($values.analytics) {
-      analytics.optIn()
-    } else {
-      analytics.optOut()
-    }
-
     // Upload logo if required
     if ($values.logo && !$values.logo.url) {
       await uploadLogo($values.logo)
@@ -64,6 +57,7 @@
     const config = {
       company: $values.company ?? "",
       platformUrl: $values.platformUrl ?? "",
+      analytics: $values.analytics,
     }
     // remove logo if required
     if (!$values.logo) {
diff --git a/packages/builder/src/stores/portal/auth.js b/packages/builder/src/stores/portal/auth.js
index fe8f87cfb2..e33a1f22ac 100644
--- a/packages/builder/src/stores/portal/auth.js
+++ b/packages/builder/src/stores/portal/auth.js
@@ -1,6 +1,7 @@
 import { derived, writable, get } from "svelte/store"
 import api from "../../builderStore/api"
 import { admin } from "stores/portal"
+import analytics from "analytics"
 
 export function createAuthStore() {
   const auth = writable({
@@ -49,6 +50,21 @@ export function createAuthStore() {
       }
       return store
     })
+
+    if (user) {
+      analytics.activate().then(() => {
+        analytics.identify(user._id, user)
+        if (user.size === "100+" || user.size === "10000+") {
+          analytics.showChat({
+            email: user.email,
+            created_at: user.createdAt || Date.now(),
+            name: user.name,
+            user_id: user._id,
+            tenant: user.tenantId,
+          })
+        }
+      })
+    }
   }
 
   async function setOrganisation(tenantId) {
diff --git a/packages/builder/vite.config.js b/packages/builder/vite.config.js
index d8b8dbba1d..12b45e7cf8 100644
--- a/packages/builder/vite.config.js
+++ b/packages/builder/vite.config.js
@@ -22,6 +22,9 @@ export default ({ mode }) => {
           isProduction ? "production" : "development"
         ),
         "process.env.POSTHOG_TOKEN": JSON.stringify(process.env.POSTHOG_TOKEN),
+        "process.env.INTERCOM_TOKEN": JSON.stringify(
+          process.env.INTERCOM_TOKEN
+        ),
         "process.env.POSTHOG_URL": JSON.stringify(process.env.POSTHOG_URL),
         "process.env.SENTRY_DSN": JSON.stringify(process.env.SENTRY_DSN),
       }),
diff --git a/packages/builder/yarn.lock b/packages/builder/yarn.lock
index 5257ba0c37..c6c8f490bf 100644
--- a/packages/builder/yarn.lock
+++ b/packages/builder/yarn.lock
@@ -2,6 +2,11 @@
 # yarn lockfile v1
 
 
+"@adobe/spectrum-css-workflow-icons@^1.2.1":
+  version "1.2.1"
+  resolved "https://registry.yarnpkg.com/@adobe/spectrum-css-workflow-icons/-/spectrum-css-workflow-icons-1.2.1.tgz#7e2cb3fcfb5c8b12d7275afafbb6ec44913551b4"
+  integrity sha512-uVgekyBXnOVkxp+CUssjN/gefARtudZC8duEn1vm0lBQFwGRZFlDEzU1QC+aIRWCrD1Z8OgRpmBYlSZ7QS003w==
+
 "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.12.13":
   version "7.12.13"
   resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.12.13.tgz#dcfc826beef65e75c50e21d3837d7d95798dd658"
@@ -870,11 +875,129 @@
   resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
   integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
 
+"@budibase/bbui@^0.9.125-alpha.18", "@budibase/bbui@^0.9.133":
+  version "0.9.133"
+  resolved "https://registry.yarnpkg.com/@budibase/bbui/-/bbui-0.9.133.tgz#91a2fb24abaaf91d2cb1e00eb51c493c1290f9ad"
+  integrity sha512-xbMmc/hee1QRNW7TrbGUBmLr1hMHXqUDA6rdl9N2PGfHFuFWbqlD8PWYanHmLevVet+CjkuKGPSbBghFK2pQyQ==
+  dependencies:
+    "@adobe/spectrum-css-workflow-icons" "^1.2.1"
+    "@spectrum-css/actionbutton" "^1.0.1"
+    "@spectrum-css/actiongroup" "^1.0.1"
+    "@spectrum-css/avatar" "^3.0.2"
+    "@spectrum-css/button" "^3.0.1"
+    "@spectrum-css/buttongroup" "^3.0.2"
+    "@spectrum-css/checkbox" "^3.0.2"
+    "@spectrum-css/dialog" "^3.0.1"
+    "@spectrum-css/divider" "^1.0.3"
+    "@spectrum-css/dropzone" "^3.0.2"
+    "@spectrum-css/fieldgroup" "^3.0.2"
+    "@spectrum-css/fieldlabel" "^3.0.1"
+    "@spectrum-css/icon" "^3.0.1"
+    "@spectrum-css/illustratedmessage" "^3.0.2"
+    "@spectrum-css/inputgroup" "^3.0.2"
+    "@spectrum-css/label" "^2.0.10"
+    "@spectrum-css/link" "^3.1.1"
+    "@spectrum-css/menu" "^3.0.1"
+    "@spectrum-css/modal" "^3.0.1"
+    "@spectrum-css/pagination" "^3.0.3"
+    "@spectrum-css/picker" "^1.0.1"
+    "@spectrum-css/popover" "^3.0.1"
+    "@spectrum-css/progressbar" "^1.0.2"
+    "@spectrum-css/progresscircle" "^1.0.2"
+    "@spectrum-css/radio" "^3.0.2"
+    "@spectrum-css/search" "^3.0.2"
+    "@spectrum-css/sidenav" "^3.0.2"
+    "@spectrum-css/statuslight" "^3.0.2"
+    "@spectrum-css/stepper" "^3.0.3"
+    "@spectrum-css/switch" "^1.0.2"
+    "@spectrum-css/table" "^3.0.1"
+    "@spectrum-css/tabs" "^3.0.1"
+    "@spectrum-css/tags" "^3.0.2"
+    "@spectrum-css/textfield" "^3.0.1"
+    "@spectrum-css/toast" "^3.0.1"
+    "@spectrum-css/tooltip" "^3.0.3"
+    "@spectrum-css/treeview" "^3.0.2"
+    "@spectrum-css/typography" "^3.0.1"
+    "@spectrum-css/underlay" "^2.0.9"
+    "@spectrum-css/vars" "^3.0.1"
+    dayjs "^1.10.4"
+    svelte-flatpickr "^3.1.0"
+    svelte-portal "^1.0.0"
+
+"@budibase/client@^0.9.125-alpha.18":
+  version "0.9.133"
+  resolved "https://registry.yarnpkg.com/@budibase/client/-/client-0.9.133.tgz#43748e189e9b92d99d1281ab62bd2c5ebed5dbab"
+  integrity sha512-JrduL9iVMGalZyIUQ+1UN/dhrOZNRJwXU8B4r/eWhVoJf3f3bCuNfpMoT2LN3HY4ooyu37VehD+J5bdDsvlNPw==
+  dependencies:
+    "@budibase/bbui" "^0.9.133"
+    "@budibase/standard-components" "^0.9.133"
+    "@budibase/string-templates" "^0.9.133"
+    regexparam "^1.3.0"
+    shortid "^2.2.15"
+    svelte-spa-router "^3.0.5"
+
 "@budibase/colorpicker@1.1.2":
   version "1.1.2"
   resolved "https://registry.yarnpkg.com/@budibase/colorpicker/-/colorpicker-1.1.2.tgz#f7436924ee746d7be9b2009c2fa193e710c30f89"
   integrity sha512-2PlZBVkATDqDC4b4Ri8Xi8X3OxhuHOGfmZwtXbZL38lNIeofaQT3Qyc1ECzEY5N+HrdGrWhY9EnliF6QM+LIuA==
 
+"@budibase/handlebars-helpers@^0.11.4":
+  version "0.11.5"
+  resolved "https://registry.yarnpkg.com/@budibase/handlebars-helpers/-/handlebars-helpers-0.11.5.tgz#e9cc90a44e94ad536992cf10906829b633e94bc5"
+  integrity sha512-ZxpyNtTHxS8Y+yTicbgWvYDAydooUSjOf3Y+wmTE2d4NpDgO0g0IjepLfZV+KASv9XBr//ylJdjE4hClX9NTFw==
+  dependencies:
+    array-sort "^1.0.0"
+    define-property "^2.0.2"
+    extend-shallow "^3.0.2"
+    "falsey" "^1.0.0"
+    for-in "^1.0.2"
+    get-object "^0.2.0"
+    get-value "^3.0.1"
+    handlebars "^4.7.7"
+    handlebars-utils "^1.0.6"
+    has-value "^2.0.2"
+    helper-date "^1.0.1"
+    helper-markdown "^1.0.0"
+    helper-md "^0.2.2"
+    html-tag "^2.0.0"
+    is-even "^1.0.0"
+    is-glob "^4.0.1"
+    kind-of "^6.0.3"
+    micromatch "^3.1.5"
+    relative "^3.0.2"
+    striptags "^3.1.1"
+    to-gfm-code-block "^0.1.1"
+    year "^0.2.1"
+
+"@budibase/standard-components@^0.9.133":
+  version "0.9.133"
+  resolved "https://registry.yarnpkg.com/@budibase/standard-components/-/standard-components-0.9.133.tgz#789c02b45dc3853b003822c09e18ce7ece4dfa29"
+  integrity sha512-xcuwTxsqk1J/YmM4YjThO/Fm0eJ+aZWm0kbFgfN+dNN9fuPlsPOLmlVEWeOUPmBa5XfRyDbx6lDYj0PPEK8CvA==
+  dependencies:
+    "@budibase/bbui" "^0.9.133"
+    "@spectrum-css/button" "^3.0.3"
+    "@spectrum-css/card" "^3.0.3"
+    "@spectrum-css/divider" "^1.0.3"
+    "@spectrum-css/link" "^3.1.3"
+    "@spectrum-css/page" "^3.0.1"
+    "@spectrum-css/typography" "^3.0.2"
+    "@spectrum-css/vars" "^3.0.1"
+    apexcharts "^3.22.1"
+    dayjs "^1.10.5"
+    svelte-apexcharts "^1.0.2"
+    svelte-flatpickr "^3.1.0"
+
+"@budibase/string-templates@^0.9.125-alpha.18", "@budibase/string-templates@^0.9.133":
+  version "0.9.133"
+  resolved "https://registry.yarnpkg.com/@budibase/string-templates/-/string-templates-0.9.133.tgz#221d81e080dc4485dcffa989d16e2bbed39f9055"
+  integrity sha512-SMHcSPwHYdAqol9YCcMoYawp5/ETr9TqGZCUsL+hUUq+LritPwu/miQ++SVvRTQbOR7Mker0S9LO3H8mwYkW8w==
+  dependencies:
+    "@budibase/handlebars-helpers" "^0.11.4"
+    dayjs "^1.10.4"
+    handlebars "^4.7.6"
+    handlebars-utils "^1.0.6"
+    lodash "^4.17.20"
+
 "@cnakazawa/watch@^1.0.3":
   version "1.0.4"
   resolved "https://registry.yarnpkg.com/@cnakazawa/watch/-/watch-1.0.4.tgz#f864ae85004d0fcab6f50be9141c4da368d1656a"
@@ -1289,6 +1412,103 @@
   dependencies:
     "@sinonjs/commons" "^1.7.0"
 
+"@spectrum-css/actionbutton@^1.0.1":
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/@spectrum-css/actionbutton/-/actionbutton-1.0.3.tgz#8f7342a69b303c5acdcfa0a59f5e9267b9f3cb30"
+  integrity sha512-P9qoCPSiZ1SB6ZYqK5hub0vGty00YYqonQE0KTjtb1i+T1nYR/87vWqVPERx9j63uhgZncjwFYaThTvRqye7eQ==
+
+"@spectrum-css/actiongroup@^1.0.1":
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/@spectrum-css/actiongroup/-/actiongroup-1.0.3.tgz#4713ce65e6f5c72c404a7b638fbc3b4fd7e3874f"
+  integrity sha512-NlB9Q4ZlWixXxymoPIYG6g2hkwAGKFodHWPFfxHD8ddkjXWRB9G2akUP7cfsJ4DcYn4VisUORCOYQwqDiSmboQ==
+
+"@spectrum-css/avatar@^3.0.2":
+  version "3.0.2"
+  resolved "https://registry.yarnpkg.com/@spectrum-css/avatar/-/avatar-3.0.2.tgz#4f1826223eae330e64b6d3cc899e9bc2e98dac95"
+  integrity sha512-wEczvSqxttTWSiL3cOvXV/RmGRwSkw2w6+slcHhnf0kb7ovymMM+9oz8vvEpEsSeo5u598bc+7ktrKFpAd6soQ==
+
+"@spectrum-css/button@^3.0.1", "@spectrum-css/button@^3.0.3":
+  version "3.0.3"
+  resolved "https://registry.yarnpkg.com/@spectrum-css/button/-/button-3.0.3.tgz#2df1efaab6c7e0b3b06cb4b59e1eae59c7f1fc84"
+  integrity sha512-6CnLPqqtaU/PcSSIGeGRi0iFIIxIUByYLKFO6zn5NEUc12KQ28dJ4PLwB6WBa0L8vRoAGlnWWH2ZZweTijbXgg==
+
+"@spectrum-css/buttongroup@^3.0.2":
+  version "3.0.3"
+  resolved "https://registry.yarnpkg.com/@spectrum-css/buttongroup/-/buttongroup-3.0.3.tgz#719d868845ac9d2c4f939c1b9f6044507902d5aa"
+  integrity sha512-eXl8U4QWMWXqyTu654FdQdEGnmczgOYlpIFSHyCMVjhtPqZp2xwnLFiGh6LKw+bLio6eeOZ0L+vpk1GcoYqgkw==
+
+"@spectrum-css/card@^3.0.3":
+  version "3.0.3"
+  resolved "https://registry.yarnpkg.com/@spectrum-css/card/-/card-3.0.3.tgz#56b2e2da6b80c1583228baa279de7407383bfb6b"
+  integrity sha512-+oKLUI2a0QmQP9EzySeq/G4FpUkkdaDNbuEbqCj2IkPMc/2v/nwzsPhh1fj2UIghGAiiUwXfPpzax1e8fyhQUg==
+
+"@spectrum-css/checkbox@^3.0.2":
+  version "3.0.3"
+  resolved "https://registry.yarnpkg.com/@spectrum-css/checkbox/-/checkbox-3.0.3.tgz#8577067fc8b97e4609f92bd242364937a533a7bb"
+  integrity sha512-QVG9uMHq+lh70Dh6mDNnY+vEvNz2p7VC6xgLfDYfijp2qeiqYPq72fQK6p/SiyqPk96ZACzNRwgeROU6Xf6Wgg==
+
+"@spectrum-css/dialog@^3.0.1":
+  version "3.0.3"
+  resolved "https://registry.yarnpkg.com/@spectrum-css/dialog/-/dialog-3.0.3.tgz#7715a4ea435e753afb623d99ca5917ed1bcd6f34"
+  integrity sha512-AhmKgfRIVyTe3ABiJ8lLUQL34VB/H6fN16na2LlbDRJvyRMzkdN1Xf2i6U3f4OMd3qQ8Gm5xat4BvMxIQPBAUQ==
+
+"@spectrum-css/divider@^1.0.3":
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/@spectrum-css/divider/-/divider-1.0.3.tgz#639e2ebaa0834efa40f42397668bbd5c153ea385"
+  integrity sha512-Zy4Rn40w8UtzMh3wx/U9+CepSCpm1aOCGftHgWDub0XZuVTzh0c1WwyzTuLCx2Hf21z5VRGNiDh8bGEEzSbtNA==
+  dependencies:
+    "@spectrum-css/vars" "^3.0.2"
+
+"@spectrum-css/dropzone@^3.0.2":
+  version "3.0.3"
+  resolved "https://registry.yarnpkg.com/@spectrum-css/dropzone/-/dropzone-3.0.3.tgz#aee71697a2c195947599d7541b858c3c198741dc"
+  integrity sha512-ujrswdtB6bHigklyHsm6zomFd6rUnKJ3xVVRjroVF4+ouK4DxK5tX0iVd0EW6AOfOjx4Cc28uyFot3fpxp+MQw==
+
+"@spectrum-css/fieldgroup@^3.0.2":
+  version "3.0.3"
+  resolved "https://registry.yarnpkg.com/@spectrum-css/fieldgroup/-/fieldgroup-3.0.3.tgz#85d85da048d08200f25ceab378026dd2b11e0bb2"
+  integrity sha512-wXUXTXN1CPnR7M4Ltd+3uh7BfcNGUV1+Xe0/h0tCpq/j+S2Sd4xo7/pUMdU19sIDrAAtpEFp1tt+nouHcU5HGQ==
+
+"@spectrum-css/fieldlabel@^3.0.1":
+  version "3.0.3"
+  resolved "https://registry.yarnpkg.com/@spectrum-css/fieldlabel/-/fieldlabel-3.0.3.tgz#f73c04d20734d4718ffb620dc46458904685b449"
+  integrity sha512-nEvIkEXCD5n4fW67Unq6Iu7VXoauEd/JGpfTY02VsC5p4FJLnwKfPDbJUuUsqClAxqw7nAsmXVKtn4zQFf5yPQ==
+
+"@spectrum-css/icon@^3.0.1":
+  version "3.0.3"
+  resolved "https://registry.yarnpkg.com/@spectrum-css/icon/-/icon-3.0.3.tgz#5c612822380927087aebd526855d82ed2c3e2cba"
+  integrity sha512-hyloKOejPCXhP3MBNsm3SjR9j8xT1R1S19p32KW/0Qhj+VMUtfyEPmevyFptpn7wcovQccdl/vZVIVDuML/imw==
+
+"@spectrum-css/illustratedmessage@^3.0.2":
+  version "3.0.2"
+  resolved "https://registry.yarnpkg.com/@spectrum-css/illustratedmessage/-/illustratedmessage-3.0.2.tgz#6a480be98b027e050b086e7899e40d87adb0a8c0"
+  integrity sha512-dqnE8X27bGcO0HN8+dYx8O4o0dNNIAqeivOzDHhe2El+V4dTzMrNIerF6G0NLm3GjVf6XliwmitsZK+K6FmbtA==
+
+"@spectrum-css/inputgroup@^3.0.2":
+  version "3.0.3"
+  resolved "https://registry.yarnpkg.com/@spectrum-css/inputgroup/-/inputgroup-3.0.3.tgz#00c9a370ddc2c55cf0f37dd6069faa9501fd7eb5"
+  integrity sha512-FqRJTiLL7jiGfzDVXWUGVLqHryJjCcqQIrqAi+Tq0oenapzsBe2qc/zIrKgh2wtMI+NTIBLXTECvij3L1HlqAg==
+
+"@spectrum-css/label@^2.0.10":
+  version "2.0.10"
+  resolved "https://registry.yarnpkg.com/@spectrum-css/label/-/label-2.0.10.tgz#2368651d7636a19385b5d300cdf6272db1916001"
+  integrity sha512-xCbtEiQkZIlLdWFikuw7ifDCC21DOC/KMgVrrVJHXFc4KRQe9LTZSqmGF3tovm+CSq1adE59mYoTbojVQ9YuEQ==
+
+"@spectrum-css/link@^3.1.1", "@spectrum-css/link@^3.1.3":
+  version "3.1.3"
+  resolved "https://registry.yarnpkg.com/@spectrum-css/link/-/link-3.1.3.tgz#b0e560a7e0acdb4a2b329b6669696aa3438f5993"
+  integrity sha512-8Pmy5d73MwKcATTPaj+fSrZYnIw7GmfX40AvpC1xx5LauOxsbUb4AVNp1kn2H8rrOBmxF7czyhoBBhEiv66QUg==
+
+"@spectrum-css/menu@^3.0.1":
+  version "3.0.3"
+  resolved "https://registry.yarnpkg.com/@spectrum-css/menu/-/menu-3.0.3.tgz#46a9b221bb5f470a2f8a934bdfd512d84d2fdc4d"
+  integrity sha512-qKA9J/MrikNKIpCEHsAazG2vY3im5tjGCmo6p9Pdnu8/aQMsiuZDHZayukeCttJUZkrb9guDVL9OIHlK5RZvcQ==
+
+"@spectrum-css/modal@^3.0.1":
+  version "3.0.2"
+  resolved "https://registry.yarnpkg.com/@spectrum-css/modal/-/modal-3.0.2.tgz#58b6621cab65f90788d310374f40df1f7090473f"
+  integrity sha512-YnIivJhoaao7Otu+HV7sgebPyFbO6sd/oMvTN/Rb2wwgnaMnIIuIRdGandSrcgotN2uNgs+P0knG6mv/xA1/dg==
+
 "@spectrum-css/page@^3.0.1":
   version "3.0.1"
   resolved "https://registry.yarnpkg.com/@spectrum-css/page/-/page-3.0.1.tgz#5e1c3dd5b1a1ee591f9d636b75f03665f542d846"
@@ -1296,11 +1516,116 @@
   dependencies:
     "@spectrum-css/vars" "^3.0.1"
 
+"@spectrum-css/pagination@^3.0.3":
+  version "3.0.3"
+  resolved "https://registry.yarnpkg.com/@spectrum-css/pagination/-/pagination-3.0.3.tgz#b204c3ada384c4af751a354bc428346d82eeea65"
+  integrity sha512-OJ/v9GeNXJOZ9Yr9LDBYPrR2NCiLOWP9wANT/a5sqFuugRnQbn/HYMnRp9TBxwpDY6ihaPo0T/wi7kLiAJFdDw==
+
+"@spectrum-css/picker@^1.0.1":
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/@spectrum-css/picker/-/picker-1.0.3.tgz#21379bcf8ae94277deeb6ad65dcd9e2bbfacb487"
+  integrity sha512-oHLGxBx5BwVCSGo7/T1C9PTHX1+/5AmVjyLiTJ4UoIdSJmOERw9YcRZbcGZgBJNWbxcjr4TyGtwj1EcSjEy97w==
+
+"@spectrum-css/popover@^3.0.1":
+  version "3.0.3"
+  resolved "https://registry.yarnpkg.com/@spectrum-css/popover/-/popover-3.0.3.tgz#6fb69873474fb968afb738eacb9e121f93e83a09"
+  integrity sha512-KvmXv4TV19FBx39KfmgVlDYtvtBqv/8RRK7RRLDDHGViTxZtShjVsVpwIgfkfgn4iJztCnXpWzFqRXWUu2XCpQ==
+
+"@spectrum-css/progressbar@^1.0.2":
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/@spectrum-css/progressbar/-/progressbar-1.0.3.tgz#f70bcc38a2a21cff2f422ec825724ebbb9455e67"
+  integrity sha512-vJHplefUuy8+NjCw1X7fLbqHVGNVBpvGFXNAeaIj4SFf4ygxiUq/5c9iRhhsCQixEsJlfD/b7BnGXU7BUDkr6Q==
+
+"@spectrum-css/progresscircle@^1.0.2":
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/@spectrum-css/progresscircle/-/progresscircle-1.0.2.tgz#258ea9170fb70f795edda03e38a61d93bef4487c"
+  integrity sha512-JLULpyzjIY95lzlWR1yE1gv4l1K6p+scQ+edmuZZUHBzwM3pUtkvHJmUlA9TYdResUYW6Uka60VRdY6lZ8gnFQ==
+
+"@spectrum-css/radio@^3.0.2":
+  version "3.0.3"
+  resolved "https://registry.yarnpkg.com/@spectrum-css/radio/-/radio-3.0.3.tgz#25c3bc5e9c30a8a8ae728717b7c7fb736cdae640"
+  integrity sha512-LaLGfz/eGNR2iyqouXYILIA+pKRqF769iPdwM0REm5RpWvMQDD7rPZ/kWlg18owjaFsyllEp25gEjmhRJIIVOw==
+
+"@spectrum-css/search@^3.0.2":
+  version "3.0.3"
+  resolved "https://registry.yarnpkg.com/@spectrum-css/search/-/search-3.0.3.tgz#3415dc106aca0d5dd996e87084a1b47c2b95a882"
+  integrity sha512-kdLpKTt0obljuhS1q1tukujRlvSs8sBwij76D4Qp8KpMzwePfZyvv1kYzuWPNZfTeISxWsmyZ6Wxd1uvzjn+UA==
+
+"@spectrum-css/sidenav@^3.0.2":
+  version "3.0.3"
+  resolved "https://registry.yarnpkg.com/@spectrum-css/sidenav/-/sidenav-3.0.3.tgz#132141fbd2500a927c312fa4e1d712c438b3d597"
+  integrity sha512-cQ+CgwjxGftrcc79i1XnGd66QTl7H7zopSU0UTV4Qq7hvqfrjjWxfZ6b+3tezrrhNlDope1ff9o8sm67PsPXtg==
+
+"@spectrum-css/statuslight@^3.0.2":
+  version "3.0.2"
+  resolved "https://registry.yarnpkg.com/@spectrum-css/statuslight/-/statuslight-3.0.2.tgz#dc54b6cd113413dcdb909c486b5d7bae60db65c5"
+  integrity sha512-xodB8g8vGJH20XmUj9ZsPlM1jHrGeRbvmVXkz0q7YvQrYAhim8pP3W+XKKZAletPFAuu8cmUOc6SWn6i4X4z6w==
+
+"@spectrum-css/stepper@^3.0.3":
+  version "3.0.3"
+  resolved "https://registry.yarnpkg.com/@spectrum-css/stepper/-/stepper-3.0.3.tgz#ae89846886431e3edeee060207b8f81540f73a34"
+  integrity sha512-prAD61ImlOTs9b6PfB3cB08x4lAfxtvnW+RZiTYky0E8GgZdrc/MfCkL5/oqQaIQUtyQv/3Lb7ELAf/0K8QTXw==
+
+"@spectrum-css/switch@^1.0.2":
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/@spectrum-css/switch/-/switch-1.0.2.tgz#f0b4c69271964573e02b08e90998096e49e1de44"
+  integrity sha512-zqmHpgWPNg1gEwdUNFYV3CBX5JaeALfIqcJIxE0FLZqr9d1C4+oLE0ItIFzt1bwr4bFAOmkEpvtiY+amluzGxQ==
+
+"@spectrum-css/table@^3.0.1":
+  version "3.0.3"
+  resolved "https://registry.yarnpkg.com/@spectrum-css/table/-/table-3.0.3.tgz#7f7f19905ef3275cbf907ce3a5818e63c30b2caf"
+  integrity sha512-nxwzVjLPsXoY/v4sdxOVYLcC+cEbGgJyLcLclT5LT9MGSbngFeUMJzzVR4EvehzuN4dH7hrATG7Mbuq29Mf0Hg==
+
+"@spectrum-css/tabs@^3.0.1":
+  version "3.0.3"
+  resolved "https://registry.yarnpkg.com/@spectrum-css/tabs/-/tabs-3.0.3.tgz#51dd6f168c897b0fdc3a7e9f901df7bd2288b4fc"
+  integrity sha512-iLP1I72bJWz9APdQB1jiw+pOv5a7N+hYOCJvRoc56Us/hJKVzowkyGRe3uH+8v36nCG9bHxiAQNLoU8eXisVrg==
+
+"@spectrum-css/tags@^3.0.2":
+  version "3.0.3"
+  resolved "https://registry.yarnpkg.com/@spectrum-css/tags/-/tags-3.0.3.tgz#fc76d2735cdc442de91b7eb3bee49a928c0767ac"
+  integrity sha512-SL8vPxVDfWcY5VdIuyl0TImEXcOU1I7yCyXkk7MudMwfnYs81FaIyY32hFV9OHj0Tz/36UzRzc7AVMSuRQ53pw==
+
+"@spectrum-css/textfield@^3.0.1":
+  version "3.0.2"
+  resolved "https://registry.yarnpkg.com/@spectrum-css/textfield/-/textfield-3.0.2.tgz#907f62d2dc82852dd6236a820be99e252b531631"
+  integrity sha512-nkFgAb0cP4jUodkUBErMNfyF78jJLtgL1Mrr9/rvGpGobo10IAbb8zZY4CkZ64o8XmMy/85+wZTKcx+KHatqpg==
+
+"@spectrum-css/toast@^3.0.1":
+  version "3.0.3"
+  resolved "https://registry.yarnpkg.com/@spectrum-css/toast/-/toast-3.0.3.tgz#97c1527384707600832ecda35643ed304615250f"
+  integrity sha512-CjLeaMs+cjUXojCCRtbj0YkD2BoZW16kjj2o5omkEpUTjA34IJ8xJ1a+CCtDILWekhXvN0MBN4sbumcnwcnx8w==
+
+"@spectrum-css/tooltip@^3.0.3":
+  version "3.0.3"
+  resolved "https://registry.yarnpkg.com/@spectrum-css/tooltip/-/tooltip-3.0.3.tgz#26b8ca3b3d30e29630244d85eb4fc11d0c841281"
+  integrity sha512-ztRF7WW1FzyNavXBRc+80z67UoOrY9wl3cMYsVD3MpDnyxdzP8cjza1pCcolKBaFqRTcQKkxKw3GWtGICRKR5A==
+
+"@spectrum-css/treeview@^3.0.2":
+  version "3.0.3"
+  resolved "https://registry.yarnpkg.com/@spectrum-css/treeview/-/treeview-3.0.3.tgz#aeda5175158b9f8d7529cb2b394428eb2a428046"
+  integrity sha512-D5gGzZC/KtRArdx86Mesc9+99W9nTbUOeyYGqoJoAfJSOttoT6Tk5CrDvlCmAqjKf5rajemAkGri1ChqvUIwkw==
+
+"@spectrum-css/typography@^3.0.1", "@spectrum-css/typography@^3.0.2":
+  version "3.0.2"
+  resolved "https://registry.yarnpkg.com/@spectrum-css/typography/-/typography-3.0.2.tgz#ea3ca0a60e18064527819d48c8c4364cab4fcd38"
+  integrity sha512-5ZOLmQe0edzsDMyhghUd4hBb5uxGsFrxzf+WasfcUw9klSfTsRZ09n1BsaaWbgrLjlMQ+EEHS46v5VNo0Ms2CA==
+
+"@spectrum-css/underlay@^2.0.9":
+  version "2.0.10"
+  resolved "https://registry.yarnpkg.com/@spectrum-css/underlay/-/underlay-2.0.10.tgz#8b75b646605a311850f6620caa18d4996cd64ed7"
+  integrity sha512-PmsmkzeGD/rY4pp3ILXHt9w8BW7uaEqXe08hQRS7rGki7wqCpG4mE0/8N3yEcA3QxWQclmG9gdkg5uz6wMmYzA==
+
 "@spectrum-css/vars@^3.0.1":
   version "3.0.1"
   resolved "https://registry.yarnpkg.com/@spectrum-css/vars/-/vars-3.0.1.tgz#561fd69098f896a647242dd8d6108af603bfa31e"
   integrity sha512-l4oRcCOqInChYXZN6OQhpe3isk6l4OE6Ys8cgdlsiKp53suNoQxyyd9p/eGRbCjZgH3xQ8nK0t4DHa7QYC0S6w==
 
+"@spectrum-css/vars@^3.0.2":
+  version "3.0.2"
+  resolved "https://registry.yarnpkg.com/@spectrum-css/vars/-/vars-3.0.2.tgz#ea9062c3c98dfc6ba59e5df14a03025ad8969999"
+  integrity sha512-vzS9KqYXot4J3AEER/u618MXWAS+IoMvYMNrOoscKiLLKYQWenaueakUWulFonToPd/9vIpqtdbwxznqrK5qDw==
+
 "@sveltejs/vite-plugin-svelte@^1.0.0-next.5":
   version "1.0.0-next.5"
   resolved "https://registry.yarnpkg.com/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-1.0.0-next.5.tgz#8cf608f7a3c33dfa5b648397aae1ba90e6a4883f"
@@ -1611,12 +1936,24 @@ anymatch@^3.0.3:
     normalize-path "^3.0.0"
     picomatch "^2.0.4"
 
+apexcharts@^3.19.2, apexcharts@^3.22.1:
+  version "3.28.3"
+  resolved "https://registry.yarnpkg.com/apexcharts/-/apexcharts-3.28.3.tgz#22a6d9b234c82f6c2e1dd4aebba05b7603e2b1d2"
+  integrity sha512-EhghB2P27/Gjhwct8sSS0V63mdpRMx/ikH34dwUTqZQnkAEyOS/RKDmYjXBNA7zsAKBE/pThOdoTya6ADyk6zQ==
+  dependencies:
+    svg.draggable.js "^2.2.2"
+    svg.easing.js "^2.0.0"
+    svg.filter.js "^2.0.2"
+    svg.pathmorphing.js "^0.1.3"
+    svg.resize.js "^1.4.3"
+    svg.select.js "^3.0.1"
+
 arch@^2.1.2:
   version "2.2.0"
   resolved "https://registry.yarnpkg.com/arch/-/arch-2.2.0.tgz#1bc47818f305764f23ab3306b0bfc086c5a29d11"
   integrity sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==
 
-argparse@^1.0.7:
+argparse@^1.0.10, argparse@^1.0.7:
   version "1.0.10"
   resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911"
   integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==
@@ -1646,6 +1983,15 @@ arr-union@^3.1.0:
   resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4"
   integrity sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=
 
+array-sort@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/array-sort/-/array-sort-1.0.0.tgz#e4c05356453f56f53512a7d1d6123f2c54c0a88a"
+  integrity sha512-ihLeJkonmdiAsD7vpgN3CRcx2J2S0TiYW+IS/5zHBI7mKUq3ySvBdzzBfD236ubDBQFiiyG3SWCPc+msQ9KoYg==
+  dependencies:
+    default-compare "^1.0.0"
+    get-value "^2.0.6"
+    kind-of "^5.0.2"
+
 array-union@^2.1.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d"
@@ -1693,6 +2039,13 @@ atob@^2.1.2:
   resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9"
   integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==
 
+autolinker@~0.28.0:
+  version "0.28.1"
+  resolved "https://registry.yarnpkg.com/autolinker/-/autolinker-0.28.1.tgz#0652b491881879f0775dace0cdca3233942a4e47"
+  integrity sha1-BlK0kYgYefB3XazgzcoyM5QqTkc=
+  dependencies:
+    gulp-header "^1.7.1"
+
 aws-sign2@~0.7.0:
   version "0.7.0"
   resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8"
@@ -2184,6 +2537,13 @@ concat-stream@^1.6.2:
     readable-stream "^2.2.2"
     typedarray "^0.0.6"
 
+concat-with-sourcemaps@*:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/concat-with-sourcemaps/-/concat-with-sourcemaps-1.1.0.tgz#d4ea93f05ae25790951b99e7b3b09e3908a4082e"
+  integrity sha512-4gEjHJFT9e+2W/77h/DS5SGUgwDaOwprX8L/gl5+3ixnzkVJJsZWDSelmN3Oilw3LNDZjZV0yqH1hLG3k6nghg==
+  dependencies:
+    source-map "^0.6.1"
+
 configent@^2.1.4:
   version "2.2.0"
   resolved "https://registry.yarnpkg.com/configent/-/configent-2.2.0.tgz#2de230fc43f22c47cfd99016aa6962d6f9546994"
@@ -2356,6 +2716,18 @@ date-fns@^1.27.2:
   resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-1.30.1.tgz#2e71bf0b119153dbb4cc4e88d9ea5acfb50dc05c"
   integrity sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw==
 
+date.js@^0.3.1:
+  version "0.3.3"
+  resolved "https://registry.yarnpkg.com/date.js/-/date.js-0.3.3.tgz#ef1e92332f507a638795dbb985e951882e50bbda"
+  integrity sha512-HgigOS3h3k6HnW011nAb43c5xx5rBXk8P2v/WIT9Zv4koIaVXiH2BURguI78VVp+5Qc076T7OR378JViCnZtBw==
+  dependencies:
+    debug "~3.1.0"
+
+dayjs@^1.10.4, dayjs@^1.10.5:
+  version "1.10.7"
+  resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.10.7.tgz#2cf5f91add28116748440866a0a1d26f3a6ce468"
+  integrity sha512-P6twpd70BcPK34K26uJ1KT3wlhpuOAPoMwJzpsIWUxHZ7wpmbdZL/hQqBDfz7hGurYSa5PhzdhDHtt319hL3ig==
+
 debug@4.3.1, debug@^4.1.0, debug@^4.1.1:
   version "4.3.1"
   resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee"
@@ -2384,6 +2756,13 @@ debug@^4.3.2:
   dependencies:
     ms "2.1.2"
 
+debug@~3.1.0:
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
+  integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==
+  dependencies:
+    ms "2.0.0"
+
 decamelize@^1.2.0:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
@@ -2409,6 +2788,13 @@ deepmerge@^4.2.2:
   resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955"
   integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==
 
+default-compare@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/default-compare/-/default-compare-1.0.0.tgz#cb61131844ad84d84788fb68fd01681ca7781a2f"
+  integrity sha512-QWfXlM0EkAbqOCbD/6HjdwT19j7WCkMyiRhWilc4H9/5h/RzTF9gv5LYh1+CmDV5d1rki6KAWLtQale0xt20eQ==
+  dependencies:
+    kind-of "^5.0.2"
+
 define-properties@^1.1.3:
   version "1.1.3"
   resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1"
@@ -2522,6 +2908,11 @@ end-of-stream@^1.1.0:
   dependencies:
     once "^1.4.0"
 
+ent@^2.2.0:
+  version "2.2.0"
+  resolved "https://registry.yarnpkg.com/ent/-/ent-2.2.0.tgz#e964219325a21d05f44466a2f686ed6ce5f5dd1d"
+  integrity sha1-6WQhkyWiHQX0RGai9obtbOX13R0=
+
 error-ex@^1.3.1:
   version "1.3.2"
   resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf"
@@ -2759,6 +3150,11 @@ extsprintf@^1.2.0:
   resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f"
   integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8=
 
+"falsey@^1.0.0":
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/falsey/-/falsey-1.0.0.tgz#71bdd775c24edad9f2f5c015ce8be24400bb5d7d"
+  integrity sha512-zMDNZ/Ipd8MY0+346CPvhzP1AsiVyNfTOayJza4reAIWf72xbkuFUDcJNxSAsQE1b9Bu0wijKb8Ngnh/a7fI5w==
+
 fast-deep-equal@^3.1.1:
   version "3.1.3"
   resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
@@ -2847,6 +3243,11 @@ find-up@^4.0.0, find-up@^4.1.0:
     locate-path "^5.0.0"
     path-exists "^4.0.0"
 
+flatpickr@^4.5.2:
+  version "4.6.9"
+  resolved "https://registry.yarnpkg.com/flatpickr/-/flatpickr-4.6.9.tgz#9a13383e8a6814bda5d232eae3fcdccb97dc1499"
+  integrity sha512-F0azNNi8foVWKSF+8X+ZJzz8r9sE1G4hl06RyceIaLvyltKvDl6vqk9Lm/6AUUCi5HWaIjiUbk7UpeE/fOXOpw==
+
 fn-name@~3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/fn-name/-/fn-name-3.0.0.tgz#0596707f635929634d791f452309ab41558e3c5c"
@@ -2888,6 +3289,11 @@ from@~0:
   resolved "https://registry.yarnpkg.com/from/-/from-0.1.7.tgz#83c60afc58b9c56997007ed1a768b3ab303a44fe"
   integrity sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4=
 
+fs-exists-sync@^0.1.0:
+  version "0.1.0"
+  resolved "https://registry.yarnpkg.com/fs-exists-sync/-/fs-exists-sync-0.1.0.tgz#982d6893af918e72d08dec9e8673ff2b5a8d6add"
+  integrity sha1-mC1ok6+RjnLQjeyehnP/K1qNat0=
+
 fs-extra@^8.1.0:
   version "8.1.0"
   resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0"
@@ -2941,6 +3347,14 @@ get-intrinsic@^1.0.2:
     has "^1.0.3"
     has-symbols "^1.0.1"
 
+get-object@^0.2.0:
+  version "0.2.0"
+  resolved "https://registry.yarnpkg.com/get-object/-/get-object-0.2.0.tgz#d92ff7d5190c64530cda0543dac63a3d47fe8c0c"
+  integrity sha1-2S/31RkMZFMM2gVD2sY6PUf+jAw=
+  dependencies:
+    is-number "^2.0.2"
+    isobject "^0.2.0"
+
 get-package-type@^0.1.0:
   version "0.1.0"
   resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a"
@@ -2965,6 +3379,13 @@ get-value@^2.0.3, get-value@^2.0.6:
   resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28"
   integrity sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=
 
+get-value@^3.0.0, get-value@^3.0.1:
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/get-value/-/get-value-3.0.1.tgz#5efd2a157f1d6a516d7524e124ac52d0a39ef5a8"
+  integrity sha512-mKZj9JLQrwMBtj5wxi6MH8Z5eSKaERpAwjg43dPtlGI1ZVEgH/qC7T8/6R2OBSUA+zzHBZgICsVJaEIV2tKTDA==
+  dependencies:
+    isobject "^3.0.1"
+
 getos@^3.2.1:
   version "3.2.1"
   resolved "https://registry.yarnpkg.com/getos/-/getos-3.2.1.tgz#0134d1f4e00eb46144c5a9c0ac4dc087cbb27dc5"
@@ -3034,6 +3455,35 @@ growly@^1.3.0:
   resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081"
   integrity sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=
 
+gulp-header@^1.7.1:
+  version "1.8.12"
+  resolved "https://registry.yarnpkg.com/gulp-header/-/gulp-header-1.8.12.tgz#ad306be0066599127281c4f8786660e705080a84"
+  integrity sha512-lh9HLdb53sC7XIZOYzTXM4lFuXElv3EVkSDhsd7DoJBj7hm+Ni7D3qYbb+Rr8DuM8nRanBvkVO9d7askreXGnQ==
+  dependencies:
+    concat-with-sourcemaps "*"
+    lodash.template "^4.4.0"
+    through2 "^2.0.0"
+
+handlebars-utils@^1.0.2, handlebars-utils@^1.0.4, handlebars-utils@^1.0.6:
+  version "1.0.6"
+  resolved "https://registry.yarnpkg.com/handlebars-utils/-/handlebars-utils-1.0.6.tgz#cb9db43362479054782d86ffe10f47abc76357f9"
+  integrity sha512-d5mmoQXdeEqSKMtQQZ9WkiUcO1E3tPbWxluCK9hVgIDPzQa9WsKo3Lbe/sGflTe7TomHEeZaOgwIkyIr1kfzkw==
+  dependencies:
+    kind-of "^6.0.0"
+    typeof-article "^0.1.1"
+
+handlebars@^4.7.6, handlebars@^4.7.7:
+  version "4.7.7"
+  resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.7.tgz#9ce33416aad02dbd6c8fafa8240d5d98004945a1"
+  integrity sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==
+  dependencies:
+    minimist "^1.2.5"
+    neo-async "^2.6.0"
+    source-map "^0.6.1"
+    wordwrap "^1.0.0"
+  optionalDependencies:
+    uglify-js "^3.1.4"
+
 har-schema@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92"
@@ -3092,6 +3542,14 @@ has-value@^1.0.0:
     has-values "^1.0.0"
     isobject "^3.0.0"
 
+has-value@^2.0.2:
+  version "2.0.2"
+  resolved "https://registry.yarnpkg.com/has-value/-/has-value-2.0.2.tgz#d0f12e8780ba8e90e66ad1a21c707fdb67c25658"
+  integrity sha512-ybKOlcRsK2MqrM3Hmz/lQxXHZ6ejzSPzpNabKB45jb5qDgJvKPa3SdapTsTLwEb9WltgWpOmNax7i+DzNOk4TA==
+  dependencies:
+    get-value "^3.0.0"
+    has-values "^2.0.1"
+
 has-values@^0.1.4:
   version "0.1.4"
   resolved "https://registry.yarnpkg.com/has-values/-/has-values-0.1.4.tgz#6d61de95d91dfca9b9a02089ad384bff8f62b771"
@@ -3105,6 +3563,13 @@ has-values@^1.0.0:
     is-number "^3.0.0"
     kind-of "^4.0.0"
 
+has-values@^2.0.1:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/has-values/-/has-values-2.0.1.tgz#3876200ff86d8a8546a9264a952c17d5fc17579d"
+  integrity sha512-+QdH3jOmq9P8GfdjFg0eJudqx1FqU62NQJ4P16rOEHeRdl7ckgwn6uqQjzYE0ZoHVV/e5E2esuJ5Gl5+HUW19w==
+  dependencies:
+    kind-of "^6.0.2"
+
 has@^1.0.3:
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796"
@@ -3117,6 +3582,39 @@ hash-sum@^2.0.0:
   resolved "https://registry.yarnpkg.com/hash-sum/-/hash-sum-2.0.0.tgz#81d01bb5de8ea4a214ad5d6ead1b523460b0b45a"
   integrity sha512-WdZTbAByD+pHfl/g9QSsBIIwy8IT+EsPiKDs0KNX+zSHhdDLFKdZu0BQHljvO+0QI/BasbMSUa8wYNCZTvhslg==
 
+helper-date@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/helper-date/-/helper-date-1.0.1.tgz#12fedea3ad8e44a7ca4c4efb0ff4104a5120cffb"
+  integrity sha512-wU3VOwwTJvGr/w5rZr3cprPHO+hIhlblTJHD6aFBrKLuNbf4lAmkawd2iK3c6NbJEvY7HAmDpqjOFSI5/+Ey2w==
+  dependencies:
+    date.js "^0.3.1"
+    handlebars-utils "^1.0.4"
+    moment "^2.18.1"
+
+helper-markdown@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/helper-markdown/-/helper-markdown-1.0.0.tgz#ee7e9fc554675007d37eb90f7853b13ce74f3e10"
+  integrity sha512-AnDqMS4ejkQK0MXze7pA9TM3pu01ZY+XXsES6gEE0RmCGk5/NIfvTn0NmItfyDOjRAzyo9z6X7YHbHX4PzIvOA==
+  dependencies:
+    handlebars-utils "^1.0.2"
+    highlight.js "^9.12.0"
+    remarkable "^1.7.1"
+
+helper-md@^0.2.2:
+  version "0.2.2"
+  resolved "https://registry.yarnpkg.com/helper-md/-/helper-md-0.2.2.tgz#c1f59d7e55bbae23362fd8a0e971607aec69d41f"
+  integrity sha1-wfWdflW7riM2L9ig6XFgeuxp1B8=
+  dependencies:
+    ent "^2.2.0"
+    extend-shallow "^2.0.1"
+    fs-exists-sync "^0.1.0"
+    remarkable "^1.6.2"
+
+highlight.js@^9.12.0:
+  version "9.18.5"
+  resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.18.5.tgz#d18a359867f378c138d6819edfc2a8acd5f29825"
+  integrity sha512-a5bFyofd/BHCX52/8i8uJkjr9DYwXIPnM/plwI6W7ezItLGqzt7X2G2nXuYSfsIJdkwwj/g9DG1LkcGJI/dDoA==
+
 hosted-git-info@^2.1.4:
   version "2.8.9"
   resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9"
@@ -3134,6 +3632,14 @@ html-escaper@^2.0.0:
   resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453"
   integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==
 
+html-tag@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/html-tag/-/html-tag-2.0.0.tgz#36c3bc8d816fd30b570d5764a497a641640c2fed"
+  integrity sha512-XxzooSo6oBoxBEUazgjdXj7VwTn/iSTSZzTYKzYY6I916tkaYzypHxy+pbVU1h+0UQ9JlVf5XkNQyxOAiiQO1g==
+  dependencies:
+    is-self-closing "^1.0.1"
+    kind-of "^6.0.0"
+
 http-signature@~1.2.0:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1"
@@ -3291,6 +3797,13 @@ is-docker@^2.0.0:
   resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.1.1.tgz#4125a88e44e450d384e09047ede71adc2d144156"
   integrity sha512-ZOoqiXfEwtGknTiuDEy8pN2CfE3TxMHprvNer1mXiqwkOT77Rw3YVrUQ52EqAOU3QAWDQ+bQdx7HJzrv7LS2Hw==
 
+is-even@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/is-even/-/is-even-1.0.0.tgz#76b5055fbad8d294a86b6a949015e1c97b717c06"
+  integrity sha1-drUFX7rY0pSoa2qUkBXhyXtxfAY=
+  dependencies:
+    is-odd "^0.1.2"
+
 is-extendable@^0.1.0, is-extendable@^0.1.1:
   version "0.1.1"
   resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89"
@@ -3345,6 +3858,13 @@ is-installed-globally@^0.3.2:
     global-dirs "^2.0.1"
     is-path-inside "^3.0.1"
 
+is-number@^2.0.2:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/is-number/-/is-number-2.1.0.tgz#01fcbbb393463a548f2f466cce16dece49db908f"
+  integrity sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=
+  dependencies:
+    kind-of "^3.0.2"
+
 is-number@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195"
@@ -3364,6 +3884,13 @@ is-observable@^1.1.0:
   dependencies:
     symbol-observable "^1.1.0"
 
+is-odd@^0.1.2:
+  version "0.1.2"
+  resolved "https://registry.yarnpkg.com/is-odd/-/is-odd-0.1.2.tgz#bc573b5ce371ef2aad6e6f49799b72bef13978a7"
+  integrity sha1-vFc7XONx7yqtbm9JeZtyvvE5eKc=
+  dependencies:
+    is-number "^3.0.0"
+
 is-path-inside@^3.0.1:
   version "3.0.3"
   resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283"
@@ -3391,6 +3918,13 @@ is-promise@^2.1.0:
   resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.2.2.tgz#39ab959ccbf9a774cf079f7b40c7a26f763135f1"
   integrity sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==
 
+is-self-closing@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/is-self-closing/-/is-self-closing-1.0.1.tgz#5f406b527c7b12610176320338af0fa3896416e4"
+  integrity sha512-E+60FomW7Blv5GXTlYee2KDrnG6srxF7Xt1SjrhWUGUEsTFIqY/nq2y3DaftCsgUMdh89V07IVfhY9KIJhLezg==
+  dependencies:
+    self-closing-tags "^1.0.1"
+
 is-stream@^1.1.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
@@ -3433,6 +3967,11 @@ isexe@^2.0.0:
   resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
   integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=
 
+isobject@^0.2.0:
+  version "0.2.0"
+  resolved "https://registry.yarnpkg.com/isobject/-/isobject-0.2.0.tgz#a3432192f39b910b5f02cc989487836ec70aa85e"
+  integrity sha1-o0MhkvObkQtfAsyYlIeDbscKqF4=
+
 isobject@^2.0.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89"
@@ -3988,7 +4527,7 @@ jsprim@^1.2.2:
     json-schema "0.2.3"
     verror "1.10.0"
 
-kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0:
+kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.1.0, kind-of@^3.2.0:
   version "3.2.2"
   resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64"
   integrity sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=
@@ -4002,12 +4541,12 @@ kind-of@^4.0.0:
   dependencies:
     is-buffer "^1.1.5"
 
-kind-of@^5.0.0:
+kind-of@^5.0.0, kind-of@^5.0.2:
   version "5.1.0"
   resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d"
   integrity sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==
 
-kind-of@^6.0.0, kind-of@^6.0.2:
+kind-of@^6.0.0, kind-of@^6.0.2, kind-of@^6.0.3:
   version "6.0.3"
   resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd"
   integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==
@@ -4096,6 +4635,11 @@ lodash-es@^4.17.11:
   resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.21.tgz#43e626c46e6591b7750beb2b50117390c609e3ee"
   integrity sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==
 
+lodash._reinterpolate@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d"
+  integrity sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=
+
 lodash.debounce@^4.0.8:
   version "4.0.8"
   resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
@@ -4106,7 +4650,22 @@ lodash.once@^4.1.1:
   resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac"
   integrity sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=
 
-lodash@4.17.21, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.21, lodash@^4.7.0:
+lodash.template@^4.4.0:
+  version "4.5.0"
+  resolved "https://registry.yarnpkg.com/lodash.template/-/lodash.template-4.5.0.tgz#f976195cf3f347d0d5f52483569fe8031ccce8ab"
+  integrity sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A==
+  dependencies:
+    lodash._reinterpolate "^3.0.0"
+    lodash.templatesettings "^4.0.0"
+
+lodash.templatesettings@^4.0.0:
+  version "4.2.0"
+  resolved "https://registry.yarnpkg.com/lodash.templatesettings/-/lodash.templatesettings-4.2.0.tgz#e481310f049d3cf6d47e912ad09313b154f0fb33"
+  integrity sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ==
+  dependencies:
+    lodash._reinterpolate "^3.0.0"
+
+lodash@4.17.21, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.7.0:
   version "4.17.21"
   resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
   integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
@@ -4207,7 +4766,7 @@ methods@^1.1.2:
   resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
   integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=
 
-micromatch@^3.1.4:
+micromatch@^3.1.4, micromatch@^3.1.5:
   version "3.1.10"
   resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23"
   integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==
@@ -4288,7 +4847,7 @@ mkdirp@^0.5.4:
   dependencies:
     minimist "^1.2.5"
 
-moment@^2.27.0:
+moment@^2.18.1, moment@^2.27.0:
   version "2.29.1"
   resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3"
   integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==
@@ -4345,6 +4904,11 @@ ncp@^2.0.0:
   resolved "https://registry.yarnpkg.com/ncp/-/ncp-2.0.0.tgz#195a21d6c46e361d2fb1281ba38b91e9df7bdbb3"
   integrity sha1-GVoh1sRuNh0vsSgbo4uR6d9727M=
 
+neo-async@^2.6.0:
+  version "2.6.2"
+  resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f"
+  integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==
+
 nice-try@^1.0.4:
   version "1.0.5"
   resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366"
@@ -4792,7 +5356,7 @@ read-pkg@^5.2.0:
     parse-json "^5.0.0"
     type-fest "^0.6.0"
 
-readable-stream@^2.2.2:
+readable-stream@^2.2.2, readable-stream@~2.3.6:
   version "2.3.7"
   resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57"
   integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==
@@ -4845,6 +5409,16 @@ regex-not@^1.0.0, regex-not@^1.0.2:
     extend-shallow "^3.0.2"
     safe-regex "^1.1.0"
 
+regexparam@2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/regexparam/-/regexparam-2.0.0.tgz#059476767d5f5f87f735fc7922d133fd1a118c8c"
+  integrity sha512-gJKwd2MVPWHAIFLsaYDZfyKzHNS4o7E/v8YmNf44vmeV2e4YfVoDToTOKTvE7ab68cRJ++kLuEXJBaEeJVt5ow==
+
+regexparam@^1.3.0:
+  version "1.3.0"
+  resolved "https://registry.yarnpkg.com/regexparam/-/regexparam-1.3.0.tgz#2fe42c93e32a40eff6235d635e0ffa344b92965f"
+  integrity sha512-6IQpFBv6e5vz1QAqI+V4k8P2e/3gRrqfCJ9FI+O1FLQTO+Uz6RXZEZOPmTJ6hlGj7gkERzY5BRCv09whKP96/g==
+
 regexpu-core@^4.7.1:
   version "4.7.1"
   resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-4.7.1.tgz#2dea5a9a07233298fbf0db91fa9abc4c6e0f8ad6"
@@ -4869,6 +5443,21 @@ regjsparser@^0.6.4:
   dependencies:
     jsesc "~0.5.0"
 
+relative@^3.0.2:
+  version "3.0.2"
+  resolved "https://registry.yarnpkg.com/relative/-/relative-3.0.2.tgz#0dcd8ec54a5d35a3c15e104503d65375b5a5367f"
+  integrity sha1-Dc2OxUpdNaPBXhBFA9ZTdbWlNn8=
+  dependencies:
+    isobject "^2.0.0"
+
+remarkable@^1.6.2, remarkable@^1.7.1:
+  version "1.7.4"
+  resolved "https://registry.yarnpkg.com/remarkable/-/remarkable-1.7.4.tgz#19073cb960398c87a7d6546eaa5e50d2022fcd00"
+  integrity sha512-e6NKUXgX95whv7IgddywbeN/ItCkWbISmc2DiqHJb0wTrqZIexqdco5b8Z3XZoo/48IdNVKM9ZCvTPJ4F5uvhg==
+  dependencies:
+    argparse "^1.0.10"
+    autolinker "~0.28.0"
+
 remixicon@2.5.0:
   version "2.5.0"
   resolved "https://registry.yarnpkg.com/remixicon/-/remixicon-2.5.0.tgz#b5e245894a1550aa23793f95daceadbf96ad1a41"
@@ -5104,6 +5693,11 @@ saxes@^5.0.1:
   dependencies:
     xmlchars "^2.2.0"
 
+self-closing-tags@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/self-closing-tags/-/self-closing-tags-1.0.1.tgz#6c5fa497994bb826b484216916371accee490a5d"
+  integrity sha512-7t6hNbYMxM+VHXTgJmxwgZgLGktuXtVVD5AivWzNTdJBM4DBjnDKDzkf2SrNjihaArpeJYNjxkELBu1evI4lQA==
+
 "semver@2 || 3 || 4 || 5", semver@^5.5.0:
   version "5.7.1"
   resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
@@ -5177,6 +5771,13 @@ shortid@2.2.15:
   dependencies:
     nanoid "^2.1.0"
 
+shortid@^2.2.15:
+  version "2.2.16"
+  resolved "https://registry.yarnpkg.com/shortid/-/shortid-2.2.16.tgz#b742b8f0cb96406fd391c76bfc18a67a57fe5608"
+  integrity sha512-Ugt+GIZqvGXCIItnsL+lvFJOiN7RYqlGy7QE41O3YC1xbNSeDGIRO7xg2JJXIAj1cAGnOeC1r7/T9pgrtQbv4g==
+  dependencies:
+    nanoid "^2.1.0"
+
 signal-exit@^3.0.0, signal-exit@^3.0.2:
   version "3.0.3"
   resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c"
@@ -5468,6 +6069,11 @@ strip-indent@^3.0.0:
   dependencies:
     min-indent "^1.0.0"
 
+striptags@^3.1.1:
+  version "3.2.0"
+  resolved "https://registry.yarnpkg.com/striptags/-/striptags-3.2.0.tgz#cc74a137db2de8b0b9a370006334161f7dd67052"
+  integrity sha512-g45ZOGzHDMe2bdYMdIvdAfCQkCTDMGBazSw1ypMowwGIee7ZQ5dU0rBJ8Jqgl+jAKIv4dbeE1jscZq9wid1Tkw==
+
 supports-color@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7"
@@ -5495,11 +6101,25 @@ supports-hyperlinks@^2.0.0:
     has-flag "^4.0.0"
     supports-color "^7.0.0"
 
+svelte-apexcharts@^1.0.2:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/svelte-apexcharts/-/svelte-apexcharts-1.0.2.tgz#4e000f8b8f7c901c05658c845457dfc8314d54c1"
+  integrity sha512-6qlx4rE+XsonZ0FZudfwqOQ34Pq+3wpxgAD75zgEmGoYhYBJcwmikTuTf3o8ZBsZue9U/pAwhNy3ed1Bkq1gmA==
+  dependencies:
+    apexcharts "^3.19.2"
+
 svelte-dnd-action@^0.9.8:
   version "0.9.8"
   resolved "https://registry.yarnpkg.com/svelte-dnd-action/-/svelte-dnd-action-0.9.8.tgz#d8e6813aba64148b38ac65ec5df7b153568b5cfa"
   integrity sha512-EWzxSpgNRkD6t8oHWcY4hqMoDJ16nkeFTza3V5K1r8GDqCJwK32zhpNv9ETDwfQiy2V2bJJMrH7io9xdlkYadg==
 
+svelte-flatpickr@^3.1.0:
+  version "3.2.3"
+  resolved "https://registry.yarnpkg.com/svelte-flatpickr/-/svelte-flatpickr-3.2.3.tgz#db5dd7ad832ef83262b45e09737955ad3d591fc8"
+  integrity sha512-PNkqK4Napx8nTvCwkaUXdnKo8dISThaxEOK+szTUXcY6H0dQM0TSyuoMaVWY2yX7pM+PN5cpCQCcVe8YvTRFSw==
+  dependencies:
+    flatpickr "^4.5.2"
+
 svelte-hmr@^0.13.3:
   version "0.13.3"
   resolved "https://registry.yarnpkg.com/svelte-hmr/-/svelte-hmr-0.13.3.tgz#fba5739b477ea44caf70e542a24a4352bee2b897"
@@ -5522,11 +6142,78 @@ svelte-portal@0.1.0:
   resolved "https://registry.yarnpkg.com/svelte-portal/-/svelte-portal-0.1.0.tgz#cc2821cc84b05ed5814e0218dcdfcbebc53c1742"
   integrity sha512-kef+ksXVKun224mRxat+DdO4C+cGHla+fEcZfnBAvoZocwiaceOfhf5azHYOPXSSB1igWVFTEOF3CDENPnuWxg==
 
+svelte-portal@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/svelte-portal/-/svelte-portal-1.0.0.tgz#36a47c5578b1a4d9b4dc60fa32a904640ec4cdd3"
+  integrity sha512-nHf+DS/jZ6jjnZSleBMSaZua9JlG5rZv9lOGKgJuaZStfevtjIlUJrkLc3vbV8QdBvPPVmvcjTlazAzfKu0v3Q==
+
+svelte-spa-router@^3.0.5:
+  version "3.2.0"
+  resolved "https://registry.yarnpkg.com/svelte-spa-router/-/svelte-spa-router-3.2.0.tgz#fae3311d292451236cb57131262406cf312b15ee"
+  integrity sha512-igemo5Vs82TGBBw+DjWt6qKameXYzNs6aDXcTxou5XbEvOjiRcAM6MLkdVRCatn6u8r42dE99bt/br7T4qe/AQ==
+  dependencies:
+    regexparam "2.0.0"
+
 svelte@^3.38.2:
   version "3.38.2"
   resolved "https://registry.yarnpkg.com/svelte/-/svelte-3.38.2.tgz#55e5c681f793ae349b5cc2fe58e5782af4275ef5"
   integrity sha512-q5Dq0/QHh4BLJyEVWGe7Cej5NWs040LWjMbicBGZ+3qpFWJ1YObRmUDZKbbovddLC9WW7THTj3kYbTOFmU9fbg==
 
+svg.draggable.js@^2.2.2:
+  version "2.2.2"
+  resolved "https://registry.yarnpkg.com/svg.draggable.js/-/svg.draggable.js-2.2.2.tgz#c514a2f1405efb6f0263e7958f5b68fce50603ba"
+  integrity sha512-JzNHBc2fLQMzYCZ90KZHN2ohXL0BQJGQimK1kGk6AvSeibuKcIdDX9Kr0dT9+UJ5O8nYA0RB839Lhvk4CY4MZw==
+  dependencies:
+    svg.js "^2.0.1"
+
+svg.easing.js@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/svg.easing.js/-/svg.easing.js-2.0.0.tgz#8aa9946b0a8e27857a5c40a10eba4091e5691f12"
+  integrity sha1-iqmUawqOJ4V6XEChDrpAkeVpHxI=
+  dependencies:
+    svg.js ">=2.3.x"
+
+svg.filter.js@^2.0.2:
+  version "2.0.2"
+  resolved "https://registry.yarnpkg.com/svg.filter.js/-/svg.filter.js-2.0.2.tgz#91008e151389dd9230779fcbe6e2c9a362d1c203"
+  integrity sha1-kQCOFROJ3ZIwd5/L5uLJo2LRwgM=
+  dependencies:
+    svg.js "^2.2.5"
+
+svg.js@>=2.3.x, svg.js@^2.0.1, svg.js@^2.2.5, svg.js@^2.4.0, svg.js@^2.6.5:
+  version "2.7.1"
+  resolved "https://registry.yarnpkg.com/svg.js/-/svg.js-2.7.1.tgz#eb977ed4737001eab859949b4a398ee1bb79948d"
+  integrity sha512-ycbxpizEQktk3FYvn/8BH+6/EuWXg7ZpQREJvgacqn46gIddG24tNNe4Son6omdXCnSOaApnpZw6MPCBA1dODA==
+
+svg.pathmorphing.js@^0.1.3:
+  version "0.1.3"
+  resolved "https://registry.yarnpkg.com/svg.pathmorphing.js/-/svg.pathmorphing.js-0.1.3.tgz#c25718a1cc7c36e852ecabc380e758ac09bb2b65"
+  integrity sha512-49HWI9X4XQR/JG1qXkSDV8xViuTLIWm/B/7YuQELV5KMOPtXjiwH4XPJvr/ghEDibmLQ9Oc22dpWpG0vUDDNww==
+  dependencies:
+    svg.js "^2.4.0"
+
+svg.resize.js@^1.4.3:
+  version "1.4.3"
+  resolved "https://registry.yarnpkg.com/svg.resize.js/-/svg.resize.js-1.4.3.tgz#885abd248e0cd205b36b973c4b578b9a36f23332"
+  integrity sha512-9k5sXJuPKp+mVzXNvxz7U0uC9oVMQrrf7cFsETznzUDDm0x8+77dtZkWdMfRlmbkEEYvUn9btKuZ3n41oNA+uw==
+  dependencies:
+    svg.js "^2.6.5"
+    svg.select.js "^2.1.2"
+
+svg.select.js@^2.1.2:
+  version "2.1.2"
+  resolved "https://registry.yarnpkg.com/svg.select.js/-/svg.select.js-2.1.2.tgz#e41ce13b1acff43a7441f9f8be87a2319c87be73"
+  integrity sha512-tH6ABEyJsAOVAhwcCjF8mw4crjXSI1aa7j2VQR8ZuJ37H2MBUbyeqYr5nEO7sSN3cy9AR9DUwNg0t/962HlDbQ==
+  dependencies:
+    svg.js "^2.2.5"
+
+svg.select.js@^3.0.1:
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/svg.select.js/-/svg.select.js-3.0.1.tgz#a4198e359f3825739226415f82176a90ea5cc917"
+  integrity sha512-h5IS/hKkuVCbKSieR9uQCj9w+zLHoPh+ce19bBYyqF53g6mnPB8sAtIbe1s9dh2S2fCmYX2xel1Ln3PJBbK4kw==
+  dependencies:
+    svg.js "^2.6.5"
+
 symbol-observable@^1.1.0:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804"
@@ -5569,6 +6256,14 @@ throttleit@^1.0.0:
   resolved "https://registry.yarnpkg.com/throttleit/-/throttleit-1.0.0.tgz#9e785836daf46743145a5984b6268d828528ac6c"
   integrity sha1-nnhYNtr0Z0MUWlmEtiaNgoUorGw=
 
+through2@^2.0.0:
+  version "2.0.5"
+  resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd"
+  integrity sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==
+  dependencies:
+    readable-stream "~2.3.6"
+    xtend "~4.0.1"
+
 through@2, through@~2.3, through@~2.3.1:
   version "2.3.8"
   resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
@@ -5582,15 +6277,20 @@ tmp@~0.2.1:
     rimraf "^3.0.0"
 
 tmpl@1.0.x:
-  version "1.0.4"
-  resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.4.tgz#23640dd7b42d00433911140820e5cf440e521dd1"
-  integrity sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE=
+  version "1.0.5"
+  resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc"
+  integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==
 
 to-fast-properties@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e"
   integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=
 
+to-gfm-code-block@^0.1.1:
+  version "0.1.1"
+  resolved "https://registry.yarnpkg.com/to-gfm-code-block/-/to-gfm-code-block-0.1.1.tgz#25d045a5fae553189e9637b590900da732d8aa82"
+  integrity sha1-JdBFpfrlUxielje1kJANpzLYqoI=
+
 to-object-path@^0.3.0:
   version "0.3.0"
   resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af"
@@ -5708,6 +6408,18 @@ typedarray@^0.0.6:
   resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
   integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
 
+typeof-article@^0.1.1:
+  version "0.1.1"
+  resolved "https://registry.yarnpkg.com/typeof-article/-/typeof-article-0.1.1.tgz#9f07e733c3fbb646ffa9e61c08debacd460e06af"
+  integrity sha1-nwfnM8P7tkb/qeYcCN66zUYOBq8=
+  dependencies:
+    kind-of "^3.1.0"
+
+uglify-js@^3.1.4:
+  version "3.14.2"
+  resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.14.2.tgz#d7dd6a46ca57214f54a2d0a43cad0f35db82ac99"
+  integrity sha512-rtPMlmcO4agTUfz10CbgJ1k6UAoXM2gWb3GoMPPZB/+/Ackf8lNWk11K4rYi2D0apgoFRLtQOZhb+/iGNJq26A==
+
 unicode-canonical-property-names-ecmascript@^1.0.4:
   version "1.0.4"
   resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz#2619800c4c825800efdd8343af7dd9933cbe2818"
@@ -5941,6 +6653,11 @@ word-wrap@~1.2.3:
   resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c"
   integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==
 
+wordwrap@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb"
+  integrity sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=
+
 wrap-ansi@^3.0.1:
   version "3.0.1"
   resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-3.0.1.tgz#288a04d87eda5c286e060dfe8f135ce8d007f8ba"
@@ -5988,6 +6705,11 @@ xmlchars@^2.2.0:
   resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb"
   integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==
 
+xtend@~4.0.1:
+  version "4.0.2"
+  resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
+  integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==
+
 y18n@^4.0.0:
   version "4.0.1"
   resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.1.tgz#8db2b83c31c5d75099bb890b23f3094891e247d4"
@@ -6036,6 +6758,11 @@ yauzl@^2.10.0:
     buffer-crc32 "~0.2.3"
     fd-slicer "~1.1.0"
 
+year@^0.2.1:
+  version "0.2.1"
+  resolved "https://registry.yarnpkg.com/year/-/year-0.2.1.tgz#4083ae520a318b23ec86037f3000cb892bdf9bb0"
+  integrity sha1-QIOuUgoxiyPshgN/MADLiSvfm7A=
+
 yup@0.29.2:
   version "0.29.2"
   resolved "https://registry.yarnpkg.com/yup/-/yup-0.29.2.tgz#5302abd9024cca335b987793f8df868e410b7b67"
diff --git a/packages/cli/package.json b/packages/cli/package.json
index c656f9976b..147a0ffe2b 100644
--- a/packages/cli/package.json
+++ b/packages/cli/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@budibase/cli",
-  "version": "0.9.125-alpha.19",
+  "version": "0.9.140-alpha.5",
   "description": "Budibase CLI, for developers, self hosting and migrations.",
   "main": "src/index.js",
   "bin": {
diff --git a/packages/client/package.json b/packages/client/package.json
index da32c89328..c739c5b80d 100644
--- a/packages/client/package.json
+++ b/packages/client/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@budibase/client",
-  "version": "0.9.125-alpha.19",
+  "version": "0.9.140-alpha.5",
   "license": "MPL-2.0",
   "module": "dist/budibase-client.js",
   "main": "dist/budibase-client.js",
@@ -19,8 +19,9 @@
     "dev:builder": "rollup -cw"
   },
   "dependencies": {
-    "@budibase/bbui": "^0.9.125-alpha.19",
-    "@budibase/string-templates": "^0.9.125-alpha.19",
+    "@budibase/bbui": "^0.9.140-alpha.5",
+    "@budibase/standard-components": "^0.9.139",
+    "@budibase/string-templates": "^0.9.140-alpha.5",
     "regexparam": "^1.3.0",
     "shortid": "^2.2.15",
     "svelte-spa-router": "^3.0.5"
diff --git a/packages/server/package.json b/packages/server/package.json
index f15cb95c2e..4d32021ff4 100644
--- a/packages/server/package.json
+++ b/packages/server/package.json
@@ -1,7 +1,7 @@
 {
   "name": "@budibase/server",
   "email": "hi@budibase.com",
-  "version": "0.9.125-alpha.19",
+  "version": "0.9.140-alpha.5",
   "description": "Budibase Web Server",
   "main": "src/index.js",
   "repository": {
@@ -13,7 +13,7 @@
     "postbuild": "copyfiles -u 1 src/**/*.svelte dist/ && copyfiles -u 1 src/**/*.hbs dist/ && copyfiles -u 1 src/**/*.json dist/",
     "test": "jest --coverage --maxWorkers=2",
     "test:watch": "jest --watch",
-    "predocker": "copyfiles -f ../client/dist/budibase-client.js ../client/manifest.json client",
+    "predocker": "copyfiles -f ../client/dist/budibase-client.js ../standard-components/manifest.json client",
     "build:docker": "yarn run predocker && docker build . -t app-service",
     "run:docker": "node dist/index.js",
     "dev:stack:up": "node scripts/dev/manage.js up",
@@ -23,10 +23,9 @@
     "format": "prettier --config ../../.prettierrc.json 'src/**/*.ts' --write",
     "lint": "eslint --fix src/",
     "lint:fix": "yarn run format && yarn run lint",
+    "initialise": "node scripts/initialise.js",
     "multi:enable": "node scripts/multiTenancy.js enable",
-    "multi:disable": "node scripts/multiTenancy.js disable",
-    "selfhost:enable": "node scripts/selfhost.js enable",
-    "selfhost:disable": "node scripts/selfhost.js disable"
+    "multi:disable": "node scripts/multiTenancy.js disable"
   },
   "jest": {
     "preset": "ts-jest",
@@ -49,8 +48,7 @@
       "!src/automations/tests/**/*",
       "!src/utilities/fileProcessor.js",
       "!src/utilities/fileSystem/**/*",
-      "!src/utilities/redis.js",
-      "!src/api/controllers/row/internalSearch.js"
+      "!src/utilities/redis.js"
     ],
     "coverageReporters": [
       "lcov",
@@ -64,9 +62,9 @@
   "author": "Budibase",
   "license": "AGPL-3.0-or-later",
   "dependencies": {
-    "@budibase/auth": "^0.9.125-alpha.19",
-    "@budibase/client": "^0.9.125-alpha.19",
-    "@budibase/string-templates": "^0.9.125-alpha.19",
+    "@budibase/auth": "^0.9.140-alpha.5",
+    "@budibase/client": "^0.9.140-alpha.5",
+    "@budibase/string-templates": "^0.9.140-alpha.5",
     "@elastic/elasticsearch": "7.10.0",
     "@koa/router": "8.0.0",
     "@sendgrid/mail": "7.1.1",
@@ -98,12 +96,13 @@
     "lodash": "4.17.21",
     "mongodb": "3.6.3",
     "mssql": "6.2.3",
-    "mysql": "^2.18.1",
+    "mysql": "2.18.1",
     "node-fetch": "2.6.0",
     "open": "7.3.0",
     "pg": "8.5.1",
     "pino-pretty": "4.0.0",
     "pouchdb": "7.2.1",
+    "pouchdb-adapter-memory": "^7.2.1",
     "pouchdb-all-dbs": "1.0.2",
     "pouchdb-find": "^7.2.2",
     "pouchdb-replication-stream": "1.2.9",
@@ -118,6 +117,7 @@
   "devDependencies": {
     "@babel/core": "^7.14.3",
     "@babel/preset-env": "^7.14.4",
+    "@budibase/standard-components": "^0.9.139",
     "@jest/test-sequencer": "^24.8.0",
     "@types/bull": "^3.15.1",
     "@types/jest": "^26.0.23",
@@ -132,7 +132,6 @@
     "express": "^4.17.1",
     "jest": "^27.0.5",
     "nodemon": "^2.0.4",
-    "pouchdb-adapter-memory": "^7.2.1",
     "prettier": "^2.3.1",
     "rimraf": "^3.0.2",
     "supertest": "^4.0.2",
diff --git a/packages/server/scripts/integrations/service-vehicles/docker-compose.yml b/packages/server/scripts/integrations/service-vehicles/docker-compose.yml
new file mode 100644
index 0000000000..7473e540db
--- /dev/null
+++ b/packages/server/scripts/integrations/service-vehicles/docker-compose.yml
@@ -0,0 +1,28 @@
+version: "3.8"
+services:
+  db:
+    container_name: postgres-vehicle
+    image: postgres
+    restart: always
+    environment:
+      POSTGRES_USER: root
+      POSTGRES_PASSWORD: root
+      POSTGRES_DB: main
+    ports:
+      - "5432:5432"
+    volumes:
+      #- pg_data:/var/lib/postgresql/data/
+      - ./init.sql:/docker-entrypoint-initdb.d/init.sql
+
+  pgadmin:
+    container_name: pgadmin
+    image: dpage/pgadmin4
+    restart: always
+    environment:
+      PGADMIN_DEFAULT_EMAIL: root@root.com
+      PGADMIN_DEFAULT_PASSWORD: root
+    ports:
+      - "5050:80"
+
+#volumes:
+#  pg_data:
diff --git a/packages/server/scripts/integrations/service-vehicles/init.sql b/packages/server/scripts/integrations/service-vehicles/init.sql
new file mode 100644
index 0000000000..3e0485313e
--- /dev/null
+++ b/packages/server/scripts/integrations/service-vehicles/init.sql
@@ -0,0 +1,52 @@
+SELECT 'CREATE DATABASE main'
+WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'main')\gexec
+CREATE TABLE Vehicles (
+    id bigint NOT NULL GENERATED ALWAYS AS IDENTITY ( INCREMENT 1 START 1 MINVALUE 1 MAXVALUE 9223372036854775807 CACHE 1 ),
+    Registration text COLLATE pg_catalog."default",
+    Make text COLLATE pg_catalog."default",
+    Model text COLLATE pg_catalog."default",
+    Colour text COLLATE pg_catalog."default",
+    Year smallint,
+    CONSTRAINT Vehicles_pkey PRIMARY KEY (id)
+);
+
+CREATE TABLE ServiceLog (
+    id bigint NOT NULL GENERATED ALWAYS AS IDENTITY ( INCREMENT 1 START 1 MINVALUE 1 MAXVALUE 9223372036854775807 CACHE 1 ),
+    Description text COLLATE pg_catalog."default",
+    VehicleId bigint,
+    ServiceDate timestamp without time zone,
+    Category text COLLATE pg_catalog."default",
+    Mileage bigint,
+    CONSTRAINT ServiceLog_pkey PRIMARY KEY (id),
+    CONSTRAINT vehicle_foreign_key FOREIGN KEY (VehicleId)
+        REFERENCES Vehicles (id) MATCH SIMPLE
+        ON UPDATE NO ACTION
+        ON DELETE NO ACTION
+);
+
+INSERT INTO Vehicles (Registration, Make, Model, Colour, Year)
+VALUES ('FAZ 9837','Volkswagen','Polo','White',2002);
+INSERT INTO Vehicles (Registration, Make, Model, Colour, Year)
+VALUES ('JHI 8827','BMW','M3','Black',2013);
+INSERT INTO Vehicles (Registration, Make, Model, Colour, Year)
+VALUES ('D903PI','Volvo','XC40','Grey',2014);
+INSERT INTO Vehicles (Registration, Make, Model, Colour, Year)
+VALUES ('YFI002','Volkswagen','Golf','Dark Blue',2018);
+INSERT INTO Vehicles (Registration, Make, Model, Colour, Year)
+VALUES ('HGT5677','Skoda','Octavia','Graphite',2009);
+INSERT INTO Vehicles (Registration, Make, Model, Colour, Year)
+VALUES ('PPF9276','Skoda','Octavia','Graphite',2021);
+INSERT INTO Vehicles (Registration, Make, Model, Colour, Year)
+VALUES ('J893FT','Toyota','Corolla','Red',2015);
+INSERT INTO Vehicles (Registration, Make, Model, Colour, Year)
+VALUES ('MJK776','Honda','HR-V','Silver',2015);
+
+
+INSERT INTO ServiceLog (Description, VehicleId, ServiceDate, Category, Mileage)
+VALUES ('Change front brakes', 1, '2021-05-04', 'Brakes', 20667);
+INSERT INTO ServiceLog (Description, VehicleId, ServiceDate, Category, Mileage)
+VALUES ('Tyres - full set', 1, '2021-05-04', 'Brakes', 20667);
+INSERT INTO ServiceLog (Description, VehicleId, ServiceDate, Category, Mileage)
+VALUES ('Engine tune up', 2, '2021-07-14', 'Brakes', 50889);
+INSERT INTO ServiceLog (Description, VehicleId, ServiceDate, Category, Mileage)
+VALUES ('Replace transmission', 3, '2021-09-26', 'Transmission', 98002);
diff --git a/packages/server/scripts/integrations/service-vehicles/reset.sh b/packages/server/scripts/integrations/service-vehicles/reset.sh
new file mode 100755
index 0000000000..32778bd11f
--- /dev/null
+++ b/packages/server/scripts/integrations/service-vehicles/reset.sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+docker-compose down
+docker volume prune -f
diff --git a/packages/server/src/api/controllers/analytics.js b/packages/server/src/api/controllers/analytics.js
index d6e1a9ce5b..eb64bc87b9 100644
--- a/packages/server/src/api/controllers/analytics.js
+++ b/packages/server/src/api/controllers/analytics.js
@@ -2,6 +2,6 @@ const env = require("../../environment")
 
 exports.isEnabled = async function (ctx) {
   ctx.body = {
-    enabled: env.ENABLE_ANALYTICS === "true",
+    enabled: !env.SELF_HOSTED && env.ENABLE_ANALYTICS === "true",
   }
 }
diff --git a/packages/server/src/api/controllers/datasource.js b/packages/server/src/api/controllers/datasource.js
index 38b6e68932..4a2fd7d86a 100644
--- a/packages/server/src/api/controllers/datasource.js
+++ b/packages/server/src/api/controllers/datasource.js
@@ -51,7 +51,7 @@ exports.buildSchemaFromDb = async function (ctx) {
   await connector.buildSchema(datasource._id, datasource.entities)
   datasource.entities = connector.tables
 
-  const response = await db.post(datasource)
+  const response = await db.put(datasource)
   datasource._rev = response.rev
 
   ctx.body = datasource
@@ -89,7 +89,7 @@ exports.save = async function (ctx) {
     ...ctx.request.body,
   }
 
-  const response = await db.post(datasource)
+  const response = await db.put(datasource)
   datasource._rev = response.rev
 
   // Drain connection pools when configuration is changed
diff --git a/packages/server/src/api/controllers/row/ExternalRequest.ts b/packages/server/src/api/controllers/row/ExternalRequest.ts
index eced518604..b809e597e4 100644
--- a/packages/server/src/api/controllers/row/ExternalRequest.ts
+++ b/packages/server/src/api/controllers/row/ExternalRequest.ts
@@ -437,7 +437,11 @@ module External {
       for (let [colName, { isMany, rows, tableId }] of Object.entries(
         related
       )) {
-        const table = this.getTable(tableId)
+        const table: Table = this.getTable(tableId)
+        // if its not the foreign key skip it, nothing to do
+        if (table.primary && table.primary.indexOf(colName) !== -1) {
+          continue
+        }
         for (let row of rows) {
           const filters = buildFilters(generateIdForRow(row, table), {}, table)
           // safety check, if there are no filters on deletion bad things happen
diff --git a/packages/server/src/api/controllers/row/internal.js b/packages/server/src/api/controllers/row/internal.js
index 2299a20580..d429c14cc7 100644
--- a/packages/server/src/api/controllers/row/internal.js
+++ b/packages/server/src/api/controllers/row/internal.js
@@ -5,17 +5,22 @@ const {
   generateRowID,
   DocumentTypes,
   InternalTables,
+  generateMemoryViewID,
 } = require("../../../db/utils")
 const userController = require("../user")
 const {
   inputProcessing,
   outputProcessing,
+  processAutoColumn,
 } = require("../../../utilities/rowProcessor")
 const { FieldTypes } = require("../../../constants")
 const { isEqual } = require("lodash")
 const { validate, findRow } = require("./utils")
 const { fullSearch, paginatedSearch } = require("./internalSearch")
 const { getGlobalUsersFromMetadata } = require("../../../utilities/global")
+const inMemoryViews = require("../../../db/inMemoryView")
+const env = require("../../../environment")
+const { migrateToInMemoryView } = require("../view/utils")
 
 const CALCULATION_TYPES = {
   SUM: "sum",
@@ -25,17 +30,84 @@ const CALCULATION_TYPES = {
 
 async function storeResponse(ctx, db, row, oldTable, table) {
   row.type = "row"
-  const response = await db.put(row)
   // don't worry about rev, tables handle rev/lastID updates
+  // if another row has been written since processing this will
+  // handle the auto ID clash
   if (!isEqual(oldTable, table)) {
-    await db.put(table)
+    try {
+      await db.put(table)
+    } catch (err) {
+      if (err.status === 409) {
+        const updatedTable = await db.get(table._id)
+        let response = processAutoColumn(null, updatedTable, row, {
+          reprocessing: true,
+        })
+        await db.put(response.table)
+        row = response.row
+      } else {
+        throw err
+      }
+    }
   }
+  const response = await db.put(row)
   row._rev = response.rev
   // process the row before return, to include relationships
   row = await outputProcessing(ctx, table, row, { squash: false })
   return { row, table }
 }
 
+// doesn't do the outputProcessing
+async function getRawTableData(ctx, db, tableId) {
+  let rows
+  if (tableId === InternalTables.USER_METADATA) {
+    await userController.fetchMetadata(ctx)
+    rows = ctx.body
+  } else {
+    const response = await db.allDocs(
+      getRowParams(tableId, null, {
+        include_docs: true,
+      })
+    )
+    rows = response.rows.map(row => row.doc)
+  }
+  return rows
+}
+
+async function getView(db, viewName) {
+  let viewInfo
+  async function getFromDesignDoc() {
+    const designDoc = await db.get("_design/database")
+    viewInfo = designDoc.views[viewName]
+    return viewInfo
+  }
+  let migrate = false
+  if (env.SELF_HOSTED) {
+    viewInfo = await getFromDesignDoc()
+  } else {
+    try {
+      viewInfo = await db.get(generateMemoryViewID(viewName))
+      if (viewInfo) {
+        viewInfo = viewInfo.view
+      }
+    } catch (err) {
+      // check if it can be retrieved from design doc (needs migrated)
+      if (err.status !== 404) {
+        viewInfo = null
+      } else {
+        viewInfo = await getFromDesignDoc()
+        migrate = !!viewInfo
+      }
+    }
+  }
+  if (migrate) {
+    await migrateToInMemoryView(db, viewName)
+  }
+  if (!viewInfo) {
+    throw "View does not exist."
+  }
+  return viewInfo
+}
+
 exports.patch = async ctx => {
   const appId = ctx.appId
   const db = new CouchDB(appId)
@@ -139,15 +211,18 @@ exports.fetchView = async ctx => {
 
   const db = new CouchDB(appId)
   const { calculation, group, field } = ctx.query
-  const designDoc = await db.get("_design/database")
-  const viewInfo = designDoc.views[viewName]
-  if (!viewInfo) {
-    throw "View does not exist."
+  const viewInfo = await getView(db, viewName)
+  let response
+  if (env.SELF_HOSTED) {
+    response = await db.query(`database/${viewName}`, {
+      include_docs: !calculation,
+      group: !!group,
+    })
+  } else {
+    const tableId = viewInfo.meta.tableId
+    const data = await getRawTableData(ctx, db, tableId)
+    response = await inMemoryViews.runView(viewInfo, calculation, group, data)
   }
-  const response = await db.query(`database/${viewName}`, {
-    include_docs: !calculation,
-    group: !!group,
-  })
 
   let rows
   if (!calculation) {
@@ -191,19 +266,9 @@ exports.fetch = async ctx => {
   const appId = ctx.appId
   const db = new CouchDB(appId)
 
-  let rows,
-    table = await db.get(ctx.params.tableId)
-  if (ctx.params.tableId === InternalTables.USER_METADATA) {
-    await userController.fetchMetadata(ctx)
-    rows = ctx.body
-  } else {
-    const response = await db.allDocs(
-      getRowParams(ctx.params.tableId, null, {
-        include_docs: true,
-      })
-    )
-    rows = response.rows.map(row => row.doc)
-  }
+  const tableId = ctx.params.tableId
+  let table = await db.get(tableId)
+  let rows = await getRawTableData(ctx, db, tableId)
   return outputProcessing(ctx, table, rows)
 }
 
diff --git a/packages/server/src/api/controllers/table/index.js b/packages/server/src/api/controllers/table/index.js
index 60b5167f66..c7b72cf1c8 100644
--- a/packages/server/src/api/controllers/table/index.js
+++ b/packages/server/src/api/controllers/table/index.js
@@ -145,7 +145,7 @@ exports.save = async function (ctx) {
   if (updatedRows && updatedRows.length !== 0) {
     await db.bulkDocs(updatedRows)
   }
-  const result = await db.post(tableToSave)
+  const result = await db.put(tableToSave)
   tableToSave._rev = result.rev
 
   tableToSave = await tableSaveFunctions.after(tableToSave)
diff --git a/packages/server/src/api/controllers/table/utils.js b/packages/server/src/api/controllers/table/utils.js
index 154a9ba8f5..d263002da6 100644
--- a/packages/server/src/api/controllers/table/utils.js
+++ b/packages/server/src/api/controllers/table/utils.js
@@ -68,23 +68,17 @@ exports.handleDataImport = async (appId, user, table, dataImport) => {
     // Populate the table with rows imported from CSV in a bulk update
     const data = await csvParser.transform(dataImport)
 
+    let finalData = []
     for (let i = 0; i < data.length; i++) {
       let row = data[i]
       row._id = generateRowID(table._id)
       row.tableId = table._id
-      const processed = inputProcessing(user, table, row)
+      const processed = inputProcessing(user, table, row, {
+        noAutoRelationships: true,
+      })
       table = processed.table
       row = processed.row
 
-      // make sure link rows are up to date
-      row = await linkRows.updateLinks({
-        appId,
-        eventType: linkRows.EventType.ROW_SAVE,
-        row,
-        tableId: row.tableId,
-        table,
-      })
-
       for (let [fieldName, schema] of Object.entries(table.schema)) {
         // check whether the options need to be updated for inclusion as part of the data import
         if (
@@ -98,10 +92,20 @@ exports.handleDataImport = async (appId, user, table, dataImport) => {
           ]
         }
       }
-      data[i] = row
+
+      // make sure link rows are up to date
+      finalData.push(
+        linkRows.updateLinks({
+          appId,
+          eventType: linkRows.EventType.ROW_SAVE,
+          row,
+          tableId: row.tableId,
+          table,
+        })
+      )
     }
 
-    await db.bulkDocs(data)
+    await db.bulkDocs(await Promise.all(finalData))
     let response = await db.put(table)
     table._rev = response._rev
   }
diff --git a/packages/server/src/api/controllers/view/index.js b/packages/server/src/api/controllers/view/index.js
index 3d0f236fce..ecaee0f32f 100644
--- a/packages/server/src/api/controllers/view/index.js
+++ b/packages/server/src/api/controllers/view/index.js
@@ -2,127 +2,93 @@ const CouchDB = require("../../../db")
 const viewTemplate = require("./viewBuilder")
 const { apiFileReturn } = require("../../../utilities/fileSystem")
 const exporters = require("./exporters")
+const { saveView, getView, getViews, deleteView } = require("./utils")
 const { fetchView } = require("../row")
-const { ViewNames } = require("../../../db/utils")
 
-const controller = {
-  fetch: async ctx => {
-    const db = new CouchDB(ctx.appId)
-    const designDoc = await db.get("_design/database")
-    const response = []
-
-    for (let name of Object.keys(designDoc.views)) {
-      // Only return custom views, not built ins
-      if (Object.values(ViewNames).indexOf(name) !== -1) {
-        continue
-      }
-      response.push({
-        name,
-        ...designDoc.views[name],
-      })
-    }
-
-    ctx.body = response
-  },
-  save: async ctx => {
-    const db = new CouchDB(ctx.appId)
-    const { originalName, ...viewToSave } = ctx.request.body
-    const designDoc = await db.get("_design/database")
-    const view = viewTemplate(viewToSave)
-
-    if (!viewToSave.name) {
-      ctx.throw(400, "Cannot create view without a name")
-    }
-
-    designDoc.views = {
-      ...designDoc.views,
-      [viewToSave.name]: view,
-    }
-
-    // view has been renamed
-    if (originalName) {
-      delete designDoc.views[originalName]
-    }
-
-    await db.put(designDoc)
-
-    // add views to table document
-    const table = await db.get(ctx.request.body.tableId)
-    if (!table.views) table.views = {}
-    if (!view.meta.schema) {
-      view.meta.schema = table.schema
-    }
-    table.views[viewToSave.name] = view.meta
-
-    if (originalName) {
-      delete table.views[originalName]
-    }
-
-    await db.put(table)
-
-    ctx.body = {
-      ...table.views[viewToSave.name],
-      name: viewToSave.name,
-    }
-  },
-  destroy: async ctx => {
-    const db = new CouchDB(ctx.appId)
-    const designDoc = await db.get("_design/database")
-    const viewName = decodeURI(ctx.params.viewName)
-    const view = designDoc.views[viewName]
-    delete designDoc.views[viewName]
-
-    await db.put(designDoc)
-
-    const table = await db.get(view.meta.tableId)
-    delete table.views[viewName]
-    await db.put(table)
-
-    ctx.body = view
-  },
-  exportView: async ctx => {
-    const db = new CouchDB(ctx.appId)
-    const designDoc = await db.get("_design/database")
-    const viewName = decodeURI(ctx.query.view)
-
-    const view = designDoc.views[viewName]
-    const format = ctx.query.format
-    if (!format) {
-      ctx.throw(400, "Format must be specified, either csv or json")
-    }
-
-    if (view) {
-      ctx.params.viewName = viewName
-      // Fetch view rows
-      ctx.query = {
-        group: view.meta.groupBy,
-        calculation: view.meta.calculation,
-        stats: !!view.meta.field,
-        field: view.meta.field,
-      }
-    } else {
-      // table all_ view
-      /* istanbul ignore next */
-      ctx.params.viewName = viewName
-    }
-
-    await fetchView(ctx)
-
-    let schema = view && view.meta && view.meta.schema
-    if (!schema) {
-      const tableId = ctx.params.tableId || view.meta.tableId
-      const table = await db.get(tableId)
-      schema = table.schema
-    }
-
-    // Export part
-    let headers = Object.keys(schema)
-    const exporter = exporters[format]
-    const filename = `${viewName}.${format}`
-    // send down the file
-    ctx.attachment(filename)
-    ctx.body = apiFileReturn(exporter(headers, ctx.body))
-  },
+exports.fetch = async ctx => {
+  const db = new CouchDB(ctx.appId)
+  ctx.body = await getViews(db)
 }
 
-module.exports = controller
+exports.save = async ctx => {
+  const db = new CouchDB(ctx.appId)
+  const { originalName, ...viewToSave } = ctx.request.body
+  const view = viewTemplate(viewToSave)
+
+  if (!viewToSave.name) {
+    ctx.throw(400, "Cannot create view without a name")
+  }
+
+  await saveView(db, originalName, viewToSave.name, view)
+
+  // add views to table document
+  const table = await db.get(ctx.request.body.tableId)
+  if (!table.views) table.views = {}
+  if (!view.meta.schema) {
+    view.meta.schema = table.schema
+  }
+  table.views[viewToSave.name] = view.meta
+  if (originalName) {
+    delete table.views[originalName]
+  }
+  await db.put(table)
+
+  ctx.body = {
+    ...table.views[viewToSave.name],
+    name: viewToSave.name,
+  }
+}
+
+exports.destroy = async ctx => {
+  const db = new CouchDB(ctx.appId)
+  const viewName = decodeURI(ctx.params.viewName)
+  const view = await deleteView(db, viewName)
+  const table = await db.get(view.meta.tableId)
+  delete table.views[viewName]
+  await db.put(table)
+
+  ctx.body = view
+}
+
+exports.exportView = async ctx => {
+  const db = new CouchDB(ctx.appId)
+  const viewName = decodeURI(ctx.query.view)
+  const view = await getView(db, viewName)
+
+  const format = ctx.query.format
+  if (!format) {
+    ctx.throw(400, "Format must be specified, either csv or json")
+  }
+
+  if (view) {
+    ctx.params.viewName = viewName
+    // Fetch view rows
+    ctx.query = {
+      group: view.meta.groupBy,
+      calculation: view.meta.calculation,
+      stats: !!view.meta.field,
+      field: view.meta.field,
+    }
+  } else {
+    // table all_ view
+    /* istanbul ignore next */
+    ctx.params.viewName = viewName
+  }
+
+  await fetchView(ctx)
+
+  let schema = view && view.meta && view.meta.schema
+  if (!schema) {
+    const tableId = ctx.params.tableId || view.meta.tableId
+    const table = await db.get(tableId)
+    schema = table.schema
+  }
+
+  // Export part
+  let headers = Object.keys(schema)
+  const exporter = exporters[format]
+  const filename = `${viewName}.${format}`
+  // send down the file
+  ctx.attachment(filename)
+  ctx.body = apiFileReturn(exporter(headers, ctx.body))
+}
diff --git a/packages/server/src/api/controllers/view/utils.js b/packages/server/src/api/controllers/view/utils.js
new file mode 100644
index 0000000000..c93604177f
--- /dev/null
+++ b/packages/server/src/api/controllers/view/utils.js
@@ -0,0 +1,109 @@
+const {
+  ViewNames,
+  generateMemoryViewID,
+  getMemoryViewParams,
+} = require("../../../db/utils")
+const env = require("../../../environment")
+
+exports.getView = async (db, viewName) => {
+  if (env.SELF_HOSTED) {
+    const designDoc = await db.get("_design/database")
+    return designDoc.views[viewName]
+  } else {
+    const viewDoc = await db.get(generateMemoryViewID(viewName))
+    return viewDoc.view
+  }
+}
+
+exports.getViews = async db => {
+  const response = []
+  if (env.SELF_HOSTED) {
+    const designDoc = await db.get("_design/database")
+    for (let name of Object.keys(designDoc.views)) {
+      // Only return custom views, not built ins
+      if (Object.values(ViewNames).indexOf(name) !== -1) {
+        continue
+      }
+      response.push({
+        name,
+        ...designDoc.views[name],
+      })
+    }
+  } else {
+    const views = (
+      await db.allDocs(
+        getMemoryViewParams({
+          include_docs: true,
+        })
+      )
+    ).rows.map(row => row.doc)
+    for (let viewDoc of views) {
+      response.push({
+        name: viewDoc.name,
+        ...viewDoc.view,
+      })
+    }
+  }
+  return response
+}
+
+exports.saveView = async (db, originalName, viewName, viewTemplate) => {
+  if (env.SELF_HOSTED) {
+    const designDoc = await db.get("_design/database")
+    designDoc.views = {
+      ...designDoc.views,
+      [viewName]: viewTemplate,
+    }
+    // view has been renamed
+    if (originalName) {
+      delete designDoc.views[originalName]
+    }
+    await db.put(designDoc)
+  } else {
+    const id = generateMemoryViewID(viewName)
+    const originalId = originalName ? generateMemoryViewID(originalName) : null
+    const viewDoc = {
+      _id: id,
+      view: viewTemplate,
+      name: viewName,
+      tableId: viewTemplate.meta.tableId,
+    }
+    try {
+      const old = await db.get(id)
+      if (originalId) {
+        const originalDoc = await db.get(originalId)
+        await db.remove(originalDoc._id, originalDoc._rev)
+      }
+      if (old && old._rev) {
+        viewDoc._rev = old._rev
+      }
+    } catch (err) {
+      // didn't exist, just skip
+    }
+    await db.put(viewDoc)
+  }
+}
+
+exports.deleteView = async (db, viewName) => {
+  if (env.SELF_HOSTED) {
+    const designDoc = await db.get("_design/database")
+    const view = designDoc.views[viewName]
+    delete designDoc.views[viewName]
+    await db.put(designDoc)
+    return view
+  } else {
+    const id = generateMemoryViewID(viewName)
+    const viewDoc = await db.get(id)
+    await db.remove(viewDoc._id, viewDoc._rev)
+    return viewDoc.view
+  }
+}
+
+exports.migrateToInMemoryView = async (db, viewName) => {
+  // delete the view initially
+  const designDoc = await db.get("_design/database")
+  const view = designDoc.views[viewName]
+  delete designDoc.views[viewName]
+  await db.put(designDoc)
+  await exports.saveView(db, null, viewName, view)
+}
diff --git a/packages/server/src/api/routes/tests/view.spec.js b/packages/server/src/api/routes/tests/view.spec.js
index 458da6e023..b1c5f655c6 100644
--- a/packages/server/src/api/routes/tests/view.spec.js
+++ b/packages/server/src/api/routes/tests/view.spec.js
@@ -205,7 +205,7 @@ describe("/views", () => {
   })
 
   describe("exportView", () => {
-    it("should be able to delete a view", async () => {
+    it("should be able to export a view", async () => {
       await config.createTable(priceTable())
       await config.createRow()
       const view = await config.createView()
diff --git a/packages/server/src/automations/steps/createRow.js b/packages/server/src/automations/steps/createRow.js
index 9706126438..9033004578 100644
--- a/packages/server/src/automations/steps/createRow.js
+++ b/packages/server/src/automations/steps/createRow.js
@@ -2,6 +2,7 @@ const rowController = require("../../api/controllers/row")
 const automationUtils = require("../automationUtils")
 const env = require("../../environment")
 const usage = require("../../utilities/usageQuota")
+const { buildCtx } = require("./utils")
 
 exports.definition = {
   name: "Create Row",
@@ -69,16 +70,12 @@ exports.run = async function ({ inputs, appId, apiKey, emitter }) {
     }
   }
   // have to clean up the row, remove the table from it
-  const ctx = {
+  const ctx = buildCtx(appId, emitter, {
+    body: inputs.row,
     params: {
       tableId: inputs.row.tableId,
     },
-    request: {
-      body: inputs.row,
-    },
-    appId,
-    eventEmitter: emitter,
-  }
+  })
 
   try {
     inputs.row = await automationUtils.cleanUpRow(
@@ -86,7 +83,7 @@ exports.run = async function ({ inputs, appId, apiKey, emitter }) {
       inputs.row.tableId,
       inputs.row
     )
-    if (env.isProd()) {
+    if (env.USE_QUOTAS) {
       await usage.update(apiKey, usage.Properties.ROW, 1)
     }
     await rowController.save(ctx)
diff --git a/packages/server/src/automations/steps/deleteRow.js b/packages/server/src/automations/steps/deleteRow.js
index 26623d628b..0f9648cc51 100644
--- a/packages/server/src/automations/steps/deleteRow.js
+++ b/packages/server/src/automations/steps/deleteRow.js
@@ -1,6 +1,7 @@
 const rowController = require("../../api/controllers/row")
 const env = require("../../environment")
 const usage = require("../../utilities/usageQuota")
+const { buildCtx } = require("./utils")
 
 exports.definition = {
   description: "Delete a row from your database",
@@ -60,19 +61,16 @@ exports.run = async function ({ inputs, appId, apiKey, emitter }) {
       },
     }
   }
-  let ctx = {
+
+  let ctx = buildCtx(appId, emitter, {
+    body: {
+      _id: inputs.id,
+      _rev: inputs.revision,
+    },
     params: {
       tableId: inputs.tableId,
     },
-    request: {
-      body: {
-        _id: inputs.id,
-        _rev: inputs.revision,
-      },
-    },
-    appId,
-    eventEmitter: emitter,
-  }
+  })
 
   try {
     if (env.isProd()) {
diff --git a/packages/server/src/automations/steps/queryRows.js b/packages/server/src/automations/steps/queryRows.js
index 64b757418e..3c4bb422a0 100644
--- a/packages/server/src/automations/steps/queryRows.js
+++ b/packages/server/src/automations/steps/queryRows.js
@@ -1,6 +1,7 @@
 const rowController = require("../../api/controllers/row")
 const tableController = require("../../api/controllers/table")
 const { FieldTypes } = require("../../constants")
+const { buildCtx } = require("./utils")
 
 const SortOrders = {
   ASCENDING: "ascending",
@@ -70,12 +71,11 @@ exports.definition = {
 }
 
 async function getTable(appId, tableId) {
-  const ctx = {
+  const ctx = buildCtx(appId, null, {
     params: {
       id: tableId,
     },
-    appId,
-  }
+  })
   await tableController.find(ctx)
   return ctx.body
 }
@@ -89,21 +89,18 @@ exports.run = async function ({ inputs, appId }) {
     sortType =
       fieldType === FieldTypes.NUMBER ? FieldTypes.NUMBER : FieldTypes.STRING
   }
-  const ctx = {
+  const ctx = buildCtx(appId, null, {
     params: {
       tableId,
     },
-    request: {
-      body: {
-        sortOrder,
-        sortType,
-        sort: sortColumn,
-        query: filters || {},
-        limit,
-      },
+    body: {
+      sortOrder,
+      sortType,
+      sort: sortColumn,
+      query: filters || {},
+      limit,
     },
-    appId,
-  }
+  })
   try {
     await rowController.search(ctx)
     return {
diff --git a/packages/server/src/automations/steps/updateRow.js b/packages/server/src/automations/steps/updateRow.js
index ac5eb16fcd..94f77bc801 100644
--- a/packages/server/src/automations/steps/updateRow.js
+++ b/packages/server/src/automations/steps/updateRow.js
@@ -1,5 +1,6 @@
 const rowController = require("../../api/controllers/row")
 const automationUtils = require("../automationUtils")
+const { buildCtx } = require("./utils")
 
 exports.definition = {
   name: "Update Row",
@@ -72,19 +73,15 @@ exports.run = async function ({ inputs, appId, emitter }) {
   }
 
   // have to clean up the row, remove the table from it
-  const ctx = {
+  const ctx = buildCtx(appId, emitter, {
+    body: {
+      ...inputs.row,
+      _id: inputs.rowId,
+    },
     params: {
       rowId: inputs.rowId,
     },
-    request: {
-      body: {
-        ...inputs.row,
-        _id: inputs.rowId,
-      },
-    },
-    appId,
-    eventEmitter: emitter,
-  }
+  })
 
   try {
     inputs.row = await automationUtils.cleanUpRowById(
diff --git a/packages/server/src/db/inMemoryView.js b/packages/server/src/db/inMemoryView.js
new file mode 100644
index 0000000000..892617e068
--- /dev/null
+++ b/packages/server/src/db/inMemoryView.js
@@ -0,0 +1,48 @@
+const PouchDB = require("pouchdb")
+const memory = require("pouchdb-adapter-memory")
+const newid = require("./newid")
+
+PouchDB.plugin(memory)
+const Pouch = PouchDB.defaults({
+  prefix: undefined,
+  adapter: "memory",
+})
+
+exports.runView = async (view, calculation, group, data) => {
+  // use a different ID each time for the DB, make sure they
+  // are always unique for each query, don't want overlap
+  // which could cause 409s
+  const db = new Pouch(newid())
+  // write all the docs to the in memory Pouch (remove revs)
+  await db.bulkDocs(
+    data.map(row => ({
+      ...row,
+      _rev: undefined,
+    }))
+  )
+  let fn = (doc, emit) => emit(doc._id)
+  eval("fn = " + view.map.replace("function (doc)", "function (doc, emit)"))
+  const queryFns = {
+    meta: view.meta,
+    map: fn,
+  }
+  if (view.reduce) {
+    queryFns.reduce = view.reduce
+  }
+  const response = await db.query(queryFns, {
+    include_docs: !calculation,
+    group: !!group,
+  })
+  // need to fix the revs to be totally accurate
+  for (let row of response.rows) {
+    if (!row._rev || !row._id) {
+      continue
+    }
+    const found = data.find(possible => possible._id === row._id)
+    if (found) {
+      row._rev = found._rev
+    }
+  }
+  await db.destroy()
+  return response
+}
diff --git a/packages/server/src/db/linkedRows/index.js b/packages/server/src/db/linkedRows/index.js
index 67412e7e89..303cd085c1 100644
--- a/packages/server/src/db/linkedRows/index.js
+++ b/packages/server/src/db/linkedRows/index.js
@@ -76,9 +76,12 @@ async function getFullLinkedDocs(ctx, appId, links) {
   // create DBs
   const db = new CouchDB(appId)
   const linkedRowIds = links.map(link => link.id)
-  let linked = (await db.allDocs(getMultiIDParams(linkedRowIds))).rows.map(
+  const uniqueRowIds = [...new Set(linkedRowIds)]
+  let dbRows = (await db.allDocs(getMultiIDParams(uniqueRowIds))).rows.map(
     row => row.doc
   )
+  // convert the unique db rows back to a full list of linked rows
+  const linked = linkedRowIds.map(id => dbRows.find(row => row._id === id))
   // need to handle users as specific cases
   let [users, other] = partition(linked, linkRow =>
     linkRow._id.startsWith(USER_METDATA_PREFIX)
@@ -112,7 +115,7 @@ exports.updateLinks = async function (args) {
   let linkController = new LinkController(args)
   try {
     if (
-      !(await linkController.doesTableHaveLinkedFields()) &&
+      !(await linkController.doesTableHaveLinkedFields(table)) &&
       (oldTable == null ||
         !(await linkController.doesTableHaveLinkedFields(oldTable)))
     ) {
diff --git a/packages/server/src/db/utils.js b/packages/server/src/db/utils.js
index ec1c267fa2..3e20b30869 100644
--- a/packages/server/src/db/utils.js
+++ b/packages/server/src/db/utils.js
@@ -39,6 +39,7 @@ const DocumentTypes = {
   QUERY: "query",
   DEPLOYMENTS: "deployments",
   METADATA: "metadata",
+  MEM_VIEW: "view",
 }
 
 const ViewNames = {
@@ -348,6 +349,14 @@ exports.getMetadataParams = (type, entityId = null, otherProps = {}) => {
   return getDocParams(DocumentTypes.METADATA, docId, otherProps)
 }
 
+exports.generateMemoryViewID = viewName => {
+  return `${DocumentTypes.MEM_VIEW}${SEPARATOR}${viewName}`
+}
+
+exports.getMemoryViewParams = (otherProps = {}) => {
+  return getDocParams(DocumentTypes.MEM_VIEW, null, otherProps)
+}
+
 /**
  * This can be used with the db.allDocs to get a list of IDs
  */
diff --git a/packages/server/src/environment.js b/packages/server/src/environment.js
index c5739a37e1..89e015b6f5 100644
--- a/packages/server/src/environment.js
+++ b/packages/server/src/environment.js
@@ -26,7 +26,7 @@ module.exports = {
   COUCH_DB_URL: process.env.COUCH_DB_URL,
   MINIO_URL: process.env.MINIO_URL,
   WORKER_URL: process.env.WORKER_URL,
-  SELF_HOSTED: !!parseInt(process.env.SELF_HOSTED),
+  SELF_HOSTED: process.env.SELF_HOSTED,
   AWS_REGION: process.env.AWS_REGION,
   ENABLE_ANALYTICS: process.env.ENABLE_ANALYTICS,
   MINIO_ACCESS_KEY: process.env.MINIO_ACCESS_KEY,
@@ -66,3 +66,10 @@ module.exports = {
     return !isDev()
   },
 }
+
+// convert any strings to numbers if required, like "0" would be true otherwise
+for (let [key, value] of Object.entries(module.exports)) {
+  if (typeof value === "string" && !isNaN(parseInt(value))) {
+    module.exports[key] = parseInt(value)
+  }
+}
diff --git a/packages/server/src/integrations/mysql.ts b/packages/server/src/integrations/mysql.ts
index 3ce21675d9..c5db35ed2a 100644
--- a/packages/server/src/integrations/mysql.ts
+++ b/packages/server/src/integrations/mysql.ts
@@ -12,7 +12,7 @@ import { getSqlQuery } from "./utils"
 module MySQLModule {
   const mysql = require("mysql")
   const Sql = require("./base/sql")
-  const { buildExternalTableId, convertType } = require("./utils")
+  const { buildExternalTableId, convertType, copyExistingPropsOver } = require("./utils")
   const { FieldTypes } = require("../constants")
 
   interface MySQLConfig {
@@ -194,18 +194,7 @@ module MySQLModule {
           }
         }
 
-        // add the existing relationships from the entities if they exist, to prevent them from being overridden
-        if (entities && entities[tableName]) {
-          const existingTableSchema = entities[tableName].schema
-          for (let key in existingTableSchema) {
-            if (!existingTableSchema.hasOwnProperty(key)) {
-              continue
-            }
-            if (existingTableSchema[key].type === "link") {
-              tables[tableName].schema[key] = existingTableSchema[key]
-            }
-          }
-        }
+        copyExistingPropsOver(tableName, tables, entities)
       }
 
       this.client.end()
diff --git a/packages/server/src/integrations/postgres.ts b/packages/server/src/integrations/postgres.ts
index dd46652871..63719980fb 100644
--- a/packages/server/src/integrations/postgres.ts
+++ b/packages/server/src/integrations/postgres.ts
@@ -12,7 +12,7 @@ module PostgresModule {
   const { Pool } = require("pg")
   const Sql = require("./base/sql")
   const { FieldTypes } = require("../constants")
-  const { buildExternalTableId, convertType } = require("./utils")
+  const { buildExternalTableId, convertType, copyExistingPropsOver } = require("./utils")
 
   interface PostgresConfig {
     host: string
@@ -173,31 +173,24 @@ module PostgresModule {
             name: tableName,
             schema: {},
           }
-
-          // add the existing relationships from the entities if they exist, to prevent them from being overridden
-          if (entities && entities[tableName]) {
-            const existingTableSchema = entities[tableName].schema
-            for (let key in existingTableSchema) {
-              if (!existingTableSchema.hasOwnProperty(key)) {
-                continue
-              }
-              if (existingTableSchema[key].type === "link") {
-                tables[tableName].schema[key] = existingTableSchema[key]
-              }
-            }
-          }
         }
 
         const type: string = convertType(column.data_type, TYPE_MAP)
-        const isAuto: boolean =
-          typeof column.column_default === "string" &&
+        const identity = !!(column.identity_generation || column.identity_start || column.identity_increment)
+        const hasDefault = typeof column.column_default === "string" &&
           column.column_default.startsWith("nextval")
+        const isGenerated = column.is_generated && column.is_generated !== "NEVER"
+        const isAuto: boolean = hasDefault || identity || isGenerated
         tables[tableName].schema[columnName] = {
           autocolumn: isAuto,
           name: columnName,
           type,
         }
       }
+
+      for (let tableName of Object.keys(tables)) {
+        copyExistingPropsOver(tableName, tables, entities)
+      }
       this.tables = tables
     }
 
diff --git a/packages/server/src/integrations/utils.ts b/packages/server/src/integrations/utils.ts
index 5b247213c0..82c35bc2d9 100644
--- a/packages/server/src/integrations/utils.ts
+++ b/packages/server/src/integrations/utils.ts
@@ -82,3 +82,21 @@ export function isIsoDateString(str: string) {
   let d = new Date(str)
   return d.toISOString() === str
 }
+
+// add the existing relationships from the entities if they exist, to prevent them from being overridden
+export function copyExistingPropsOver(tableName: string, tables: { [key: string]: any }, entities: { [key: string]: any }) {
+  if (entities && entities[tableName]) {
+    if (entities[tableName].primaryDisplay) {
+      tables[tableName].primaryDisplay = entities[tableName].primaryDisplay
+    }
+    const existingTableSchema = entities[tableName].schema
+    for (let key in existingTableSchema) {
+      if (!existingTableSchema.hasOwnProperty(key)) {
+        continue
+      }
+      if (existingTableSchema[key].type === "link") {
+        tables[tableName].schema[key] = existingTableSchema[key]
+      }
+    }
+  }
+}
diff --git a/packages/server/src/utilities/fileSystem/index.js b/packages/server/src/utilities/fileSystem/index.js
index 5226fd66ca..172afaf609 100644
--- a/packages/server/src/utilities/fileSystem/index.js
+++ b/packages/server/src/utilities/fileSystem/index.js
@@ -124,11 +124,13 @@ exports.performBackup = async (appId, backupName) => {
       ),
   })
   // write the file to the object store
-  await streamUpload(
-    ObjectStoreBuckets.BACKUPS,
-    join(appId, backupName),
-    fs.createReadStream(path)
-  )
+  if (env.SELF_HOSTED) {
+    await streamUpload(
+      ObjectStoreBuckets.BACKUPS,
+      join(appId, backupName),
+      fs.createReadStream(path)
+    )
+  }
   return fs.createReadStream(path)
 }
 
diff --git a/packages/server/src/utilities/rowProcessor/index.js b/packages/server/src/utilities/rowProcessor/index.js
index bb4ac98bb7..07549dd8a8 100644
--- a/packages/server/src/utilities/rowProcessor/index.js
+++ b/packages/server/src/utilities/rowProcessor/index.js
@@ -89,10 +89,16 @@ const TYPE_TRANSFORM_MAP = {
  * @param {Object} user The user to be used for an appId as well as the createdBy and createdAt fields.
  * @param {Object} table The table which is to be used for the schema, as well as handling auto IDs incrementing.
  * @param {Object} row The row which is to be updated with information for the auto columns.
+ * @param {Object} opts specific options for function to carry out optional features.
  * @returns {{row: Object, table: Object}} The updated row and table, the table may need to be updated
  * for automatic ID purposes.
  */
-function processAutoColumn(user, table, row) {
+function processAutoColumn(
+  user,
+  table,
+  row,
+  opts = { reprocessing: false, noAutoRelationships: false }
+) {
   let now = new Date().toISOString()
   // if a row doesn't have a revision then it doesn't exist yet
   const creating = !row._rev
@@ -102,7 +108,7 @@ function processAutoColumn(user, table, row) {
     }
     switch (schema.subtype) {
       case AutoFieldSubTypes.CREATED_BY:
-        if (creating) {
+        if (creating && !opts.reprocessing && !opts.noAutoRelationships) {
           row[key] = [user.userId]
         }
         break
@@ -112,7 +118,9 @@ function processAutoColumn(user, table, row) {
         }
         break
       case AutoFieldSubTypes.UPDATED_BY:
-        row[key] = [user.userId]
+        if (!opts.reprocessing && !opts.noAutoRelationships) {
+          row[key] = [user.userId]
+        }
         break
       case AutoFieldSubTypes.UPDATED_AT:
         row[key] = now
@@ -127,6 +135,7 @@ function processAutoColumn(user, table, row) {
   }
   return { table, row }
 }
+exports.processAutoColumn = processAutoColumn
 
 /**
  * This will coerce a value to the correct types based on the type transform map
@@ -151,9 +160,15 @@ exports.coerce = (row, type) => {
  * @param {object} user the user which is performing the input.
  * @param {object} row the row which is being created/updated.
  * @param {object} table the table which the row is being saved to.
+ * @param {object} opts some input processing options (like disabling auto-column relationships).
  * @returns {object} the row which has been prepared to be written to the DB.
  */
-exports.inputProcessing = (user = {}, table, row) => {
+exports.inputProcessing = (
+  user = {},
+  table,
+  row,
+  opts = { noAutoRelationships: false }
+) => {
   let clonedRow = cloneDeep(row)
   // need to copy the table so it can be differenced on way out
   const copiedTable = cloneDeep(table)
@@ -176,7 +191,7 @@ exports.inputProcessing = (user = {}, table, row) => {
     }
   }
   // handle auto columns - this returns an object like {table, row}
-  return processAutoColumn(user, copiedTable, clonedRow)
+  return processAutoColumn(user, copiedTable, clonedRow, opts)
 }
 
 /**
diff --git a/packages/server/yarn.lock b/packages/server/yarn.lock
index 6e7e7a868d..8062860f7f 100644
--- a/packages/server/yarn.lock
+++ b/packages/server/yarn.lock
@@ -943,10 +943,10 @@
   resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
   integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
 
-"@budibase/auth@^0.9.125-alpha.17":
-  version "0.9.133"
-  resolved "https://registry.yarnpkg.com/@budibase/auth/-/auth-0.9.133.tgz#280d581820c9069b6bc021f88178c215ee48ad08"
-  integrity sha512-DL7zIYRXE6xSKE/qbHMf/SX3+bceGxM4xzUmLTk4OHtEOP/vaUJr35tkhznAZF7VpUR9Yh20D6/Zw8z/3sxj/A==
+"@budibase/auth@^0.9.139":
+  version "0.9.139"
+  resolved "https://registry.yarnpkg.com/@budibase/auth/-/auth-0.9.139.tgz#0610582800df062372582f9139c7aa99606af3e1"
+  integrity sha512-2JUAKC3AA74O3TXHjoGCoXkDxXqUS1K8KGFrJtrUQQrVq1YeQGSjD6Km+Ho8PqUaNdpEfZinBS1/3qFUqaQbuQ==
   dependencies:
     "@techpass/passport-openidconnect" "^0.3.0"
     aws-sdk "^2.901.0"
@@ -966,10 +966,10 @@
     uuid "^8.3.2"
     zlib "^1.0.5"
 
-"@budibase/bbui@^0.9.133":
-  version "0.9.133"
-  resolved "https://registry.yarnpkg.com/@budibase/bbui/-/bbui-0.9.133.tgz#91a2fb24abaaf91d2cb1e00eb51c493c1290f9ad"
-  integrity sha512-xbMmc/hee1QRNW7TrbGUBmLr1hMHXqUDA6rdl9N2PGfHFuFWbqlD8PWYanHmLevVet+CjkuKGPSbBghFK2pQyQ==
+"@budibase/bbui@^0.9.139":
+  version "0.9.139"
+  resolved "https://registry.yarnpkg.com/@budibase/bbui/-/bbui-0.9.139.tgz#e6cfc90e8f6c2aa3526fc6a7bef251bccdaf51bb"
+  integrity sha512-HllzXwfCnxqlV/ifdOR4Got6yrvK2rUFwKUWQIcYU0wk8h6hwYmLehP7HqgBa6l8+bvO1Ep9g+rjP2xJPJG21w==
   dependencies:
     "@adobe/spectrum-css-workflow-icons" "^1.2.1"
     "@spectrum-css/actionbutton" "^1.0.1"
@@ -1015,14 +1015,14 @@
     svelte-flatpickr "^3.1.0"
     svelte-portal "^1.0.0"
 
-"@budibase/client@^0.9.125-alpha.17":
-  version "0.9.133"
-  resolved "https://registry.yarnpkg.com/@budibase/client/-/client-0.9.133.tgz#43748e189e9b92d99d1281ab62bd2c5ebed5dbab"
-  integrity sha512-JrduL9iVMGalZyIUQ+1UN/dhrOZNRJwXU8B4r/eWhVoJf3f3bCuNfpMoT2LN3HY4ooyu37VehD+J5bdDsvlNPw==
+"@budibase/client@^0.9.139":
+  version "0.9.139"
+  resolved "https://registry.yarnpkg.com/@budibase/client/-/client-0.9.139.tgz#acec8dc746295f7793b188f4950ab2268170366c"
+  integrity sha512-PSSSaWjUrY/C4kG8r46aOVfq0aCEZGuI2Uv4jkqmk1zgt0GTXiJ+iQBkg7WZqTDBm7JIUzYUzV1T102tN4L1Jg==
   dependencies:
-    "@budibase/bbui" "^0.9.133"
-    "@budibase/standard-components" "^0.9.133"
-    "@budibase/string-templates" "^0.9.133"
+    "@budibase/bbui" "^0.9.139"
+    "@budibase/standard-components" "^0.9.139"
+    "@budibase/string-templates" "^0.9.139"
     regexparam "^1.3.0"
     shortid "^2.2.15"
     svelte-spa-router "^3.0.5"
@@ -1055,12 +1055,12 @@
     to-gfm-code-block "^0.1.1"
     year "^0.2.1"
 
-"@budibase/standard-components@^0.9.133":
-  version "0.9.133"
-  resolved "https://registry.yarnpkg.com/@budibase/standard-components/-/standard-components-0.9.133.tgz#789c02b45dc3853b003822c09e18ce7ece4dfa29"
-  integrity sha512-xcuwTxsqk1J/YmM4YjThO/Fm0eJ+aZWm0kbFgfN+dNN9fuPlsPOLmlVEWeOUPmBa5XfRyDbx6lDYj0PPEK8CvA==
+"@budibase/standard-components@^0.9.139":
+  version "0.9.139"
+  resolved "https://registry.yarnpkg.com/@budibase/standard-components/-/standard-components-0.9.139.tgz#cf8e2b759ae863e469e50272b3ca87f2827e66e3"
+  integrity sha512-Av0u9Eq2jerjhG6Atta+c0mOQGgE5K0QI3cm+8s/3Vki6/PXkO1YL5Alo3BOn9ayQAVZ/xp4rtZPuN/rzRibHw==
   dependencies:
-    "@budibase/bbui" "^0.9.133"
+    "@budibase/bbui" "^0.9.139"
     "@spectrum-css/button" "^3.0.3"
     "@spectrum-css/card" "^3.0.3"
     "@spectrum-css/divider" "^1.0.3"
@@ -1073,10 +1073,10 @@
     svelte-apexcharts "^1.0.2"
     svelte-flatpickr "^3.1.0"
 
-"@budibase/string-templates@^0.9.125-alpha.17", "@budibase/string-templates@^0.9.133":
-  version "0.9.133"
-  resolved "https://registry.yarnpkg.com/@budibase/string-templates/-/string-templates-0.9.133.tgz#221d81e080dc4485dcffa989d16e2bbed39f9055"
-  integrity sha512-SMHcSPwHYdAqol9YCcMoYawp5/ETr9TqGZCUsL+hUUq+LritPwu/miQ++SVvRTQbOR7Mker0S9LO3H8mwYkW8w==
+"@budibase/string-templates@^0.9.139":
+  version "0.9.139"
+  resolved "https://registry.yarnpkg.com/@budibase/string-templates/-/string-templates-0.9.139.tgz#f87de1d7382a81164bb734ef62ba552839805134"
+  integrity sha512-T7FR3GSmc/3vs6bynYrL/POjGP/z4pjlwjI4P6b2u10Fg2HWtI0QPZ+ifnOUf53Ry2r/PvDELATqkElpKh9Spg==
   dependencies:
     "@budibase/handlebars-helpers" "^0.11.4"
     dayjs "^1.10.4"
@@ -11110,9 +11110,9 @@ tmp@^0.0.33:
     os-tmpdir "~1.0.2"
 
 tmpl@1.0.x:
-  version "1.0.4"
-  resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.4.tgz#23640dd7b42d00433911140820e5cf440e521dd1"
-  integrity sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE=
+  version "1.0.5"
+  resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc"
+  integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==
 
 to-buffer@^1.1.1:
   version "1.1.1"
diff --git a/packages/string-templates/package.json b/packages/string-templates/package.json
index c784f4dce6..f78a7cdf72 100644
--- a/packages/string-templates/package.json
+++ b/packages/string-templates/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@budibase/string-templates",
-  "version": "0.9.125-alpha.19",
+  "version": "0.9.140-alpha.5",
   "description": "Handlebars wrapper for Budibase templating.",
   "main": "src/index.cjs",
   "module": "dist/bundle.mjs",
diff --git a/packages/string-templates/yarn.lock b/packages/string-templates/yarn.lock
index 0188a9ec1d..82f99d7b31 100644
--- a/packages/string-templates/yarn.lock
+++ b/packages/string-templates/yarn.lock
@@ -4633,9 +4633,9 @@ time-stamp@^1.0.1:
   integrity sha1-dkpaEa9QVhkhsTPztE5hhofg9cM=
 
 tmpl@1.0.x:
-  version "1.0.4"
-  resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.4.tgz#23640dd7b42d00433911140820e5cf440e521dd1"
-  integrity sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE=
+  version "1.0.5"
+  resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc"
+  integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==
 
 to-fast-properties@^2.0.0:
   version "2.0.0"
diff --git a/packages/worker/package.json b/packages/worker/package.json
index 571543b1cf..689e7d8569 100644
--- a/packages/worker/package.json
+++ b/packages/worker/package.json
@@ -1,7 +1,7 @@
 {
   "name": "@budibase/worker",
   "email": "hi@budibase.com",
-  "version": "0.9.125-alpha.19",
+  "version": "0.9.140-alpha.5",
   "description": "Budibase background service",
   "main": "src/index.js",
   "repository": {
@@ -25,8 +25,8 @@
   "author": "Budibase",
   "license": "AGPL-3.0-or-later",
   "dependencies": {
-    "@budibase/auth": "^0.9.125-alpha.19",
-    "@budibase/string-templates": "^0.9.125-alpha.19",
+    "@budibase/auth": "^0.9.140-alpha.5",
+    "@budibase/string-templates": "^0.9.140-alpha.5",
     "@koa/router": "^8.0.0",
     "@techpass/passport-openidconnect": "^0.3.0",
     "aws-sdk": "^2.811.0",
diff --git a/packages/worker/src/api/controllers/global/users.js b/packages/worker/src/api/controllers/global/users.js
index 8f754e2922..1375240f34 100644
--- a/packages/worker/src/api/controllers/global/users.js
+++ b/packages/worker/src/api/controllers/global/users.js
@@ -31,7 +31,12 @@ async function allUsers() {
   return response.rows.map(row => row.doc)
 }
 
-async function saveUser(user, tenantId, hashPassword = true) {
+async function saveUser(
+  user,
+  tenantId,
+  hashPassword = true,
+  requirePassword = true
+) {
   if (!tenantId) {
     throw "No tenancy specified."
   }
@@ -57,12 +62,13 @@ async function saveUser(user, tenantId, hashPassword = true) {
     hashedPassword = hashPassword ? await hash(password) : password
   } else if (dbUser) {
     hashedPassword = dbUser.password
-  } else {
+  } else if (requirePassword) {
     throw "Password must be specified."
   }
 
   _id = _id || generateGlobalUserID()
   user = {
+    createdAt: Date.now(),
     ...dbUser,
     ...user,
     _id,
@@ -106,16 +112,21 @@ exports.save = async ctx => {
   }
 }
 
+const parseBooleanParam = param => {
+  if (param && param == "false") {
+    return false
+  } else {
+    return true
+  }
+}
+
 exports.adminUser = async ctx => {
   const { email, password, tenantId } = ctx.request.body
 
   // account portal sends a pre-hashed password - honour param to prevent double hashing
-  let hashPassword = ctx.request.query.hashPassword
-  if (hashPassword && hashPassword == "false") {
-    hashPassword = false
-  } else {
-    hashPassword = true
-  }
+  const hashPassword = parseBooleanParam(ctx.request.query.hashPassword)
+  // account portal sends no password for SSO users
+  const requirePassword = parseBooleanParam(ctx.request.query.requirePassword)
 
   if (await doesTenantExist(tenantId)) {
     ctx.throw(403, "Organisation already exists.")
@@ -138,6 +149,7 @@ exports.adminUser = async ctx => {
   const user = {
     email: email,
     password: password,
+    createdAt: Date.now(),
     roles: {},
     builder: {
       global: true,
@@ -148,7 +160,7 @@ exports.adminUser = async ctx => {
     tenantId,
   }
   try {
-    ctx.body = await saveUser(user, tenantId, hashPassword)
+    ctx.body = await saveUser(user, tenantId, hashPassword, requirePassword)
   } catch (err) {
     ctx.throw(err.status || 400, err)
   }
diff --git a/packages/worker/src/api/controllers/global/workspaces.js b/packages/worker/src/api/controllers/global/workspaces.js
index 95a1ec296d..48a710c92d 100644
--- a/packages/worker/src/api/controllers/global/workspaces.js
+++ b/packages/worker/src/api/controllers/global/workspaces.js
@@ -11,7 +11,7 @@ exports.save = async function (ctx) {
   }
 
   try {
-    const response = await db.post(workspaceDoc)
+    const response = await db.put(workspaceDoc)
     ctx.body = {
       _id: response.id,
       _rev: response.rev,
diff --git a/packages/worker/src/api/routes/global/users.js b/packages/worker/src/api/routes/global/users.js
index 9af249260d..1a04944a30 100644
--- a/packages/worker/src/api/routes/global/users.js
+++ b/packages/worker/src/api/routes/global/users.js
@@ -10,7 +10,7 @@ function buildAdminInitValidation() {
   return joiValidator.body(
     Joi.object({
       email: Joi.string().required(),
-      password: Joi.string().required(),
+      password: Joi.string(),
       tenantId: Joi.string().required(),
     })
       .required()
diff --git a/packages/worker/yarn.lock b/packages/worker/yarn.lock
index 59dec93830..d41e1a799c 100644
--- a/packages/worker/yarn.lock
+++ b/packages/worker/yarn.lock
@@ -287,10 +287,10 @@
   resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
   integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
 
-"@budibase/auth@^0.9.128":
-  version "0.9.128"
-  resolved "https://registry.yarnpkg.com/@budibase/auth/-/auth-0.9.128.tgz#6bb6c716b6647b7e9362e3faf12b191650ea0ad4"
-  integrity sha512-WCcrtAXilT/4++7PdzyTYgrdVqZcKhUev3NcGrFQf7WbDhkVCuigWbb8Q01KXODjbs0BZC0RshVv/PxrgLbBQA==
+"@budibase/auth@^0.9.139":
+  version "0.9.139"
+  resolved "https://registry.yarnpkg.com/@budibase/auth/-/auth-0.9.139.tgz#0610582800df062372582f9139c7aa99606af3e1"
+  integrity sha512-2JUAKC3AA74O3TXHjoGCoXkDxXqUS1K8KGFrJtrUQQrVq1YeQGSjD6Km+Ho8PqUaNdpEfZinBS1/3qFUqaQbuQ==
   dependencies:
     "@techpass/passport-openidconnect" "^0.3.0"
     aws-sdk "^2.901.0"
@@ -338,10 +338,10 @@
     to-gfm-code-block "^0.1.1"
     year "^0.2.1"
 
-"@budibase/string-templates@^0.9.128":
-  version "0.9.128"
-  resolved "https://registry.yarnpkg.com/@budibase/string-templates/-/string-templates-0.9.128.tgz#50ee46dc0d726d481bd5139cd0b38364649a8463"
-  integrity sha512-4TzmnX2o5S2cts08ukB86El4wYm7cHuV2t6a7yDMGPe1mWeKP1WEtVF6rKhXEdbPTiotW8oYondOlgOP7DT9lA==
+"@budibase/string-templates@^0.9.139":
+  version "0.9.139"
+  resolved "https://registry.yarnpkg.com/@budibase/string-templates/-/string-templates-0.9.139.tgz#f87de1d7382a81164bb734ef62ba552839805134"
+  integrity sha512-T7FR3GSmc/3vs6bynYrL/POjGP/z4pjlwjI4P6b2u10Fg2HWtI0QPZ+ifnOUf53Ry2r/PvDELATqkElpKh9Spg==
   dependencies:
     "@budibase/handlebars-helpers" "^0.11.4"
     dayjs "^1.10.4"
@@ -6184,9 +6184,9 @@ tiny-queue@^0.2.0:
   integrity sha1-JaZ/LG4lOyypQZd7XvdELvl6YEY=
 
 tmpl@1.0.x:
-  version "1.0.4"
-  resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.4.tgz#23640dd7b42d00433911140820e5cf440e521dd1"
-  integrity sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE=
+  version "1.0.5"
+  resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc"
+  integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==
 
 to-fast-properties@^2.0.0:
   version "2.0.0"