Our goal is to set up two WordPress websites which will share logins and the same users. Once a user has subscribed one website, she would be able to access the other website with the same role and capabilities.

To achieve this goal, we should be able to edit WordPress configuration file and update database tables. A general understanding of WordPress architecture and database structure is essential, as well as a basic knowledge of WordPress development. No worries if you’re not a pro. Just follow this post directives and ask your questions in the comments.

Before we start coding, we need to know where WordPress user roles and capabilities are stored. So, our first step is to dive deep into database tables.

Important: The following will not work out of the box in the Kinsta environment due to the fact that we only allow one installation of WordPress for each site (unless you’re running WordPress multisite). It may be possible to get this working on our platform, but it would require some additional setup or development. We recommend discussing this with a WordPress developer.

User Data and Metadata

By default, WordPress stores user-related data into three tables: {$pref}options, {$pref}users and {$pref}usermeta.

  • The {$pref}options table stores the full list of available roles and capabilities in a row whose option_key field is {$pref}user_roles.
  • The {$pref}users table stores basic user data, like login, password, email, url, etc.
  • The {$pref}usermeta table stores user metadata.

When working on new WordPress installations, we don’t have to care about {$pref}user_roles row in {$pref}options table, because the corresponding option_value field has always the same value. We should consider this row just in case we’re working on existing installations where roles or capabilities have been changed.
No worries about {$pref}users table, as well, because it stores basic user data that we won’t change when sharing users between websites.
The {$pref}usermeta table is the only table we’re going to update to achieve our goal.

users and usermeta table structure (source: Codex Database Description)
users and usermeta table structure
(source: Codex Database Description)

{$pref}usermeta stores user metadata in key/value pairs. In this table five rows store the data that we have to consider.

Five rows in usermeta table store data concerning user capabilities, level and dashboard settings
Five rows in usermeta table store data concerning user capabilities, level and dashboard settings

The first row has the meta_key field set to {$pref}capabilities, and the corresponding meta_value field is a serialized array holding the user role. The second row stores the user level (note that user levels are deprecated from WordPress 3.0). The remaining three rows concern dashboard settings we won’t dive into in this post.
User role, level and settings are specific to the WordPress installation and are identified by the same $pref value. It is an important bit of information when our goal is to share users between websites, because we will have to duplicate these rows and change the meta_key field accordingly.

That’s all we have to know about user tables when we aim to share logins and users between new WordPress installations. When working on existing websites, we should consider that many plugins add extra rows to {$pref}usermeta, and we may be required to have a deeper look at database tables.

That being said about user tables, we can move a step forward. Now we have to define two specific constants into wp-config.php file.

Defining Custom User Tables – Share Logins

WordPress allows us to set custom tables instead of {$pref}users and {$pref}usermeta. This means that if two (or more) WordPress websites share one database, we can set the same users and usermeta tables for all of them. As a consequence, all websites which share these table will share the same users.

Note: In order to share the same users and usermeta tables, WordPress installations must share the same database.

We just need to define CUSTOM_USER_TABLE and CUSTOM_USER_META_TABLE into wp-config.php file, as shown in the following code:

// custom users and usermeta tables
define( 'CUSTOM_USER_TABLE', 'my_users_table' );
define( 'CUSTOM_USER_META_TABLE', 'my_usermeta_table' );
Note: On existing websites it’s mandatory to back-up WordPress installations before you make any changes to wp-config.php files and data tables

Now that we know what has to be done, it’s time to run our two WordPress installations.

Installing WordPress

For convenience, I will name the WordPress root folders first and second. first_ and second_ will be the respective table prefixes.
Now let’s run the first installation.

In this example we set the table prefix field to first_
In this example, we set the table prefix field to first_
Note: All installations will share a single database, and we should provide each installation with a unique table prefix.

When the first WordPress website is up and running, we can edit its configuration file. Open /first/wp-config.php and add the following lines above the ‘stop editing’ comment:

$table_prefix  = 'first_';

define('WP_DEBUG', true);
define( 'WP_DEBUG_LOG', true );
define( 'WP_DEBUG_DISPLAY', false );
@ini_set( 'display_errors', 0 );

// custom users and usermeta tables
define( 'CUSTOM_USER_TABLE', $table_prefix . 'users' );
define( 'CUSTOM_USER_META_TABLE', $table_prefix . 'usermeta' );

/* That's all, stop editing! Happy blogging. */

We have enabled debug mode forcing WordPress to store error notices and warnings into debug.log file (read more about this topic in An in-depth view on how to configure WordPress).
Then, we have defined CUSTOM_USER_TABLE and CUSTOM_USER_META_TABLE constants to first_users and first_usermeta tables. This way we’re not changing WordPress default settings.

