GeneratorsAdminUser Contact Methods

User Contact Methods Generator

Add fields to the Contact Info table on the user profile screen, with sanitisation, validation, REST exposure and an output helper.

acme-contact-methods.php
Saved just now
Your fields
4 fields
Mastodonmastodon
Required
In REST
LinkedInlinkedin
Required
In REST
GitHubgithub
Required
In REST
Press emailpress_email
Required
In REST
What ships alongside
Validate on save — hooks user_profile_update_errors so bad URLs, bad emails and empty required fields are rejected before core writes them.
Core methods
3 removed
<?php
// Generated with WP CodeKit — powered by GrowQuest (https://growquest.io).
/**
 * Plugin Name:       acme contact methods
 * Description:       Adds 4 contact fields to the user profile screen.
 * Version:           1.0.0
 * Requires PHP:      7.4
 * Text Domain:       acme
 */

defined( 'ABSPATH' ) || exit;

/**
 * Add the fields this site needs to the Contact Info table.
 *
 * @param array        $methods Method key => label.
 * @param WP_User|null $user    The user being edited, null on the list screen.
 * @return array
 */
function acme_user_contact_methods( $methods, $user ) {
	unset( $methods['aim'] );
	unset( $methods['yim'] );
	unset( $methods['jabber'] );

	$methods = array_merge(
		$methods,
		array(
			'acme_mastodon'    => __( 'Mastodon', 'acme' ),
			'acme_linkedin'    => __( 'LinkedIn', 'acme' ),
			'acme_github'      => __( 'GitHub', 'acme' ),
			'acme_press_email' => __( 'Press email', 'acme' ),
		)
	);

	return $methods;
}
add_filter( 'user_contactmethods', 'acme_user_contact_methods', 10, 2 );

/**
 * The fields worth checking before the profile saves.
 *
 * @return array
 */
function acme_contact_field_rules() {
	return array(
		'acme_mastodon'    => array(
			'label'    => __( 'Mastodon', 'acme' ),
			'type'     => 'url',
			'required' => false,
		),
		'acme_linkedin'    => array(
			'label'    => __( 'LinkedIn', 'acme' ),
			'type'     => 'url',
			'required' => false,
		),
		'acme_press_email' => array(
			'label'    => __( 'Press email', 'acme' ),
			'type'     => 'email',
			'required' => false,
		),
	);
}

/**
 * Reject bad values before core writes them to usermeta.
 *
 * Core sanitises contact methods with sanitize_text_field() and nothing more,
 * so this is the only chance to say no.
 *
 * @param WP_Error $errors Error collector, passed by reference.
 * @param bool     $update Whether this is an existing user.
 * @param stdClass $user   The user object about to be written.
 */
function acme_validate_contact_methods( $errors, $update, $user ) {
	foreach ( acme_contact_field_rules() as $key => $rule ) {
		$value = isset( $_POST[ $key ] ) ? trim( sanitize_text_field( wp_unslash( $_POST[ $key ] ) ) ) : '';

		if ( '' === $value ) {
			if ( ! empty( $rule['required'] ) ) {
				$errors->add(
					$key . '_empty',
					sprintf(
						/* translators: %s: field label */
						__( '<strong>Error</strong>: %s cannot be empty.', 'acme' ),
						$rule['label']
					)
				);
			}

			continue;
		}

		if ( 'url' === $rule['type'] && ! wp_http_validate_url( $value ) ) {
			$errors->add(
				$key . '_invalid',
				sprintf(
					/* translators: %s: field label */
					__( '<strong>Error</strong>: %s must be a full URL, starting with https://.', 'acme' ),
					$rule['label']
				)
			);
		}

		if ( 'email' === $rule['type'] && ! is_email( $value ) ) {
			$errors->add(
				$key . '_invalid',
				sprintf(
					/* translators: %s: field label */
					__( '<strong>Error</strong>: %s is not a valid email address.', 'acme' ),
					$rule['label']
				)
			);
		}
	}
}
add_action( 'user_profile_update_errors', 'acme_validate_contact_methods', 10, 3 );

/**
 * Expose the fields on the user REST resource.
 *
 * Without this the values exist in usermeta but never appear in /wp/v2/users.
 */
function acme_register_contact_meta() {
	register_meta(
		'user',
		'acme_mastodon',
		array(
			'type'              => 'string',
			'single'            => true,
			'show_in_rest'      => true,
			'description'       => __( 'Mastodon', 'acme' ),
			'sanitize_callback' => 'esc_url_raw',
			'auth_callback'     => function () {
				return current_user_can( 'edit_users' );
			},
		)
	);

	register_meta(
		'user',
		'acme_linkedin',
		array(
			'type'              => 'string',
			'single'            => true,
			'show_in_rest'      => true,
			'description'       => __( 'LinkedIn', 'acme' ),
			'sanitize_callback' => 'esc_url_raw',
			'auth_callback'     => function () {
				return current_user_can( 'edit_users' );
			},
		)
	);

	register_meta(
		'user',
		'acme_github',
		array(
			'type'              => 'string',
			'single'            => true,
			'show_in_rest'      => true,
			'description'       => __( 'GitHub', 'acme' ),
			'sanitize_callback' => 'sanitize_text_field',
			'auth_callback'     => function () {
				return current_user_can( 'edit_users' );
			},
		)
	);
}
add_action( 'init', 'acme_register_contact_meta' );

