• Skip to main content
  • Skip to primary sidebar
  • Skip to footer

nSiteful Web Builders

Building a Better Web - One Site at a Time.

  • Home
  • About
    • Testimonials
    • Resources
    • Partner With Me
    • Frequently Asked Questions
  • Web Sites
  • Online Marketing
  • WordPress Support
    • Customized WordPress Training
    • 60-for-60 Sessions
  • Web Applications
  • Blog
    • At-A-Glance
    • Blog Articles Grouped by Category
    • Case Studies
    • General
    • Portfolio
    • Reviews
    • Snippets
    • Techniques
    • Projects
  • Contact
    • Purchase Retainer Consulting Hours
    • About Retainer Consulting Hours
    • Book a Meeting with Jeff
    • Tell Me About Your Web Project
    • nSiteful Newsletter Archives
    • nSiteful Rewards

By Jeff - 7/10/2024
Cats: Case Studies · Tags: Advanced Custom Fields, Custom Fields, MemberPress, Membership Sites, WordPress

case-studies

How to Create a Custom Member Directory for a WordPress/MemberPress Membership Site: Details

On This Page...

Toggle
  • Two Memberships, Two Directories
    • Links to the Live SEBA Directories
  • We need custom fields for each membership
    • Personal Membership Custom Fields
    • Business Membership Custom Fields
  • Creating the Forms for Users to Update their Profiles
    • Code to create shortcode that will display the ACF front-end form.
    • Pages for Displaying the Forms
    • Content of Personal Profile Form Page
    • Content of Business Profile Form Page
  • Archive Pages
    • Creating the Archive Page for Personal Memberships
    • Creating the Archive Page for Business Memberships
  • Single Pages
    • Creating the Code for the Single Personal Member Profile
    • Creating the Code for the Single Business Member Profile
  • Let’s Recap

Last updated September 14th, 2024 at 05:12 pm

One of the questions that comes up a lot in WordPress discussion forums is how to set up a member directory. Often the question is posed as “What’s the best plugin for creating a member directory?”

Last October I published a blog post that summarized how I created a custom member directory for a WordPress membership site. When I shared links to that article in WordPress-related Facebook groups, a common response was, “How about some details, man?”

Hence this blog post.

As I explained in that earlier post, my solution was not to use any of the third-party plugins I’ve used in the past for directories (GeoDirectory, Directories Pro, Ultimate Member). Instead, I tapped into the WordPress user metadata infrastructure.

Two Memberships, Two Directories

The SEBA website has two directories, each corresponding to one of the two memberships: Personal and Business.

Here’s a short (1’30”) video showing what the two directories look like on the public facing website:

Here’s a screen shot of the plan comparison page (and you can see it live here):

SEBA Plan Comparison Page

Links to the Live SEBA Directories

As you read through this article, it might help to occasionally visit each directory on the SEBA website:

  • SEBA Personal Member Directory
  • SEBA Business Member Directory

We need custom fields for each membership

Out of the box, WordPress has some built-in custom fields associated with each user.

Examples are first name, last name, nickname, description (which is what a user can enter into the Biographical Info box in their profile), and these key/value pairs are stored in the usermeta table.

For both memberships, I’ve added custom fields that are not built in, and I’m using Advanced Custom Fields Pro (“ACF”) for creating the field groups that map custom fields to each directory.

About ACF Field Groups and Location Rules

If you’re not familiar with the ACF plugin, here’s a very basic explanation of how custom fields work in ACF.

In ACF, you add custom fields to what’s called a field group. The reason you need to organize custom fields into field groups is so that WordPress knows which fields it should add to which admin screens (official documentation here).

Here’s an example.

Let’s suppose for some reason that you wanted to allow blog authors to indicate what they had for breakfast on the morning they published each post. (Presumably, you would modify the post template to include that information somewhere in the post.)

Using ACF, you would create a field group; call it something like “Extra Blog Post Fields”; add the custom field “breakfast” to this field group; and then associate this field group with the “post” post type. The way you perform that association is by defining an ACF location rule (post_type = post).

Adding field group in ACF
Now, whenever someone authors a blog post, there will be a box on the add/edit post screen into which he or she could enter cream of wheat or ham and eggs or whatever.
I think it’s fair to say that the most common use case for creating custom fields is to associate them with a specific post type. However, in my situation, I’m adding custom fields to the WordPress user object. It’s not a custom post type, but it kinda behaves that way.

I also introduced custom user roles, and here’s why.

I knew I was going to need to create two different forms — one for Personal members to add and edit their profile information and another for Business members to add and edit their profile information.

Before setting up my two memberships in MemberPress, I created two new custom user roles that I also named “Personal” and “Business”. Then, when creating the memberships, I assigned members in each membership to its respective User Role. This way, every person with an active “Personal” membership had the role of “Personal”, and every person with an active “Business” membership had the role of “Business”.

