Back to Tips & Tricks

Track JetEngine Profile Builder Profile Views with a Shortcode

Atif JanPublished on Updated
Abstract user profile cards with a rising view counter, illustrating a JetEngine Profile Builder profile views counter

To count profile views on a JetEngine Profile Builder single user page, add one PHP snippet. It registers a [user_profile_view_count] shortcode, sends a small AJAX request when someone opens the profile, and saves the total in the user meta key profile_view_count. Each visitor is counted once per 24 hours and the owner's own visits are skipped. Because the count runs through AJAX, it keeps working behind full-page caching.

What You'll Build

  • A PHP snippet you add through a snippets plugin or your child theme.
  • An AJAX endpoint that adds 1 to the profile's view count, with a 24-hour limit per visitor.
  • A shortcode, [user_profile_view_count], that shows the number on the profile page.
  • User lookup that works with every JetEngine "User Page Rewrite" option: user ID, username or nicename.

Prerequisites

  • JetEngine with the Profile Builder module turned on. Profile Builder is a module inside JetEngine. It is not the separate Cozmoslabs plugin that is also called "Profile Builder". This snippet only works with the JetEngine module.
  • A snippets plugin such as Code Snippets or WPCode. A child theme's functions.php also works.
  • Access to edit the single user page template in Elementor, Gutenberg or Bricks.

If you prefer a plugin, Post Views Counter Pro counts JetEngine Profile Builder user profiles from version 1.5.5. The snippet below is the free, lightweight option. It adds no database tables or settings screens, and the count lives in user meta, so JetEngine listings can read it directly.

Step 1: Set up the JetEngine Profile Builder single user page

The counter needs a public single user page. If you already have one, skip to Step 2.

  1. Go to WordPress Dashboard > JetEngine > JetEngine > Modules, turn on the Profile Builder toggle and click Save.
  2. Open JetEngine > Profile Builder. On the Pages tab, turn on Single user page and choose the WordPress page to use for it in the page select. That page's URL becomes the base URL for every profile.
  3. In the User Page Rewrite field, pick Username, User nicename or User id. JetEngine then builds pretty URLs such as base-URL/username. You don't add a query string yourself, and the snippet handles all three options.
  4. Open the User Page tab, which appears once Single user page is on, and assign a template to the default Main subpage.
  5. Go to Settings > Permalinks and click Save Changes. Profile Builder URLs return 404 errors until you do this.

When it works, visiting the base URL followed by a username, nicename or ID loads your template for that user. The JetEngine Profile Builder overview covers every setting on these tabs.

Step 2: Create the PHP snippet

  1. Create a new PHP snippet in your snippets plugin.
  2. Paste the full code below. Most snippets plugins add the opening <?php tag for you, so the code leaves it out. In functions.php, paste it at the end of the file.
  3. Set the snippet to run everywhere, not front end only. AJAX requests go through wp-admin/admin-ajax.php, which WordPress treats as the admin area. With a front-end-only setting, the shortcode appears but the count never goes up.
  4. Save and activate the snippet.

Here is the full snippet:

Code

// JetEngine Profile Builder: profile view counter.
// Shortcode: [user_profile_view_count]  or  [user_profile_view_count user_id="123"]
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

if ( ! defined( 'BMC_PV_META' ) ) {
	define( 'BMC_PV_META', 'profile_view_count' );
}

/**
 * ID of the user whose JetEngine Profile Builder single page is being viewed, or 0.
 */
function bmc_pv_queried_user_id() {
	if ( ! class_exists( '\Jet_Engine\Modules\Profile_Builder\Module' ) ) {
		return 0;
	}
	$module = \Jet_Engine\Modules\Profile_Builder\Module::instance();
	if ( empty( $module->query ) ) {
		return 0;
	}
	$slug = $module->query->get_queried_user_slug();
	if ( empty( $slug ) ) {
		return 0;
	}
	$slug = rawurldecode( (string) $slug );
	$user = false;

	if ( ctype_digit( $slug ) ) {      // "User id" rewrite
		$user = get_user_by( 'id', (int) $slug );
	}
	if ( ! $user ) {                    // "User nicename" rewrite
		$user = get_user_by( 'slug', $slug );
	}
	if ( ! $user ) {                    // "Username" rewrite
		$user = get_user_by( 'login', $slug );
	}
	return $user ? (int) $user->ID : 0;
}

