diff --git a/lms/djangoapps/instructor/tests/test_api.py b/lms/djangoapps/instructor/tests/test_api.py
index 9acf4e434062065677b0f29d552e01b4bdeaf029..9f4499a42e5de44105758bcdb986bcb429e7d6e1 100644
--- a/lms/djangoapps/instructor/tests/test_api.py
+++ b/lms/djangoapps/instructor/tests/test_api.py
@@ -81,6 +81,7 @@ from lms.djangoapps.instructor_task.api_helper import (
     QueueConnectionError,
     generate_already_running_error_message
 )
+from lms.djangoapps.program_enrollments.tests.factories import ProgramEnrollmentFactory
 from openedx.core.djangoapps.course_date_signals.handlers import extract_dates
 from openedx.core.djangoapps.course_groups.cohorts import set_course_cohorted
 from openedx.core.djangoapps.django_comment_common.models import FORUM_ROLE_COMMUNITY_TA
@@ -2616,6 +2617,32 @@ class TestInstructorAPILevelsDataDump(SharedModuleStoreTestCase, LoginEnrollment
 
         assert ('team' in res_json['feature_names']) == has_teams
 
+    @ddt.data(True, False)
+    def test_get_students_features_external_user_key(self, has_program_enrollments):
+        external_key_dict = {}
+        if has_program_enrollments:
+            for i in range(len(self.students)):
+                student = self.students[i]
+                external_key = "{}_{}".format(student.username, i)
+                ProgramEnrollmentFactory.create(user=student, external_user_key=external_key)
+                external_key_dict[student.username] = external_key
+
+        url = reverse('get_students_features', kwargs={'course_id': str(self.course.id)})
+
+        response = self.client.post(url, {})
+        res_json = json.loads(response.content.decode('utf-8'))
+        assert 'external_user_key' in res_json['feature_names']
+        for student in self.students:
+            student_json = [
+                x for x in res_json['students']
+                if x['username'] == student.username
+            ][0]
+            assert student_json['username'] == student.username
+            if has_program_enrollments:
+                assert student_json['external_user_key'] == external_key_dict[student.username]
+            else:
+                assert student_json['external_user_key'] == ''
+
     def test_get_students_who_may_enroll(self):
         """
         Test whether get_students_who_may_enroll returns an appropriate
diff --git a/lms/djangoapps/instructor/views/api.py b/lms/djangoapps/instructor/views/api.py
index d5bc241267636b193f549159c389e915b375ab28..959f8cce3cf25c466e44308bc93893cfa74ccfbe 100644
--- a/lms/djangoapps/instructor/views/api.py
+++ b/lms/djangoapps/instructor/views/api.py
@@ -1141,7 +1141,7 @@ def get_students_features(request, course_id, csv=False):  # pylint: disable=red
             'id', 'username', 'name', 'email', 'language', 'location',
             'year_of_birth', 'gender', 'level_of_education', 'mailing_address',
             'goals', 'enrollment_mode', 'verification_status',
-            'last_login', 'date_joined',
+            'last_login', 'date_joined', 'external_user_key'
         ]
 
     # Provide human-friendly and translatable names for these features. These names
@@ -1163,6 +1163,7 @@ def get_students_features(request, course_id, csv=False):  # pylint: disable=red
         'verification_status': _('Verification Status'),
         'last_login': _('Last Login'),
         'date_joined': _('Date Joined'),
+        'external_user_key': _('External User Key'),
     }
 
     if is_course_cohorted(course.id):
diff --git a/lms/djangoapps/instructor_analytics/basic.py b/lms/djangoapps/instructor_analytics/basic.py
index 492a365faa1ca16b2ff3f350a09ec50198949a1d..26cd63542a44751852ab0e5632cc6e70955e76e1 100644
--- a/lms/djangoapps/instructor_analytics/basic.py
+++ b/lms/djangoapps/instructor_analytics/basic.py
@@ -23,6 +23,7 @@ from common.djangoapps.student.models import CourseEnrollment, CourseEnrollmentA
 from lms.djangoapps.certificates.models import CertificateStatuses, GeneratedCertificate
 from lms.djangoapps.courseware.models import StudentModule
 from lms.djangoapps.grades.api import context as grades_context
+from lms.djangoapps.program_enrollments.api import fetch_program_enrollments_by_students
 from lms.djangoapps.verify_student.services import IDVerificationService
 from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
 from openedx.core.djangolib.markup import HTML, Text
@@ -35,6 +36,7 @@ STUDENT_FEATURES = ('id', 'username', 'first_name', 'last_name', 'is_staff', 'em
 PROFILE_FEATURES = ('name', 'language', 'location', 'year_of_birth', 'gender',
                     'level_of_education', 'mailing_address', 'goals', 'meta',
                     'city', 'country')
+PROGRAM_ENROLLMENT_FEATURES = ('external_user_key', )
 ORDER_ITEM_FEATURES = ('list_price', 'unit_cost', 'status')
 ORDER_FEATURES = ('purchase_time',)
 
@@ -46,7 +48,7 @@ SALE_ORDER_FEATURES = ('id', 'company_name', 'company_contact_name', 'company_co
                        'bill_to_street2', 'bill_to_city', 'bill_to_state', 'bill_to_postalcode',
                        'bill_to_country', 'order_type', 'created')
 
-AVAILABLE_FEATURES = STUDENT_FEATURES + PROFILE_FEATURES
+AVAILABLE_FEATURES = STUDENT_FEATURES + PROFILE_FEATURES + PROGRAM_ENROLLMENT_FEATURES
 COURSE_REGISTRATION_FEATURES = ('code', 'course_id', 'created_by', 'created_at', 'is_valid')
 COUPON_FEATURES = ('code', 'course_id', 'percentage_discount', 'description', 'expiration_date', 'is_active')
 CERTIFICATE_FEATURES = ('course_id', 'mode', 'status', 'grade', 'created_date', 'is_active', 'error_reason')
@@ -96,6 +98,8 @@ def enrolled_students_features(course_key, features):
     include_team_column = 'team' in features
     include_enrollment_mode = 'enrollment_mode' in features
     include_verification_status = 'verification_status' in features
+    include_program_enrollments = 'external_user_key' in features
+    external_user_key_dict = {}
 
     students = User.objects.filter(
         courseenrollment__course_id=course_key,
@@ -108,6 +112,11 @@ def enrolled_students_features(course_key, features):
     if include_team_column:
         students = students.prefetch_related('teams')
 
+    if include_program_enrollments and len(students) > 0:
+        program_enrollments = fetch_program_enrollments_by_students(users=students, realized_only=True)
+        for program_enrollment in program_enrollments:
+            external_user_key_dict[program_enrollment.user_id] = program_enrollment.external_user_key
+
     def extract_attr(student, feature):
         """Evaluate a student attribute that is ready for JSON serialization"""
         attr = getattr(student, feature)
@@ -167,6 +176,10 @@ def enrolled_students_features(course_key, features):
             if include_enrollment_mode:
                 student_dict['enrollment_mode'] = enrollment_mode
 
+        if include_program_enrollments:
+            # extra external_user_key
+            student_dict['external_user_key'] = external_user_key_dict.get(student.id, '')
+
         return student_dict
 
     return [extract_student(student, features) for student in students]
diff --git a/lms/djangoapps/instructor_analytics/tests/test_basic.py b/lms/djangoapps/instructor_analytics/tests/test_basic.py
index a7478de3e5b7d96c1e47917e5ee2b8a7dcf7c65c..a4469a93d1c022ff7257eccdaeae9beaa5dc4ef9 100644
--- a/lms/djangoapps/instructor_analytics/tests/test_basic.py
+++ b/lms/djangoapps/instructor_analytics/tests/test_basic.py
@@ -16,6 +16,7 @@ from lms.djangoapps.courseware.tests.factories import InstructorFactory
 from lms.djangoapps.instructor_analytics.basic import (  # lint-amnesty, pylint: disable=unused-import
     AVAILABLE_FEATURES,
     PROFILE_FEATURES,
+    PROGRAM_ENROLLMENT_FEATURES,
     STUDENT_FEATURES,
     StudentModule,
     enrolled_students_features,
@@ -24,6 +25,7 @@ from lms.djangoapps.instructor_analytics.basic import (  # lint-amnesty, pylint:
     list_may_enroll,
     list_problem_responses
 )
+from lms.djangoapps.program_enrollments.tests.factories import ProgramEnrollmentFactory
 from openedx.core.djangoapps.course_groups.tests.helpers import CohortFactory
 from common.djangoapps.student.models import CourseEnrollment, CourseEnrollmentAllowed
 from common.djangoapps.student.tests.factories import UserFactory
@@ -224,9 +226,31 @@ class TestAnalyticsBasic(ModuleStoreTestCase):
             else:
                 assert report['cohort'] == '[unassigned]'
 
+    def test_enrolled_student_features_external_user_keys(self):
+        query_features = ('username', 'name', 'email', 'city', 'country', 'external_user_key')
+        username_with_external_user_key_dict = {}
+        for i in range(len(self.users)):
+            # Setup some users with ProgramEnrollments
+            if i % 2 == 0:
+                user = self.users[i]
+                external_user_key = '{}_{}'.format(user.username, i)
+                ProgramEnrollmentFactory.create(user=user, external_user_key=external_user_key)
+                username_with_external_user_key_dict[user.username] = external_user_key
+
+        with self.assertNumQueries(2):
+            userreports = enrolled_students_features(self.course_key, query_features)
+        assert len(userreports) == 30
+        for report in userreports:
+            username = report['username']
+            external_key = username_with_external_user_key_dict.get(username)
+            if external_key:
+                assert external_key == report['external_user_key']
+            else:
+                assert '' == report['external_user_key']
+
     def test_available_features(self):
-        assert len(AVAILABLE_FEATURES) == len((STUDENT_FEATURES + PROFILE_FEATURES))
-        assert set(AVAILABLE_FEATURES) == set((STUDENT_FEATURES + PROFILE_FEATURES))
+        assert len(AVAILABLE_FEATURES) == len((STUDENT_FEATURES + PROFILE_FEATURES + PROGRAM_ENROLLMENT_FEATURES))
+        assert set(AVAILABLE_FEATURES) == set((STUDENT_FEATURES + PROFILE_FEATURES + PROGRAM_ENROLLMENT_FEATURES))
 
     def test_list_may_enroll(self):
         may_enroll = list_may_enroll(self.course_key, ['email'])
diff --git a/lms/djangoapps/program_enrollments/api/__init__.py b/lms/djangoapps/program_enrollments/api/__init__.py
index 1b35870eeddefaff144277ff61bad366497273c7..0d3762db0fd01790699906114c5a19e5df916e51 100644
--- a/lms/djangoapps/program_enrollments/api/__init__.py
+++ b/lms/djangoapps/program_enrollments/api/__init__.py
@@ -21,6 +21,7 @@ from .reading import (
     fetch_program_course_enrollments_by_students,
     fetch_program_enrollments,
     fetch_program_enrollments_by_student,
+    fetch_program_enrollments_by_students,
     get_external_key_by_user_and_course,
     get_org_key_for_program,
     get_program_course_enrollment,
diff --git a/lms/djangoapps/program_enrollments/api/reading.py b/lms/djangoapps/program_enrollments/api/reading.py
index 4b2dae923c25e4ca0a7be3c15d6593460e7f0f35..1ec0f5000349ca512aa6f5ee9b27d35e20001d09 100644
--- a/lms/djangoapps/program_enrollments/api/reading.py
+++ b/lms/djangoapps/program_enrollments/api/reading.py
@@ -271,6 +271,47 @@ def fetch_program_enrollments_by_student(
     return ProgramEnrollment.objects.filter(**_remove_none_values(filters))
 
 
+def fetch_program_enrollments_by_students(
+    users=None,
+    external_user_keys=None,
+    program_enrollment_statuses=None,
+    realized_only=False,
+    waiting_only=False,
+):
+    """
+    Fetch program enrollments for a specific list of students.
+
+    Required arguments (at least one must be provided):
+        * users (iterable[User])
+        * external_user_keys (iterable[str])
+
+    Optional arguments:
+        * program_enrollment_statuses (iterable[str])
+        * realized_only (bool)
+        * waiting_only (bool)
+
+    Optional arguments are used as filtersets if they are not None.
+
+    Returns: queryset[ProgramEnrollment]
+    """
+    if not (users or external_user_keys):
+        raise ValueError(_STUDENT_LIST_ARG_ERROR_MESSAGE)
+    if realized_only and waiting_only:
+        raise ValueError(
+            _REALIZED_FILTER_ERROR_TEMPLATE.format("realized_only", "waiting_only")
+        )
+    filters = {
+        "user__in": users,
+        "external_user_key__in": external_user_keys,
+        "status__in": program_enrollment_statuses,
+    }
+    if realized_only:
+        filters["user__isnull"] = False
+    if waiting_only:
+        filters["user__isnull"] = True
+    return ProgramEnrollment.objects.filter(**_remove_none_values(filters))
+
+
 def fetch_program_course_enrollments_by_students(
         users=None,
         external_user_keys=None,
diff --git a/lms/djangoapps/program_enrollments/api/tests/test_reading.py b/lms/djangoapps/program_enrollments/api/tests/test_reading.py
index 0e41ea64844105266c1a7025d3d0abeed38aa0fb..b5d60c54616a055aeaaeb6aabc6c53650d3239ae 100644
--- a/lms/djangoapps/program_enrollments/api/tests/test_reading.py
+++ b/lms/djangoapps/program_enrollments/api/tests/test_reading.py
@@ -42,6 +42,7 @@ from ..reading import (
     fetch_program_course_enrollments_by_students,
     fetch_program_enrollments,
     fetch_program_enrollments_by_student,
+    fetch_program_enrollments_by_students,
     get_external_key_by_user_and_course,
     get_program_course_enrollment,
     get_program_enrollment,
@@ -371,6 +372,51 @@ class ProgramEnrollmentReadingTests(TestCase):
         actual_enrollment_ids = {enrollment.id for enrollment in actual_enrollments}
         assert actual_enrollment_ids == expected_enrollment_ids
 
+    @ddt.data(
+
+        # User with no enrollments
+        (
+            {'usernames': [username_0]},
+            set(),
+        ),
+
+        # Filters
+        (
+            {
+                'usernames': [username_3],
+            },
+            {3, 7},
+        ),
+
+        # More filters
+        (
+            {
+                'usernames': [username_3],
+                'external_user_keys': [ext_3],
+                'program_enrollment_statuses': {PEStatuses.SUSPENDED, PEStatuses.CANCELED},
+            },
+            {7},
+        ),
+
+        # Realized-only filter
+        (
+            {'usernames': [username_4], 'realized_only': True},
+            {4},
+        ),
+
+        # Waiting-only filter
+        (
+            {'external_user_keys': [ext_4], 'waiting_only': True},
+            {8},
+        ),
+    )
+    @ddt.unpack
+    def test_fetch_program_enrollments_by_students(self, kwargs, expected_enrollment_ids):
+        kwargs = self._usernames_to_users(kwargs)
+        actual_enrollments = fetch_program_enrollments_by_students(**kwargs)
+        actual_enrollment_ids = {enrollment.id for enrollment in actual_enrollments}
+        assert actual_enrollment_ids == expected_enrollment_ids
+
     @ddt.data(
 
         # User with no program enrollments