diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 38df26d8d85d044290c680095dd88697a9a2b895..29402570ca77777b8bc61a72a3ad3f89732cbfa0 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -16,6 +16,14 @@ test:
     - yarn install
     - yarn run test
 
+linting:
+  stage: test
+  allow_failure: true
+  script:
+    - yarn config set cache-folder "$CI_PROJECT_DIR/_yarn_cache"
+    - yarn install
+    - yarn run lint
+
 publish:
   stage: deploy
   only:
diff --git a/package.json b/package.json
index 45f7a0360f25116d1ee38dee3e0784eec37187b6..ce4dbed6ee0fef89956d1d45385f3b3b6e86c138 100644
--- a/package.json
+++ b/package.json
@@ -10,6 +10,7 @@
   "scripts": {
     "test": "lerna run test",
     "build": "lerna run build",
+    "lint": "lerna run lint",
     "publish": "lerna publish from-package --yes"
   },
   "author": "",
diff --git a/packages/check-in-place-select/.eslintignore b/packages/check-in-place-select/.eslintignore
new file mode 100644
index 0000000000000000000000000000000000000000..7b596da7b5a30a2b742e9bc9bc8002606940e18a
--- /dev/null
+++ b/packages/check-in-place-select/.eslintignore
@@ -0,0 +1,3 @@
+/vendor/**
+/dist/**
+/*.js
\ No newline at end of file
diff --git a/packages/check-in-place-select/.eslintrc.json b/packages/check-in-place-select/.eslintrc.json
new file mode 100644
index 0000000000000000000000000000000000000000..1ccd30a3fee0b4867a0172e746714eb0083fb07d
--- /dev/null
+++ b/packages/check-in-place-select/.eslintrc.json
@@ -0,0 +1,25 @@
+{
+    "env": {
+        "browser": true,
+        "es6": true,
+        "mocha": true
+    },
+    "extends": ["eslint:recommended", "plugin:jsdoc/recommended"],
+    "globals": {
+        "Atomics": "readonly",
+        "SharedArrayBuffer": "readonly"
+    },
+    "parser": "babel-eslint",
+    "parserOptions": {
+        "ecmaVersion": 2018,
+        "sourceType": "module"
+    },
+    "rules": {
+        "no-unused-vars": ["error", { "args": "none" }],
+        "semi": [2, "always"],
+        "jsdoc/require-jsdoc": 0,
+        "jsdoc/require-param-description": 0,
+        "jsdoc/require-returns": 0,
+        "jsdoc/require-param-type": 0
+    }
+}
\ No newline at end of file
diff --git a/packages/check-in-place-select/package.json b/packages/check-in-place-select/package.json
index 97cf65a7ba2f84a74a03d5398a76aa715fcc3621..78374980255edffc3949317bdb473ffe32a1c820 100644
--- a/packages/check-in-place-select/package.json
+++ b/packages/check-in-place-select/package.json
@@ -31,7 +31,9 @@
     "rollup-plugin-copy": "^3.1.0",
     "rollup-plugin-delete": "^2.0.0",
     "rollup-plugin-serve": "^1.0.1",
-    "rollup-plugin-terser": "^7.0.2"
+    "rollup-plugin-terser": "^7.0.2",
+    "eslint": "^7.3.1",
+    "eslint-plugin-jsdoc": "^30.6.4"
   },
   "dependencies": {
     "@dbp-toolkit/auth": "^0.1.0",
@@ -53,6 +55,7 @@
     "watch": "npm run watch-local",
     "watch-local": "rollup -c --watch",
     "watch-dev": "rollup -c --watch --environment BUILD:development",
-    "test": "npm run build-test && karma start --singleRun"
+    "test": "npm run build-test && karma start --singleRun",
+    "lint": "eslint ."
   }
 }
diff --git a/packages/check-in-place-select/src/check-in-place-select.js b/packages/check-in-place-select/src/check-in-place-select.js
index d2a2594896e49ab03c178bf7b7fd5c2583b43667..ae3d9eea80860342e8d3b57604145dd3845b1790 100644
--- a/packages/check-in-place-select/src/check-in-place-select.js
+++ b/packages/check-in-place-select/src/check-in-place-select.js
@@ -1,6 +1,6 @@
 import {findObjectInApiResults} from './utils.js';
-import select2LangDe from './i18n/de/select2'
-import select2LangEn from './i18n/en/select2'
+import select2LangDe from './i18n/de/select2';
+import select2LangEn from './i18n/en/select2';
 import JSONLD from '@dbp-toolkit/common/jsonld';
 import {css, html, LitElement} from 'lit-element';
 import {ScopedElementsMixin} from '@open-wc/scoped-elements';
@@ -124,6 +124,8 @@ export class CheckInPlaceSelect extends ScopedElementsMixin(LitElement) {
 
     /**
      * Initializes the Select2 selector
+     *
+     * @param ignorePreset
      */
     initSelect2(ignorePreset = false) {
         const that = this;
diff --git a/packages/check-in-place-select/src/dbp-check-in-place-select-demo.js b/packages/check-in-place-select/src/dbp-check-in-place-select-demo.js
index abcf3330bd82a519a57628132936ce136a68eb6c..539baa5028726c382c2169ff5e0961e659d6001f 100644
--- a/packages/check-in-place-select/src/dbp-check-in-place-select-demo.js
+++ b/packages/check-in-place-select/src/dbp-check-in-place-select-demo.js
@@ -17,6 +17,7 @@ export class CheckInPlaceSelectDemo extends ScopedElementsMixin(LitElement) {
         return {
           'dbp-login-button': LoginButton,
           'dbp-check-in-place-select': CheckInPlaceSelect,
+          'dbp-auth-keycloak': AuthKeycloak,
         };
     }
 
diff --git a/packages/check-in-place-select/src/i18n/de/select2.js b/packages/check-in-place-select/src/i18n/de/select2.js
index 487734d315bddea78931e81907b44033bceb504e..bef156f0f506c91c494fa5fc1c7a7b49a8a97d9c 100644
--- a/packages/check-in-place-select/src/i18n/de/select2.js
+++ b/packages/check-in-place-select/src/i18n/de/select2.js
@@ -43,4 +43,4 @@ export default function () {
             return 'Entferne alle Gegenstände';
         }
     };