Now I could write an ACF location rule that would ensure that the custom fields for a Personal membership would show up on the form for the Personal membership profile (and that the custom fields for a Business membership would show up on the form for the Business membership profile).

I needed to add this step (of assigning custom roles to members) because while ACF field groups do support location rules based on user role, they do not support location rules based on MemberPress membership. (To create and manage these roles, I installed and used the Members Role Editor plugin, which was originally developed by Justin Tadlack and acquired by the makers of MemberPress in 2021. You could use any role editor plugin — or roll your own roles with code.)

Personal Membership Custom Fields

Let’s deal with the Personal membership first. In some ways it’s the easier of the two to understand.

That ACF location rule for the Personal member profile field group is:

  • User Form is equal to Add/Edit and
  • User Role is equal to Personal

These are the custom fields for the Personal Membership

Here are all the custom fields mapped to the Personal membership. Highlighted fields below are MemberPress custom fields (either basic or custom). All but one of the lines not highlighted (description) were added as ACF custom fields.

directory_options
first_name
last_name
mepr_telephone
preferred_email
mepr-address-one
mepr-address-two
mepr-address-city
mepr-address-state
mepr-address-zip
mepr-address-country
description
mepr_instruments_played
personal_profile_image
photo_gallery

Here are explanations for the non-highlighted fields:

directory_options
This is an ACF group field consisting of six True/False fields for users to configure their preferences with respect to what personal information will be displayed on the public-facing directory.

  • Include me in Directory?
  • Show my Account Email?
  • Show my Preferred Email?
  • Show my Telephone Number?
  • Show my Street Address?
  • Show my City?

By default, all of the above options except Show my City are set to false. We don’t want members’ personal information displaying on the front end just because they didn’t expressly configure their options.

preferred_email
Primary email is required of all WordPress users. I added a field for preferred email for members who don’t want to be contacted using their WordPress email of record.
description
This is the default WordPress custom field that will be populated when a user updates his or her profile by adding content to the Biographical Info box.
personal_profile_image
ACF image field type; the user’s profile image.
photo_gallery
ACF Gallery field type, allowing users to create photo galleries.

Business Membership Custom Fields

The ACF location rule for the Business membership is identical to the location rule for the Personal memberhsip, except for the User Role:

  • User Form is equal to Add/Edit
  • User Role is equal to Business

These custom fields were all created with Advanced Custom Fields. Most are probably self-explanatory. The ones that might not be (highlighted) are explained below.

business_name
business_summary
business_type
business_address
about_my_business
websites
business_profile_image
photo_gallery
the_team
embed
business_type
This is an ACF radio field. At this writing, the choices are Band, Promoter, Instructor, Retail, or Other. If I had it to do over again, I might make these terms into a custom taxonomy.
websites
This is an ACF repeater field, allowing users to enter the web URL of a website (e.g., his/her Facebook page or independent website) and a label for that website.
the_team
An ACF repeater field, allowing users to list members of their team (band members, employees, etc.), including name, email telephone, and brief note (e.g., banjo player; Vice President; Marketing Manager)
embed
ACF oEmbed field for adding videos

Creating the Forms for Users to Update their Profiles

Are you still with me? Good!

The process for creating the form for updating profiles is the same for Personal members and Business members. In both cases, I’m beholden to the people at UserInsights, where I got the needed code.

Here are the steps.

Code to create shortcode that will display the ACF front-end form.

Note that on  Line 14, the script calls the acf_form() function, which displays a form to add or edit a post.

function seba_member_profile_front_end_edit_shortcode( $atts ) {
	$a = shortcode_atts( array(
		'field_group' => ''
	), $atts );
	$uid = get_current_user_id();
  
	if ( ! empty ( $a['field_group'] ) &&; ! empty ( $uid ) ) {
		$options = array(
			'post_id' 		=> 'user_'.$uid,
			'field_groups' 	=> array( intval( $a['field_group'] ) ),
			'return' 		=> add_query_arg( 'updated', 'true', get_permalink() )
		);
		ob_start();
		acf_form( $options );
		$form = ob_get_contents();
		ob_end_clean();
	}
	return $form;
}add_shortcode( 'seba_member_profile', 'seba_member_profile_front_end_edit_shortcode' );
I originally created two separate functions and shortcodes, but I realized when documenting the process that I needed only one function and shortcode, since it’s the field_group parameter that determines which form is loaded.

Pages for Displaying the Forms

Screen capture of the Personal Profile form

Form for editing a Personal Profile

 

Here’s a section detail of that form:

Section detail from Personal profile form

Screen capture of the Business Profile form

Form for editing a Business Profile

Here is a section detail of that form:

Section detail of Business profile form

Content of Personal Profile Form Page

What follows is the complete content for the Personal Profile Form page — consisting almost entirely of MemberPress shortcodes and the shortcode I created above for generating the front-end form.

The field_group parameter on line 8 is what tells WordPress which form to load. (“1621” is the post ID for the Personal profile field group.)

