By default, authentik shows the users initials as an avatar or pulls the profile picture from Gravatar (if one is available).
However, unfortunately it does not offer users to set avatars directly in the interface. In this article (based on this GitHub discussion), we will add this functionality.
Tested with authentik 2025.10.0
1. Create a folder to store your avatars
Avatar image files will be stored in a sub-directory /media/user-avatars/ Let's create this:
mkdir -p /authentik/media/user-avatars/
Make sure the directory is writable by authentik, e.g.:
chown -R 1000:1000 /authentik/media/user-avatars/
chmod -R 644 /authentik/media/user-avatars/
2. Create the prompts
This is where we define the fields for uploading an image and deleting it.
- Head to
Administration Settings→Flows and Stages→Prompts - Create a new file-prompt:
- Name: e.g.
default-user-settings-field-avatar - Field Key:
attributes.avatar - Label (how its shown to the user in the UI): e.g.
User Avatar - Type:
File - Order: e.g.
205
- Name: e.g.
- Create a new checkbox-prompt
- Name: e.g.
default-user-settings-field-avatar-reset - Field Key:
attributes.avatar_reset - Label: e.g.
Delete Avatar - Type:
Checkbox - Order: e.g.
206
- Name: e.g.
3. Create a custom expression policy
This is where the uploaded file is saved, any previous file is removed, and the new URL is prepared to be stored in the user profile.
- Head to
Administration Settings→Customization→Policies - Click on
Create - Select
Expression Policy - Name it e.g.
- Paste the following expression. Make sure to set your
AK_DOMAIN(and optionallyFILE_PATHin case you changed it). You can also adjustMAX_UPLOAD_SIZEandACCEPTED_FILE_TYPES:
AK_DOMAIN = "authentik.company"
FILE_PATH = "/media/user-avatars/"
URL_PATH = f"https://{AK_DOMAIN}{FILE_PATH}"
MAX_UPLOAD_SIZE = 5 * 1024 * 1024
ACCEPTED_FILE_TYPES = {
"image/png": "png",
"image/jpeg": "jpeg",
"image/webp": "webp",
# "image/svg+xml": "svg", # if you trust your users not to upload malicious svg files
"image/gif": "gif",
"image/bmp": "bmp",
"image/vnd.microsoft.icon": "ico"
}
EMPTY_FILE = "data:application/octet-stream;base64,"
# from humanize import naturalsize
from uuid import uuid4
from base64 import b64decode
from os import remove
from os.path import isfile
def naturalsize(value, binary=False, format="%.1f"):
"""
Convert a size in bytes into a human-readable format.
:param value: size in bytes
:param binary: if True, use 1024-based units (KiB, MiB), otherwise 1000-based (KB, MB)
:param format: number format string (default "%.1f")
:return: string like "10.5 MB"
"""
units = ["B", "KB", "MB", "GB", "TB", "PB", "EB"]
if binary:
step = 1024.0
units = ["B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"]
else:
step = 1000.0
size = float(value)
for unit in units:
if abs(size) < step:
return (format % size).rstrip("0").rstrip(".") + " " + unit
size /= step
return (format % size).rstrip("0").rstrip(".") + " " + units[-1]
def remove_old_avatar_file():
if not request.user.is_authenticated:
return
avatar = request.user.attributes.get("avatar", None)
if avatar:
components = avatar.split(URL_PATH, 1)
if len(components) == 2 and components[0] == "" and components[1]:
old_filename = FILE_PATH + components[1]
if isfile(old_filename):
remove(old_filename)
prompt_data = request.context.get("prompt_data")
avatar_overwritten = False
if "avatar" in prompt_data.get("attributes", {}):
avatar = prompt_data["attributes"]["avatar"]
if avatar == EMPTY_FILE:
# No upload file specified, ignore
del prompt_data["attributes"]["avatar"]
else:
avatar_mimetype = avatar.split("data:", 1)[1].split(";", 1)[0]
if avatar_mimetype not in ACCEPTED_FILE_TYPES:
ak_message("User avatar must be an image (" + ", ".join(ACCEPTED_FILE_TYPES.values()) + ") file.")
return False
# Now we know it is one of the accepted image file types
avatar_base64 = avatar.split(",", 1)[1]
avatar_binary = b64decode(avatar_base64)
avatar_size = len(avatar_binary)
if avatar_size > MAX_UPLOAD_SIZE:
ak_message("User avatar file size must not exceed " + naturalsize(MAX_UPLOAD_SIZE, binary = True, format = "%.0f") + ".")
return False
# Set a random file name with extension reflecting mime type
avatar_filename = str(uuid4()) + "." + ACCEPTED_FILE_TYPES[avatar_mimetype]
try:
with (open(FILE_PATH + avatar_filename, "wb") as f):
f.write(avatar_binary)
except:
ak_message("Could not write avatar file.")
return False
avatar_overwritten = True
remove_old_avatar_file()
prompt_data["attributes"]["avatar"] = URL_PATH + avatar_filename
if "avatar_reset" in prompt_data.get("attributes", {}):
del prompt_data["attributes"]["avatar_reset"]
# If a new avatar was uploaded, previous avatar is deleted anyway, so ignore
if not avatar_overwritten:
prompt_data["attributes"]["avatar"] = None
remove_old_avatar_file()
ak_message("Deleted user avatar.")
return True
4. Modify the user prompt stage
- Head to
Administration Settings→Flows and Stages→Stages - Select the
default-user-settingsPromptStage (or create a new, custom prompt stage, if you prefer) - Add
default-user-settings-field-avataranddefault-user-settings-field-avatar-resetto theFieldssection (alongside the default values). - Add
default-user-settings-avatar-authorizationto theValidation Policies(alongside the default values).
5. Enable attributes.avatar
- Head to
Administration Settings→System→Settings - For
Avatars, set the value toattributes.avatar,gravatar,initials. You can change order if you like (in this examplegravataris the fallback, ifattributes.avatar=Noneandinitialsifgravataris also none.
6. Test it out!
Go to your user profile settings if/user/#/settings. You should now see the fields we just created and be able to upload and delete your profile picture! When refreshing the site, it will be displayed in the top right where your initials used to be.
You can double-check the media/user-avatars/ folder on your server.

Top comments (0)