How7o
  • Home
  • Tools
  • Prank Screens
  • Contact
  • Blog
Reading: How to Change a User Profile Picture in WordPress Without a Plugin
Share
Subscribe Now
How7oHow7o
Font ResizerAa
  • Marketing
  • OS
  • Features
  • Guide
  • Complaint
  • Advertise
Search
  • Home
  • Tools
  • Prank Screens
  • Contact
  • Blog
Follow US
Copyright © 2014-2023 Ruby Theme Ltd. All Rights Reserved.
How7o > Blog > Web Development > How to Change a User Profile Picture in WordPress Without a Plugin
Web Development

How to Change a User Profile Picture in WordPress Without a Plugin

how7o
By how7o
Last updated: May 10, 2026
10 Min Read
WordPress custom avatar without a plugin — media uploader writes user meta, get_avatar filter renders the image
SHARE

A wordpress custom avatar no plugin setup has three small parts: add an upload button to the user profile screen, drive the WordPress media library from JavaScript, and filter get_avatar to return the uploaded image instead of Gravatar. Around 80 lines of PHP + 20 lines of JS, all in the theme’s functions.php and a helper uploader.js. This guide walks through each piece and the security checks that keep the feature from becoming an avatar-swap-any-user bug.

Contents
  • TL;DR
  • Part 1 — Upload button on the profile screen
  • Part 2 — Enqueue the media library + uploader script
  • Part 3 — The uploader JS
  • Part 4 — Save the attachment ID to user meta
  • Part 5 — Swap the avatar on render
  • Security recap
  • Frequently asked questions
  • Related guides
  • References

Last verified: 2026-04-23 on WordPress 6.5 and PHP 8.3. Originally published 2023-10-16, rewritten and updated 2026-04-23.

TL;DR

Three hooks: user_profile_picture_description for the button, profile_update for the save, get_avatar for the render. Upload UI stored attachment ID in user meta (_custom_avatar); the filter swaps that attachment’s image into the avatar HTML.

Part 1 — Upload button on the profile screen

// functions.php

add_action( 'user_profile_picture_description', 'how7o_avatar_upload_button', 10, 2 );

function how7o_avatar_upload_button( $description, $user ) {
    if ( ! current_user_can( 'upload_files' ) ) {
        return $description;
    }

    $attachment_id = get_user_meta( $user->ID, '_custom_avatar', true );

    return '
        <input type="hidden"  name="_custom_avatar" id="_custom_avatar" value="' . esc_attr( $attachment_id ) . '" />
        <input type="button"  class="button" id="custom-avatar-btn" value="Upload Picture" />
    ';
}

The user_profile_picture_description filter lets you append or replace the description under the “Profile Picture” area. The capability check (current_user_can('upload_files')) gates the button to users with upload rights. The hidden _custom_avatar input carries the current attachment ID so the save handler has a value to read on submit.

Part 2 — Enqueue the media library + uploader script

add_action( 'admin_enqueue_scripts', 'how7o_avatar_admin_scripts' );

function how7o_avatar_admin_scripts() {
    wp_enqueue_media();  // loads wp.media.editor

    wp_enqueue_script(
        'how7o-avatar-uploader',
        get_stylesheet_directory_uri() . '/js/uploader.js',
        array( 'jquery' ),
        '1.0.0',
        true
    );
}

wp_enqueue_media() loads the WordPress media library JavaScript stack (the modal frame, the attachment grid, the upload tab). wp_enqueue_script registers the theme’s small jQuery wrapper. A hardcoded version string (see why not filemtime() in general) keeps browser caches sane — bump it when you edit the JS.

Part 3 — The uploader JS

// wp-content/themes/<your-theme>/js/uploader.js
(function ($) {
    $('#custom-avatar-btn').on('click', function () {
        var $button = $(this);

        wp.media.editor.send.attachment = function (props, attachment) {
            if (attachment.type === 'image') {
                $('#_custom_avatar').val(attachment.id);
                $button.closest('td').find('img').attr('src', attachment.url);
            } else {
                alert('Please select a valid image file');
                return false;
            }
        };

        wp.media.editor.open();
        return false;
    });
})(jQuery);