<div class="your-account-meta">
Welcome, <strong>[mepr-account-info field="full_name"]</strong>!
Your account email is <strong>[mepr-account-info field="user_email"]</strong>
Your active subscription(s):
[mepr-list-subscriptions status="active"]
</div>
Use this form to edit your Personal Profile Page
[seba_profile_edit field_group="1621"]

The numbered items in the screen capture below correspond to the numbered lines in the code snippet above.

Content of Business Profile Form Page

The field_group parameter on line 5 is what tells WordPress which form to load. (“1585” is the post ID for the Business profile field group.)

Welcome, <strong>[mepr-account-info field="full_name"]</strong>!
Your active subscription(s):
[mepr-list-subscriptions status="active"] 
Use this form to edit your Business Profile Page
[seba_profile field_group="1585"]

The numbered items in the screen capture below correspond to the numbered lines in the code snippet above.

Archive Pages

The archive pages for both the Personal Memberships and the Business Memberships are also generated by simple shortcodes (keep reading for the details).

Also, both scripts call the same function — seba_get_members() — for returning an array of all active members:

function seba_get_members($roles) {
	if ( isset($_GET['type']) && !empty($_GET['type']) ) {
		/* If the query string for biz type was passed; This won't apply to personal memberships */
		$args = array(
			'meta_query' 	=> array(
				'key'	 	=> 'business_type',
				'value' 	=> $_GET['type'],
				'compare' 	=> '=',
			),
			'role__in' 		=> array( $roles ), 
			'orderby' 		=> 'meta_value',
			'meta_key' 		=> 'last_name',
			'order' 		=> 'ASC',
		);
	} else {
		$args = array(
		'role__in' 			=> array( $roles ), 
		'orderby' 			=> 'meta_value',
		'meta_key' 			=> 'last_name',
		'order' 			=> 'ASC',
		);
	}
	$users = get_users($args);
	return $users;
}

The shortcode scripts (below) pass the $roles variable to the above script.

Line 2: Users can filter the Business Members list by type (Band, Instructor, Promoter, Other); this line checks for a query string in the form of type=some_value and performs the appropriate filter (lines 5-8).

Creating the Archive Page for Personal Memberships

First, let’s see what this archive page looks like:

Screen Shot of Personal Members Archive Page

Custom Member Directory screenshot
Front end of the Personal Members directory archive

Personal Members Archive Page Contents

And here are the contents of that Personal Membership archive page (/personal-profiles/):

Here is a listing of all SEBA Personal Members who have elected to be included in the member directory:
[seba_pers_members_show]

Script for Generating Personal Members Archive Page

Below is the script that generates the Personal Member Directory archive page via a shortcode:

  • Line 3: Passes the ‘personal’ role to the seba_get_members function.
  • Line 7: This is used on line 55 in the hyperlink to the single profile for a member.
  • Line 9: Calls the seba_get_members function.
  • Line 14: Starting here, the script checks whether the user has opted to have his/her profile public and then defines variables accordingly.
function seba_pers_profile_display_all_shortcode($atts) {
	$atts = shortcode_atts( array(
		'roles' => 'personal',
	), $atts, 'seba_pers_members_show' );
	$roles = $atts['roles'];
	
	$single_page = '/personal-profile/';
	
	$users = seba_get_members($roles);
	$output = '<div class="all-profiles-wrap personal">';
	foreach ( $users as $user ) {
		$user_id = $member_id = $user->ID; // for compat
		
		/* Does the member want to be displayed in the directory? 2023-03-23 19:10:42 */
		$include_in_directory 	= get_user_meta($member_id, 'directory_options_include_in_directory', true);
		$show_city 				= get_user_meta($member_id, 'directory_options_show_city', true);
		
		if ( ! $include_in_directory ) continue;

		$account_email 		= $this_member->user_email;
		$telephone 			= get_user_meta($member_id, 'mepr_telephone', true);
		$preferred_email	= get_user_meta($member_id, 'preferred_email', true);
		$bio 				= get_user_meta($member_id, 'description', true);
		$instruments 		= get_user_meta($member_id, 'mepr_instruments_played', true);
		$address_1 			= get_user_meta($member_id, 'mepr-address-one', true);
		$address_2 			= get_user_meta($member_id, 'mepr-address-two', true);
		$city 				= get_user_meta($member_id, 'mepr-address-city', true);
		$state 				= get_user_meta($member_id, 'mepr-address-state', true);
		$zip 				= get_user_meta($member_id, 'mepr-address-zip', true);
		$country 			= get_user_meta($member_id, 'mepr-address-country', true);

		/* Profile Image2023-10-02 18:10:34 */
		$profile_image 		= get_user_meta($member_id, 'personal_profile_image', true);
		$profile_image_size ='thumbnail';
		if ( $profile_image ) {
			$profile_image_render = wp_get_attachment_image($profile_image, $profile_image_size);
		} else {
			$profile_image_render = '<img src="/wp-content/uploads/profile-anonymous_648x648-150x150.png">';
		}
		
		/* 2023-10-02 19:13:35, */
		$bio_character_count = strlen($bio);
		$average_characters_per_word = 6.47;
		$bio_word_count = str_word_count($bio);
		$bio_cos = ( $bio_word_count ) ? $bio_word_count . ' words': 'None' ;
		
		$output .= '<div class="profile-wrap archive">';

			/* Thumb image */
			$output.= '<div class="member-profile-image">';
				$output .= $profile_image_render;
			$output .= '</div>';
			
			$output.= '<div class="member-name">';
				$output .= '<a href="' . $single_page . '?member_id=' . $user_id . '">' . $user->display_name . '</a>';
			$output .= '</div>';

			/* Location: Show only if she opted to show this 2023-05-02 17:31:54 */
			if ( $show_city ) {
				$output.= '<div class="member-city-state">';
					$output.= $city . ', ' . $state;
				$output .= '</div>';
			}

			/* Instruments */
			$output .= '<div class="instruments-played">';
				$output .= '<span class="prompt">Instruments Played: </span>';
				$output .= $instruments;
			$output .= '</div>';

			/* Bio */
			$output .= '<div class="bio">';
				$output .= 'Bio Word Count: ' . $bio_cos;
			$output .= '</div>';
			
			
		$output .= '</div>';
	}

	$output .= '</div>';
	return $output;
}
add_shortcode('seba_pers_members_show', 'seba_pers_profile_display_all_shortcode');
About all the get_user_meta() calls: You may note I use that native WordPress function for custom fields and do NOT use the corresponding ACF functions. Bill Erickson convinced me to do this in his 2014 blog post, Remove frontend dependency from ACF.

