diff --git a/cms/djangoapps/contentstore/tests/test_transcripts_utils.py b/cms/djangoapps/contentstore/tests/test_transcripts_utils.py
index e92e1f937adf273c70d6ba1d54e74966434cb6d7..4e9de8075f13c0851450aed0f1186274b5f11213 100644
--- a/cms/djangoapps/contentstore/tests/test_transcripts_utils.py
+++ b/cms/djangoapps/contentstore/tests/test_transcripts_utils.py
@@ -156,7 +156,7 @@ class TestSaveSubsToStore(SharedModuleStoreTestCase):
 
     def test_save_unjsonable_subs_to_store(self):
         """
-        Assures that subs, that can't be dumped, can't be found later.
+        Ensures that subs, that can't be dumped, can't be found later.
         """
         with self.assertRaises(NotFoundError):
             contentstore().find(self.content_location_unjsonable)
diff --git a/lms/djangoapps/course_goals/__init__.py b/lms/djangoapps/course_goals/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/lms/djangoapps/course_goals/api.py b/lms/djangoapps/course_goals/api.py
new file mode 100644
index 0000000000000000000000000000000000000000..b52c1794faf7b0520db87875ac4e731baed1dbb5
--- /dev/null
+++ b/lms/djangoapps/course_goals/api.py
@@ -0,0 +1,76 @@
+"""
+Course Goals Python API
+"""
+from enum import Enum
+from opaque_keys.edx.keys import CourseKey
+from django.utils.translation import ugettext as _
+from openedx.core.djangolib.markup import Text
+
+from .models import CourseGoal
+
+
+def add_course_goal(user, course_id, goal_key):
+    """
+    Add a new course goal for the provided user and course.
+
+    Arguments:
+        user: The user that is setting the goal
+        course_id (string): The id for the course the goal refers to
+        goal_key (string): The goal key that maps to one of the
+            enumerated goal keys from CourseGoalOption.
+
+    """
+    # Create and save a new course goal
+    course_key = CourseKey.from_string(str(course_id))
+    new_goal = CourseGoal(user=user, course_key=course_key, goal_key=goal_key)
+    new_goal.save()
+
+
+def get_course_goal(user, course_key):
+    """
+    Given a user and a course_key, return their course goal.
+
+    If a course goal does not exist, returns None.
+    """
+    course_goals = CourseGoal.objects.filter(user=user, course_key=course_key)
+    return course_goals[0] if course_goals else None
+
+
+def remove_course_goal(user, course_key):
+    """
+    Given a user and a course_key, remove the course goal.
+    """
+    course_goal = get_course_goal(user, course_key)
+    if course_goal:
+        course_goal.delete()
+
+
+class CourseGoalOption(Enum):
+    """
+    Types of goals that a user can select.
+
+    These options are set to a string goal key so that they can be
+    referenced elsewhere in the code when necessary.
+    """
+    CERTIFY = 'certify'
+    COMPLETE = 'complete'
+    EXPLORE = 'explore'
+    UNSURE = 'unsure'
+
+    @classmethod
+    def get_course_goal_keys(self):
+        return [key.value for key in self]
+
+
+def get_goal_text(goal_option):
+    """
+    This function is used to translate the course goal option into
+    a translated, user-facing string to be used to represent that
+    particular goal.
+    """
+    return {
+        CourseGoalOption.CERTIFY.value: Text(_('Earn a certificate')),
+        CourseGoalOption.COMPLETE.value: Text(_('Complete the course')),
+        CourseGoalOption.EXPLORE.value: Text(_('Explore the course')),
+        CourseGoalOption.UNSURE.value: Text(_('Not sure yet')),
+    }[goal_option]
diff --git a/lms/djangoapps/course_goals/migrations/0001_initial.py b/lms/djangoapps/course_goals/migrations/0001_initial.py
new file mode 100644
index 0000000000000000000000000000000000000000..bcf13e339e9696d763095205a2fb8553557b8aef
--- /dev/null
+++ b/lms/djangoapps/course_goals/migrations/0001_initial.py
@@ -0,0 +1,29 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import openedx.core.djangoapps.xmodule_django.models
+from django.conf import settings
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='CourseGoal',
+            fields=[
+                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+                ('course_key', openedx.core.djangoapps.xmodule_django.models.CourseKeyField(max_length=255, db_index=True)),
+                ('goal_key', models.CharField(default=b'unsure', max_length=100, choices=[(b'certify', 'Earn a certificate.'), (b'complete', 'Complete the course.'), (b'explore', 'Explore the course.'), (b'unsure', 'Not sure yet.')])),
+                ('user', models.ForeignKey(to=settings.AUTH_USER_MODEL)),
+            ],
+        ),
+        migrations.AlterUniqueTogether(
+            name='coursegoal',
+            unique_together=set([('user', 'course_key')]),
+        ),
+    ]
diff --git a/lms/djangoapps/course_goals/migrations/__init__.py b/lms/djangoapps/course_goals/migrations/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/lms/djangoapps/course_goals/models.py b/lms/djangoapps/course_goals/models.py
new file mode 100644
index 0000000000000000000000000000000000000000..fac52a729a7a28f338d260a4ea56593f5c671676
--- /dev/null
+++ b/lms/djangoapps/course_goals/models.py
@@ -0,0 +1,35 @@
+"""
+Course Goals Models
+"""
+from django.contrib.auth.models import User
+from django.db import models
+from openedx.core.djangoapps.xmodule_django.models import CourseKeyField
+
+
+class CourseGoal(models.Model):
+    """
+    Represents a course goal set by a user on the course home page.
+
+    The goal_key represents the goal key that maps to a translated
+    string through using the CourseGoalOption class.
+    """
+    GOAL_KEY_CHOICES = (
+        ('certify', 'Earn a certificate.'),
+        ('complete', 'Complete the course.'),
+        ('explore', 'Explore the course.'),
+        ('unsure', 'Not sure yet.'),
+    )
+
+    user = models.ForeignKey(User, blank=False)
+    course_key = CourseKeyField(max_length=255, db_index=True)
+    goal_key = models.CharField(max_length=100, choices=GOAL_KEY_CHOICES, default='unsure')
+
+    def __unicode__(self):
+        return 'CourseGoal: {user} set goal to {goal} for course {course}'.format(
+            user=self.user.username,
+            course=self.course_key,
+            goal_key=self.goal_key,
+        )
+
+    class Meta:
+        unique_together = ("user", "course_key")
diff --git a/lms/djangoapps/course_goals/signals.py b/lms/djangoapps/course_goals/signals.py
new file mode 100644
index 0000000000000000000000000000000000000000..2957c2eb274e27368c9b2281c49ef482a10db8d2
--- /dev/null
+++ b/lms/djangoapps/course_goals/signals.py
@@ -0,0 +1,19 @@
+"""
+Course Goals Signals
+"""
+from django.db.models.signals import post_save
+from django.dispatch import receiver
+from eventtracking import tracker
+
+from .models import CourseGoal
+
+
+@receiver(post_save, sender=CourseGoal, dispatch_uid="emit_course_goal_event")
+def emit_course_goal_event(sender, instance, **kwargs):
+    name = 'edx.course.goal.added' if kwargs.get('created', False) else 'edx.course.goal.updated'
+    tracker.emit(
+        name,
+        {
+            'goal_key': instance.goal_key,
+        }
+    )
diff --git a/lms/djangoapps/course_goals/tests/__init__.py b/lms/djangoapps/course_goals/tests/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/lms/djangoapps/course_goals/tests/test_api.py b/lms/djangoapps/course_goals/tests/test_api.py
new file mode 100644
index 0000000000000000000000000000000000000000..1c20dd7ba37a0e780c531bbfd981abfbc2244968
--- /dev/null
+++ b/lms/djangoapps/course_goals/tests/test_api.py
@@ -0,0 +1,62 @@
+"""
+Unit tests for course_goals.api methods.
+"""
+
+from django.contrib.auth.models import User
+from django.core.urlresolvers import reverse
+from lms.djangoapps.course_goals.models import CourseGoal
+from rest_framework.test import APIClient
+from student.models import CourseEnrollment
+from track.tests import EventTrackingTestCase
+from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase
+from xmodule.modulestore.tests.factories import CourseFactory
+
+TEST_PASSWORD = 'test'
+
+
+class TestCourseGoalsAPI(EventTrackingTestCase, SharedModuleStoreTestCase):
+    """
+    Testing the Course Goals API.
+    """
+
+    def setUp(self):
+        # Create a course with a verified track
+        super(TestCourseGoalsAPI, self).setUp()
+        self.course = CourseFactory.create(emit_signals=True)
+
+        self.user = User.objects.create_user('john', 'lennon@thebeatles.com', 'password')
+        CourseEnrollment.enroll(self.user, self.course.id)
+
+        self.client = APIClient(enforce_csrf_checks=True)
+        self.client.login(username=self.user.username, password=self.user.password)
+        self.client.force_authenticate(user=self.user)
+
+        self.apiUrl = reverse('course_goals_api:v0:course_goal-list')
+
+    def test_add_valid_goal(self):
+        """ Ensures a correctly formatted post succeeds. """
+        response = self.post_course_goal(valid=True)
+        self.assert_events_emitted()
+        self.assertEqual(response.status_code, 201)
+        self.assertEqual(len(CourseGoal.objects.filter(user=self.user, course_key=self.course.id)), 1)
+
+    def test_add_invalid_goal(self):
+        """ Ensures a correctly formatted post succeeds. """
+        response = self.post_course_goal(valid=False)
+        self.assertEqual(response.status_code, 400)
+        self.assertEqual(len(CourseGoal.objects.filter(user=self.user, course_key=self.course.id)), 0)
+
+    def post_course_goal(self, valid=True, goal_key='certify'):
+        """
+        Sends a post request to set a course goal and returns the response.
+        """
+        goal_key = goal_key if valid else 'invalid'
+        response = self.client.post(
+            self.apiUrl,
+            {
+                'goal_key': goal_key,
+                'course_key': self.course.id,
+                'user': self.user.username,
+            },
+        )
+        return response
diff --git a/lms/djangoapps/course_goals/urls.py b/lms/djangoapps/course_goals/urls.py
new file mode 100644
index 0000000000000000000000000000000000000000..cb87b3db771ef0a18811456c88b53cc930038b7e
--- /dev/null
+++ b/lms/djangoapps/course_goals/urls.py
@@ -0,0 +1,15 @@
+"""
+Course Goals URLs
+"""
+from django.conf.urls import include, patterns, url
+from rest_framework import routers
+
+from .views import CourseGoalViewSet
+
+router = routers.DefaultRouter()
+router.register(r'course_goals', CourseGoalViewSet, base_name='course_goal')
+
+urlpatterns = patterns(
+    '',
+    url(r'^v0/', include(router.urls, namespace='v0')),
+)
diff --git a/lms/djangoapps/course_goals/views.py b/lms/djangoapps/course_goals/views.py
new file mode 100644
index 0000000000000000000000000000000000000000..513d52c44056845f1122f419c00a69ed094a617b
--- /dev/null
+++ b/lms/djangoapps/course_goals/views.py
@@ -0,0 +1,92 @@
+"""
+Course Goals Views - includes REST API
+"""
+from django.contrib.auth import get_user_model
+from django.db.models.signals import post_save
+from django.dispatch import receiver
+from edx_rest_framework_extensions.authentication import JwtAuthentication
+from eventtracking import tracker
+from opaque_keys.edx.keys import CourseKey
+from openedx.core.lib.api.permissions import IsStaffOrOwner
+from rest_framework import permissions, serializers, viewsets
+from rest_framework.authentication import SessionAuthentication
+
+from .api import CourseGoalOption
+from .models import CourseGoal
+
+User = get_user_model()
+
+
+class CourseGoalSerializer(serializers.ModelSerializer):
+    """
+    Serializes CourseGoal models.
+    """
+    user = serializers.SlugRelatedField(slug_field='username', queryset=User.objects.all())
+
+    class Meta:
+        model = CourseGoal
+        fields = ('user', 'course_key', 'goal_key')
+
+    def validate_goal_key(self, value):
+        """
+        Ensure that the goal_key is valid.
+        """
+        if value not in CourseGoalOption.get_course_goal_keys():
+            raise serializers.ValidationError(
+                'Provided goal key, {goal_key}, is not a valid goal key (options= {goal_options}).'.format(
+                    goal_key=value,
+                    goal_options=[option.value for option in CourseGoalOption],
+                )
+            )
+        return value
+
+    def validate_course_key(self, value):
+        """
+        Ensure that the course_key is valid.
+        """
+        course_key = CourseKey.from_string(value)
+        if not course_key:
+            raise serializers.ValidationError(
+                'Provided course_key ({course_key}) does not map to a course.'.format(
+                    course_key=course_key
+                )
+            )
+        return course_key
+
+
+class CourseGoalViewSet(viewsets.ModelViewSet):
+    """
+    API calls to create and retrieve a course goal.
+
+    **Use Case**
+        * Create a new goal for a user.
+
+            Http400 is returned if the format of the request is not correct,
+            the course_id or goal is invalid or cannot be found.
+
+        * Retrieve goal for a user and a particular course.
+
+            Http400 is returned if the format of the request is not correct,
+            or the course_id is invalid or cannot be found.
+
+    **Example Requests**
+        GET /api/course_goals/v0/course_goals/
+        POST /api/course_goals/v0/course_goals/
+            Request data: {"course_key": <course-key>, "goal_key": "<goal-key>", "user": "<username>"}
+
+    """
+    authentication_classes = (JwtAuthentication, SessionAuthentication,)
+    permission_classes = (permissions.IsAuthenticated, IsStaffOrOwner,)
+    queryset = CourseGoal.objects.all()
+    serializer_class = CourseGoalSerializer
+
+
+@receiver(post_save, sender=CourseGoal, dispatch_uid="emit_course_goals_event")
+def emit_course_goal_event(sender, instance, **kwargs):
+    name = 'edx.course.goal.added' if kwargs.get('created', False) else 'edx.course.goal.updated'
+    tracker.emit(
+        name,
+        {
+            'goal_key': instance.goal_key,
+        }
+    )
diff --git a/lms/envs/common.py b/lms/envs/common.py
index beb140bea528e8f541e930adae23771f0dc202c5..b9863b88c9e946d46355b38964001c80d71fefef 100644
--- a/lms/envs/common.py
+++ b/lms/envs/common.py
@@ -401,6 +401,9 @@ FEATURES = {
 
     # Whether the bulk enrollment view is enabled.
     'ENABLE_BULK_ENROLLMENT_VIEW': False,
+
+    # Whether course goals is enabled.
+    'ENABLE_COURSE_GOALS': True,
 }
 
 # Settings for the course reviews tool template and identification key, set either to None to disable course reviews