-};
+}
diff --git a/packages/check-in-place-select/src/i18n/en/select2.js b/packages/check-in-place-select/src/i18n/en/select2.js
index 12ba14cc3c6a8af6a9c24a7366aef7ca739f3509..d172d72ef06e17651dda4d2f6d73ef49e50cf845 100644
--- a/packages/check-in-place-select/src/i18n/en/select2.js
+++ b/packages/check-in-place-select/src/i18n/en/select2.js
@@ -47,4 +47,4 @@ export default function () {
             return 'Remove all items';
         }
     };
-};
+}
diff --git a/packages/common/styles.js b/packages/common/styles.js
index 31248cd3c84f51de7f1ebae40760d83315239dd7..3c9748c91e20561bc14de5709c71ca771d81938b 100644
--- a/packages/common/styles.js
+++ b/packages/common/styles.js
@@ -1,4 +1,4 @@
-import {css, unsafeCSS, CSSResult} from 'lit-element';
+import {css, unsafeCSS} from 'lit-element';
 import {getIconSVGURL} from './src/icon.js';
 
 /**
diff --git a/packages/common/utils.js b/packages/common/utils.js
index fb31adb97ed00f544c6d35c6575d9301e1ee4aef..88cd1c53a6a4c85b29a428bf8a20c808b703c6c0 100644
--- a/packages/common/utils.js
+++ b/packages/common/utils.js
@@ -212,7 +212,7 @@ export const getAssetURL = (pkg, path) => {
         // assume it is a full path
         fullPath = pkg;
     } else {
-        fullPath = 'local/' + pkg + '/' + path
+        fullPath = 'local/' + pkg + '/' + path;
     }
     return new URL(fullPath, new URL('..', import.meta.url).href).href;
 };
@@ -298,21 +298,21 @@ export async function getMimeTypeOfFile(file) {
     const getMimeType = (signature) => {
         switch (signature) {
             case '89504E47':
-                return 'image/png'
+                return 'image/png';
             case '47494638':
-                return 'image/gif'
+                return 'image/gif';
             case '25504446':
-                return 'application/pdf'
+                return 'application/pdf';
             case 'FFD8FFDB':
             case 'FFD8FFE0':
             case 'FFD8FFE1':
-                return 'image/jpeg'
+                return 'image/jpeg';
             case '504B0304':
-                return 'application/zip'
+                return 'application/zip';
             default:
-                return 'Unknown filetype'
+                return 'Unknown filetype';
         }
-    }
+    };
 
     return await new Promise((resolve) => {
         let fileReader = new FileReader();
@@ -323,15 +323,15 @@ export async function getMimeTypeOfFile(file) {
                 let bytes = [];
 
                 uint.forEach((byte) => {
-                    bytes.push(byte.toString(16))
-                })
+                    bytes.push(byte.toString(16));
+                });
 
                 const hex = bytes.join('').toUpperCase();
                 const mimeType = getMimeType(hex);
 
                 resolve(mimeType);
             }
-        }
+        };
 
         fileReader.readAsArrayBuffer(file.slice(0, 4));
     });
diff --git a/packages/data-table-view/.eslintignore b/packages/data-table-view/.eslintignore
new file mode 100644
index 0000000000000000000000000000000000000000..c139ed4c11bae3aeb416c866e2a9aa248930c65f
--- /dev/null
+++ b/packages/data-table-view/.eslintignore
@@ -0,0 +1,4 @@
+/vendor/**
+/dist/**
+*.conf.js
+*.config.js
\ No newline at end of file
diff --git a/packages/data-table-view/.eslintrc.json b/packages/data-table-view/.eslintrc.json
new file mode 100644
index 0000000000000000000000000000000000000000..dcb3daa3e43a01208a022e30711870901c24f1f0
--- /dev/null
+++ b/packages/data-table-view/.eslintrc.json
@@ -0,0 +1,25 @@
+{
+    "env": {
+        "browser": true,
+        "es6": true,
+        "mocha": true
+    },
+    "globals": {
+        "Atomics": "readonly",
+        "SharedArrayBuffer": "readonly"
+    },
+    "parser": "babel-eslint",
+    "parserOptions": {
+        "ecmaVersion": 2018,
+        "sourceType": "module"
+    },
+    "rules": {
+        "no-unused-vars": ["error", { "args": "none" }],
+        "semi": [2, "always"],
+        "jsdoc/require-jsdoc": 0,
+        "jsdoc/require-param-description": 0,
+        "jsdoc/require-returns": 0,
+        "jsdoc/require-returns-description": 0,
+        "jsdoc/require-param-type": 0
+    }
+}
\ No newline at end of file
diff --git a/packages/data-table-view/package.json b/packages/data-table-view/package.json
index ae907ff514b6fff810398e240c4937ff9008da99..40d518d622639483d094f003baef254875d32d5c 100644
--- a/packages/data-table-view/package.json
+++ b/packages/data-table-view/package.json
@@ -30,7 +30,9 @@
     "rollup-plugin-copy": "^3.1.0",
     "rollup-plugin-delete": "^2.0.0",
     "rollup-plugin-serve": "^1.0.1",
-    "rollup-plugin-terser": "^7.0.2"
+    "rollup-plugin-terser": "^7.0.2",
+    "eslint": "^7.3.1",
+    "eslint-plugin-jsdoc": "^30.6.4"
   },
   "dependencies": {
     "@dbp-toolkit/auth": "^0.1.0",
@@ -59,6 +61,7 @@
     "watch": "npm run watch-local",
     "watch-local": "rollup -c --watch",
     "watch-dev": "rollup -c --watch --environment BUILD:development",
-    "test": "npm run build-test && karma start --singleRun"
+    "test": "npm run build-test && karma start --singleRun",
+    "lint": "eslint ."
   }
 }
diff --git a/packages/data-table-view/src/data-table-view.js b/packages/data-table-view/src/data-table-view.js
index 565c38b8c567b0223333436a08c7526387c5c1a6..834a211e647355609e87b8f4c7a0593edeee6f54 100644
--- a/packages/data-table-view/src/data-table-view.js
+++ b/packages/data-table-view/src/data-table-view.js
@@ -227,12 +227,12 @@ export class DataTableView extends LitElement {
 
         changedProperties.forEach((oldValue, propName) => {
             if (propName === "lang") {
-                i18n.changeLanguage(this.lang).catch(e => { console.log(e)});
+                i18n.changeLanguage(this.lang).catch(e => { console.log(e);});
                 languageChange = true;
             }
         });
 
-        this.updateComplete.then(this.set_datatable(this.data, languageChange)).catch(e => { console.log(e)});
+        this.updateComplete.then(this.set_datatable(this.data, languageChange)).catch(e => { console.log(e);});
         super.update(changedProperties);
     }
 
diff --git a/packages/language-select/.eslintignore b/packages/language-select/.eslintignore
new file mode 100644
index 0000000000000000000000000000000000000000..7b596da7b5a30a2b742e9bc9bc8002606940e18a
--- /dev/null
+++ b/packages/language-select/.eslintignore
@@ -0,0 +1,3 @@
+/vendor/**
+/dist/**
+/*.js
\ No newline at end of file
diff --git a/packages/language-select/.eslintrc.json b/packages/language-select/.eslintrc.json
new file mode 100644
index 0000000000000000000000000000000000000000..1ccd30a3fee0b4867a0172e746714eb0083fb07d
--- /dev/null
+++ b/packages/language-select/.eslintrc.json
@@ -0,0 +1,25 @@
+{
+    "env": {
+        "browser": true,
+        "es6": true,
+        "mocha": true
+    },
+    "extends": ["eslint:recommended", "plugin:jsdoc/recommended"],
+    "globals": {
+        "Atomics": "readonly",
+        "SharedArrayBuffer": "readonly"
+    },
+    "parser": "babel-eslint",
+    "parserOptions": {
+        "ecmaVersion": 2018,
+        "sourceType": "module"
+    },
+    "rules": {
+        "no-unused-vars": ["error", { "args": "none" }],
+        "semi": [2, "always"],
+        "jsdoc/require-jsdoc": 0,
+        "jsdoc/require-param-description": 0,
+        "jsdoc/require-returns": 0,
+        "jsdoc/require-param-type": 0
+    }
+}
\ No newline at end of file
diff --git a/packages/language-select/package.json b/packages/language-select/package.json
index 157c4bedfae58faef72613cf530a8b4929c4f1e6..19509f3c314b2b564b789782b61d83181de55e02 100644
--- a/packages/language-select/package.json
+++ b/packages/language-select/package.json
@@ -29,7 +29,9 @@
     "rollup-plugin-copy": "^3.1.0",
     "rollup-plugin-delete": "^2.0.0",
     "rollup-plugin-serve": "^1.0.1",
-    "rollup-plugin-terser": "^7.0.2"
+    "rollup-plugin-terser": "^7.0.2",
+    "eslint": "^7.3.1",
+    "eslint-plugin-jsdoc": "^30.6.4"
   },
   "dependencies": {
     "@dbp-toolkit/common": "^0.1.0",
@@ -48,6 +50,7 @@
     "watch": "npm run watch-local",
     "watch-local": "rollup -c --watch",
     "watch-dev": "rollup -c --watch --environment BUILD:development",
-    "test": "npm run build-test && karma start --singleRun"
+    "test": "npm run build-test && karma start --singleRun",
+    "lint": "eslint ."
   }
 }
diff --git a/packages/language-select/src/language-select.js b/packages/language-select/src/language-select.js
index f337483fa82b47905a2a343d2ca4b6522f9c145b..661e603126331353474c240fa34846c25b927ba0 100644
--- a/packages/language-select/src/language-select.js
+++ b/packages/language-select/src/language-select.js
@@ -106,7 +106,7 @@ export class LanguageSelect extends LitElement {
     }
 
     onExternalChange(e) {
-        this.lang = e.detail.lang
+        this.lang = e.detail.lang;
     }
 
     connectedCallback() {
diff --git a/packages/matomo/.eslintignore b/packages/matomo/.eslintignore
new file mode 100644
index 0000000000000000000000000000000000000000..7b596da7b5a30a2b742e9bc9bc8002606940e18a
--- /dev/null
+++ b/packages/matomo/.eslintignore
@@ -0,0 +1,3 @@
+/vendor/**
+/dist/**
+/*.js
\ No newline at end of file
diff --git a/packages/matomo/.eslintrc.json b/packages/matomo/.eslintrc.json
new file mode 100644
index 0000000000000000000000000000000000000000..1ccd30a3fee0b4867a0172e746714eb0083fb07d
--- /dev/null
+++ b/packages/matomo/.eslintrc.json
@@ -0,0 +1,25 @@
+{
+    "env": {
+        "browser": true,
+        "es6": true,
+        "mocha": true
+    },
+    "extends": ["eslint:recommended", "plugin:jsdoc/recommended"],
+    "globals": {
+        "Atomics": "readonly",
+        "SharedArrayBuffer": "readonly"
+    },
+    "parser": "babel-eslint",
+    "parserOptions": {
+        "ecmaVersion": 2018,
+        "sourceType": "module"
+    },
+    "rules": {
+        "no-unused-vars": ["error", { "args": "none" }],
+        "semi": [2, "always"],
+        "jsdoc/require-jsdoc": 0,
+        "jsdoc/require-param-description": 0,
+        "jsdoc/require-returns": 0,
+        "jsdoc/require-param-type": 0
+    }
+}
\ No newline at end of file
diff --git a/packages/matomo/package.json b/packages/matomo/package.json
index ced97981bd2662095757cf49dae4bac59dec44fc..a95d83e913a4da0af76c4e84fa272355c04a7729 100644
--- a/packages/matomo/package.json
+++ b/packages/matomo/package.json
@@ -30,7 +30,9 @@
     "rollup-plugin-copy": "^3.1.0",
     "rollup-plugin-delete": "^2.0.0",
     "rollup-plugin-serve": "^1.0.1",
-    "rollup-plugin-terser": "^7.0.2"
+    "rollup-plugin-terser": "^7.0.2",
+    "eslint": "^7.3.1",
+    "eslint-plugin-jsdoc": "^30.6.4"
   },
   "dependencies": {
     "@dbp-toolkit/auth": "^0.1.0",
@@ -49,6 +51,7 @@
     "watch": "rollup -c --watch",
     "watch-local": "yarn run watch",
     "watch-dev": "rollup -c --watch --environment BUILD:development",
-    "test": "rollup -c --environment BUILD:test && karma start --singleRun"
+    "test": "rollup -c --environment BUILD:test && karma start --singleRun",
+    "lint": "eslint ."
   }
 }
diff --git a/packages/matomo/src/matomo.js b/packages/matomo/src/matomo.js
index 68fc5290a0c05c25b97627c3fb5003a02c0a2be4..8e189c7283e1d4d07d3b4a0d4198ecc3d9e9646b 100644
--- a/packages/matomo/src/matomo.js
+++ b/packages/matomo/src/matomo.js
@@ -1,9 +1,11 @@
-import {i18n} from './i18n.js';
-import {LitElement} from "lit-element";
 import DBPLitElement from '@dbp-toolkit/common/dbp-lit-element';
 import {EventBus} from '@dbp-toolkit/common';
 import buildInfo from 'consts:buildinfo';
 
+function pushEvent(event) {
+    window._paq = window._paq || [];
+    window._paq.push(event);
+}
 
 export class MatomoElement extends DBPLitElement {
 
@@ -53,16 +55,15 @@ export class MatomoElement extends DBPLitElement {
             }
             console.log('add matomo...');
 
-            window._paq = window._paq || [];
-            _paq.push(['setCustomVariable', 1, "GitCommit", buildInfo.info, "visit"]);
-            _paq.push(['enableHeartBeatTimer']);
-            _paq.push(['disableCookies']);
-            _paq.push(['trackPageView']);
-            _paq.push(['enableLinkTracking']);
+            pushEvent(['setCustomVariable', 1, "GitCommit", buildInfo.info, "visit"]);
+            pushEvent(['enableHeartBeatTimer']);
+            pushEvent(['disableCookies']);
+            pushEvent(['trackPageView']);
+            pushEvent(['enableLinkTracking']);
 
             (function (endpoint, siteId) {
-                _paq.push(['setTrackerUrl', endpoint+'matomo.php']);
-                _paq.push(['setSiteId', siteId]);
+                pushEvent(['setTrackerUrl', endpoint+'matomo.php']);
+                pushEvent(['setSiteId', siteId]);
 
                 var g = document.createElement('script');
                 var s = document.getElementsByTagName('script')[0];
@@ -75,31 +76,31 @@ export class MatomoElement extends DBPLitElement {
 
             // track changed locations
             window.addEventListener('locationchanged', function(e) {
-                _paq.push(['setReferrerUrl', e.detail.referrerUrl]);
-                _paq.push(['setCustomUrl', location.href]);
-                // _paq.push(['setDocumentTitle', '']);
-                _paq.push(['trackPageView']);
+                pushEvent(['setReferrerUrl', e.detail.referrerUrl]);
+                pushEvent(['setCustomUrl', location.href]);
+                // pushEvent(['setDocumentTitle', '']);
+                pushEvent(['trackPageView']);
 
                 // make Matomo aware of newly added content
                 var content = document.getElementById('content');
-                _paq.push(['MediaAnalytics::scanForMedia', content]);
-                _paq.push(['FormAnalytics::scanForForms', content]);
-                _paq.push(['trackContentImpressionsWithinNode', content]);
+                pushEvent(['MediaAnalytics::scanForMedia', content]);
+                pushEvent(['FormAnalytics::scanForForms', content]);
+                pushEvent(['trackContentImpressionsWithinNode', content]);
             });
 
             // track errors
             window.addEventListener('error', function(e) {
-                _paq.push(['trackEvent', 'Error', e.error.message + '\n' + e.error.stack]);
+                pushEvent(['trackEvent', 'Error', e.error.message + '\n' + e.error.stack]);
             });
 
             window.addEventListener('unhandledrejection', function(e) {
-                _paq.push(['trackEvent', 'UnhandledRejection', e.reason]);
+                pushEvent(['trackEvent', 'UnhandledRejection', e.reason]);
             });
 
             this.isRunning = true;
             if (this.lastEvent.length > 0) {
                 console.log('MatomoElement* (' + this.isRunning + '): ' + this.lastEvent[1] + ', ' + this.lastEvent[2]);
-                _paq.push(this.lastEvent);
+                pushEvent(this.lastEvent);
                 this.lastEvent = [];
             }
             return;
@@ -115,7 +116,7 @@ export class MatomoElement extends DBPLitElement {
         console.log('MatomoElement  (' + this.isRunning + '): ' + action + ', ' + message);
         const event = ['trackEvent', action, message];
         if (this.isRunning) {
-            _paq.push(event);
+            pushEvent(event);
         } else {
             this.lastEvent = event;
         }