Creating the Archive Page for Business Memberships

First, let’s see what the Business Members archive page looks like:

Screen Shot of Business Members Archive Page

Front end of the Business Members directory archive

Business Members Archive Page Contents

And here are the contents of that Business Members archive page (/personal-profiles/):

<p>Listing of all SEBA Members with active Business Memberships</p>
<div>Filter the directory by <strong>type</strong> of Business Membership:</div>
[biz_types]
[seba_members_show]

Script for Generating Business Members Archive Page

And here’s the actual script that generates the Business Member Directory archive page via a shortcode:

  • Line 3: Passes the ‘business’ role to the seba_get_members function.
  • Line 6: This is used on line 27 as a link to the single profile page.
  • Line 7: Calls the seba_get_members function.
  • Line 10-13, 16: Tests whether the user has an active business membership.
  • Line 22: Starts generating the output.
function seba_shortcode_members_display_all($atts) {
	$atts = shortcode_atts( array(
		'roles' => 'business',
	), $atts, 'seba_members_show' );
	$roles = $atts['roles'];
	$single_page = '/business-profile/';
	$users = seba_get_members($roles);
	$output = '<div class="all-profiles-wrap business">';
	foreach ( $users as $user ) {
		$user_id = $user->ID;
		$business_name 		= get_user_meta($user_id, 'business_name', true);
		$business_type 		= get_user_meta($user_id, 'business_type', true);
		$business_summary 	= get_user_meta($user_id, 'business_summary', true);

		/* If we don't have all 3 of the above, then it's not an active profile */
		$profile_is_active 	= ( $business_name && $business_type && $business_summary);
		$profile_class 		= ( $profile_is_active ) ? 'profile-active' : 'profile-inactive' ;
		
		if ( ! $profile_is_active ) {
			//continue;
		}
		$output .= '<div class="profile-wrap ' . $profile_class . ' business-type-' . $business_type . '">';

			$business_name_cos = ($business_name) ? $business_name : $user->display_name;

			$output.= '<div class="business-name">';
				$output .= '<a href="' . $single_page . '?member_id=' . $user_id . '">' . $business_name_cos . '</a>';
			$output .= '</div>';

			$output.= '<div class="member-name">';
				$output.= '<span class="prompt">Member:</span> ' .  $user->display_name;
			$output .= '</div>';

			$business_type_cos = ($business_type) ? ucwords($business_type) : 'No Business Type provided' ;
			$output .= '<div class="business-type">';
				$output .= '<span class="prompt">Business Type:</span> ' . $business_type_cos;
			$output .= '</div>';

			$business_summary_cos = ($business_summary) ? $business_summary : 'No Business Summary provided' ;
			$output .= '<div class="profile-summary">';
				$output .= $business_summary_cos;
			$output .= '</div>';

		$output .= '</div>';
	}

	$output .= '</div>';
	return $output;
}
add_shortcode('seba_members_show', 'seba_shortcode_members_display_all');

Single Pages

Finally we come to the single pages!

Unlike the archive pages that used shortcodes for rendering the content, I’m using the the_content filter for the single pages. Both scripts have a conditional statement that aborts the mission (and returns the existing content) if the slug of the current page is not equal to the slug of the page used for single profiles.