/**
 * [user_profile_view_count] shortcode.
 */
function bmc_pv_shortcode( $atts ) {
	$atts    = shortcode_atts( array( 'user_id' => 0 ), $atts, 'user_profile_view_count' );
	$fixed   = absint( $atts['user_id'] );
	$user_id = $fixed ? $fixed : bmc_pv_queried_user_id();
	if ( ! $user_id ) {
		return '';
	}
	$count = (int) get_user_meta( $user_id, BMC_PV_META, true );

	// Only load the tracker on the profile page itself, once per page.
	if ( ! $fixed && ! has_action( 'wp_footer', 'bmc_pv_print_js' ) ) {
		$GLOBALS['bmc_pv_user'] = $user_id;
		add_action( 'wp_footer', 'bmc_pv_print_js' );
	}

	return sprintf(
		'<span class="bmc-profile-views" data-user="%1$d">%2$s</span>',
		$user_id,
		esc_html( number_format_i18n( $count ) )
	);
}
add_shortcode( 'user_profile_view_count', 'bmc_pv_shortcode' );

/**
 * Prints the tracker script in the footer.
 */
function bmc_pv_print_js() {
	if ( empty( $GLOBALS['bmc_pv_user'] ) ) {
		return;
	}
	$uid = (int) $GLOBALS['bmc_pv_user'];
	?>
	<script>
	(function () {
		var uid  = <?php echo (int) $uid; ?>;
		var ajax = <?php echo wp_json_encode( admin_url( 'admin-ajax.php' ) ); ?>;
		var key  = 'bmc_pv_' + uid;

		// Browser-side 24h limit.
		try {
			var last = parseInt( localStorage.getItem( key ), 10 );
			if ( last && Date.now() - last < 86400000 ) { return; }
		} catch ( e ) {}

		var body = new URLSearchParams( { action: 'bmc_profile_view', user_id: String( uid ) } );

		fetch( ajax, { method: 'POST', credentials: 'same-origin', body: body } )
			.then( function ( r ) { return r.json(); } )
			.then( function ( res ) {
				if ( ! res || ! res.success ) { return; }
				try { localStorage.setItem( key, String( Date.now() ) ); } catch ( e ) {}
				document.querySelectorAll( '.bmc-profile-views[data-user="' + uid + '"]' ).forEach( function ( el ) {
					el.textContent = res.data.formatted;
				} );
			} )
			.catch( function () {} );
	})();
	</script>
	<?php
}

/**
 * AJAX endpoint: adds 1 view, limited to once per 24h per profile + IP + user agent.
 */
