diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index 3bc4b5e713bc41db755ae3d952d72732cf7890d7..d0f787c6fafd11885a85a547e84156e0ed0652be 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -20,6 +20,7 @@ import { ErrorEffects } from '@app/degree-planner/store/effects/error.effects';
 import { MatAutocompleteModule } from '@angular/material/autocomplete';
 import { CourseDetailsDialogComponent } from './degree-planner/dialogs/course-details-dialog/course-details-dialog.component';
 import { FeedbackDialogComponent } from './degree-planner/dialogs/feedback-dialog/feedback-dialog.component';
+import { CreditOverloadDialogComponent } from './degree-planner/dialogs/credit-overload-dialog/credit-overload-dialog.component';
 @NgModule({
   imports: [
     StoreModule.forRoot({
@@ -44,7 +45,11 @@ import { FeedbackDialogComponent } from './degree-planner/dialogs/feedback-dialo
     }),
   ],
   declarations: [AppComponent, HeaderComponent],
-  entryComponents: [CourseDetailsDialogComponent, FeedbackDialogComponent],
+  entryComponents: [
+    CourseDetailsDialogComponent,
+    FeedbackDialogComponent,
+    CreditOverloadDialogComponent,
+  ],
   providers: [],
   bootstrap: [AppComponent],
   schemas: [CUSTOM_ELEMENTS_SCHEMA],
diff --git a/src/app/core/models/course.ts b/src/app/core/models/course.ts
index 1a642843cf943bc44d14e4dd69965fe0ff9efd4b..3dc6c319efdb913a5161c4e9a2ee343353aefe5e 100644
--- a/src/app/core/models/course.ts
+++ b/src/app/core/models/course.ts
@@ -7,8 +7,8 @@ export interface CourseBase {
   subjectCode: string;
   catalogNumber: string;
   credits?: number;
-  creditMin: number;
-  creditMax: number;
+  creditMin?: number;
+  creditMax?: number;
   grade?: any;
   classNumber: string | null;
   courseOrder: number;
diff --git a/src/app/core/models/student-info.ts b/src/app/core/models/student-info.ts
new file mode 100644
index 0000000000000000000000000000000000000000..992d5ea7a0c28910c50da32866a2d04f46dc8c37
--- /dev/null
+++ b/src/app/core/models/student-info.ts
@@ -0,0 +1,24 @@
+export interface StudentInfo {
+  personAttributes: {
+    emplid: string;
+    pvi: string;
+    name: {
+      first: string;
+      last: string;
+    };
+    email: string;
+    netid: string;
+  };
+  ferpaAttributes: {
+    name: boolean;
+    email: boolean;
+  };
+  primaryCareer: null | {
+    careerCode: string;
+    programName: string;
+    enrollmentStatusCode: string;
+    academicLevelDescription: string;
+    academicLoadDescription: string;
+    termCode: string;
+  };
+}
diff --git a/src/app/degree-planner/dialogs/credit-overload-dialog/credit-overload-dialog.component.html b/src/app/degree-planner/dialogs/credit-overload-dialog/credit-overload-dialog.component.html
new file mode 100644
index 0000000000000000000000000000000000000000..8e485cbe27089b131da5250198086d7c5ee4da5f
--- /dev/null
+++ b/src/app/degree-planner/dialogs/credit-overload-dialog/credit-overload-dialog.component.html
@@ -0,0 +1,23 @@
+<h1 mat-dialog-title style="margin:1rem">Warning: Credit Overload</h1>
+<div mat-dialog-content class="mat-typography" style="padding:0;margin:1rem">
+  <p style="max-width:500px;margin:0">
+    Undergraduate students who wish to take more than {{ maxCredits }} during
+    the {{ termName }} semester must recieve approval from their School or College.
+  </p>
+</div>
+<div mat-dialog-actions style="padding:0;margin:1rem">
+  <a
+    mat-button
+    target="_blank"
+    href="https://registrar.wisc.edu/enrollment-related-info/">
+    More information
+  </a>
+  <button
+    mat-raised-button
+    mat-dialog-close
+    cdkFocusInitial
+    color="primary"
+    aria-label="Dismiss dialog">
+    Dismiss
+  </button>
+</div>
diff --git a/src/app/degree-planner/dialogs/credit-overload-dialog/credit-overload-dialog.component.ts b/src/app/degree-planner/dialogs/credit-overload-dialog/credit-overload-dialog.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..f39441acc5dfd6f8f7810f897304bc36b61ad0a4
--- /dev/null
+++ b/src/app/degree-planner/dialogs/credit-overload-dialog/credit-overload-dialog.component.ts
@@ -0,0 +1,17 @@
+import { Component, Input, Inject } from '@angular/core';
+import { TermCode } from '@app/core/models/termcode';
+import { MAT_DIALOG_DATA } from '@angular/material';
+
+@Component({
+  selector: 'cse-credit-overload-dialog',
+  templateUrl: './credit-overload-dialog.component.html',
+})
+export class CreditOverloadDialogComponent {
+  public termName: TermCode['termName'];
+  public maxCredits: number;
+
+  constructor(@Inject(MAT_DIALOG_DATA) data: any) {
+    this.termName = data.termName;
+    this.maxCredits = data.maxCredits;
+  }
+}
diff --git a/src/app/degree-planner/services/api.service.ts b/src/app/degree-planner/services/api.service.ts
index 19a7f082b94202b9ba8b9a315f1d2918e9bf7ddd..e9b6823fc18e67247b1e6e2c2122a6ac2e1ba278 100644
--- a/src/app/degree-planner/services/api.service.ts
+++ b/src/app/degree-planner/services/api.service.ts
@@ -14,6 +14,7 @@ import { Profile } from '@app/core/models/profile';
 import { SavedForLaterCourseBase } from '@app/core/models/saved-for-later-course';
 import { SearchResults } from '@app/core/models/search-results';
 import { TermCode } from '@app/core/models/termcode';
+import { StudentInfo } from '@app/core/models/student-info';
 
 const HTTP_OPTIONS = {
   headers: new HttpHeaders({
@@ -351,6 +352,11 @@ export class DegreePlannerApiService {
     );
   }
 
+  public getStudentInfo() {
+    const url = `${environment.apiEnrollUrl}/studentInfo`;
+    return this.http.get<StudentInfo>(url, HTTP_OPTIONS);
+  }
+
   /**
    * Helper function for building API endpoint URLs
    */
diff --git a/src/app/degree-planner/services/constants.service.ts b/src/app/degree-planner/services/constants.service.ts
index deabf63a9560d53a4889ecaf1aaa5005953f01c3..5a2aa26d81e87a19bc02ac5a3e0f6ffd311686f5 100644
--- a/src/app/degree-planner/services/constants.service.ts
+++ b/src/app/degree-planner/services/constants.service.ts
@@ -5,9 +5,11 @@ import { DegreePlannerApiService } from '@app/degree-planner/services/api.servic
 import { map, tap } from 'rxjs/operators';
 import { TermCode } from '@app/core/models/termcode';
 import { SubjectDescription, SubjectCodesTo } from '@app/core/models/course';
+import { StudentInfo } from '@app/core/models/student-info';
 
 export interface ConstantData {
   activeTermCodes: TermCode[];
+  studentInfo: Partial<StudentInfo>;
   subjectDescriptions: SubjectCodesTo<SubjectDescription>;
 }
 
@@ -15,6 +17,7 @@ export interface ConstantData {
 export class ConstantsService implements Resolve<ConstantData> {
   private constants: ConstantData = {
     activeTermCodes: [],
+    studentInfo: {},
     subjectDescriptions: {},
   };
 
@@ -32,6 +35,19 @@ export class ConstantsService implements Resolve<ConstantData> {
     return this.constants.activeTermCodes;
   }
 
+  public getStudentInfo() {
+    return this.constants.studentInfo;
+  }
+
+  public isUndergrad(): boolean {
+    const info = this.getStudentInfo();
+    if (info.primaryCareer) {
+      return info.primaryCareer.careerCode === 'UGRD';
+    } else {
+      return false;
+    }
+  }
+
   public allSubjectDescriptions() {
     return this.constants.subjectDescriptions;
   }
@@ -49,6 +65,8 @@ export class ConstantsService implements Resolve<ConstantData> {
   public resolve() {
     const activeTermCodes = this.api.getActiveTermCodes();
 
+    const studentInfo = this.api.getStudentInfo();
+
     const subjectDescriptions = forkJoinWithKeys({
       short: this.api.getSubjectShortDescriptions(),
       longByTerm: this.api.getSubjectLongDescriptions(),
@@ -78,6 +96,7 @@ export class ConstantsService implements Resolve<ConstantData> {
 
     return forkJoinWithKeys({
       activeTermCodes,
+      studentInfo,
       subjectDescriptions,
     }).pipe(tap(constants => (this.constants = constants)));
   }
diff --git a/src/app/degree-planner/term-container/term-container.component.html b/src/app/degree-planner/term-container/term-container.component.html
index 1e609fd2b95edfa77b19fabbd99d7d60e899ffe0..343124bb86bf95ec47ec9a5fa39f1c0d10668721 100644
--- a/src/app/degree-planner/term-container/term-container.component.html
+++ b/src/app/degree-planner/term-container/term-container.component.html
@@ -137,6 +137,10 @@
       </div>
     </div>
 
+    <p class="credit-overload-warning" *ngIf="tooManyCredits$ | async">
+      Warning: credit overload
+    </p>
+
     <!-- Render term note (if it exists) -->
     <ng-container *ngIf="(note$ | async) as note">
       <ng-container *ngIf="note.isLoaded; else noteIsLoading">
diff --git a/src/app/degree-planner/term-container/term-container.component.scss b/src/app/degree-planner/term-container/term-container.component.scss
index 3439d5ae1b26bdc2040fbdb195ff7d04c6125597..deabfd2f8a61bee2dc5938b83f4a1b9196e86780 100644
--- a/src/app/degree-planner/term-container/term-container.component.scss
+++ b/src/app/degree-planner/term-container/term-container.component.scss
@@ -101,6 +101,11 @@
   }
 }
 
+.credit-overload-warning {
+  margin-top: 0;
+  color: #ff8000;
+}
+
 .term-container h2 {
   color: #494949;
   font-weight: 400;
diff --git a/src/app/degree-planner/term-container/term-container.component.ts b/src/app/degree-planner/term-container/term-container.component.ts
index 4c656fff59f4efc7e5a76500c84e446f55fc82bf..f008a300e1515cb3eae794e9771a24a230e36ae9 100644
--- a/src/app/degree-planner/term-container/term-container.component.ts
+++ b/src/app/degree-planner/term-container/term-container.component.ts
@@ -1,39 +1,59 @@
 import { Component, Input, OnInit, OnDestroy } from '@angular/core';
 import { CdkDragDrop } from '@angular/cdk/drag-drop';
-import { MatDialog, ErrorStateMatcher } from '@angular/material';
-import { Observable, Subscription, pipe } from 'rxjs';
-import { filter, map, distinctUntilChanged } from 'rxjs/operators';
+import { MatDialog } from '@angular/material';
+import { Observable, Subscription } from 'rxjs';
+import { filter, map, distinctUntilChanged, pairwise } from 'rxjs/operators';
 import { Store, select } from '@ngrx/store';
 import { DegreePlannerState } from '@app/degree-planner/store/state';
-import {
-  ToggleCourseSearch,
-  OpenCourseSearch,
-} from '@app/degree-planner/store/actions/ui.actions';
-
-// Models
+import { OpenCourseSearch } from '@app/degree-planner/store/actions/ui.actions';
 import * as actions from '@app/degree-planner/store/actions/course.actions';
 import { CloseCourseSearch } from '@app/degree-planner/store/actions/ui.actions';
 import * as selectors from '@app/degree-planner/store/selectors';
 import { PlannedTerm, PlannedTermNote } from '@app/core/models/planned-term';
-import { Note } from '@app/core/models/note';
 import { Course } from '@app/core/models/course';
 import {
   NotesDialogComponent,
   NotesDialogData,
 } from '@app/degree-planner/dialogs/notes-dialog/notes-dialog.component';
 import * as utils from '@app/degree-planner/shared/utils';
-import { findSafariExecutable } from 'selenium-webdriver/safari';
 import { TermCode } from '@app/core/models/termcode';
-
-// Dialogs
 import { ConfirmDialogComponent } from '@app/shared/dialogs/confirm-dialog/confirm-dialog.component';
-import { maybeQueueResolutionOfComponentResources } from '@angular/core/src/metadata/resource_loading';
-import { WeekDay } from '@angular/common';
 import { MediaMatcher } from '@angular/cdk/layout';
+import { ConstantsService } from '../services/constants.service';
+import { CreditOverloadDialogComponent } from '../dialogs/credit-overload-dialog/credit-overload-dialog.component';
+
 const isntUndefined = <T>(thing: T | undefined): thing is T => {
   return thing !== undefined;
 };
 
+const sumCredits = (
+  courses: ReadonlyArray<{ creditMin?: number; creditMax?: number }>,
+) => {
+  const min = courses.reduce((sum, course) => {
+    return sum + (course.creditMin !== undefined ? course.creditMin : 0);
+  }, 0);
+
+  const max = courses.reduce((sum, course) => {
+    return sum + (course.creditMax !== undefined ? course.creditMax : 0);
+  }, 0);
+
+  return { min, max };
+};
+
+// Both the summer and fall/spring undergrad credit limits are INCLUSIVE.
+const SUMMER_CREDIT_LIMIT = 12;
+const FALL_SPRING_CREDIT_LIMIT = 18;
+
+const maximumAllowedCreditsForTerm = (termCode: TermCode) => {
+  switch (termCode.termName) {
+    case 'fall':
+    case 'spring':
+      return FALL_SPRING_CREDIT_LIMIT;
+    case 'summer':
+      return SUMMER_CREDIT_LIMIT;
+  }
+};
+
 @Component({
   selector: 'cse-term-container',
   templateUrl: './term-container.component.html',
@@ -45,6 +65,7 @@ export class TermContainerComponent implements OnInit, OnDestroy {
   public term$: Observable<PlannedTerm>;
   public note$: Observable<PlannedTermNote | undefined>;
   public dropZoneIds$: Observable<string[]>;
+  public tooManyCredits$: Observable<boolean>;
 
   public termSubscription: Subscription;
   public activeTermHasNotOffered: boolean;
@@ -63,6 +84,7 @@ export class TermContainerComponent implements OnInit, OnDestroy {
   constructor(
     public dialog: MatDialog,
     private store: Store<{ degreePlanner: DegreePlannerState }>,
+    private constants: ConstantsService,
     mediaMatcher: MediaMatcher,
   ) {
     this.mobileView = mediaMatcher.matchMedia('(max-width: 900px)');
@@ -77,6 +99,55 @@ export class TermContainerComponent implements OnInit, OnDestroy {
       distinctUntilChanged(),
     );
 
+    this.tooManyCredits$ = this.term$.pipe(
+      map(term => {
+        const credits = sumCredits(term.plannedCourses);
+        const maxAllowedCredits = maximumAllowedCreditsForTerm(term.termCode);
+        return credits.min >= maxAllowedCredits;
+      }),
+    );
+
+    /**
+     * Alert the user that they are adding too many credits to a term IFF:
+     * 1. The user is an undergrad
+     * 2. The term did not exceed the credit limit before the most recent change
+     * 3. The term's minimum credit amount exceeds the term's credit limit
+     */
+
+    // Condition #1
+    if (this.constants.isUndergrad()) {
+      this.term$.pipe(pairwise()).subscribe(([prev, curr]) => {
+        // Sanity check: don't compare two terms if they're different terms.
+        if (prev.termCode.equals(curr.termCode) === false) {
+          return;
+        }
+
+        const prevCredits = sumCredits(prev.plannedCourses);
+        const currCredits = sumCredits(curr.plannedCourses);
+        const maxAllowedCredits = maximumAllowedCreditsForTerm(curr.termCode);
+        const prevWasOverLimit = prevCredits.min >= maxAllowedCredits;
+        const currIsUnderLimit = currCredits.min < maxAllowedCredits;
+        const currHasFewerCreditsThanPrev = currCredits.min < prevCredits.min;
+
+        if (prevWasOverLimit || currHasFewerCreditsThanPrev) {
+          // Failed condition #2
+          return;
+        }
+
+        if (currIsUnderLimit) {
+          // Failed condition #3
+          return;
+        }
+
+        this.dialog.open(CreditOverloadDialogComponent, {
+          data: {
+            termName: curr.termCode.termName,
+            maxCredits: maxAllowedCredits,
+          },
+        });
+      });
+    }
+
     this.termSubscription = this.term$.subscribe(term => {
       // const {plannedCourses, enrolledCourses} = term;
       this.plannedCourses = term.plannedCourses;
diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts
index b30e7bd3ac8b7075bd49494d6b4874323a3b07ba..4a9ab01286568b0a696ed0ee87eb43ac76f28a8b 100644
--- a/src/app/shared/shared.module.ts
+++ b/src/app/shared/shared.module.ts
@@ -38,6 +38,7 @@ import {
 
 import { ClickStopPropagationDirective } from '@app/shared/directives/click-stop-propigation';
 import { CourseDescriptionPipe } from './pipes/course-description.pipe';
+import { CreditOverloadDialogComponent } from '@app/degree-planner/dialogs/credit-overload-dialog/credit-overload-dialog.component';
 
 const modules = [
   CommonModule,
@@ -85,6 +86,7 @@ const directives = [ClickStopPropagationDirective];
     CourseDetailsComponent,
     CourseDetailsDialogComponent,
     FeedbackDialogComponent,
+    CreditOverloadDialogComponent,
     ConfirmDialogComponent,
     PromptDialogComponent,
   ],
diff --git a/src/environments/environment.prod.ts b/src/environments/environment.prod.ts
index 89d7282bda1cf58726a9b62ad8d5ed674a75c8f2..83e945bf411007ef1a2b9fe4a37a3713affb22de 100644
--- a/src/environments/environment.prod.ts
+++ b/src/environments/environment.prod.ts
@@ -3,5 +3,6 @@ export const environment = {
   version: '0.0.1',
   apiPlannerUrl: '/api/planner/v1',
   apiSearchUrl: '/api/search/v1',
+  apiEnrollUrl: '/api/enroll/v1',
   snackbarDuration: 4000,
 };
diff --git a/src/environments/environment.ts b/src/environments/environment.ts
index 3e1f1732c1fc72a8d5901baa1d3d323f364d0b98..8af2633f68511a4660cdd50caa76e61b474a1b0d 100644
--- a/src/environments/environment.ts
+++ b/src/environments/environment.ts
@@ -8,6 +8,7 @@ export const environment = {
   version: 'DEV',
   apiPlannerUrl: '/api/planner/v1',
   apiSearchUrl: '/api/search/v1',
+  apiEnrollUrl: '/api/enroll/v1',
   snackbarDuration: 4000,
 };