If anyone wants explanations of any of this code, let me know in the comments.

Creating the Code for the Single Personal Member Profile

function seba_pers_profile_display_single($content) {
	if ( ! is_page( 'personal-profile' ) ) return $content;
	if ( ! isset( $_GET['member_id'] ) ) {
		$content = '<p>No Member ID defined..</p>';
		return $content;
	}
	$member_id = $_GET['member_id'];
	if ( ! $this_member = get_user_by('ID', $member_id) ) {
		$content = '<p>That member (ID of ' . $member_id . ') was not found.</p>';
		return $content;
	}
	/* Assemble the stuff now! */

	/* Get the member's options */
	$include_in_directory 	= get_user_meta($member_id, 'directory_options_include_in_directory', true);
	/* Show nothing if s/he opted out of directory. */
	if ( ! $include_in_directory ) {
		$content = '<p>This member has elected not to be included in the Member Directory.</p>';
		return $content;
	}
	$show_account_email 	= get_user_meta($member_id, 'directory_options_show_account_email', true);
	$show_preferred_email 	= get_user_meta($member_id, 'directory_options_show_preferred_email', true);
	$show_telephone 		= get_user_meta($member_id, 'directory_options_show_telephone', true);
	$show_address 			= get_user_meta($member_id, 'directory_options_show_address', true);
	$show_city	 			= get_user_meta($member_id, 'directory_options_show_city', true);
	$name 					= $this_member->display_name;
	$account_email 			= $this_member->user_email;
	$telephone 				= get_user_meta($member_id, 'mepr_telephone', true);
	$preferred_email		= get_user_meta($member_id, 'preferred_email', true);
	$bio 					= get_user_meta($member_id, 'description', true);
	$instruments 			= get_user_meta($member_id, 'mepr_instruments_played', true);
	$address_1 				= get_user_meta($member_id, 'mepr-address-one', true);
	$address_2 				= get_user_meta($member_id, 'mepr-address-two', true);
	$city 					= get_user_meta($member_id, 'mepr-address-city', true);
	$state 					= get_user_meta($member_id, 'mepr-address-state', true);
	$zip 					= get_user_meta($member_id, 'mepr-address-zip', true);
	$country 				= get_user_meta($member_id, 'mepr-address-country', true);
	
	/* Hide stuff according to options 2023-03-23 18:40:23, Thu */
	$hide_word = 'NA';
	$blank_word = '';
	$account_email 			= ( $show_account_email) 	? $account_email 	: $hide_word ;
	$telephone 				= ( $show_telephone) 		? $telephone 		: $hide_word ;
	$preferred_email		= ( $show_preferred_email ) ? $preferred_email 	: $hide_word ;
	$address_1 				= ( $show_address ) 		? $address_1 		: $hide_word ;
	$address_2 				= ( $show_address ) 		? $address_2 		: $hide_word ;
	$city 					= ( $show_city ) 			? $city 			: $hide_word ;
	$state 					= ( $show_city ) 			? $state 			: $hide_word ;
	$zip 					= ( $show_city ) 			? $zip 				: $hide_word ;
	$country 				= ( $show_city ) 			? $country 			: $hide_word ;

	$gallery_images = seba_profile_gallery_get($member_id); // Returns gallery IDs
	$size = 'thumbnail';

	$content .= '<h2 class="member-name section">'. $name . '</h2>';
	$content .= '<div class="single-profile-wrap personal compact">';
		$content .= '<div class="basics">';
			/* Profile Image */
			$profile_image 	= get_user_meta($member_id, 'personal_profile_image', true);
			// $the_size ='thumbnail';
			$the_size ='medium';
			$content .= '<div class="profile-image personal">';
			if ( $profile_image ) {
				$content .= wp_get_attachment_image($profile_image, $the_size);
			} else {
				$content .= "<p>No Profile Image Provided</p>";
			}
			$content .= '</div>';
			/* Contact Info */
			$content .= '<div class="contact-info section">';
				/* Account Email */
				$content .= '<div class="account-email item">';
					if ( $show_account_email ) {
						$content .= '<span class="prompt">Account Email:</span> <a href="mailto:' . $account_email . '">' . $account_email . '</a>';
					} else {
						$content .= '<span class="prompt">Account Email:</span> ' . $account_email;
					}
				$content .= '</div>';
				/* Preferred Email */
				$content .= '<div class="preferred-email item">';
					if ( $show_preferred_email ) {
						$content .= '<span class="prompt">Preferred Email:</span> <a href="mailto:' . $preferred_email . '">' . $preferred_email . '</a>';
					} else {
						$content .= '<span class="prompt">Preferred Email:</span> ' . $preferred_email;
					}
				$content .= '</div>';
				/* Telephone */
				$content .= '<div class="telephone item">';
					$content .= '<span class="prompt">Telephone:</span> ' . $telephone;
				$content .= '</div>';
			$content .= '</div>';
			/* Address */
			$content .= '<div class="address-wrap section">';
				$content .= '<div class="prompt">Address:</div>';
				if ( $show_address ) {
					$content .= '<div class="address-1 item">' . $address_1 . '</div>';
					$content .= '<div class="address-2 item">' . $address_2 . '</div>';
				} else {
					$content .= '<div>(Street Address hidden)</div>';
				}
			if ( $show_city ) {
				$content .= '<div class="city-state-zip item">';
				$content .= $city . ', ' . $state . ' ' . $zip;
				$content .= '</div>';
				$content .= '<div class="country item">' . $country . '</div>';
			} else {
				$content .= '<div>(City hidden)</div>';
			}
			$content .= '</div>';
			/* Instruments */
			$content .= '<div class="instruments-played section">';
				if ( $instruments ) {
					$content .= '<span class="prompt">Instruments I Play:</span><br />' . $instruments;
				} else {
					$content .= 'No Instruments provided';
				}
			$content .= '</div>';
		$content .= '</div><!-- .basics -->';
		/* Bio */
		$content .= '<div class="pers-bio section">';
			$content .= '<h3>About Me</h3>';
			if ( $bio ) {
				$content .= nl2br($bio);
			} else {
				$content .= 'No Bio provided';
			}
		$content .= '</div>';
	/*  Gallery */
	if ( $gallery_images ) {
		$content .= '<div class="gallery section">';
			$content .= '<h3>' . $name . ' Gallery Images</h3>';
			$content .= '<p>Click on thumbnail to enlarge</p>';
			$content .= '<div class="gallery-images">';
				foreach ( $gallery_images as $image_id ) {
					$content .= '<div class="gallery-image">';
						/* Here's the clickable thumb: */
						$content .= '<a class="foobox" rel="gallery" href="' . wp_get_attachment_url($image_id) . '">' . wp_get_attachment_image($image_id, $size) . '</a>';
					$content .= '</div>';
				}
			$content .= '</div>';
		$content .= '</div>';
	}
	$content .= '</div><!-- .wrap -->';
	return $content;
}
add_filter('the_content', 'seba_pers_profile_display_single');

