diff --git a/app/controllers/my_courses.php b/app/controllers/my_courses.php
index e42b2c86387b2703548dc3e1ce35889ca0a09bbe..ee356164973d20dc8814c0952a34dfde7149ec70 100644
--- a/app/controllers/my_courses.php
+++ b/app/controllers/my_courses.php
@@ -81,8 +81,6 @@ class MyCoursesController extends AuthenticatedController
         PageLayout::setHelpKeyword('Basis.MeineVeranstaltungen');
         PageLayout::setTitle(_('Meine Veranstaltungen'));
 
-        $this->sem_data = Semester::getAllAsArray();
-
         $sem_key     = $this->getSemesterKey();
         $group_field = $this->getGroupField();
 
@@ -115,89 +113,7 @@ class MyCoursesController extends AuthenticatedController
         }
 
         $this->setupSidebar($sem_key, $group_field, $this->check_for_new($sem_courses, $group_field));
-
-        $temp_courses = [];
-        $groups = [];
-        if (is_array($sem_courses)) {
-            foreach ($sem_courses as $_outer_index => $_outer) {
-                if ($group_field === 'sem_number') {
-                    $_courses = [];
-
-                    foreach ($_outer as $course) {
-                        $_courses[$course['seminar_id']] = $course;
-                        if (!empty($course['children']) && is_array($course['children'])) {
-                            foreach ($course['children'] as $child) {
-                                $_courses[$child['seminar_id']] = $child;
-                            }
-                        }
-                    }
-
-                    $groups[] = [
-                        'id' => $_outer_index,
-                        'name' => (string)$this->sem_data[$_outer_index]['name'],
-                        'data' => [
-                            [
-                                'id' => md5($_outer_index),
-                                'label' => false,
-                                'ids' => array_keys($_courses),
-                            ],
-                        ],
-                    ];
-                    $temp_courses = array_merge($temp_courses, $_courses);
-                } else {
-                    $count = 1;
-                    $_groups = [];
-                    foreach ($_outer as $_inner_index => $_inner) {
-                        $_courses = [];
-
-                        foreach ($_inner as $course) {
-                            $_courses[$course['seminar_id']] = $course;
-                            if ($course['children']) {
-                                foreach ($course['children'] as $child) {
-                                    $_courses[$child['seminar_id']] = $child;
-                                }
-                            }
-                        }
-
-                        $label = $_inner_index;
-                        if ($group_field === 'sem_tree_id' && !$label) {
-                            $label = _('keine Zuordnung');
-                        } elseif ($group_field === 'gruppe') {
-                            $label = _('Gruppe') . ' ' . $count++;
-                        }
-
-                        $_groups[] = [
-                            'id' => md5($_outer_index . $_inner_index),
-                            'label' => $label,
-                            'ids' => array_keys($_courses),
-                        ];
-
-                        $temp_courses = array_merge($temp_courses, $_courses);
-                    }
-
-                    $groups[] = [
-                        'id' => $_outer_index,
-                        'name' => (string)$this->sem_data[$_outer_index]['name'],
-                        'data' => $_groups,
-                    ];
-                }
-            }
-        }
-
-        $data = [
-            'courses' => $this->sanitizeNavigations(array_map([$this, 'convertCourse'], $temp_courses)),
-            'groups'  => $groups,
-            'user_id' => $GLOBALS['user']->id,
-            'config'  => [
-                'allow_dozent_visibility'  => Config::get()->ALLOW_DOZENT_VISIBILITY,
-                'open_groups'              => array_values($GLOBALS['user']->cfg->MY_COURSES_OPEN_GROUPS),
-                'sem_number'               => Config::get()->IMPORTANT_SEMNUMBER,
-                'display_type'             => $GLOBALS['user']->cfg->MY_COURSES_TILED_DISPLAY ? 'tiles' : 'tables',
-                'responsive_type'          => $GLOBALS['user']->cfg->MY_COURSES_TILED_DISPLAY_RESPONSIVE ? 'tiles' : 'tables',
-                'navigation_show_only_new' => $GLOBALS['user']->cfg->MY_COURSES_SHOW_NEW_ICONS_ONLY,
-                'group_by'                 => $this->getGroupField(),
-            ],
-        ];
+        $data = $this->getMyCoursesData($sem_courses, $group_field);
 
         PageLayout::addHeadElement(
             'script',
@@ -768,6 +684,119 @@ class MyCoursesController extends AuthenticatedController
         $this->redirect($this->url_for('my_courses'));
     }
 
