diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 6f3f6abb19c7045df41cb9aa0833a9fd3a517aa0..544304bc019e2ff523ea14d2d02db82897f3ff30 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -69,22 +69,29 @@ jobs:
   formatter:
     runs-on: ubuntu-18.04
 
-    container:
-      image: python:3.7
-
     name: Formatting
 
     steps:
       - name: Check out repository code
         uses: actions/checkout@v2
-      - name: Install dependencies
+      - uses: actions/setup-python@v2
+        with:
+          python-version: 3.7
+      - name: Install Python dependencies
         run: pip install -r requirements-dev.txt
+      - name: Setup Node
+        uses: actions/setup-node@v2
+      - name: Install Node dependencies
+        run: npm ci
       - name: Add localsettings
         run: cp evap/settings_test.py evap/localsettings.py
       - name: Check code formatting
         run: black --check evap
       - name: Check imports formatting
         run: isort . --check --diff
+      - run: ls -laR evap/static/ts
+      - name: Check TypeScript formatting
+        run: npx prettier --list-different --loglevel debug --config evap/static/ts/.prettierrc.json evap/static/ts/src
 
 
   backup-process:
diff --git a/.gitignore b/.gitignore
index d4ac1d1cbc3e63711edbbfc13738ff11cd736bed..ddeed6d5c38d321cb3e67f6436b3f417d206a2d3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -39,6 +39,7 @@ htmlcov
 
 # pip puts editable packages here
 src
+!evap/static/ts/.prettierrc.json
 !evap/static/ts/src
 
 node_modules
diff --git a/evap/evaluation/management/commands/format.py b/evap/evaluation/management/commands/format.py
index a1994d7d51016ce243fa2ed06e28bf1e50deed4e..f513276cf2f95d061b28617e5c8cb9169f9d591d 100644
--- a/evap/evaluation/management/commands/format.py
+++ b/evap/evaluation/management/commands/format.py
@@ -11,3 +11,4 @@ class Command(BaseCommand):
     def handle(self, *args, **options):
         subprocess.run(["black", "evap"], check=False)  # nosec
         subprocess.run(["isort", "."], check=False)  # nosec
+        subprocess.run(["npx", "prettier", "--write", "evap/static/ts/src"], check=False)  # nosec
diff --git a/evap/evaluation/tests/test_commands.py b/evap/evaluation/tests/test_commands.py
index 4824fcd16ec9fe358b7fafba1c8bba7fa7fdb9e9..f0f23478782232cc07f16e562c5264569da658da 100644
--- a/evap/evaluation/tests/test_commands.py
+++ b/evap/evaluation/tests/test_commands.py
@@ -352,11 +352,12 @@ class TestFormatCommand(TestCase):
     @patch("subprocess.run")
     def test_formatters_called(self, mock_subprocess_run):
         management.call_command("format")
-        self.assertEqual(len(mock_subprocess_run.mock_calls), 2)
+        self.assertEqual(len(mock_subprocess_run.mock_calls), 3)
         mock_subprocess_run.assert_has_calls(
             [
                 call(["black", "evap"], check=False),
                 call(["isort", "."], check=False),
+                call(["npx", "prettier", "--write", "evap/static/ts/src"], check=False),
             ]
         )
 