Creating the Code for the Single Business Member Profile

function seba_business_member_display_single($content) {
	if ( ! is_page( 'business-profile' ) ) return $content;
	if ( ! isset( $_GET['member_id'] ) ) {
		$content = '<p>No Member ID defined..</p>';
		return $content;
	}
	$member_id = $_GET['member_id'];
	if ( ! $this_member = get_user_by('ID', $member_id) ) {
		$content = '<p>That member (ID of ' . $member_id . ') was not found.</p>';
		return $content;
	}
	/* Assemble the stuff now! */
	$member_name 			= $this_member->display_name;
	$business_name 			= get_user_meta($member_id, 'business_name', true);
	$business_summary 		= get_user_meta($member_id, 'business_summary', true);
	$about_my_business 		= nl2br(get_user_meta($member_id, 'about_my_business', true));
	$business_type 			= get_user_meta($member_id, 'business_type', true);
	$business_type_label 	=  ( !empty($business_type) ) ? ucwords($business_type) : 'Not Provided';

	/* 2023-02-08 15:15:19: Get those callery images, if they exist */
	$gallery_images = seba_profile_gallery_get($member_id); // Returns gallery IDs
	$size = 'thumbnail';

	/* Team Members, assemble */
	$the_team = get_user_meta($member_id, 'the_team', true); // To get the count
	$team_cos = ''; // The team display, init
	if ( $the_team ) {
		$team_cos .= '<div class="the-team section">';
			$team_cos .= '<h3>' . $business_name . ': The Team</h3>';
			$team_cos .= '<table class="team-listing">';
			$team_cos .= '<thead>';
				$team_cos .= '<tr>';
				$team_cos .= '<th>Name</th>';
				$team_cos .= '<th>Role</th>';
				$team_cos .= '<th>Telephone</th>';
				$team_cos .= '</tr>';
			$team_cos .= '</thead>';
			$team_cos .= '<tbody>';
			for ( $i=0; $i < $the_team; $i++ ) {
				/* Assemble */
				$name 		= get_user_meta($member_id, 'the_team_' . $i 	. '_team_member_name', true);
				$about 		= get_user_meta($member_id, 'the_team_' . $i 	. '_team_member_about', true);
				$email 		= get_user_meta($member_id, 'the_team_' . $i 	. '_team_member_email', true);
				$tele 		= get_user_meta($member_id, 'the_team_' . $i 	. '_team_member_telephone', true);
				$tele_cos 	= ( !empty( $tele ) ) ? $tele : '';
				$email_cos	= (!empty($email) ) ? '<a href="mailto:' . $email . '">' . $email . '</a>' : '';
				$team_cos .= '<tr>';
				$team_cos .= '<td class="has-data-label name" data-label="Name">' . $name . '<br /><span class="has-data-label" data-label="Email">' . $email_cos . '</span></td>';
				$team_cos .= '<td class="has-data-label role" data-label="Role">' . $about . '</td>';
				$team_cos .= '<td class="has-data-label tele" data-label="Tele">' . $tele_cos . '</td>';
				$team_cos .= '</tr>';
			}
			$team_cos .= '</tbody>';
			$team_cos .= '</table>';
		$team_cos .= '</div>';
	}
	/* Assemble Address */
	$business_address = 	get_user_meta($member_id, 'business_address', true);
	$embed = 				get_user_meta($member_id, 'embed', true);
	/* Telephone - 2024-06-05 14:46:24 */
	$telephone 				= get_user_meta($member_id, 'mepr_telephone', true);
	$telephone				= ( $telephone ) ? $telephone : 'NA'; // If blank

	$content = ''; // Initialize
	$content .= '<div class="single-profile-wrap business business-type-' . $business_type . '">';
		$business_name_cos = ( $business_name ) ? $business_name : 'No Business Name Provided';
		$content .= '<h2 class="business-name">'. $business_name_cos . '</h2>';

		/* Business Type */
		$content .= '<div class="business-type">';
			$content .= '<span class="prompt">Business Type: </span>';
			$content .= $business_type_label;
		$content .= '</div>';

		/* Business Profile Image */
		$bpi 	= get_user_meta($member_id, 'business_profile_image', true);
		$the_size ='full';
		if ( $bpi ) {
			$content .= '<div class="business-profile-image">';
			$content .= wp_get_attachment_image($bpi, $the_size);
			$content .= '</div>';
		} else {
			$content .= "<p>No Business Profile Image Provided)</p>";
		}

		/* Member Name */
		$content .= '<div class="member-name section">';
			$content .= '<h3>SEBA Member</h3>';
			$content .= $member_name;
		$content .= '</div>';
	

		/* Mailing Address */
		$content .= '<div class="mailing-address section">';
			$content .= '<h3>Address/Telephone</h3>';
			if( $business_address ) {
				$content .= '<div class="business-address">' . nl2br($business_address) . '</div>';
			} else {
				$content .= '<div class="business-address none">No business address provided.</div>';
			}
			/* Now tele */
			$content .= '<div class="business-tele">Telephone: ' . $telephone . '</div>';
		$content .= '</div>';
	
		/* Business Summary */
		$content .= '<div class="business-summary section">';
			$content .= '<h3>Business Summary</h3>';
			if ( $business_summary ) {
				$content .= $business_summary;
			} else {
				$content .= 'No business summary provided';
			}
		$content .= '</div>';
	
		/* Gallery */
		if ( $gallery_images ) {
			$content .= '<div class="gallery section">';
				$content .= '<h3>' . $business_name . ' Gallery Images</h3>';
				$content .= '<p>Click on thumbnail to enlarge</p>';
				$content .= '<div class="gallery-images">';
					foreach ( $gallery_images as $image_id ) {
						$content .= '<div class="gallery-image">';
							/* Here's the clickable thumb: */
							$content .= '<a class="foobox" rel="gallery" href="' . wp_get_attachment_url($image_id) . '">' . wp_get_attachment_image($image_id, $size) . '</a>';
						$content .= '</div>';
					}
				$content .= '</div>';
			$content .= '</div>';
		}

		/* About My Business */
		$content .= '<div class="about-my-business section">';
			$content .= '<h3>Full Business Profile</h3>';
			if ( $about_my_business ) {
				$content .= $about_my_business;
			} else {
				$content .= 'No business profile provided';
			}
		$content .= '</div>';
		
		/* Websites */
		$websites = get_user_meta($member_id, 'websites', true); // To get the count
		
	if ( $websites ) {
		$content .= '<div class="websites section">';
			$content .= '<h3>Websites</h3>';
			$content .= '<table>';
			$content .= '<thead>';
				$content .= '<tr>';
				$content .= '<th>Website URL</th>';
				$content .= '<th>Website Name</th>';
				$content .= '</tr>';
			$content .= '</thead>';
			$content .= '<tbody>';
			
			for ( $i=0; $i < $websites; $i++ ) {
				/* Assemble */
				$url 		= get_user_meta($member_id, 'websites_' . $i 	. '_website_url', true);
				$name 		= get_user_meta($member_id, 'websites_' . $i 	. '_website_text', true);
				
				$content .= '<tr>';
				$content .= '<td><a target="_blank" href="' . $url . '">' . $url . '</a></td>';
				$content .= '<td>' . $name . '</td>';
				$content .= '</tr>';
			}
			$content .= '</tbody>';
			$content .= '</table>';
		$content .= '</div>';
	}
		/* Team table here */
		$content .= $team_cos;


		/* Embed */
		if ( $embed ) {
			$content .= '<div class="embed section">';
			$content .= '<h3>Featured Video</h3>';
			$content .= wp_oembed_get($embed);
			$content .= '</div>';
		}
	$content .= '</div>';
	return $content;
}
add_filter('the_content', 'seba_business_member_display_single');

