Skip to content

feat: generate initials avatar as default profile image#38638

Draft
SantiagoSuHe wants to merge 1 commit into
openedx:masterfrom
Schema-Education:feat/initials-avatar-backend
Draft

feat: generate initials avatar as default profile image#38638
SantiagoSuHe wants to merge 1 commit into
openedx:masterfrom
Schema-Education:feat/initials-avatar-backend

Conversation

@SantiagoSuHe
Copy link
Copy Markdown

@SantiagoSuHe SantiagoSuHe commented May 13, 2026

Summary

Open edX shows a generic static placeholder when a user has not uploaded a profile photo. This PR replaces that placeholder with a personalized avatar: a colored circle with the user's initials, similar to what Google and other platforms do.

A proposal for this feature was posted in the Open edX community forum for visibility: https://discuss.openedx.org/t/profile-avatar-modernization/19046

How it works

Images are generated lazily: nothing happens at account creation time. The first time someone requests the profile image URLs for a user (for example, when the user logs in and the edx-user-info cookie is built), the avatar is generated using Pillow and saved to storage. Every subsequent request finds the file already in storage and returns the URL directly, with no regeneration cost.

The avatar is personalized in two ways: the initials come from the user's full name (first letter of first and last name), and the background color is deterministically derived from the username, so the same user always gets the same color. If the user has no name set, the first letter of the username is used as a fallback.

When a user updates their name, the cache key changes automatically (it is based on md5(username + name)), so a new avatar is generated on the next request reflecting the updated name.

Screenshot 2026-05-13 at 12 48 23 AM

Why the backend?

The initial approach considered implementing this in the Profile MFE as a React component. However, profile images are displayed in many parts of the platform, not just the profile page. Implementing the initials avatar only in the frontend would mean replicating the logic in every MFE and leaving most of the platform unaffected.

The correct solution is to generate a real JPEG image in the backend and serve it as the default profile image URL. This way, every consumer of get_profile_image_urls_for_user() gets the initials avatar automatically without any additional changes.

Where profile images are displayed in Open edX

  • LMS legacy header template (Django rendered)
  • Django Admin
  • Header MFE (frontend-app-header) -- reads user_image_urls from the edx-user-info cookie set at login
  • Discussions MFE (frontend-app-discussions) -- same cookie
  • Learner Dashboard MFE (frontend-app-learner-dashboard) -- same cookie
  • Course pages and course cards
  • Profile MFE (frontend-app-profile) -- not affected by this PR (see below)
  • Account Settings MFE (frontend-app-account) -- not affected by this PR (see below)

Known limitations -- separate PRs required

Two MFEs ignore the URL returned by the backend when has_image is false and render their own SVG fallback instead:

  • Profile MFE (frontend-app-profile): the ProfileAvatar component checks isDefault and renders a hardcoded SVG regardless of the src URL from the API
  • Account Settings MFE (frontend-app-account): same pattern

These require separate PRs in their respective repositories to use the backend URL unconditionally.

Note: the Profile MFE fix has already been implemented in openedx/frontend-app-profile as a companion PR.

Changes

openedx/core/djangoapps/profile_images/images.py

  • Added generate_initials_image(username, name) which generates a JPEG avatar for each configured size using Pillow
  • Images are cached in storage using a content-addressable key based on md5(username + name), generated only once per username/name combination
  • A name change produces a new cache key and a fresh image on the next request. The old file remains in storage as an unreferenced orphan
  • Background color is deterministically derived from the username using a 10-color palette
  • Falls back to PIL's built-in default font if system fonts are not available

openedx/core/djangoapps/user_api/accounts/image_helpers.py

  • Updated _get_default_profile_image_urls() to call generate_initials_image() instead of returning static placeholder URLs
  • UserProfile.has_profile_image semantics are unchanged -- it remains False until the user explicitly uploads a photo

Testing

Added tests for _get_initials(), _get_avatar_color(), and generate_initials_image() covering edge cases (empty name, None, whitespace, name change cache invalidation, storage caching behavior).

Updated existing tests in test_image_helpers.py to mock generate_initials_image for the default image path.

Dependencies

No new dependencies. Pillow is already a required dependency of openedx-platform.

@SantiagoSuHe SantiagoSuHe requested a review from a team as a code owner May 13, 2026 05:46
@openedx-webhooks openedx-webhooks added open-source-contribution PR author is not from Axim or 2U core contributor PR author is a Core Contributor (who may or may not have write access to this repo). labels May 13, 2026
@openedx-webhooks
Copy link
Copy Markdown

openedx-webhooks commented May 13, 2026

Thanks for the pull request, @SantiagoSuHe!

This repository is currently maintained by @openedx/wg-maintenance-openedx-platform.

Once you've gone through the following steps feel free to tag them in a comment and let them know that your changes are ready for engineering review.

🔘 Get product approval

If you haven't already, check this list to see if your contribution needs to go through the product review process.

  • If it does, you'll need to submit a product proposal for your contribution, and have it reviewed by the Product Working Group.
    • This process (including the steps you'll need to take) is documented here.
  • If it doesn't, simply proceed with the next step.
🔘 Provide context

To help your reviewers and other members of the community understand the purpose and larger context of your changes, feel free to add as much of the following information to the PR description as you can:

  • Dependencies

    This PR must be merged before / after / at the same time as ...

  • Blockers

    This PR is waiting for OEP-1234 to be accepted.

  • Timeline information

    This PR must be merged by XX date because ...

  • Partner information

    This is for a course on edx.org.

  • Supporting documentation
  • Relevant Open edX discussion forum threads
🔘 Get a green build

If one or more checks are failing, continue working on your changes until this is no longer the case and your build turns green.

🔘 Update the status of your PR

Your PR is currently marked as a draft. After completing the steps above, update its status by clicking "Ready for Review", or removing "WIP" from the title, as appropriate.


Where can I find more information?

If you'd like to get more details on all aspects of the review process for open source pull requests (OSPRs), check out the following resources:

When can I expect my changes to be merged?

Our goal is to get community contributions seen and reviewed as efficiently as possible.

However, the amount of time that it takes to review and merge a PR can vary significantly based on factors such as:

  • The size and impact of the changes that it introduces
  • The need for product review
  • Maintenance status of the parent repository

💡 As a result it may take up to several weeks or months to complete a review and merge your PR.

When a user has not uploaded a profile photo, generate a personalized
JPEG avatar with their initials on a colored circle background instead
of returning a generic static placeholder image.

Images are generated on first request and cached in storage using a
content-addressable key based on username + name. A name change
automatically produces a new cache key and a fresh image on the next
request.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@SantiagoSuHe SantiagoSuHe force-pushed the feat/initials-avatar-backend branch from 8657283 to 0a196f5 Compare May 13, 2026 06:05


_AVATAR_COLORS = [
'#1565C0', '#2E7D32', '#6A1B9A', '#C62828', '#E65100',
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

update to reference paragon / brand color options - may want to use the paragon label colors here (PR is still a draft so TBD)

@SantiagoSuHe SantiagoSuHe marked this pull request as draft May 14, 2026 05:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

core contributor PR author is a Core Contributor (who may or may not have write access to this repo). open-source-contribution PR author is not from Axim or 2U

Projects

Status: Needs Triage

Development

Successfully merging this pull request may close these issues.

3 participants