Clicking the button overrides wp.media.editor.send.attachment with a handler that writes the selected attachment’s ID into the hidden input and updates the preview image. Then wp.media.editor.open() launches the standard WP media modal. The validation check (attachment.type === 'image') blocks non-image media from being selected.

wordpress custom avatar no plugin — upload UI writes user meta, get_avatar filter reads it to render the image

Part 4 — Save the attachment ID to user meta

add_action( 'profile_update', 'how7o_save_custom_avatar' );

function how7o_save_custom_avatar( $user_id ) {
    if ( ! current_user_can( 'upload_files' ) ) {
        return;
    }

    if ( isset( $_POST['_custom_avatar'] ) ) {
        $attachment_id = absint( $_POST['_custom_avatar'] );
        update_user_meta( $user_id, '_custom_avatar', $attachment_id );
    }
}

profile_update fires when any user profile is saved. The capability guard mirrors the one on the button. absint() forces the ID to a positive integer — a cheap validation that rejects non-numeric tampering. update_user_meta() writes the value (creating the row if it doesn’t exist, updating if it does).

Part 5 — Swap the avatar on render

add_filter( 'get_avatar', 'how7o_custom_avatar_filter', 10, 5 );

function how7o_custom_avatar_filter( $avatar, $id_or_email, $size, $default, $alt ) {
    $user = false;

    if ( is_numeric( $id_or_email ) ) {
        $user = get_user_by( 'id', (int) $id_or_email );
    } elseif ( is_object( $id_or_email ) && ! empty( $id_or_email->user_id ) ) {
        $user = get_user_by( 'id', (int) $id_or_email->user_id );
    } elseif ( is_string( $id_or_email ) ) {
        $user = get_user_by( 'email', $id_or_email );
    }

    if ( $user instanceof WP_User ) {
        $attachment_id = get_user_meta( $user->ID, '_custom_avatar', true );

        if ( ! empty( $attachment_id ) ) {
            $image = wp_get_attachment_image_src( $attachment_id, array( $size, $size ) );

            if ( $image ) {
                $avatar = sprintf(
                    '<img alt="%s" src="%s" class="avatar avatar-%d photo" height="%d" width="%d" />',
                    esc_attr( $alt ),
                    esc_url( $image[0] ),
                    (int) $size,
                    (int) $size,
                    (int) $size
                );
            }
        }
    }

    return $avatar;
}

The filter’s $id_or_email argument can be an integer user ID, a comment/post object carrying user_id, or an email string — the three branches above resolve each to a WP_User. When the user has a _custom_avatar meta, the function rebuilds the <img> with the attachment’s URL; otherwise it passes through the original Gravatar HTML unchanged.

wp_get_attachment_image_src‘s second arg asks for an appropriately-sized intermediate image; WordPress picks the nearest size from the registered image sizes (so a 96px avatar request doesn’t ship a 2000-pixel-wide original).

Security recap

  • current_user_can('upload_files') gates both the button render and the save — non-upload users never see the button and any spoofed POST is ignored.
  • absint() forces the submitted ID to a non-negative integer. The filter’s later wp_get_attachment_image_src call only returns an image if the ID maps to a real attachment — a submitted ID that doesn’t resolve just gets ignored.
  • WordPress’s standard profile-update nonce covers CSRF; the save handler runs inside profile_update, which only fires after that nonce has been validated.

Frequently asked questions

How does this wordpress custom avatar no plugin approach actually work?

Three pieces: (1) a file upload button on the user profile screen that stores the media library attachment ID in user meta, (2) a jQuery snippet using wp.media.editor to drive the upload, and (3) a filter on get_avatar that replaces the Gravatar image with the stored attachment. The pieces are independent — the upload UI writes the meta, the filter reads it. Skip the Gravatar fallback entirely by returning your own <img> from the filter when the attachment exists.

Why user meta instead of a profile field plugin?

User meta is WordPress’s built-in per-user key/value store — no schema change, no plugin dependency, no admin UI to migrate. update_user_meta() and get_user_meta() are the read/write primitives; _custom_avatar (note the leading underscore, which hides the field from the default UI) holds the attachment ID. When you’re deciding between user meta and a plugin’s data model, user meta wins for everything this simple.