Let’s Recap

Here’s my attempt at a concise recap of the various parts that went into my custom member directories.

  • MemberPress
    I installed and configured MemberPress for creating memberships and restricting access to content.
  • Custom Fields
    Using Advanced Custom Fields Pro, I created a set of custom fields for each membership. The pro version was necessary because I used two pro features: Repeater and Gallery fields.
  • Custom User Roles
    I created custom user roles (using the Members Role Editor plugin) so that I had a way to associate the custom fields to the appropriate front end forms.
  • Front End Forms
    I created the front end forms using code “borrowed” from a colleague.
  • Archive Templates
    Custom code for rendering the archive pages (using shortcodes)
  • Single Post Tempates
    Custom code for rendering the single pages (using the the_content WordPress filter hook)

Let me know in the comments if you found this article helpful, have questions or comments, want to recommend other techniques or solutions, or just want to say hi.

Like what you see? Share with others and join my mailing list. No long-term commitment, unsubscribe any time.

Related Posts

  1. Southeastern Bluegrass Association Membership Site
  2. Custom Front-End Member Listing for MemberPress Website
  3. Create a Membership Card for a MemberPress Site
  4. Visualize an Audience of One
  5. MemberPress: How to Create a Custom MemberPress Course Progress Page

Reader Interactions