+    /**
+     * Get the data array for presenting the course list in the portal widget.
+     */
+    public function getPortalWidgetData()
+    {
+        $sem_key     = $this->getSemesterKey();
+        $group_field = $this->getGroupField();
+
+        $sem_courses  = MyRealmModel::getPreparedCourses($sem_key, [
+            'group_field'         => $group_field,
+            'order_by'            => null,
+            'order'               => 'asc',
+            'studygroups_enabled' => Config::get()->MY_COURSES_ENABLE_STUDYGROUPS,
+            'deputies_enabled'    => Config::get()->DEPUTIES_ENABLE,
+        ]);
+
+        return $this->getMyCoursesData($sem_courses, $group_field);
+    }
+
+    /**
+     * Get the data array for presenting the course list in Vue.
+     *
+     * @param array $sem_courses
+     * @param string $group_field
+     */
+    private function getMyCoursesData($sem_courses, $group_field)
+    {
+        $sem_data = Semester::getAllAsArray();
+        $temp_courses = [];
+        $groups = [];
+
+        if (is_array($sem_courses)) {
+            foreach ($sem_courses as $_outer_index => $_outer) {
+                if ($group_field === 'sem_number') {
+                    $_courses = [];
+
+                    foreach ($_outer as $course) {
+                        $_courses[$course['seminar_id']] = $course;
+                        if (!empty($course['children']) && is_array($course['children'])) {
+                            foreach ($course['children'] as $child) {
+                                $_courses[$child['seminar_id']] = $child;
+                            }
+                        }
+                    }
+
+                    $groups[] = [
+                        'id' => $_outer_index,
+                        'name' => (string) $sem_data[$_outer_index]['name'],
+                        'data' => [
+                            [
+                                'id' => md5($_outer_index),
+                                'label' => false,
+                                'ids' => array_keys($_courses),
+                            ],
+                        ],
+                    ];
+                    $temp_courses = array_merge($temp_courses, $_courses);
+                } else {
+                    $count = 1;
+                    $_groups = [];
+                    foreach ($_outer as $_inner_index => $_inner) {
+                        $_courses = [];
+
+                        foreach ($_inner as $course) {
+                            $_courses[$course['seminar_id']] = $course;
+                            if ($course['children']) {
+                                foreach ($course['children'] as $child) {
+                                    $_courses[$child['seminar_id']] = $child;
+                                }
+                            }
+                        }
+
+                        $label = $_inner_index;
+                        if ($group_field === 'sem_tree_id' && !$label) {
+                            $label = _('keine Zuordnung');
+                        } elseif ($group_field === 'gruppe') {
+                            $label = _('Gruppe') . ' ' . $count++;
+                        }
+
+                        $_groups[] = [
+                            'id' => md5($_outer_index . $_inner_index),
+                            'label' => $label,
+                            'ids' => array_keys($_courses),
+                        ];
+
+                        $temp_courses = array_merge($temp_courses, $_courses);
+                    }
+
+                    $groups[] = [
+                        'id' => $_outer_index,
+                        'name' => (string) $sem_data[$_outer_index]['name'],
+                        'data' => $_groups,
+                    ];
+                }
+            }
+        }
+
+        return [
+            'courses' => $this->sanitizeNavigations(array_map([$this, 'convertCourse'], $temp_courses)),
+            'groups'  => $groups,
+            'user_id' => $GLOBALS['user']->id,
+            'config'  => [
+                'allow_dozent_visibility'  => Config::get()->ALLOW_DOZENT_VISIBILITY,
+                'open_groups'              => array_values($GLOBALS['user']->cfg->MY_COURSES_OPEN_GROUPS),
+                'sem_number'               => Config::get()->IMPORTANT_SEMNUMBER,
+                'display_type'             => $GLOBALS['user']->cfg->MY_COURSES_TILED_DISPLAY ? 'tiles' : 'tables',
+                'responsive_type'          => $GLOBALS['user']->cfg->MY_COURSES_TILED_DISPLAY_RESPONSIVE ? 'tiles' : 'tables',
+                'navigation_show_only_new' => $GLOBALS['user']->cfg->MY_COURSES_SHOW_NEW_ICONS_ONLY,
+                'group_by'                 => $this->getGroupField(),
+            ],
+        ];
+    }
+
     /**
      * Get widget for grouping selected courses (e.g. by colors, ...)
      *
diff --git a/db/migrations/5.3.7_add_my_courses_widget.php b/db/migrations/5.3.7_add_my_courses_widget.php
new file mode 100644
index 0000000000000000000000000000000000000000..5837e35b7ebd24d9d9173a0d628833e5d562a7c9
--- /dev/null
+++ b/db/migrations/5.3.7_add_my_courses_widget.php
@@ -0,0 +1,44 @@
+<?php
+
+class AddMyCoursesWidget extends Migration
+{
+    public function description()
+    {
+        return 'add MyCoursesWidget (if not previously installed)';
+    }
+
+    public function up()
+    {
+        $db = DBManager::get();
+
+        // check for previous installation
+        $plugin_id = $db->fetchColumn('SELECT pluginid FROM plugins WHERE pluginclassname = ?', ['MyCoursesWidget']);
+
+        if ($plugin_id) {
+            $db->execute("UPDATE plugins SET pluginpath = '' WHERE pluginid = ?", [$plugin_id]);
+        } else {
+            // get position
+            $pos = $db->fetchColumn("SELECT MAX(navigationpos) + 1 FROM plugins WHERE plugintype = 'PortalPlugin'");
+
+            // install as portal plugin
+            $sql = "INSERT INTO plugins (pluginclassname, pluginname, plugintype, enabled, navigationpos) VALUES (?)";
+            $db->execute($sql, [['MyCoursesWidget', 'MyCoursesWidget', 'PortalPlugin', 'yes', $pos]]);
+
+            $sql = "INSERT INTO roles_plugins (roleid, pluginid)
+                    SELECT roleid, ? FROM roles WHERE `system` = 'y' AND rolename != 'Nobody'";
+            $db->execute($sql, [$db->lastInsertId()]);
+        }
+    }
+
+    public function down()
+    {
+        $db = DBManager::get();
+
+        $plugin_id = $db->fetchColumn('SELECT pluginid FROM plugins WHERE pluginclassname = ?', ['MyCoursesWidget']);
+
+        $db->execute('DELETE FROM widget_default WHERE pluginid = ?', [$plugin_id]);
+        $db->execute('DELETE FROM widget_user WHERE pluginid = ?', [$plugin_id]);
+        $db->execute('DELETE FROM roles_plugins WHERE pluginid = ?', [$plugin_id]);
+        $db->execute('DELETE FROM plugins WHERE pluginid = ?', [$plugin_id]);
+    }
+}
diff --git a/lib/modules/MyCoursesWidget.php b/lib/modules/MyCoursesWidget.php
new file mode 100644
index 0000000000000000000000000000000000000000..81aceeda0d7103bed8983241955866fe1f629cdc
--- /dev/null
+++ b/lib/modules/MyCoursesWidget.php
@@ -0,0 +1,37 @@
+<?php
+/**
+ * My courses widget. Displays a user's courses on the start page.
+ *
+ * @author  Viktoria Wiebe <viktoria.wiebe@web.de>
+ * @license GPL2 or any later version
+ * @since   Stud.IP 5.3
+ */
+
+require_once 'app/controllers/my_courses.php';
+
+class MyCoursesWidget extends CorePlugin implements PortalPlugin
+{
+    public function getPluginName()
+    {
+        return _('Meine Veranstaltungen');
+    }
+
+    public function getMetadata()
+    {
+        return [
+            'description' => _('Dieses Widget zeigt eine Liste Ihrer Veranstaltungen an.')
+        ];
+    }
+
+    public function getPortalTemplate()
+    {
+        // get the MyCoursesController in order to prepare the correct data for the overview
+        $controller = app(MyCoursesController::class, ['dispatcher' => app(\Trails_Dispatcher::class)]);
+        $data = $controller->getPortalWidgetData();
+
+        // add the json data to the head so vue can grab it
+        PageLayout::addHeadElement('script', [], 'STUDIP.MyCoursesData = ' . json_encode($data) . ';');
+
+        return $GLOBALS['template_factory']->open('start/my_courses');
+    }
+}
diff --git a/templates/start/my_courses.php b/templates/start/my_courses.php
new file mode 100644
index 0000000000000000000000000000000000000000..cf060340290656499177df444ea5f5764f502a4e
--- /dev/null
+++ b/templates/start/my_courses.php
@@ -0,0 +1,3 @@
+<div class="my-courses-vue-app" style="margin: 10px;">
+    <my-courses />
+</div>