diff --git a/src/components/sites/editCustomProjectSite/editCustomProjectSite.tsx b/src/components/sites/editCustomProjectSite/editCustomProjectSite.tsx
index 03a34b97a2573b0715c8bd7afcd42a445d79d8fb..e9b14f069966b0024d2b05d126c38b75a9334c02 100644
--- a/src/components/sites/editCustomProjectSite/editCustomProjectSite.tsx
+++ b/src/components/sites/editCustomProjectSite/editCustomProjectSite.tsx
@@ -581,7 +581,7 @@ class editCustomProjectSite extends React.Component<Props & RouteComponentProps<
 
                         const hasAnyTestFailed = await this.props.runCustomProjectTestAsync(command, this.props.langId)
 
-                        return hasAnyTestFailed as any //wrong infered because magic redux binding
+                        return hasAnyTestFailed as any //wrong inferred because magic redux binding
                       }}
 
                       resetCustomTestResults={this.props.resetCustomProjectTestResults}
diff --git a/src/components/sites/manageGroupExercisesSite/groupExercisesSite.tsx b/src/components/sites/manageGroupExercisesSite/groupExercisesSite.tsx
index bf4d9aa4f8d2f1425a9c6f2f9343c5c576db17d0..5cc4956cd7ce74cf38a02a3e68d34373ffa0287a 100644
--- a/src/components/sites/manageGroupExercisesSite/groupExercisesSite.tsx
+++ b/src/components/sites/manageGroupExercisesSite/groupExercisesSite.tsx
@@ -12,13 +12,16 @@ import SinglePanelSiteWrapper from '../../singlePanelSiteWrapper'
 import MaterialSearchInput from '../../material/materialSearchInput'
 import Spinner from "../../helpers/spinner";
 import {
-  loadManageGroupExercisesSite,
+  loadManageGroupExercisesSite, set_groupsOrder,
   setActivePaginationPage, setIsLoading,
   setPaginationPageSize,
   setSearchText,
   setSortByKey
 } from "../../../state/actions/manageGroupExercisesSite/groupExercisesSiteActions";
-import {EditableExercisePreviewFromBackend} from "../../../types/exercisePreview";
+import {
+  EditableExercisePreviewFromBackend,
+  GroupPreviewExerciseTupleFrontendOnly
+} from "../../../types/exercisePreview";
 import TagFilterPanel from '../../tagComponents/tagsFilterPanel'
 import {InputOnChangeEvent} from '../../../types/reactEvents'
 import {
@@ -26,6 +29,16 @@ import {
   getGroupExercisesAsync
 } from '../../../state/actions/manageGroupExercisesSite/groupExercisesCrudSiteActions'
 import {ErrorHelper} from '../../../helpers/errorHelper'
+import {Icon} from 'semantic-ui-react'
+import {
+  frontendSettings_moveDisplaySortItemAbsolute,
+  frontendSettings_moveDisplaySortItemDown,
+  frontendSettings_moveDisplaySortItemUp,
+  FrontendSettingsManager
+} from '../../../helpers/frontendSettingsManager'
+import orderBy from 'lodash-es/orderBy'
+import {SimpleVDivider} from '../../helpers/simpleVDivider'
+// import {orderBy} from 'lodash'
 
 //const css = require('./styles.styl');
 
@@ -62,6 +75,8 @@ const mapDispatchToProps = (dispatch: Dispatch) => bindActionCreators({
                                                                         setActivePaginationPage,
                                                                         getGroupExercisesAsync,
                                                                         setIsLoading,
+
+                                                                        set_groupsOrder,
                                                                       }, dispatch)
 
 
@@ -80,7 +95,38 @@ class GroupExercisesSite extends React.Component<Props, any> {
     }
   }
 