Leave a Reply Cancel reply

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

Primary Sidebar

mailchimp signup

Subscribe to get notified when new articles are published. Unsubscribe any time. No spam. I promise. Check out my newsletter archives.

social

Twitter Facebook LinkedIn

Recent Articles

  • Post-Specific CSS for WordPress October 5, 2026
  • You built an online course, but they haven’t come. September 16, 2026
  • Custom MemberPress Pricing Pages December 19, 2025
  • CSS Data Attribute Override December 11, 2025
  • MemberPress Rules Using Custom Taxonomies and Custom Roles December 10, 2025

Filter By Category/Tag

Categories

  • Case Studies (8)
  • For Staff (1)
  • General (73)
  • Portfolio (7)
  • Reviews (14)
  • Snippets (24)
  • Techniques (52)

Popular Tags

Advanced Custom Fields Blogging Child Themes Content Marketing CSS Customer Service Custom Fields Custom Post Types Diagnostics Domain Names Facebook FooGallery Genesis Hosting HTML Images iPhone Libra Live Chat Marketing Media MemberPress MemberPress Courses Membership Sites Mobile-Friendly MySQL Photo Gallery php Pinterest Plugins Post Formats Pricing Project Management SEBA SEO Seth Godin Shortcodes Social Networking Surveys Taxonomies Twitter Video Web design Web forms WordPress

Footer

Background

Web Sites | WordPress Support | Web Applications.

Formally trained in liberal arts and education (I have a B.A. in Government from Harvard and studied Secondary Education at Rutgers Graduate School), I have honed my skills in the communication arts and sciences as a teacher, trainer, instructional designer, writer, photographer, calligrapher, helpdesk manager, database programmer, and multimedia developer.

(I've also been a group counselor, waiter, bartender, bicycle messenger boy, computer salesman, carpenter's helper, financial analyst, and school board president.)

Tech

Systems since 1983.
Web sites since 1994.
PHP since 2001.
WordPress since 2007.

Contact

Book Meeting
770-772-5134
Email Jeff
Send Money
All Ways

Copyright 2026, nSiteful Web Builders, Inc.

Cookies Consent
To provide the best experiences, we use technologies like cookies to store and/or access device information. Consenting to these technologies will allow us to process data such as browsing behavior or unique IDs on this site. Not consenting or withdrawing consent, may adversely affect certain features and functions.
Functional Always active
The technical storage or access is strictly necessary for the legitimate purpose of enabling the use of a specific service explicitly requested by the subscriber or user, or for the sole purpose of carrying out the transmission of a communication over an electronic communications network.
Preferences
The technical storage or access is necessary for the legitimate purpose of storing preferences that are not requested by the subscriber or user.
Statistics
The technical storage or access that is used exclusively for statistical purposes. The technical storage or access that is used exclusively for anonymous statistical purposes. Without a subpoena, voluntary compliance on the part of your Internet Service Provider, or additional records from a third party, information stored or retrieved for this purpose alone cannot usually be used to identify you.
Marketing
The technical storage or access is required to create user profiles to send advertising, or to track the user on a website or across several websites for similar marketing purposes.
  • Manage options
  • Manage services
  • Manage {vendor_count} vendors
  • Read more about these purposes
View preferences
  • {title}
  • {title}
  • {title}
https://iframe.mediadelivery.net/embed/392008/42d18bc1-2adc-4741-b733-053d08d09c32
https://vz-000c5976-3ab.b-cdn.net/42d18bc1-2adc-4741-b733-053d08d09c32/play_720p.mp4

Receive occasional emails from Jeff