Is the $_POST['_custom_avatar'] handler safe from CSRF?

The standard WordPress user-edit screen already includes a nonce that WordPress validates before firing profile_update. As long as your save function only runs inside that hook (not from an unrelated endpoint), you inherit that protection. The current_user_can('upload_files') check closes the other half — only users with upload privileges can set someone’s avatar.

Can editors upload avatars for other users, or only admins?

The upload_files capability is granted to Administrators, Editors, Authors, and Contributors by default in WordPress. If you want stricter behavior — only admins can set other users’ avatars, but users can manage their own — check capability plus target: if (current_user_can('edit_user', $user_id) && current_user_can('upload_files')). Customize the role list by editing roles via a role-editor plugin or by adjusting your theme’s user model.

Does this affect comment avatars?

Yes — the get_avatar filter runs for every avatar render, including comment avatars. The filter resolves the user from the $id_or_email argument (integer ID, object with user_id, or email string) and falls back to the default Gravatar if no custom avatar meta exists. So comments from users with a custom avatar show the custom image; comments from guests or non-configured users still use Gravatar.

Related guides

  • How to Search Users by Multiple Fields in WordPress — complementary user-admin tooling.
  • How to Login a User Programmatically in WordPress — another user-flow customization.
  • How to Check If a User Is Logged In in WordPress — gating avatar rendering to authenticated sessions.
  • How to Disable Revisions and Autosave in WordPress — another theme-level functions.php tweak.

References

WordPress developer reference for get_avatar, user_profile_picture_description, and wp.media: developer.wordpress.org/reference/hooks/get_avatar.

TAGGED:jQueryphpwordpress

Sign Up For Daily Newsletter

Be keep up! Get the latest breaking news delivered straight to your inbox.
By signing up, you agree to our Terms of Use and acknowledge the data practices in our Privacy Policy. You may unsubscribe at any time.
Share This Article
Facebook Copy Link Print
Previous Article WordPress pre_get_posts scoped to a custom post type with is_post_type_archive How to Apply pre_get_posts on Custom Post Types in WordPress
Next Article WordPress posts by date range — date_query with after/before/inclusive How to Get Posts by Date Range in WordPress
Leave a Comment

Leave a Reply Cancel reply

You must be logged in to post a comment.

FacebookLike
XFollow
PinterestPin
InstagramFollow

Subscribe Now

Subscribe to our newsletter to get our newest articles instantly!
Most Popular
Display PHP errors — ini_set + php.ini configuration
How to Display PHP Errors
May 10, 2026
PHP convert string to uppercase — strtoupper and mb_strtoupper
How to Convert a String to Uppercase in PHP
May 10, 2026
PHP string to float conversion with cast, regex cleanup, NumberFormatter
How to Convert a String to Float in PHP
May 10, 2026
PHP merge arrays without duplicates — union operator and array_unique
How to Combine Two Arrays Without Duplicates in PHP
May 10, 2026
PHP delete array element — unset, array_splice, array_filter, array_search
How to Delete an Element from a PHP Array
May 10, 2026

You Might Also Like

Dynamically set site title and tagline in WordPress by country
Web Development

How to Dynamically Set Site Title and Tagline in WordPress (By Country)

6 Min Read
Laravel last inserted ID — Eloquent save populates model primary key illustration
Web Development

How to Retrieve the Last Inserted ID in Laravel Eloquent

8 Min Read
Laravel DataTables HTML column — rawColumns opt-out of the default escaping
Web Development

How to Add an HTML Column in Laravel DataTables

7 Min Read
Find prime numbers in JavaScript thumbnail
Web Development

How to Find Prime Numbers in JavaScript (1 to 100) — Fast & Simple Methods

5 Min Read
How7o

We provide tips, tricks, and advice for improving websites and doing better search.

Latest News

  • SEO Audit Tool
  • Client ReferralsNew
  • Execution of SEO
  • Reporting Tool

Resouce

  • Google Search Console
  • Google Keyword Planner
  • Google OptimiseHot
  • SEO Spider

Get the Top 10 in Search!

Looking for a trustworthy service to optimize the company website?
Request a Quote
Welcome Back!

Sign in to your account

Username or Email Address
Password

Lost your password?