function bmc_pv_ajax() {
	$user_id = isset( $_POST['user_id'] ) ? absint( wp_unslash( $_POST['user_id'] ) ) : 0;
	if ( ! $user_id || ! get_userdata( $user_id ) ) {
		wp_send_json_error( null, 400 );
	}

	$ua = isset( $_SERVER['HTTP_USER_AGENT'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ) : '';
	if ( '' === $ua || preg_match( '/bot|crawl|spider|slurp|preview|facebookexternalhit/i', $ua ) ) {
		wp_send_json_error( null, 403 ); // Skip bots.
	}

	$count = (int) get_user_meta( $user_id, BMC_PV_META, true );

	// Do not count the owner viewing their own profile.
	if ( get_current_user_id() !== $user_id ) {
		$ip    = isset( $_SERVER['REMOTE_ADDR'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ) ) : '';
		$guard = 'bmc_pv_' . md5( $user_id . '|' . $ip . '|' . $ua );

		if ( ! get_transient( $guard ) ) { // Server-side 24h limit.
			$count++;
			update_user_meta( $user_id, BMC_PV_META, $count );
			set_transient( $guard, 1, DAY_IN_SECONDS );
		}
	}

	wp_send_json_success( array( 'formatted' => number_format_i18n( $count ) ) );
}
add_action( 'wp_ajax_bmc_profile_view', 'bmc_pv_ajax' );
add_action( 'wp_ajax_nopriv_bmc_profile_view', 'bmc_pv_ajax' );

Step 3: Add the shortcode to the profile

Where the shortcode goes depends on the Template mode setting on the Pages tab of JetEngine > Profile Builder:

  • Rewrite mode: each subpage is replaced by its assigned template. Add the shortcode to the template of every subpage where the count should appear, at least Main.
  • Content mode: add the shortcode to the single user page itself.

Use the Shortcode widget in Elementor, the Shortcode block in Gutenberg or the Shortcode element in Bricks. Add a label next to it, for example Profile views: followed by [user_profile_view_count]. The shortcode outputs a plain span, so you can style it with your builder's typography settings or your theme CSS.

How the code works

  • Finding the user. bmc_pv_queried_user_id() calls JetEngine's get_queried_user_slug(), which returns the user part of the current profile URL. It never reads $_GET. It then looks the user up by ID, then nicename, then username, so it works with all three User Page Rewrite options. If you use the Username option and some usernames are purely numeric, move the 'login' check to the top so a username like "2024" is not read as user ID 2024.
  • Counting through AJAX. If the count ran in PHP during page load, a full-page cache would serve the same HTML to everyone and most views would be lost. The footer script sends a POST request to admin-ajax.php instead. That request is never cached, and the response updates the number on the page.
  • Once per 24 hours. The browser stores a timestamp in localStorage and skips the request for a day. On the server, a transient keyed to profile + IP + user agent blocks repeat counts for DAY_IN_SECONDS, even if someone clears their browser storage.
  • Skipping bots and owners. Requests with an empty user agent or a common crawler signature are rejected. When the logged-in user is the profile owner, the endpoint returns the current number without adding to it.
  • No nonce, on purpose. On cached pages a nonce goes stale, and requests with an expired nonce fail without any visible error, so views would be lost. The endpoint only adds 1 to a public counter for a user that exists. It reads no other data and changes nothing else, and both 24-hour limits cap repeat hits. If your profile pages are never cached, you can add check_ajax_referer(). Exclude those pages from full-page caching first.

Treat this as a lightweight popularity counter, not fraud-proof analytics. Anyone can add a view by sending the request with a different user agent string, so don't use it for anything that rewards high counts.

Test the counter

  1. Open a user's profile in a private window while logged out. The number shows, then updates by 1 a moment after the page loads.
  2. Refresh the page. The number stays the same.
  3. Open DevTools > Network and reload. You should see a POST to admin-ajax.php that returns {"success":true,"data":{"formatted":"1"}} on the first visit. On later visits within 24 hours, the browser makes no request at all.
  4. Log in as that user and open your own profile. The count does not change.
  5. To test again, use a different browser. Its user agent differs, so the server-side limit treats it as a new visitor.

Use the count in listings and elsewhere

  • User listings: the count is ordinary user meta. In a JetEngine Listing Grid built on users, add a Dynamic Field that outputs the user meta field profile_view_count. Inside a listing, use this instead of the shortcode without attributes. On a profile page, that shortcode always shows the profile owner's count, not the count for each listing item.
  • A specific user anywhere: [user_profile_view_count user_id="123"] shows that user's count and never loads the tracker, so it cannot add views.
  • Most viewed members: sort a JetEngine Users query by the profile_view_count meta key as a numeric value. Users who have never been viewed have no meta yet, so check that your query doesn't drop them if you need them. Our guide to common JetEngine Query Builder mistakes covers meta-query pitfalls.
  • Owner-only display: put the shortcode on a subpage whose Page visibility is set to Owner. Only the profile owner can see their stats. The tracker still has to run on a public subpage, such as Main, for views to be counted.
  • Resetting counts: with WP-CLI, run the command below. Replace 123 with the user ID.
wp user meta delete 123 profile_view_count

For site-wide visitor stats rather than per-profile counts, see our visitor tracker for WordPress.

Privacy: the snippet stores a hashed IP + user agent in a transient for 24 hours, and a timestamp in the visitor's localStorage. Mention this in your privacy or consent policy. Plugins such as Post Views Counter use a first-party cookie for the same job. WordPress deletes expired transients on its own schedule.

Common problems

  • The shortcode shows nothing. You are not on a JetEngine Profile Builder single user page, Single user page is off, or permalinks were not re-saved. Go to Settings > Permalinks > Save Changes and load a real profile URL.
  • The count never goes up. Check these causes in order: the snippet is set to front end only (change it to run everywhere), you are logged in as the profile owner, a security plugin blocks admin-ajax.php for visitors, or an optimization plugin delays or combines inline scripts. Exclude the inline script that contains bmc_profile_view from JavaScript delay or combine settings.
  • The count rises on every refresh. The server-side limit is not holding. Make sure nothing on the server clears transients on each request. Transients work with the default database storage, and a persistent object cache is not required.
  • Views are undercounted behind a CDN or reverse proxy. If REMOTE_ADDR holds the proxy's IP, every visitor with the same browser version and operating system produces the same IP + user agent key, so only the first of them is counted for each profile every 24 hours. Configure your server to pass the real visitor IP to PHP (for Cloudflare, restore it from the CF-Connecting-IP header).
  • The number looks old on cached pages. This is expected. The AJAX response replaces it right after load. Visitors who were already counted in the last 24 hours skip the request, so they may see the cached number until the cache refreshes.
  • A PHP error after pasting. You probably pasted a second <?php tag, or the snippet ran twice. Remove the duplicate.

That's the whole setup: one snippet, one shortcode and a meta key you can reuse in any JetEngine listing. If you want a custom member directory, profile stats or other Profile Builder features built for you, post a request on BoostMyCroco.

Frequently Asked Questions

Does this profile view counter work with page caching plugins?
Yes. The count is added through an AJAX request to admin-ajax.php, which is not cached, so views are counted even when the profile HTML comes from a full-page cache. The number on the page is updated from the AJAX response right after it loads.
Does it count my own visits to my profile?
No. When the logged-in user is the owner of the profile being viewed, the endpoint returns the current number without increasing it. Visits from other logged-in users and from logged-out visitors are counted, once per 24 hours each.
Can I show profile view counts in a JetEngine user listing?
Yes. The total is stored in the user meta key profile_view_count, so a Dynamic Field in a user Listing Grid can output it. You can also use the shortcode with a fixed ID, such as [user_profile_view_count user_id="123"], which displays the count without adding a view.
Does it work with username and nicename profile URLs?
Yes. The snippet gets the user part of the URL from JetEngine's get_queried_user_slug() and checks it as a user ID, then a nicename, then a username. That covers all three User Page Rewrite options in JetEngine Profile Builder.
Is there a plugin that counts JetEngine Profile Builder profile views?
Post Views Counter Pro counts JetEngine Profile Builder user profiles from version 1.5.5. The snippet in this guide is a free alternative that adds no settings screens or tables and stores the count in user meta for use in JetEngine listings.

Sources

  1. User Profile Builder Overview (crocoblock.com)
  2. Post Views Counter (wordpress.org)
  3. naswp visitors (github.com)
  4. JetEngine. Profile Builder. Allow to add single user page without base page slug in the URL. Final URL format will be - site.url/// · GitHub (gist.github.com)
  5. Macros Guide — JetEngine (crocoblock.com)
  6. How to Use JetEngine Macros in Query Builder — JetEngine (crocoblock.com)
  7. December 2025 Digest: JetEngine 3.8.2, AI Tools & Plugin Updates - Crocoblock (crocoblock.com)
  8. JetEngine: WordPress Plugin for Elementor, Gutenberg & Bricks (crocoblock.com)

Related tips