We are done with the first installation. Next we have to copy wp-config.php from the first installation folder and paste it into the root folder of the second installation. Be careful to change the $table_prefix value accordingly:

$table_prefix  = 'second_';

define('WP_DEBUG', true);
define( 'WP_DEBUG_LOG', true );
define( 'WP_DEBUG_DISPLAY', false );
@ini_set( 'display_errors', 0 );

// custom users and usermeta tables
define( 'CUSTOM_USER_TABLE', 'first_users' );
define( 'CUSTOM_USER_META_TABLE', 'first_usermeta' );

CUSTOM_USER_TABLE and CUSTOM_USER_META_TABLE are set to the first installation’s values: first_users and first_usermeta. That’s all for the first installation.

share logins
WordPress is aware of existing users and we should set a non-existent email address for admin user

When running the second installation, we should set a non-existent email address for admin user as WordPress finds a number of existing users from first_users table.

WordPress creates an admin username for the second installation
WordPress creates an admin username for the second installation

Log into the second installation admin panel as admin and list WordPress users. You’ll find the new admin user and all users from the first website (this allows them to share logins). At this point, users from one site won’t be able to log into the other website.

The users in the second website won't inherit their roles from the first website
The users in the second website won’t inherit their roles from the first website

To grant users the same capabilities in both websites, we have to update {$pref}usermeta table.

Roles and Capabilities

If you are running new WordPress installations, you have not to care about {$pref}options table. You just need to update {$pref}usermeta table.

In our example, when a new user is created in the first website, WordPress adds first_capabilities and first_user_level rows in first_usermeta table. To give access to the second website, these rows should be duplicated, as shown in the image below:

second_usermeta_fields

When a new user is created in the second website, second_capabilities and second_user_level rows will be added to first_usermeta table.
In order to give the same roles and caps to users across websites, first_capabilities and first_user_level rows should be duplicated in second_capabilities and second_user_level. With these two pairs of rows in the same first_usermeta table, users would be able to access both websites with the same privileges.

To update all existing usermeta rows you may run a SQL query or update tables from phpMyAdmin. But what for users that will subscribe our websites from now on? According to WordPress Codex, we’d use a plugin or build a custom function.
And there we go!

Automatically Duplicate Caps and Levels with a Function

set_user_role is an action hook which triggers anytime a new user is created or an existing user’s role has been edited. Thanks to this action, we can automate usermeta table updates.
So, in the main file of a plugin add the following function:

function ksu_save_role( $user_id, $role ) {

	// Site 1
	// Change value if needed
	$prefix_1 = 'first_';
	
	// Site 2 prefix
	// Change value if needed
	$prefix_2 = 'second_';
	
	$caps = get_user_meta( $user_id, $prefix_1 . 'capabilities', true );
	$level = get_user_meta( $user_id, $prefix_1 . 'user_level', true );

	if ( $caps ){
		update_user_meta( $user_id, $prefix_2 . 'capabilities', $caps );
	}

	if ( $level ){
		update_user_meta( $user_id, $prefix_2 . 'user_level', $level );
	}
}

add_action( 'set_user_role', 'ksu_save_role', 10, 2 );

The callback function keeps three arguments, two of which are required: $user_id and $role.
What the function does is quite self-explanatory. get_user_meta returns the specified user meta field value. We have called this function twice to retrieve first_capabilities and first_user_level fields. Then we have used these values to add second_capabilities and second_user_level fields to first_usermeta table.

Upload ad activate this plugin into the first website.

To make installations work symmetrically, we just need to upload and activate the plugin in any installation, but setting the right values to prefixes. For instance, if we would activate this feature in the second website, we just have to declare the variables as follows:

$prefix_1 = 'second_';
$prefix_2 = 'first_';

So, edit and install the plugin into the second website and create a new user or change an existing user role. Then check the first website. The user roles will be exactly the same as the second website.

Summary

In this post, I’ve explained how to grant the same privileges to users across independent WordPress installations. Once registered into a website, the user will be able to access all websites sharing the same users and usermeta tables.
I’ve supposed to work with new installations. If you’re working on existing websites, you should consider that some plugins could have updated usermeta table, or even created new tables storing user related data. In this case, a more accurate analysis of the database would be appropriate.

If you have any questions about how to share logins in WordPress, or you’d like to share your experience with us, feel free to join the conversation posting your comments.

The full code of our plugin is available in this public Gist

Carlo Daniele Kinsta

Carlo is a passionate lover of webdesign and front-end development. He has been playing with WordPress for more than 20 years, also in collaboration with Italian and European universities and educational institutions. He has written hundreds of articles and guides about WordPress, published both on Italian and international websites, as well as on printed magazines. You can find him on LinkedIn.