/**
 * The author contact links, ready to print.
 *
 * @param int $user_id User id. Defaults to the current post author.
 * @return string Escaped markup, or an empty string when nothing is filled in.
 */
function acme_contact_links( $user_id = 0 ) {
	$user_id = $user_id ? (int) $user_id : get_the_author_meta( 'ID' );
	$fields  = array(
		'acme_mastodon'    => array(
			'label'  => __( 'Mastodon', 'acme' ),
			'escape' => 'esc_url',
			'link'   => true,
		),
		'acme_linkedin'    => array(
			'label'  => __( 'LinkedIn', 'acme' ),
			'escape' => 'esc_url',
			'link'   => true,
		),
		'acme_github'      => array(
			'label'  => __( 'GitHub', 'acme' ),
			'escape' => 'esc_html',
			'link'   => false,
		),
		'acme_press_email' => array(
			'label'  => __( 'Press email', 'acme' ),
			'escape' => 'esc_html',
			'link'   => true,
		),
	);

	$items = array();

	foreach ( $fields as $key => $field ) {
		$value = get_user_meta( $user_id, $key, true );

		if ( ! $value ) {
			continue;
		}

		$label = esc_html( $field['label'] );

		if ( $field['link'] ) {
			$href    = ( false === strpos( $value, '@' ) || false !== strpos( $value, '://' ) ) ? esc_url( $value ) : 'mailto:' . esc_attr( sanitize_email( $value ) );
			$items[] = sprintf( '<li><a href="%1$s" rel="me nofollow">%2$s</a></li>', $href, $label );
			continue;
		}

		$items[] = sprintf( '<li>%1$s: %2$s</li>', $label, call_user_func( $field['escape'], $value ) );
	}

	if ( ! $items ) {
		return '';
	}

	return '<ul class="acme-contact">' . implode( '', $items ) . '</ul>';
}

About this generator

User Contact Methods Generator Online

Add Mastodon, LinkedIn, GitHub, a direct line or a press email to the Contact Info table on the WordPress user profile screen, and drop the legacy AIM, Yahoo IM and Jabber rows while you are there. The contact-methods filter takes a key => label array, and each key you add becomes a user meta key verbatim — so the generator prefixes them for you, then writes the optional validation, REST registration and front-end output helpers alongside.

The profile-screen preview redraws the Contact Info table as you add fields, with sample values in the type you chose, so you can see the labels and column widths before pasting. Output as a snippet, a functions.php block, a plugin file, or a single static class. Free, no account, and nothing you enter leaves the browser.

Reviewed Output tested on WordPress 6.8 · PHP 8.3

Why this user profile fields generator beats a bare array_merge

Adding a contact method is one filter and three lines. What that leaves you with is an unprefixed meta key shared with every other plugin that picked the same word, a URL field that happily stores the word "facebook" with no scheme, a value that never appears in /wp/v2/users, and no way to print it on the front end without writing get_user_meta() by hand every time. This generator writes the filter and the four things that have to go with it.

Keys prefixed because they are meta keys

Whatever you use as an array key is written straight into usermeta as-is. The generator prefixes every key with your namespace and flags the reserved names core already owns on the user object — url, description, nickname, first_name, display_name and the rest — with a one-click fix.

Validation that runs before the write

Core sanitises contact methods with sanitize_text_field() and nothing more, so the only chance to reject a value is user_profile_update_errors. The generated check runs wp_http_validate_url() on URL fields, is_email() on email fields, and an empty check on anything you marked required, adding a real WP_Error per failure.

REST registration, per field

Tick REST on a field and you get a register_meta( 'user', … ) call with show_in_rest, the right sanitize_callback for the type, and an auth_callback gated on edit_users. Without it the value exists in usermeta but never appears in /wp/v2/users, which is where block themes and headless front ends read authors from.

Output helpers you would otherwise rewrite

Choose a single-value getter that defaults to the current post author and escapes with esc_url() or esc_html() depending on the value, or a full list helper that builds an escaped <ul> of links with rel="me nofollow" and skips anything not filled in.

Legacy core rows removed correctly

AIM, Yahoo IM and Jabber are unset() from the array — that works. The Website field is not: it lives on the wp_users table, not in contact methods, so unsetting url here does nothing. The checker calls that out as an error rather than letting you ship a line that has no effect.