diff --git a/evap/static/ts/.prettierrc.json b/evap/static/ts/.prettierrc.json
new file mode 100644
index 0000000000000000000000000000000000000000..d59f9815241ab1c9586226f793ead6443596cf06
--- /dev/null
+++ b/evap/static/ts/.prettierrc.json
@@ -0,0 +1,6 @@
+{
+    "tabWidth": 4,
+    "arrowParens": "avoid",
+    "trailingComma": "all",
+    "printWidth": 120
+}
diff --git a/evap/static/ts/src/csrf-utils.ts b/evap/static/ts/src/csrf-utils.ts
index b221865a44d58fa1a8eab91df6f68e258c8e754d..5300b1b03d59d37fcee61aa446acf6224f46db4a 100644
--- a/evap/static/ts/src/csrf-utils.ts
+++ b/evap/static/ts/src/csrf-utils.ts
@@ -1,7 +1,8 @@
 // based on: https://docs.djangoproject.com/en/3.1/ref/csrf/#ajax
 function getCookie(name: string): string | null {
     if (document.cookie !== "") {
-        const cookie = document.cookie.split(";")
+        const cookie = document.cookie
+            .split(";")
             .map(cookie => cookie.trim())
             .find(cookie => cookie.substring(0, name.length + 1) === `${name}=`);
         if (cookie) {
@@ -19,7 +20,7 @@ function isMethodCsrfSafe(method: string): boolean {
 
 // setup ajax sending csrf token
 $.ajaxSetup({
-    beforeSend: function(xhr: JQuery.jqXHR, settings: JQuery.AjaxSettings) {
+    beforeSend: function (xhr: JQuery.jqXHR, settings: JQuery.AjaxSettings) {
         const isMethodSafe = settings.method && isMethodCsrfSafe(settings.method);
         if (!isMethodSafe && !this.crossDomain) {
             xhr.setRequestHeader("X-CSRFToken", csrftoken);
diff --git a/evap/static/ts/src/datagrid.ts b/evap/static/ts/src/datagrid.ts
index 5262d356c5eed251d402e084d459d2cb8cb88d6d..8f0584c68d9c5ede9a89df6cf6539c3a239facf3 100644
--- a/evap/static/ts/src/datagrid.ts
+++ b/evap/static/ts/src/datagrid.ts
@@ -1,27 +1,27 @@
 declare const Sortable: typeof import("sortablejs");
 
 interface Row {
-    element: HTMLElement,
-    searchWords: string[],
-    filterValues: Map<string, string[]>,
-    orderValues: Map<string, string | number>,
-    isDisplayed: boolean,
+    element: HTMLElement;
+    searchWords: string[];
+    filterValues: Map<string, string[]>;
+    orderValues: Map<string, string | number>;
+    isDisplayed: boolean;
 }
 
 interface State {
-    search: string,
-    filter: Map<string, string[]>,
-    order: [string, "asc" | "desc"][],
+    search: string;
+    filter: Map<string, string[]>;
+    order: [string, "asc" | "desc"][];
 }
 
 interface BaseParameters {
-    storageKey: string,
-    searchInput: HTMLInputElement,
+    storageKey: string;
+    searchInput: HTMLInputElement;
 }
 
 interface DataGridParameters extends BaseParameters {
-    head: HTMLElement,
-    container: HTMLElement
+    head: HTMLElement;
+    container: HTMLElement;
 }
 
 abstract class DataGrid {
@@ -33,7 +33,7 @@ abstract class DataGrid {
     private delayTimer: any | null;
     protected state: State;
 
-    protected constructor({storageKey, head, container, searchInput}: DataGridParameters) {
+    protected constructor({ storageKey, head, container, searchInput }: DataGridParameters) {
         this.storageKey = storageKey;
         this.sortableHeaders = new Map();
         head.querySelectorAll<HTMLElement>(".col-order").forEach(header => {
@@ -83,16 +83,19 @@ abstract class DataGrid {
     private static NUMBER_REGEX = /^[+-]?\d+(?:[.,]\d*)?$/;
 
     private fetchRows(): Row[] {
-        let rows = [...this.container.children].map(row => row as HTMLElement).map(row => {
-            const searchWords = this.findSearchableCells(row)
-                .flatMap(element => DataGrid.searchWordsOf(element.textContent!));
-            return {
-                element: row,
-                searchWords,
-                filterValues: this.fetchRowFilterValues(row),
-                orderValues: this.fetchRowOrderValues(row),
-            } as Row;
-        });
+        let rows = [...this.container.children]
+            .map(row => row as HTMLElement)
+            .map(row => {
+                const searchWords = this.findSearchableCells(row).flatMap(element =>
+                    DataGrid.searchWordsOf(element.textContent!),
+                );
+                return {
+                    element: row,
+                    searchWords,
+                    filterValues: this.fetchRowFilterValues(row),
+                    orderValues: this.fetchRowOrderValues(row),
+                } as Row;
+            });
         for (const column of this.sortableHeaders.keys()) {
             const orderValues = rows.map(row => row.orderValues.get(column) as string);
             const isNumericalColumn = orderValues.every(orderValue => DataGrid.NUMBER_REGEX.test(orderValue));
@@ -100,7 +103,7 @@ abstract class DataGrid {
                 rows.forEach(row => {
                     const numberString = (row.orderValues.get(column) as string).replace(",", ".");
                     row.orderValues.set(column, parseFloat(numberString));
-                })
+                });
             }
         }
         return rows;
@@ -173,9 +176,7 @@ abstract class DataGrid {
     // Reflects changes to the rows to the DOM
     protected renderToDOM() {
         [...this.container.children].map(element => element as HTMLElement).forEach(element => element.remove());
-        const elements = this.rows
-            .filter(row => row.isDisplayed)
-            .map(row => row.element);
+        const elements = this.rows.filter(row => row.isDisplayed).map(row => row.element);
         this.container.append(...elements);
         this.saveStateToStorage();
     }
@@ -206,15 +207,15 @@ abstract class DataGrid {
 }
 
 interface TableGridParameters extends BaseParameters {
-    table: HTMLTableElement,
-    resetSearch: HTMLButtonElement,
+    table: HTMLTableElement;
+    resetSearch: HTMLButtonElement;
 }
 
 // Table based data grid which uses its head and body
 export class TableGrid extends DataGrid {
     private resetSearch: HTMLButtonElement;
 
-    constructor({table, resetSearch, ...options}: TableGridParameters) {
+    constructor({ table, resetSearch, ...options }: TableGridParameters) {
         super({
             head: table.querySelector("thead")!,
             container: table.querySelector("tbody")!,
@@ -252,13 +253,13 @@ export class TableGrid extends DataGrid {
 }
 
 interface EvaluationGridParameters extends TableGridParameters {
-    filterButtons: HTMLButtonElement[],
+    filterButtons: HTMLButtonElement[];
 }
 
 export class EvaluationGrid extends TableGrid {
     private filterButtons: HTMLButtonElement[];
 
-    constructor({filterButtons, ...options}: EvaluationGridParameters) {
+    constructor({ filterButtons, ...options }: EvaluationGridParameters) {
         super(options);
         this.filterButtons = filterButtons;
     }
@@ -295,8 +296,9 @@ export class EvaluationGrid extends TableGrid {
     }
 
     protected fetchRowFilterValues(row: HTMLElement): Map<string, string[]> {
-        const evaluationState = [...row.querySelectorAll<HTMLElement>("[data-filter]")]
-            .map(element => element.dataset.filter!);
+        const evaluationState = [...row.querySelectorAll<HTMLElement>("[data-filter]")].map(
+            element => element.dataset.filter!,
+        );
         return new Map([["evaluationState", evaluationState]]);
     }
 
@@ -315,13 +317,13 @@ export class EvaluationGrid extends TableGrid {
 }
 
 interface QuestionnaireParameters extends TableGridParameters {
-    updateUrl: string,
+    updateUrl: string;
 }
 
 export class QuestionnaireGrid extends TableGrid {
     private readonly updateUrl: string;
 
-    constructor({updateUrl, ...options}: QuestionnaireParameters) {
+    constructor({ updateUrl, ...options }: QuestionnaireParameters) {
         super(options);
         this.updateUrl = updateUrl;
     }
@@ -338,35 +340,41 @@ export class QuestionnaireGrid extends TableGrid {
                 }
                 const questionnaireIndices = this.rows.map((row, index) => [$(row.element).data("id"), index]);
                 $.post(this.updateUrl, Object.fromEntries(questionnaireIndices));
-            }
+            },
         });
     }
 
     private reorderRow(oldPosition: number, newPosition: number) {
-        const displayedRows = this.rows.map((row, index) => ({row, index}))
-            .filter(({row}) => row.isDisplayed);
+        const displayedRows = this.rows.map((row, index) => ({ row, index })).filter(({ row }) => row.isDisplayed);
         this.rows.splice(displayedRows[oldPosition].index, 1);
         this.rows.splice(displayedRows[newPosition].index, 0, displayedRows[oldPosition].row);
     }
 }
 
 interface ResultGridParameters extends DataGridParameters {
-    filterCheckboxes: Map<string, {selector: string, checkboxes: HTMLInputElement[]}>,
-    sortColumnSelect: HTMLSelectElement,
-    sortOrderCheckboxes: HTMLInputElement[],
-    resetFilter: HTMLButtonElement,
-    resetOrder: HTMLButtonElement,
+    filterCheckboxes: Map<string, { selector: string; checkboxes: HTMLInputElement[] }>;
+    sortColumnSelect: HTMLSelectElement;
+    sortOrderCheckboxes: HTMLInputElement[];
+    resetFilter: HTMLButtonElement;
+    resetOrder: HTMLButtonElement;
 }
 
 // Grid based data grid which has its container separated from its header
 export class ResultGrid extends DataGrid {
-    private readonly filterCheckboxes: Map<string, {selector: string, checkboxes: HTMLInputElement[]}>;
+    private readonly filterCheckboxes: Map<string, { selector: string; checkboxes: HTMLInputElement[] }>;
     private sortColumnSelect: HTMLSelectElement;
     private sortOrderCheckboxes: HTMLInputElement[];
     private resetFilter: HTMLButtonElement;
     private resetOrder: HTMLButtonElement;
 
-    constructor({filterCheckboxes, sortColumnSelect, sortOrderCheckboxes, resetFilter, resetOrder, ...options}: ResultGridParameters) {
+    constructor({
+        filterCheckboxes,
+        sortColumnSelect,
+        sortOrderCheckboxes,
+        resetFilter,
+        resetOrder,
+        ...options
+    }: ResultGridParameters) {
         super(options);
         this.filterCheckboxes = filterCheckboxes;
         this.sortColumnSelect = sortColumnSelect;
@@ -377,7 +385,7 @@ export class ResultGrid extends DataGrid {
 
     public bindEvents() {
         super.bindEvents();
-        for (const [name, {checkboxes}] of this.filterCheckboxes.entries()) {
+        for (const [name, { checkboxes }] of this.filterCheckboxes.entries()) {
             checkboxes.forEach(checkbox => {
                 checkbox.addEventListener("change", () => {
                     const values = checkboxes.filter(checkbox => checkbox.checked).map(elem => elem.value);
@@ -413,21 +421,23 @@ export class ResultGrid extends DataGrid {
         const order = this.sortOrderCheckboxes.find(checkbox => checkbox.checked)!.value;
         if (order === "asc" || order === "desc") {
             if (column === "name-semester") {
-                this.sort([["name", order], ["semester", order]]);
+                this.sort([
+                    ["name", order],
+                    ["semester", order],
+                ]);
             } else {
                 this.sort([[column, order]]);
             }
         }
     }
 
-
     protected findSearchableCells(row: HTMLElement): HTMLElement[] {
         return [...row.querySelectorAll<HTMLElement>(".evaluation-name, [data-col=responsible]")];
     }
 
     protected fetchRowFilterValues(row: HTMLElement): Map<string, string[]> {
         let filterValues = new Map();
-        for (const [name, {selector, checkboxes}] of this.filterCheckboxes.entries()) {
+        for (const [name, { selector, checkboxes }] of this.filterCheckboxes.entries()) {
             // To store filter values independent of the language, use the corresponding id from the checkbox
             const values = [...row.querySelectorAll(selector)]
                 .map(element => element.textContent!.trim())
@@ -438,12 +448,15 @@ export class ResultGrid extends DataGrid {
     }
 
     protected get defaultOrder(): [string, "asc" | "desc"][] {
-        return [["name", "asc"], ["semester", "asc"]];
+        return [
+            ["name", "asc"],
+            ["semester", "asc"],
+        ];
     }
 
     protected reflectFilterStateOnInputs() {
         super.reflectFilterStateOnInputs();
-        for (const [name, {checkboxes}] of this.filterCheckboxes.entries()) {
+        for (const [name, { checkboxes }] of this.filterCheckboxes.entries()) {
             checkboxes.forEach(checkbox => {
                 let isActive;
                 if (this.state.filter.has(name)) {
diff --git a/package-lock.json b/package-lock.json
index 206271ac7bef72b53ac568409dee0f35e3f464e4..4b83b774ba13360f69e3b42181accff0056f72fe 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -12,6 +12,7 @@
                 "@types/sortablejs": "^1.3.0",
                 "jest": "^27.3.1",
                 "jest-environment-puppeteer": "^6.0.0",
+                "prettier": "^2.4.1",
                 "puppeteer": "^10.4.0",
                 "sass": "1.32.13",
                 "ts-jest": "^27.0.7",
@@ -6426,6 +6427,18 @@
                 "node": ">= 0.8.0"
             }
         },
+        "node_modules/prettier": {
+            "version": "2.4.1",
+            "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.4.1.tgz",
+            "integrity": "sha512-9fbDAXSBcc6Bs1mZrDYb3XKzDLm4EXXL9sC1LqKP5rZkT6KRr/rf9amVUcODVXgguK/isJz0d0hP72WeaKWsvA==",
+            "dev": true,
+            "bin": {
+                "prettier": "bin-prettier.js"
+            },
+            "engines": {
+                "node": ">=10.13.0"
+            }
+        },
         "node_modules/pretty-format": {
             "version": "26.6.2",
             "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz",
@@ -12653,6 +12666,12 @@
             "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=",
             "dev": true
         },
+        "prettier": {
+            "version": "2.4.1",
+            "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.4.1.tgz",
+            "integrity": "sha512-9fbDAXSBcc6Bs1mZrDYb3XKzDLm4EXXL9sC1LqKP5rZkT6KRr/rf9amVUcODVXgguK/isJz0d0hP72WeaKWsvA==",
+            "dev": true
+        },
         "pretty-format": {
             "version": "26.6.2",
             "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz",
diff --git a/package.json b/package.json
index ae7bee343a3852b5b0f4736ccf970a0f96f49811..ed919724738ba8bb70d51e1499cffc65fa56b574 100644
--- a/package.json
+++ b/package.json
@@ -7,10 +7,11 @@
         "@types/sortablejs": "^1.3.0",
         "jest": "^27.3.1",
         "jest-environment-puppeteer": "^6.0.0",
+        "prettier": "^2.4.1",
         "puppeteer": "^10.4.0",
+        "sass": "1.32.13",
         "ts-jest": "^27.0.7",
-        "typescript": "^4.4.4",
-        "sass": "1.32.13"
+        "typescript": "^4.4.4"
     },
     "jest": {
         "testRunner": "jest-jasmine2",