+
+  /**
+   * sorts the groups and groupExercises with the given sorting and applies it to the state
+   * @param groupIdSorting
+   */
+  updateGroupOrder(groupIdSorting: number[]) {
+
+    const groupList = orderBy(this.props.groups, (val, index) => {
+      const _index = groupIdSorting.findIndex(p => p === val.id)
+      return _index
+    })
+
+    const groupWithExercisesList = orderBy(this.props.groupExercises, (val, index) => {
+      const _index = groupIdSorting.findIndex(p => p === val.userGroup.id)
+      return _index
+    })
+
+    this.props.set_groupsOrder(groupList, groupWithExercisesList)
+  }
+
   render(): JSX.Element {
+
+    //foreach
+    //via tag search remove groups with no hits... but keep the ones where we entered search text
+    //if we entered search text then don't hide the group (else we would hide the searched group... and we can no longer access the search input)
+    let groupList = this.props.groupExercises.filter(
+        p => p.exercisePreviews.length > 0 || p.pagination.searchText.trim() !== '' || //when we use tags we want to hide groups without matches
+          (this.props.selectedFilterTagsIds.length === 0 && this.props.selectedNegativeFilterTagsIds.length === 0) //when we have no tag filters we want to display all groups
+      )
+
+    //we don't need to sort groupList because we immediately sync the sorting with the sort (which also gives us rerender)
+
     return (
 
       <div className="multiple-single-panels">
@@ -113,11 +159,7 @@ class GroupExercisesSite extends React.Component<Props, any> {
 
 
         {
-          //foreach
-          //via tag search remove groups with no hits... but keep the ones where we entered search text
-          //if we entered search text then don't hide the group (else we would hide the searched group... and we can no longer access the search input)
-          this.props.groupExercises.filter(
-            p => p.exercisePreviews.length > 0 || p.pagination.searchText.trim() !== '').map((groupTuple, index) => {
+          groupList.map((groupTuple, index) => {
             return (
               <SinglePanelSiteWrapper key={groupTuple.userGroup.id}>
                 <div className="view-padding">
@@ -128,6 +170,37 @@ class GroupExercisesSite extends React.Component<Props, any> {
                       </h1>
                     </div>
                     <div className="view-options">
+
+                      <div className="v-centered-in-flex">
+                        <div className="flexed mar-right hover-child">
+                          <Icon name="angle down" className="clickable" onClick={() => {
+                            const res = frontendSettings_moveDisplaySortItemDown(FrontendSettingsManager.getFrontendSettings().groupExercisesGroupSorting, groupTuple.userGroup.id)
+                            if (!res) return
+                            FrontendSettingsManager.set_groupExercisesGroupSorting(res)
+                            this.updateGroupOrder(res)
+                          }}/>
+                          <Icon name="angle double down" className="clickable" onClick={() => {
+                            const res = frontendSettings_moveDisplaySortItemAbsolute(FrontendSettingsManager.getFrontendSettings().groupExercisesGroupSorting, groupTuple.userGroup.id, false)
+                            if (!res) return
+                            FrontendSettingsManager.set_groupExercisesGroupSorting(res)
+                            this.updateGroupOrder(res)
+                          }}/>
+                          <Icon name="angle up" className="clickable" onClick={() => {
+                            const res = frontendSettings_moveDisplaySortItemUp(FrontendSettingsManager.getFrontendSettings().groupExercisesGroupSorting, groupTuple.userGroup.id)
+                            if (!res) return
+                            FrontendSettingsManager.set_groupExercisesGroupSorting(res)
+                            this.updateGroupOrder(res)
+                          }}/>
+                          <Icon name="angle double up" className="clickable" onClick={() => {
+                            const res = frontendSettings_moveDisplaySortItemAbsolute(FrontendSettingsManager.getFrontendSettings().groupExercisesGroupSorting, groupTuple.userGroup.id, true)
+                            if (!res) return
+                            FrontendSettingsManager.set_groupExercisesGroupSorting(res)
+                            this.updateGroupOrder(res)
+                          }}/>
+                        </div>
+
+                      </div>
+
                       <MaterialSearchInput
                         value={groupTuple.pagination.searchText}
                         onChange={(e: InputOnChangeEvent) => {
diff --git a/src/constants.ts b/src/constants.ts
index ff3d5ef9e9e43b73d1978a606cd6c5c984a54366..87a9f0bfe379a9d6d7d90f818d9b988dc2b7f0d8 100644
--- a/src/constants.ts
+++ b/src/constants.ts
@@ -13,7 +13,7 @@ import Logger from './helpers/logger'
  * y - breaking changes / new features
  * z - fixes, small changes
  */
-export const versionString = '2.5.13'
+export const versionString = '2.5.14'
 
 
 export const supportMail = 'yapex@informatik.uni-halle.de'
diff --git a/src/helpers/frontendSettingsManager.ts b/src/helpers/frontendSettingsManager.ts
index 59f2d7274fdcda046b27c68e586e8afb6ee39953..c3f5bd4cdbbb5de9e6821207f6bc9ed27bc947e6 100644
--- a/src/helpers/frontendSettingsManager.ts
+++ b/src/helpers/frontendSettingsManager.ts
@@ -23,6 +23,7 @@ interface SortingSettingTuple<T> {
   readonly sortDirection: boolean | null
 }
 
+//TODO there is currently no handling when we change this...
 const currentVersion = '2'
 
 /**
@@ -41,15 +42,19 @@ type State = {
    */
   version: string
 
+  //--- various sorting
+
+  groupExercisesGroupSorting: FrontendSortingElement[] | null
+
   //--- site tags filter displayed
 
-  allOpenExercisesSiteTagsFilterIsDisplayed: boolean,
+  allOpenExercisesSiteTagsFilterIsDisplayed: boolean
 
-  allClosedExercisesSiteTagsFilterIsDisplayed: boolean,
+  allClosedExercisesSiteTagsFilterIsDisplayed: boolean
 
-  ownExercisesSiteTagsFilterIsDisplayed: boolean,
+  ownExercisesSiteTagsFilterIsDisplayed: boolean
 
-  groupExercisesSiteTagsFilterIsDisplayed: boolean,
+  groupExercisesSiteTagsFilterIsDisplayed: boolean
 
 
   //--- store sortings
@@ -141,6 +146,7 @@ const initial: State = {
   version: currentVersion,
 
   submissionsSiteSorting: null,
+  groupExercisesGroupSorting: null,
 
   allOpenExercisesSiteTagsFilterIsDisplayed: false,
   allClosedExercisesSiteTagsFilterIsDisplayed: false,
@@ -227,6 +233,12 @@ export class FrontendSettingsManager {
     return FrontendSettingsManager.isFrozen
   }
 
+  static set_groupExercisesGroupSorting(newSorting: FrontendSortingElement[] | null)  {
+    if (FrontendSettingsManager.getIsFrozen()) return
+    frontendSettings.groupExercisesGroupSorting = newSorting
+    forceSave() //no throttling needed
+  }
+
   //--- page size
 
   static set_manageActivatedUsersPageSize(pageSize: number): void {
@@ -513,7 +525,7 @@ export class FrontendSettingsManager {
       for (const key in frontendSettings) {
 
         if (maybeSettings.hasOwnProperty(key)) {
-          //take value
+          //take old value
           frontendSettings[key] = maybeSettings[key]
         }
         else {
@@ -602,3 +614,141 @@ function byteLength(str: string): number {
   }
   return s;
 }
+
+
+/**
+ * structure to store the display index for other objects (identified by id)
+ * use the functions {@link frontendSettings_moveDisplaySortItemUp}, {@link frontendSettings_moveDisplaySortItemDown}, {@link frontendSettings_moveDisplaySortItemAbsolute} to change the properties in
+ * {@link FrontendSettingsManager}
+ */
+type FrontendSortingElement = number
+
+//--- use these functions to
+
+/**
+ * moves the given id in the {@param sortArray} up if possible
+ * @param sortArray
+ * @param id
+ * @returns null if element with id was not found or out or range, else: new array with the result
+ */
+export function frontendSettings_moveDisplaySortItemUp(sortArray: FrontendSortingElement[] | null, id: number) : FrontendSortingElement[] | null {
+
+  if (!sortArray) return null
+
+  const index = sortArray.findIndex(p => p === id)
+  if (index === -1 || index === 0) return null
+
+  const newIndex = index-1
+
+  let resultArray = Array.from(sortArray)
+
+  const temp = resultArray[index]
+  resultArray[index] = resultArray[newIndex]
+  resultArray[newIndex] = temp
+
+  return resultArray
+}
+
+/**
+ * moves the given id in the {@param sortArray} down if possible
+ * @param sortArray
+ * @param id
+ * @returns null if element with id was not found or out or range, else: new array with the result
+ */
+export function frontendSettings_moveDisplaySortItemDown(sortArray: FrontendSortingElement[] | null, id: number) : FrontendSortingElement[] | null {
+
+  if (!sortArray) return null
+
+  const index = sortArray.findIndex(p => p === id)
+  if (index === -1 || index === sortArray.length-1) return null
+
+  const newIndex = index+1
+
+  let resultArray = Array.from(sortArray)
+
+  const temp = resultArray[index]
+  resultArray[index] = resultArray[newIndex]
+  resultArray[newIndex] = temp
+
+  return resultArray
+}
+
+/**
+ * moves the given id in the {@param sortArray} absolute up/down if possible
+ * @param sortArray
+ * @param id
+ * @param moveToFront true: absolute top (move to front), false: absolute bot (move to back)
+ * @returns null if element with id was not found or out or range, else: new array with the result
+ */
+export function frontendSettings_moveDisplaySortItemAbsolute(sortArray: FrontendSortingElement[] | null, id: number, moveToFront: boolean): FrontendSortingElement[] | null {
+
+  if (!sortArray) return null
+
+  const index = sortArray.findIndex(p => p === id)
+  if (index === -1) return null
+
+  let resultArray = Array.from(sortArray)
+
+  //move to back
+  const temp = resultArray[index]
+  resultArray.splice(index, 1)
+
+  if (moveToFront) {
+    resultArray.unshift(temp)
+  } else {
+    resultArray.push(temp)
+  }
+
+  return resultArray
+}
+
+type SyncResult = {
+  result: FrontendSortingElement[]
+  /**
+   * something was changed, need to be saved into settings
+   */
+  anyChanges: boolean
+}
+/**
+ * adds the new {@param ids} to {@param sortArray} and removes unused ids from {@param sortArray}
+ * @param sortArray
+ * @param ids this is the ground truth from the backend
+ */
+export function frontendSettings_syncDisplaySortingItems(sortArray: FrontendSortingElement[] | null, ids: ReadonlyArray<number>): SyncResult {
+
+  let changed = false
+  let resultArray = !sortArray ? [] : Array.from(sortArray)
+
+  //use a try in case we change the frontend settings and this gets another type...
+  try {
+    //add all missing ids to the back of our old sortObj
+    for(let i = 0; i < ids.length;i++) {
+      const id = ids[i]
+      if (resultArray.findIndex(p => p === id) === -1) {
+        //new id is not found in sortObj
+        resultArray.push(ids[i])
+        changed = true
+      }
+    }
+
+    //then remove all old entries in sortArray that are not longer found in ids
+    for(let i = 0; i < resultArray.length;i++) {
+      const id = resultArray[i]
+      if (ids.findIndex(p => p === id) === -1) {
+        resultArray.splice(i,1)
+        changed = true
+        i--
+      }
+    }
+  } catch(err) {
+    resultArray = []
+    changed = true
+  }
+
+
+  return {
+    result: resultArray,
+    anyChanges: changed
+  }
+
+}
diff --git a/src/state/actions/manageGroupExercisesSite/groupExercisesSiteActions.ts b/src/state/actions/manageGroupExercisesSite/groupExercisesSiteActions.ts
index ee6dd390fd8b3cd2e0e6a563811a02e20500e50d..99bff05c82d560ab2266c7b0aed2acd5e8504b9f 100644
--- a/src/state/actions/manageGroupExercisesSite/groupExercisesSiteActions.ts
+++ b/src/state/actions/manageGroupExercisesSite/groupExercisesSiteActions.ts
@@ -5,9 +5,12 @@ import {
   ResetAction, SET_isLoadingManageGroupExercisesSiteACtion,
   SET_isTagsFilterDisplayedAction,
   SET_sortByKeyAction,
-  SetActivePaginationPageAction, SetPaginationPageSizeAction, SetSearchTextAction
+  SetActivePaginationPageAction, SetPaginationPageSizeAction, SetSearchTextAction, SET_groupsOrderAction
 } from "../../reducers/manageGroupExercisesSite/groupExercisesSiteReducer";
-import {EditableExercisePreviewFromBackend} from "../../../types/exercisePreview";
+import {
+  EditableExercisePreviewFromBackend,
+  GroupPreviewExerciseTupleFrontendOnly
+} from "../../../types/exercisePreview";
 import {ActionType} from "../../reducers/manageGroupExercisesSite/groupExercisesSiteActionTypes";
 import {AwaitActions, MultiActions} from "../types";
 import {loadManageTagsSite} from "../manageTagsSite/manageTagsActions";
@@ -15,6 +18,7 @@ import {resetTagsFilter} from "../tagsFilterActions";
 import {getGroupExercisesAsync, getGroupsForExercisesAsync} from './groupExercisesCrudSiteActions'
 import debounce from 'lodash-es/debounce'
 import {searchInputDebounceInMs} from '../../../constants'
+import {UserGroupFromBackend} from '../../../types/group'
 
 
 export function setIsLoading(isLoading: boolean): SET_isLoadingManageGroupExercisesSiteACtion {
@@ -150,4 +154,12 @@ export function setSearchText(searchText: string, groupId: number): MultiActions
 
     debouncedRefresh(dispatch, getState, groupId)
   }
-}
\ No newline at end of file
+}
+
+export function set_groupsOrder(groups: ReadonlyArray<UserGroupFromBackend>, groupExercises: ReadonlyArray<GroupPreviewExerciseTupleFrontendOnly>): SET_groupsOrderAction {
+  return {
+    type: ActionType.SET_groupsOrder,
+    groups,
+    groupExercises
+  }
+}
diff --git a/src/state/reducers/manageGroupExercisesSite/getGroupExercisesReducer.ts b/src/state/reducers/manageGroupExercisesSite/getGroupExercisesReducer.ts
index 030425042565dc07f1ed2b775d8d63a3be3acd68..117028601e810a7f873dc6bd5f61f4ae213e82a3 100644
--- a/src/state/reducers/manageGroupExercisesSite/getGroupExercisesReducer.ts
+++ b/src/state/reducers/manageGroupExercisesSite/getGroupExercisesReducer.ts
@@ -13,6 +13,7 @@ import {EditableExercisePreviewFromBackend} from "../../../types/exercisePreview
 import {initial, State} from "./groupExercisesSiteReducer";
 import {PaginatedData} from '../../../types/pagination'
 import {PaginationHelper} from '../../../helpers/paginationHelper'
+import {FrontendSettingsManager, frontendSettings_syncDisplaySortingItems} from '../../../helpers/frontendSettingsManager'
 
 interface Meta {
   readonly groupId: number
diff --git a/src/state/reducers/manageGroupExercisesSite/getGroupsForExercisesCrudReducer.ts b/src/state/reducers/manageGroupExercisesSite/getGroupsForExercisesCrudReducer.ts
index a574b00a63e23aa5182aa9c736414a2ec312e230..5f9e2cdefc9449167c07eb09cb7ecfaefb7d3055 100644
--- a/src/state/reducers/manageGroupExercisesSite/getGroupsForExercisesCrudReducer.ts
+++ b/src/state/reducers/manageGroupExercisesSite/getGroupsForExercisesCrudReducer.ts
@@ -5,7 +5,8 @@ import {initial, State} from './groupExercisesSiteReducer'
 import {UserGroupBase, UserGroupFromBackend} from '../../../types/group'
 import {GroupPreviewExerciseTupleFrontendOnly} from '../../../types/exercisePreview'
 import {getInitialPaginationData} from '../../../constants'
-import {FrontendSettingsManager} from '../../../helpers/frontendSettingsManager'
+import {FrontendSettingsManager, frontendSettings_syncDisplaySortingItems} from '../../../helpers/frontendSettingsManager'
+import orderBy from 'lodash-es/orderBy'
 
 
 export interface GET_groupsForExercisesAction
@@ -54,12 +55,31 @@ export function reducer(state: State = initial, action: AllActions): State {
 
       const pageSizeSetting = FrontendSettingsManager.getFrontendSettings().groupExercisesPageSize
 
+      //ensure our frontend group sorting is up-to-date
+
+
+      const synResult = frontendSettings_syncDisplaySortingItems(FrontendSettingsManager.getFrontendSettings().groupExercisesGroupSorting, action.payload.map(p => p.id))
+
+      if (synResult.anyChanges) {
+        FrontendSettingsManager.set_groupExercisesGroupSorting(synResult.result)
+      }
+
+      const frontendSorting = FrontendSettingsManager.getFrontendSettings().groupExercisesGroupSorting
+
+      let groupList = action.payload
+
+      if (frontendSorting) {
+        groupList = orderBy(action.payload, (val, index) => {
+          const _index = frontendSorting.findIndex(p => p === val.id)
+          return _index
+        })
+      }
 
       return {
         ...state,
         isLoading: false,
-        groups: action.payload,
-        groupExercises: action.payload.map<GroupPreviewExerciseTupleFrontendOnly>(p => {
+        groups: groupList,
+        groupExercises: groupList.map<GroupPreviewExerciseTupleFrontendOnly>(p => {
 
           // noinspection TsLint
           let pageSize = pageSizeSetting[p.id]
diff --git a/src/state/reducers/manageGroupExercisesSite/groupExercisesSiteActionTypes.ts b/src/state/reducers/manageGroupExercisesSite/groupExercisesSiteActionTypes.ts
index 6e9ecd1790fec25b536d9018fef2e06171bff574..ab71e457db77cdecab0baf898bd7cc32a07557ee 100644
--- a/src/state/reducers/manageGroupExercisesSite/groupExercisesSiteActionTypes.ts
+++ b/src/state/reducers/manageGroupExercisesSite/groupExercisesSiteActionTypes.ts
@@ -38,6 +38,8 @@ export enum ActionType {
 
   SET_isTagsFilterDisplayed = 'groupExercisesReducer_SET_isTagsFilterDisplayed',
 
+  SET_groupsOrder = 'groupExercisesReducer_SET_groupsOrder',
+
   RESET = 'groupExercisesReducer_RESET',
 }
 
diff --git a/src/state/reducers/manageGroupExercisesSite/groupExercisesSiteReducer.ts b/src/state/reducers/manageGroupExercisesSite/groupExercisesSiteReducer.ts
index 32f9938e5ad316b4e9ef310c5b8b2b1bb315d91b..905240ce041cbcd1d2431c2028f6629892c36f36 100644
--- a/src/state/reducers/manageGroupExercisesSite/groupExercisesSiteReducer.ts
+++ b/src/state/reducers/manageGroupExercisesSite/groupExercisesSiteReducer.ts
@@ -80,6 +80,15 @@ export interface SET_isLoadingManageGroupExercisesSiteACtion extends ActionBase
   readonly isLoading: boolean
 }
 
+/**
+ * when we change the group display order we set the new order to the state and we render the correct order
+ */
+export interface SET_groupsOrderAction extends ActionBase {
+  readonly type: ActionType.SET_groupsOrder
+  readonly groups: ReadonlyArray<UserGroupFromBackend>
+  readonly groupExercises: ReadonlyArray<GroupPreviewExerciseTupleFrontendOnly>
+}
+
 export interface ResetAction extends ActionBase {
   readonly type: ActionType.RESET
 }
@@ -98,6 +107,8 @@ export type AllActions =
   | DeleteGroupExerciseActions
   | GetGroupsForExercisesActions
 
+  | SET_groupsOrderAction
+
   | RESET_GlobalAction
 
 export function reducer(state: State = initial, action: AllActions): State {
@@ -112,6 +123,14 @@ export function reducer(state: State = initial, action: AllActions): State {
         isLoading: action.isLoading
       }
 
+    case ActionType.SET_groupsOrder:
+
+      return {
+        ...state,
+        groups: action.groups,
+        groupExercises: action.groupExercises,
+      }
+
     case ActionType.SET_isTagsFilterDisplayed:
 
       FrontendSettingsManager.setGroupExercisesSiteTagsFilterIsDisplayed(action.isTagsFilterDisplayed)
diff --git a/src/styles/common.styl b/src/styles/common.styl
index 40effa713d1cdad64f62a59e24d97d5c4fecbec3..b2701b218cd8cafeb9ea8a19037f2ed8d65217da 100644
--- a/src/styles/common.styl
+++ b/src/styles/common.styl
@@ -293,6 +293,10 @@ html, body {
   }
 }
 
+.v-centered-in-flex {
+  align-self center
+}
+
 .div-disabled {
   opacity 0.2 !important
   pointer-events none