@@ -2245,6 +2248,9 @@ INSTALLED_APPS = [
     'openedx.core.djangoapps.waffle_utils',
     'openedx.core.djangoapps.schedules.apps.SchedulesConfig',
 
+    # Course Goals
+    'lms.djangoapps.course_goals',
+
     # Features
     'openedx.features.course_bookmarks',
     'openedx.features.course_experience',
diff --git a/lms/static/sass/features/_course-experience.scss b/lms/static/sass/features/_course-experience.scss
index 48060b0db977d36675a5184cf7f3b203439d0b3c..fff4702801d65c1ee49995ce0b3c4caaf6126e61 100644
--- a/lms/static/sass/features/_course-experience.scss
+++ b/lms/static/sass/features/_course-experience.scss
@@ -15,11 +15,12 @@
   }
 
   .message-content {
+    @include margin(0, 0, $baseline, $baseline);
     position: relative;
     border: 1px solid $lms-border-color;
-    margin: 0 $baseline $baseline/2;
-    padding: $baseline/2 $baseline;
+    padding: $baseline;
     border-radius: $baseline/4;
+    width: calc(100% - 90px);
 
     @media (max-width: $grid-breakpoints-md) {
       width: 100%;
@@ -30,7 +31,7 @@
     &::before {
       @include left(0);
 
-      bottom: 35%;
+      top: 25px;
       border: solid transparent;
       height: 0;
       width: 0;
@@ -58,13 +59,49 @@
 
     .message-header {
       font-weight: $font-semibold;
-      margin-bottom: $baseline/4;
+      margin-bottom: $baseline/2;
+      width: calc(100% - 40px)
     }
 
     a {
       font-weight: $font-semibold;
       text-decoration: underline;
     }
+    .dismiss {
+      @include right($baseline/4);
+      top: $baseline/4;
+      position: absolute;
+      cursor: pointer;
+      color: $black-t3;
+
+      &:hover {
+        color: $black-t2;
+      }
+    }
+    // Course Goal Styling
+    .goal-options-container {
+      margin-top: $baseline;
+      text-align: center;
+
+      .goal-option {
+        text-decoration: none;
+        font-size: font-size(x-small);
+        padding: $baseline/2;
+
+        &.dismissible {
+          @include right($baseline/4);
+          position: absolute;
+          top: $baseline/2;
+          font-size: font-size(small);
+          color: $uxpl-blue-base;
+          cursor: pointer;
+
+          &:hover {
+            color: $black-t2;
+          }
+        }
+      }
+    }
   }
 }
 
diff --git a/lms/templates/navigation/navigation.html b/lms/templates/navigation/navigation.html
index 2f1972b527277fc53a91f51437985dcc5bb3a97e..26d43a9e5f1a09ad0f2854fd4c2ce3ab4ba1c0b6 100644
--- a/lms/templates/navigation/navigation.html
+++ b/lms/templates/navigation/navigation.html
@@ -50,7 +50,7 @@ site_status_msg = get_site_status_msg(course_id)
 </%block>
 
 % if uses_bootstrap:
-  <header class="navigation-container header-global ${"slim" if course else ""}">
+  <header class="navigation-container header-global ${'slim' if course else ''}">
     <nav class="navbar navbar-expand-lg navbar-light">
       <%include file="bootstrap/navbar-logo-header.html" args="online_help_token=online_help_token"/>
       <button class="navbar-toggler navbar-toggler-right mt-2" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
diff --git a/lms/urls.py b/lms/urls.py
index 8da75b2aedd4f4830c10051baec7d5c87f10c1b4..c37d39bb18cf25d97bbec24ab5da8043e7ee5402 100644
--- a/lms/urls.py
+++ b/lms/urls.py
@@ -829,6 +829,11 @@ urlpatterns += (
     url(r'^commerce/', include('commerce.urls', namespace='commerce')),
 )
 
+# Course goals
+urlpatterns += (
+    url(r'^api/course_goals/', include('lms.djangoapps.course_goals.urls', namespace='course_goals_api')),
+)
+
 # Embargo
 if settings.FEATURES.get('EMBARGO'):
     urlpatterns += (
diff --git a/openedx/core/lib/api/permissions.py b/openedx/core/lib/api/permissions.py
index c71381f2cd3743fdd52afd624078f04d9d941606..6bbf3eef1540d2df0119cf943d98b21a60ec1891 100644
--- a/openedx/core/lib/api/permissions.py
+++ b/openedx/core/lib/api/permissions.py
@@ -166,4 +166,5 @@ class IsStaffOrOwner(permissions.BasePermission):
         return user.is_staff \
             or (user.username == request.GET.get('username')) \
             or (user.username == getattr(request, 'data', {}).get('username')) \
+            or (user.username == getattr(request, 'data', {}).get('user')) \
             or (user.username == getattr(view, 'kwargs', {}).get('username'))
diff --git a/openedx/features/course_experience/__init__.py b/openedx/features/course_experience/__init__.py
index 31f994e4b4643200d310ba65a110acc97abe97ad..8c9980f5788f219e24bc56abc56c9b4d07c1af83 100644
--- a/openedx/features/course_experience/__init__.py
+++ b/openedx/features/course_experience/__init__.py
@@ -16,15 +16,18 @@ COURSE_OUTLINE_PAGE_FLAG = CourseWaffleFlag(WAFFLE_FLAG_NAMESPACE, 'course_outli
 # Waffle flag to enable a single unified "Course" tab.
 UNIFIED_COURSE_TAB_FLAG = CourseWaffleFlag(WAFFLE_FLAG_NAMESPACE, 'unified_course_tab')
 
-# Waffle flag to enable the sock on the footer of the home and courseware pages
+# Waffle flag to enable the sock on the footer of the home and courseware pages.
 DISPLAY_COURSE_SOCK_FLAG = CourseWaffleFlag(WAFFLE_FLAG_NAMESPACE, 'display_course_sock')
 
-# Waffle flag to let learners access a course before its start date
+# Waffle flag to let learners access a course before its start date.
 COURSE_PRE_START_ACCESS_FLAG = CourseWaffleFlag(WAFFLE_FLAG_NAMESPACE, 'pre_start_access')
 
-# Waffle flag to enable a review page link from the unified home page
+# Waffle flag to enable a review page link from the unified home page.
 SHOW_REVIEWS_TOOL_FLAG = CourseWaffleFlag(WAFFLE_FLAG_NAMESPACE, 'show_reviews_tool')
 
+# Waffle flag to enable the setting of course goals.
+ENABLE_COURSE_GOALS = CourseWaffleFlag(WAFFLE_FLAG_NAMESPACE, 'enable_course_goals')
+
 SHOW_UPGRADE_MSG_ON_COURSE_HOME = CourseWaffleFlag(WAFFLE_FLAG_NAMESPACE, 'show_upgrade_msg_on_course_home')
 
 # Waffle flag to switch between the 'welcome message' and 'latest update' on the course home page.
diff --git a/openedx/features/course_experience/static/course_experience/js/CourseGoals.js b/openedx/features/course_experience/static/course_experience/js/CourseGoals.js
new file mode 100644
index 0000000000000000000000000000000000000000..644d15802856f2a2e07ae2fce594a4d213ac0669
--- /dev/null
+++ b/openedx/features/course_experience/static/course_experience/js/CourseGoals.js
@@ -0,0 +1,40 @@
+/* globals gettext */
+
+export class CourseGoals {  // eslint-disable-line import/prefer-default-export
+
+  constructor(options) {
+    $('.goal-option').click((e) => {
+      const goalKey = $(e.target).data().choice;
+      $.ajax({
+        method: 'POST',
+        url: options.goalApiUrl,
+        headers: { 'X-CSRFToken': $.cookie('csrftoken') },
+        data: {
+          goal_key: goalKey,
+          course_key: options.courseId,
+          user: options.username,
+        },
+        dataType: 'json',
+        success: () => {
+          // LEARNER-2522 will address the success message
+          const successMsg = gettext('Thank you for setting your course goal!');
+          // xss-lint: disable=javascript-jquery-html
+          $('.message-content').html(`<div class="success-message">${successMsg}</div>`);
+        },
+        error: () => {
+          // LEARNER-2522 will address the error message
+          const errorMsg = gettext('There was an error in setting your goal, please reload the page and try again.'); // eslint-disable-line max-len
+          // xss-lint: disable=javascript-jquery-html
+          $('.message-content').html(`<div class="error-message"> ${errorMsg} </div>`);
+        },
+      });
+    });
+
+    // Allow goal selection with an enter press for accessibility purposes
+    $('.goal-option').keyup((e) => {
+      if (e.which === 13) {
+        $(e.target).trigger('click');
+      }
+    });
+  }
+}
diff --git a/openedx/features/course_experience/static/course_experience/js/CourseHome.js b/openedx/features/course_experience/static/course_experience/js/CourseHome.js
index 523c4c54181ce0b3ac754c41ad1d238a0c505e26..7b67781f1931e656ee61b3530e64f7a3da66a91f 100644
--- a/openedx/features/course_experience/static/course_experience/js/CourseHome.js
+++ b/openedx/features/course_experience/static/course_experience/js/CourseHome.js
@@ -30,6 +30,18 @@ export class CourseHome {  // eslint-disable-line import/prefer-default-export
       );
     });
 
+    // Dismissibility for in course messages
+    $(document.body).on('click', '.course-message .dismiss', (event) => {
+      $(event.target).closest('.course-message').hide();
+    });
+
+    // Allow dismiss on enter press for accessibility purposes
+    $(document.body).on('keyup', '.course-message .dismiss', (event) => {
+      if (event.which === 13) {
+        $(event.target).trigger('click');
+      }
+    });
+
     $(document).ready(() => {
       this.configureUpgradeMessage();
     });
diff --git a/openedx/features/course_experience/static/course_experience/js/CourseSock.js b/openedx/features/course_experience/static/course_experience/js/CourseSock.js
index c4a321e4c811ba0b7d19a74632ec51ad471fa49d..9e9126e39e3dbc45262d8992dec4b68d76431039 100644
--- a/openedx/features/course_experience/static/course_experience/js/CourseSock.js
+++ b/openedx/features/course_experience/static/course_experience/js/CourseSock.js
@@ -19,7 +19,7 @@ export class CourseSock {  // eslint-disable-line import/prefer-default-export
       const startFixed = $verificationSock.offset().top + 320;
       const endFixed = (startFixed + $verificationSock.height()) - 220;
 
-      // Assure update button stays in sock even when max-width is exceeded
+      // Ensure update button stays in sock even when max-width is exceeded
       const distLeft = ($verificationSock.offset().left + $verificationSock.width())
         - ($upgradeToVerifiedButton.width() + 22);
 
diff --git a/openedx/features/course_experience/templates/course_experience/course-messages-fragment.html b/openedx/features/course_experience/templates/course_experience/course-messages-fragment.html
index 1cf6f94bd1595e2b4b2bd6335d5ffdc66af814d8..18df52b2ee76779022640509ee9f34c1de323d54 100644
--- a/openedx/features/course_experience/templates/course_experience/course-messages-fragment.html
+++ b/openedx/features/course_experience/templates/course_experience/course-messages-fragment.html
@@ -5,6 +5,8 @@
 
 <%!
 from django.utils.translation import get_language_bidi
+from django.utils.translation import ugettext as _
+from openedx.core.djangolib.js_utils import js_escaped_string
 from openedx.core.djangolib.markup import HTML
 from openedx.features.course_experience import CourseHomeMessages
 %>
@@ -17,14 +19,22 @@ is_rtl = get_language_bidi()
     % for message in course_home_messages:
         <div class="course-message grid-manual">
             % if not is_rtl:
-                <img class="message-author col col-2" src="${static.url(image_src)}"/>
+                <img class="message-author" alt="${_('Course message author')}" role="none" src="${static.url(image_src)}"/>
             % endif
-            <div class="message-content col col-9">
+            <div class="message-content">
                 ${HTML(message.message_html)}
             </div>
             % if is_rtl:
-                <img class="message-author col col-2" src="${static.url(image_src)}"/>
+                <img class="message-author" alt="${_('Course message author')}" role="none" src="${static.url(image_src)}"/>
             % endif
         </div>
     % endfor
 % endif
+
+<%static:webpack entry="CourseGoals">
+    new CourseGoals({
+        goalApiUrl: "${goal_api_url | n, js_escaped_string}",
+        courseId: "${course_id | n, js_escaped_string}",
+        username: "${username | n, js_escaped_string}",
+    });
+</%static:webpack>
diff --git a/openedx/features/course_experience/tests/views/test_course_home.py b/openedx/features/course_experience/tests/views/test_course_home.py
index d655d495d8834f5dd97beb1bf88f79f171fd02f8..a6a118d10e3f8ccee099f7abff5247717b52e572 100644
--- a/openedx/features/course_experience/tests/views/test_course_home.py
+++ b/openedx/features/course_experience/tests/views/test_course_home.py
@@ -16,6 +16,7 @@ from waffle.testutils import override_flag
 
 from commerce.models import CommerceConfiguration
 from commerce.utils import EcommerceService
+from lms.djangoapps.course_goals.api import add_course_goal, remove_course_goal
 from course_modes.models import CourseMode
 from courseware.tests.factories import StaffFactory
 from openedx.core.djangoapps.waffle_utils.testutils import WAFFLE_TABLES, override_waffle_flag
@@ -25,14 +26,14 @@ from openedx.features.course_experience import (
     UNIFIED_COURSE_TAB_FLAG
 )
 from student.models import CourseEnrollment
-from student.tests.factories import UserFactory
+from student.tests.factories import UserFactory, CourseEnrollmentFactory
 from util.date_utils import strftime_localized
 from xmodule.modulestore import ModuleStoreEnum
 from xmodule.modulestore.tests.django_utils import CourseUserType, ModuleStoreTestCase, SharedModuleStoreTestCase
 from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory, check_mongo_calls
 from .helpers import add_course_mode
 from .test_course_updates import create_course_update, remove_course_updates
-from ... import COURSE_PRE_START_ACCESS_FLAG
+from ... import COURSE_PRE_START_ACCESS_FLAG, ENABLE_COURSE_GOALS
 
 TEST_PASSWORD = 'test'
 TEST_CHAPTER_NAME = 'Test Chapter'
@@ -43,6 +44,8 @@ TEST_COURSE_HOME_MESSAGE = 'course-message'
 TEST_COURSE_HOME_MESSAGE_ANONYMOUS = '/login'
 TEST_COURSE_HOME_MESSAGE_UNENROLLED = 'Enroll now'
 TEST_COURSE_HOME_MESSAGE_PRE_START = 'Course starts in'
+TEST_COURSE_GOAL_OPTIONS = 'goal-options-container'
+COURSE_GOAL_DISMISS_OPTION = 'unsure'
 
 QUERY_COUNT_TABLE_BLACKLIST = WAFFLE_TABLES
 
@@ -170,7 +173,7 @@ class TestCourseHomePage(CourseHomePageTestCase):
         course_home_url(self.course)
 
         # Fetch the view and verify the query counts
-        with self.assertNumQueries(41, table_blacklist=QUERY_COUNT_TABLE_BLACKLIST):
+        with self.assertNumQueries(44, table_blacklist=QUERY_COUNT_TABLE_BLACKLIST):
             with check_mongo_calls(4):
                 url = course_home_url(self.course)
                 self.client.get(url)
@@ -375,11 +378,13 @@ class TestCourseHomePageAccess(CourseHomePageTestCase):
         self.assertContains(response, TEST_COURSE_HOME_MESSAGE)
         self.assertContains(response, TEST_COURSE_HOME_MESSAGE_UNENROLLED)
 
-        # Verify that enrolled users are not shown a message when enrolled and course has begun
+        # Verify that enrolled users are not shown any state warning message when enrolled and course has begun.
         CourseEnrollment.enroll(user, self.course.id)
         url = course_home_url(self.course)
         response = self.client.get(url)
-        self.assertNotContains(response, TEST_COURSE_HOME_MESSAGE)
+        self.assertNotContains(response, TEST_COURSE_HOME_MESSAGE_ANONYMOUS)
+        self.assertNotContains(response, TEST_COURSE_HOME_MESSAGE_UNENROLLED)
+        self.assertNotContains(response, TEST_COURSE_HOME_MESSAGE_PRE_START)
 
         # Verify that enrolled users are shown 'days until start' message before start date
         future_course = self.create_future_course()
@@ -389,6 +394,50 @@ class TestCourseHomePageAccess(CourseHomePageTestCase):
         self.assertContains(response, TEST_COURSE_HOME_MESSAGE)
         self.assertContains(response, TEST_COURSE_HOME_MESSAGE_PRE_START)
 
+    @override_waffle_flag(UNIFIED_COURSE_TAB_FLAG, active=True)
+    @override_waffle_flag(COURSE_PRE_START_ACCESS_FLAG, active=True)
+    @override_waffle_flag(ENABLE_COURSE_GOALS, active=True)
+    def test_course_goals(self):
+        """
+        Ensure that the following five use cases work as expected.
+
+        1) Unenrolled users are not shown the set course goal message.
+        2) Enrolled users are shown the set course goal message if they have not yet set a course goal.
+        3) Enrolled users are not shown the set course goal message if they have set a course goal.
+        4) Enrolled and verified users are not shown the set course goal message.
+        5) Enrolled users are not shown the set course goal message in a course that cannot be verified.
+        """
+        # Create a course with a verified track.
+        verifiable_course = CourseFactory.create()
+        add_course_mode(verifiable_course, upgrade_deadline_expired=False)
+
+        # Verify that unenrolled users are not shown the set course goal message.
+        user = self.create_user_for_course(verifiable_course, CourseUserType.UNENROLLED)
+        response = self.client.get(course_home_url(verifiable_course))
+        self.assertNotContains(response, TEST_COURSE_GOAL_OPTIONS)
+
+        # Verify that enrolled users are shown the set course goal message in a verified course.
+        CourseEnrollment.enroll(user, verifiable_course.id)
+        response = self.client.get(course_home_url(verifiable_course))
+        self.assertContains(response, TEST_COURSE_GOAL_OPTIONS)
+
+        # Verify that enrolled users that have set a course goal are not shown the set course goal message.
+        add_course_goal(user, verifiable_course.id, COURSE_GOAL_DISMISS_OPTION)
+        response = self.client.get(course_home_url(verifiable_course))
+        self.assertNotContains(response, TEST_COURSE_GOAL_OPTIONS)
+
+        # Verify that enrolled and verified users are not shown the set course goal message.
+        remove_course_goal(user, verifiable_course.id)
+        CourseEnrollment.enroll(user, verifiable_course.id, CourseMode.VERIFIED)
+        response = self.client.get(course_home_url(verifiable_course))
+        self.assertNotContains(response, TEST_COURSE_GOAL_OPTIONS)
+
+        # Verify that enrolled users are not shown the set course goal message in an audit only course.
+        audit_only_course = CourseFactory.create()
+        CourseEnrollment.enroll(user, audit_only_course.id)
+        response = self.client.get(course_home_url(audit_only_course))
+        self.assertNotContains(response, TEST_COURSE_GOAL_OPTIONS)
+
 
 class CourseHomeFragmentViewTests(ModuleStoreTestCase):
     CREATE_USER = False
diff --git a/openedx/features/course_experience/tests/views/test_course_sock.py b/openedx/features/course_experience/tests/views/test_course_sock.py
index 3d3beff4332a2b5f36d960afc2f84ad3b7eaa0b9..c4cebe8b1d29019baca7e3df715499c7b1ee0825 100644
--- a/openedx/features/course_experience/tests/views/test_course_sock.py
+++ b/openedx/features/course_experience/tests/views/test_course_sock.py
@@ -56,7 +56,7 @@ class TestCourseSockView(SharedModuleStoreTestCase):
     @override_waffle_flag(DISPLAY_COURSE_SOCK_FLAG, active=True)
     def test_standard_course(self):
         """
-        Assure that a course that cannot be verified does
+        Ensure that a course that cannot be verified does
         not have a visible verification sock.
         """
         response = self.client.get(course_home_url(self.standard_course))
@@ -65,7 +65,7 @@ class TestCourseSockView(SharedModuleStoreTestCase):
     @override_waffle_flag(DISPLAY_COURSE_SOCK_FLAG, active=True)
     def test_verified_course(self):
         """
-        Assure that a course that can be verified has a
+        Ensure that a course that can be verified has a
         visible verification sock.
         """
         response = self.client.get(course_home_url(self.verified_course))
@@ -74,7 +74,7 @@ class TestCourseSockView(SharedModuleStoreTestCase):
     @override_waffle_flag(DISPLAY_COURSE_SOCK_FLAG, active=True)
     def test_verified_course_updated_expired(self):
         """
-        Assure that a course that has an expired upgrade
+        Ensure that a course that has an expired upgrade
         date does not display the verification sock.
         """
         response = self.client.get(course_home_url(self.verified_course_update_expired))
@@ -83,7 +83,7 @@ class TestCourseSockView(SharedModuleStoreTestCase):
     @override_waffle_flag(DISPLAY_COURSE_SOCK_FLAG, active=True)
     def test_verified_course_user_already_upgraded(self):
         """
-        Assure that a user that has already upgraded to a
+        Ensure that a user that has already upgraded to a
         verified status cannot see the verification sock.
         """
         response = self.client.get(course_home_url(self.verified_course_already_enrolled))
diff --git a/openedx/features/course_experience/views/course_home_messages.py b/openedx/features/course_experience/views/course_home_messages.py
index c00cc82afb174ff777c6649a6af4705381a347f1..9b580c85828f12fa29844903e6bf4ee111d4792a 100644
--- a/openedx/features/course_experience/views/course_home_messages.py
+++ b/openedx/features/course_experience/views/course_home_messages.py
@@ -1,22 +1,30 @@
 """
 View logic for handling course messages.
 """
-
-from babel.dates import format_date, format_timedelta
+import math
 from datetime import datetime
 
-from courseware.courses import get_course_with_access
+from babel.dates import format_date, format_timedelta
+from django.conf import settings
+from django.contrib import auth
 from django.template.loader import render_to_string
 from django.utils.http import urlquote_plus
 from django.utils.timezone import UTC
-from django.utils.translation import get_language, to_locale
 from django.utils.translation import ugettext as _
-from openedx.core.djangolib.markup import Text, HTML
+from django.utils.translation import get_language, to_locale
 from opaque_keys.edx.keys import CourseKey
+from rest_framework.reverse import reverse
 from web_fragments.fragment import Fragment
 
+from course_modes.models import CourseMode
+from courseware.courses import get_course_with_access
+from lms.djangoapps.course_goals.api import CourseGoalOption, get_course_goal, get_goal_text
 from openedx.core.djangoapps.plugin_api.views import EdxFragmentView
+from openedx.core.djangolib.markup import HTML, Text
 from openedx.features.course_experience import CourseHomeMessages
+from student.models import CourseEnrollment
+
+from .. import ENABLE_COURSE_GOALS
 
 
 class CourseHomeMessageFragmentView(EdxFragmentView):
@@ -55,69 +63,140 @@ class CourseHomeMessageFragmentView(EdxFragmentView):
         }
 
         # Register the course home messages to be loaded on the page
-        self.register_course_home_messages(request, course, user_access, course_start_data)
+        _register_course_home_messages(request, course_id, user_access, course_start_data)
 
         # Grab the relevant messages
         course_home_messages = list(CourseHomeMessages.user_messages(request))
 
-        # Return None if user is enrolled and course has begun
-        if user_access['is_enrolled'] and already_started:
-            return None
+        # Pass in the url used to set a course goal
+        goal_api_url = reverse('course_goals_api:v0:course_goal-list', request=request)
 
         # Grab the logo
         image_src = "course_experience/images/home_message_author.png"
 
         context = {
             'course_home_messages': course_home_messages,
+            'goal_api_url': goal_api_url,
             'image_src': image_src,
+            'course_id': course_id,
+            'username': request.user.username,
         }
 
         html = render_to_string('course_experience/course-messages-fragment.html', context)
         return Fragment(html)
 
-    @staticmethod
-    def register_course_home_messages(request, course, user_access, course_start_data):
-        """
-        Register messages to be shown in the course home content page.
-        """
-        if user_access['is_anonymous']:
-            CourseHomeMessages.register_info_message(
-                request,
-                Text(_(
-                    " {sign_in_link} or {register_link} and then enroll in this course."
-                )).format(
-                    sign_in_link=HTML("<a href='/login?next={current_url}'>{sign_in_label}</a>").format(
-                        sign_in_label=_("Sign in"),
-                        current_url=urlquote_plus(request.path),
-                    ),
-                    register_link=HTML("<a href='/register?next={current_url}'>{register_label}</a>").format(
-                        register_label=_("register"),
-                        current_url=urlquote_plus(request.path),
-                    )
+
+def _register_course_home_messages(request, course_id, user_access, course_start_data):
+    """
+    Register messages to be shown in the course home content page.
+    """
+    course_key = CourseKey.from_string(course_id)
+    course = get_course_with_access(request.user, 'load', course_key)
+    if user_access['is_anonymous']:
+        CourseHomeMessages.register_info_message(
+            request,
+            Text(_(
+                " {sign_in_link} or {register_link} and then enroll in this course."
+            )).format(
+                sign_in_link=HTML("<a href='/login?next={current_url}'>{sign_in_label}</a>").format(
+                    sign_in_label=_("Sign in"),
+                    current_url=urlquote_plus(request.path),
                 ),
-                title='You must be enrolled in the course to see course content.'
+                register_link=HTML("<a href='/register?next={current_url}'>{register_label}</a>").format(
+                    register_label=_("register"),
+                    current_url=urlquote_plus(request.path),
+                )
+            ),
+            title=Text(_('You must be enrolled in the course to see course content.'))
+        )
+    if not user_access['is_anonymous'] and not user_access['is_staff'] and not user_access['is_enrolled']:
+        CourseHomeMessages.register_info_message(
+            request,
+            Text(_(
+                "{open_enroll_link} Enroll now{close_enroll_link} to access the full course."
+            )).format(
+                open_enroll_link='',
+                close_enroll_link=''
+            ),
+            title=Text(_('Welcome to {course_display_name}')).format(
+                course_display_name=course.display_name
+            )
+        )
+    if user_access['is_enrolled'] and not course_start_data['already_started']:
+        CourseHomeMessages.register_info_message(
+            request,
+            Text(_(
+                "Don't forget to add a calendar reminder!"
+            )),
+            title=Text(_("Course starts in {days_until_start_string} on {course_start_date}.")).format(
+                days_until_start_string=course_start_data['days_until_start_string'],
+                course_start_date=course_start_data['course_start_date']
             )
-        if not user_access['is_anonymous'] and not user_access['is_staff'] and not user_access['is_enrolled']:
-            CourseHomeMessages.register_info_message(
-                request,
-                Text(_(
-                    "{open_enroll_link} Enroll now{close_enroll_link} to access the full course."
-                )).format(
-                    open_enroll_link='',
-                    close_enroll_link=''
+        )
+
+    # Only show the set course goal message for enrolled, unverified
+    # users that have not yet set a goal in a course that allows for
+    # verified statuses.
+    has_verified_mode = CourseMode.has_verified_mode(CourseMode.modes_for_course_dict(unicode(course.id)))
+    is_already_verified = CourseEnrollment.is_enrolled_as_verified(request.user, course_key)
+    user_goal = get_course_goal(auth.get_user(request), course_key) if not request.user.is_anonymous() else None
+    if user_access['is_enrolled'] and has_verified_mode and not is_already_verified and not user_goal \
+            and ENABLE_COURSE_GOALS.is_enabled(course_key) and settings.FEATURES.get('ENABLE_COURSE_GOALS'):
+        goal_choices_html = Text(_(
+            'To start, set a course goal by selecting the option below that best describes '
+            'your learning plan. {goal_options_container}'
+        )).format(
+            goal_options_container=HTML('<div class="row goal-options-container">')
+        )
+
+        # Add the dismissible option for users that are unsure of their goal
+        goal_choices_html += Text(
+            '{initial_tag}{choice}{closing_tag}'
+        ).format(
+            initial_tag=HTML(
+                '<div tabindex="0" aria-label="{aria_label_choice}" class="goal-option dismissible" '
+                'data-choice="{goal_key}">'
+            ).format(
+                goal_key=CourseGoalOption.UNSURE.value,
+                aria_label_choice=Text(_("Set goal to: {choice}")).format(
+                    choice=get_goal_text(CourseGoalOption.UNSURE.value)
                 ),
-                title=Text('Welcome to {course_display_name}').format(
-                    course_display_name=course.display_name
-                )
+            ),
+            choice=Text(_('{choice}')).format(
+                choice=get_goal_text(CourseGoalOption.UNSURE.value),
+            ),
+            closing_tag=HTML('</div>'),
+        )
+
+        # Add the option to set a goal to earn a certificate,
+        # complete the course or explore the course
+        goal_options = [CourseGoalOption.CERTIFY.value, CourseGoalOption.COMPLETE.value, CourseGoalOption.EXPLORE.value]
+        for goal_key in goal_options:
+            goal_text = get_goal_text(goal_key)
+            goal_choices_html += HTML(
+                '{initial_tag}{goal_text}{closing_tag}'
+            ).format(
+                initial_tag=HTML(
+                    '<div tabindex="0" aria-label="{aria_label_choice}" class="goal-option {col_sel} btn" '
+                    'data-choice="{goal_key}">'
+                ).format(
+                    goal_key=goal_key,
+                    aria_label_choice=Text(_("Set goal to: {goal_text}")).format(
+                        goal_text=Text(_(goal_text))
+                    ),
+                    col_sel='col-' + str(int(math.floor(12 / len(goal_options))))
+                ),
+                goal_text=goal_text,
+                closing_tag=HTML('</div>')
             )
-        if user_access['is_enrolled'] and not course_start_data['already_started']:
-            CourseHomeMessages.register_info_message(
-                request,
-                Text(_(
-                    "Don't forget to add a calendar reminder!"
-                )),
-                title=Text("Course starts in {days_until_start_string} on {course_start_date}.").format(
-                    days_until_start_string=course_start_data['days_until_start_string'],
-                    course_start_date=course_start_data['course_start_date']
-                )
+
+        CourseHomeMessages.register_info_message(
+            request,
+            HTML('{goal_choices_html}{closing_tag}').format(
+                goal_choices_html=goal_choices_html,
+                closing_tag=HTML('</div>')
+            ),
+            title=Text(_('Welcome to {course_display_name}')).format(
+                course_display_name=course.display_name
             )
+        )
diff --git a/webpack.config.js b/webpack.config.js
index 4c5fc62047b5bf306d7f24b9cb2f530ff8d00d9d..6e56c1ef039d233c3ae83a133d5c920e7deb39cc 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -23,6 +23,7 @@ var wpconfig = {
         StudioIndex: './cms/static/js/features_jsx/studio/index.jsx',
 
         // Features
+        CourseGoals: './openedx/features/course_experience/static/course_experience/js/CourseGoals.js',
         CourseHome: './openedx/features/course_experience/static/course_experience/js/CourseHome.js',
         CourseOutline: './openedx/features/course_experience/static/course_experience/js/CourseOutline.js',
         CourseSock: './openedx/features/course_experience/static/course_experience/js/CourseSock.js',