Privacy checked, not assumed

Exposing a phone number or an email address over REST is flagged, because /wp/v2/users is readable by anyone who can list users on most sites. Required fields are flagged too: they also block administrators editing other users and the add-new-user screen.

How does the User Contact Methods Generator work?

Four steps: add the fields, decide what ships with them, clear the legacy rows, then export.

  1. 01

    Add your fields

    Start from a preset — Mastodon, LinkedIn, GitHub, a direct line, a press email — or add your own. Each field takes a label, a meta key, a type (URL, handle, email, phone or plain text), a required flag and a REST flag.

  2. 02

    Decide what ships alongside

    Turn on profile validation, and choose whether to emit no output helper, a single-value getter, or a full escaped list of author contact links ready to print in a template.

  3. 03

    Clear the legacy core methods

    Tick AIM, Yahoo IM and Jabber to unset them from the array. They only still appear on installs old enough to carry them, but the rows are the first thing a client asks about.

  4. 04

    Check and export

    Fix anything flagged — an unprefixed key, a reserved name, a URL field with validation off — then copy the snippet or download it as a plugin file or a static class.

Worked example — two fields added, three legacy rows removed

The filter callback the generator writes. Note that the keys carry the prefix: they become the usermeta keys, and there is no namespacing anywhere else.

/**
 * Add the fields this site needs to the Contact Info table.
 *
 * @param array        $methods Method key => label.
 * @param WP_User|null $user    The user being edited, null on the list screen.
 * @return array
 */
function acme_user_contact_methods( $methods, $user ) {
	unset( $methods['aim'] );
	unset( $methods['yim'] );
	unset( $methods['jabber'] );

	$methods = array_merge(
		$methods,
		array(
			'acme_mastodon' => __( 'Mastodon', 'acme' ),
			'acme_github'   => __( 'GitHub', 'acme' ),
		)
	);

	return $methods;
}
add_filter( 'user_contactmethods', 'acme_user_contact_methods', 10, 2 );

The filter name is user_contactmethods — one word, no underscore between contact and methods. It is applied inside wp_get_user_contact_methods() and passes the WP_User object as a second argument, which is null on the users list screen. Turn REST on for a field and a matching register_meta( 'user', 'acme_mastodon', … ) call is appended on init.

User contact methods — frequently asked questions

What developers ask when adding fields to the WordPress user profile screen.

Where are WordPress contact methods stored?

In the wp_usermeta table, under exactly the key you used in the filter array. There is no namespacing and no separate table, so twitter as a key is the literal meta key every other plugin that picked twitter will also write to. Prefix the key, always.

How do I remove AIM, Yahoo IM and Jabber from the user profile?

Filter the contact methods array and unset() those keys — aim, yim and jabber. Core only registers them on installs whose initial_db_version predates WordPress 3.6, so on a newer site they may not be there at all, but the unset() is harmless either way.

Why can I not remove the Website field this way?

The Website field is not a contact method. It is the user_url column on the wp_users table and is rendered separately by the profile screen, so unsetting url from the contact methods array does nothing at all. Removing it means hiding it on the profile form itself, not filtering this array.

How do I display an author's contact link on the front end?

Inside the loop, get_the_author_meta( 'acme_mastodon' ) returns the raw stored value; outside it, get_user_meta( $user_id, 'acme_mastodon', true ). Escape on output — esc_url() for a URL, esc_html() for a handle — because core stored whatever was typed. The generator can emit a helper that does both plus the empty check.

Why does my custom contact field not appear in the REST API?

Adding a contact method only affects the profile screen. To expose the value you have to register the meta key separately with register_meta( 'user', $key, array( 'show_in_rest' => true, 'single' => true, … ) ) on init, including an auth_callback if it should be writable. Without that, /wp/v2/users never mentions it.

Can I make a contact method required?

Not through the filter — it only supplies labels. Enforce it on user_profile_update_errors, adding a WP_Error when the field is empty. Be aware that this blocks every profile save, including an administrator editing someone else and the add-new-user screen, which is a common way to lock yourself out of user management.

Where do I paste the code from the User Contact Methods generator?

You have three good options and one bad one. Best is a small site-specific plugin — the generator can output one for you, and it keeps working when you change theme. A code-snippets plugin is fine too. A child theme's functions.php works but ties the code to that theme. Never edit the parent theme: the next update overwrites it and your code is gone.

Can I use the generated code in client work or a commercial plugin?

Yes. The output is ordinary WordPress API calls that you configured — there is no licence attached to it and no attribution required. Use it in client sites, commercial plugins and themes, or products you sell. The generated file carries a short credit comment you are free to delete.

The other Admin generators in the library, plus the WordPress tools most often used alongside this one.