How to Allow Users to Upload Profile Photos in WordPress Without a Plugin

By default, WordPress does not allow users to upload a profile picture directly from the dashboard. Instead, it uses Gravatar.

In this guide, you’ll learn:

  • How Gravatar works in WordPress
  • Why WordPress does not provide a direct upload option
  • How to add a custom profile picture upload feature using PHP
  • How to store the image in Media Library
  • How to allow users to remove or replace their image

No plugin required.

How Gravatar Works in WordPress

WordPress uses Gravatar, which stands for Globally Recognized Avatar.

It is owned by Automattic, the company behind WordPress.

How it works:

  1. A user registers an email on a WordPress site.
  2. WordPress converts that email into an MD5 hash.
  3. It checks Gravatar’s servers for an image linked to that email.
  4. If found, it displays the image.
  5. If not, it shows a default avatar.

So technically:

  • WordPress does not store profile pictures.
  • The image is pulled externally from Gravatar.
  • The same image appears on all WordPress sites using that email.

That is why you see this message in WordPress profile settings:

“You can change your profile picture on Gravatar.”

Why WordPress Has No Upload Option

There are two main reasons:

1. Global Avatar System

WordPress was designed to use a centralized avatar system. This avoids storing millions of images across websites.

2. Database and Storage Optimization

By not storing profile images:

  • Hosting storage is reduced
  • Database remains lightweight
  • No need for additional upload handling

But in many cases, especially membership sites or private projects, you may want:

  • Full control over profile pictures
  • Images stored locally
  • No dependency on Gravatar

Let’s fix that.

Add Custom Profile Picture Upload in WordPress (Without Plugin)

YouTube video

We will:

  • Add an image upload field in user profile
  • Save image in Media Library
  • Replace Gravatar output with uploaded image

You can add the following code inside:

  • functions.php (not recommended for long term)
  • OR use a Code Snippets plugin
add_action('admin_enqueue_scripts', function($hook) {
    if ($hook === 'profile.php' || $hook === 'user-edit.php') {
        wp_enqueue_media();
    }
});
add_action('show_user_profile', 'cs_add_avatar_field');
add_action('edit_user_profile', 'cs_add_avatar_field');

function cs_add_avatar_field($user) {
    $avatar_id = get_user_meta($user->ID, 'custom_avatar_id', true);
    $avatar_url = $avatar_id ? wp_get_attachment_image_url($avatar_id, 'thumbnail') : '';
    ?>
    <h2>Profile Picture</h2>
    <table class="form-table">
        <tr>
            <th>Avatar</th>
            <td>
                <div id="custom-avatar-preview">
                    <?php if ($avatar_url) : ?>
                        <img src="<?php echo esc_url($avatar_url); ?>" width="96" style="display:block;margin-bottom:10px;">
                    <?php endif; ?>
                </div>

                <input type="hidden" name="custom_avatar_id" id="custom_avatar_id" value="<?php echo esc_attr($avatar_id); ?>" />

                <button type="button" class="button" id="upload_custom_avatar">
                    Select from Media Library
                </button>

                <button type="button" class="button" id="remove_custom_avatar">
                    Remove
                </button>

                <p class="description">Choose image from Media Library.</p>
            </td>
        </tr>
    </table>

    <script>
    jQuery(document).ready(function($){

        var frame;

        $('#upload_custom_avatar').on('click', function(e){
            e.preventDefault();

            if (frame) {
                frame.open();
                return;
            }

            frame = wp.media({
                title: 'Select or Upload Avatar',
                button: { text: 'Use this image' },
                multiple: false
            });

            frame.on('select', function(){
                var attachment = frame.state().get('selection').first().toJSON();
                $('#custom_avatar_id').val(attachment.id);
                $('#custom-avatar-preview').html('<img src="'+attachment.sizes.thumbnail.url+'" width="96" style="display:block;margin-bottom:10px;">');
            });

            frame.open();
        });

        $('#remove_custom_avatar').on('click', function(){
            $('#custom_avatar_id').val('');
            $('#custom-avatar-preview').html('');
        });

    });
    </script>

    <?php
}
add_action('personal_options_update', 'cs_save_avatar');
add_action('edit_user_profile_update', 'cs_save_avatar');

function cs_save_avatar($user_id) {

    if (!current_user_can('upload_files')) {
        return;
    }

    if (isset($_POST['custom_avatar_id'])) {

        $new_avatar = intval($_POST['custom_avatar_id']);

        if ($new_avatar) {
            update_user_meta($user_id, 'custom_avatar_id', $new_avatar);
        } else {
            delete_user_meta($user_id, 'custom_avatar_id');
        }
    }
}
add_filter('get_avatar', 'cs_replace_avatar', 10, 5);

function cs_replace_avatar($avatar, $id_or_email, $size, $default, $alt) {

    $user = false;

    if (is_numeric($id_or_email)) {
        $user = get_user_by('id', $id_or_email);
    } elseif (is_object($id_or_email) && isset($id_or_email->user_id)) {
        $user = get_user_by('id', $id_or_email->user_id);
    } else {
        $user = get_user_by('email', $id_or_email);
    }

    if ($user) {

        $avatar_id = get_user_meta($user->ID, 'custom_avatar_id', true);

        if ($avatar_id) {

            return wp_get_attachment_image(
                $avatar_id,
                array($size, $size),
                false,
                array(
                    'class' => 'avatar avatar-' . $size,
                    'alt'   => esc_attr($alt)
                )
            );
        }
    }

    return $avatar;
}

What This Code Does

  • Adds image upload option in profile
  • Stores image in WordPress uploads folder
  • Saves image URL in user meta
  • Overrides default Gravatar
  • Allows image removal

Now your site does not depend on Gravatar anymore.

Optional: Disable Gravatar Completely

If you want to fully stop Gravatar requests, add this filter:

add_filter('get_avatar_url', '__return_false');

Conclusion

In this tutorial, you learned how Gravatar connects to WordPress, why the upload option is missing, and how to bring it back using custom PHP code. With a few simple functions, you can add an upload field to user profiles, store images inside your Media Library, replace the default avatar system, and even allow users to remove their pictures.

The best part is that everything works without installing any extra plugin. This keeps your website lightweight, secure, and fully under your control.

If you are building a membership site, private platform, or simply want more flexibility, this approach gives you complete ownership of user profile images while keeping WordPress clean and optimized.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *

Hostinger Hosting
Get 20% Discount on Checkout
Hostinger managed Wordpress hosting
Get 20% Discount on Checkout