This article covers the technical implementation of nine WordPress modifications built for acteursbelangen.nl. Each task is documented with the specific approach used, the code that was written, and any non-obvious decisions made along the way.

Task 1: YouTube Thumbnail Fix

Actor profile pages on the site displayed video thumbnails using a lazy loader that read URLs from a data-bg attribute. The URLs were being generated with YouTube share parameters attached — for example, https://img.youtube.com/vi/4FM_loh9Y0w?si=ADwjtrwjmQgLSZau/mqdefault.jpg — which produced 404 errors because the ?si= parameter broke the path structure before /mqdefault.jpg.

The fix was a small JavaScript snippet added via WPCode to the footer. It runs on DOMContentLoaded, before the lazy loader processes the elements, and rewrites any malformed data-bg values to the correct format:

document.addEventListener("DOMContentLoaded", function () {
  const videoThumbnails = document.querySelectorAll(".video_box_img_container");

  videoThumbnails.forEach(function (el) {
    let originalBg = el.getAttribute("data-bg");

    if (originalBg && originalBg.includes("youtube.com/vi/")) {
      // Extract clean video ID, strip any query string parameters
      const match = originalBg.match(/youtube.com/vi/([a-zA-Z0-9_-]+)/);
      if (match && match[1]) {
        const videoId = match[1];
        const cleanUrl = "https://img.youtube.com/vi/" + videoId + "/mqdefault.jpg";
        el.setAttribute("data-bg", cleanUrl);
        el.style.backgroundImage = "url(" + cleanUrl + ")";
      }
    }
  });
});

Task 2: Mobile Menu for Logged-In Actors

The mobile navigation slot (mobile_nav_2025) was showing the general site menu for all users, including logged-in actors who should see a profile-specific menu with links to their profile, photos, videos, CV, and logout. The correct menu was already built in the admin (menu ID 2, named Profiel menu) — it just was not being displayed for the right users.

A wp_nav_menu_args filter intercepts the menu arguments before output. When the current user is logged in with the subscriber role (which is the role used for actor members on this site), the filter replaces the default menu assignment with menu ID 2:

add_filter("wp_nav_menu_args", "replace_mobile_menu_for_logged_in_actors");
function replace_mobile_menu_for_logged_in_actors($args) {
    if ($args["theme_location"] === "mobile_nav_2025") {
        $user = wp_get_current_user();
        if (is_user_logged_in() && in_array("subscriber", (array) $user->roles)) {
            $args["menu"] = 2;
        }
    }
    return $args;
}

Task 3 and 6: Casting Director Role and Custom Fields

The Casting Director role was registered with a targeted set of capabilities — enough to create and manage their own tom_project posts, but without access to the broader admin panel used by editors and administrators:

add_role("casting_director", "Casting Director", [
    "read"                    => true,
    "publish_posts"           => false,
    "edit_posts"              => true,
    "edit_published_posts"    => true,
    "delete_posts"            => false,
    "upload_files"            => true,
    "edit_tom_projects"       => true,
    "publish_tom_projects"    => true,
]);

An ACF field group was registered for the casting_director user role, covering: company name, full name, address (street, postal code, city, country), email, phone, website, Instagram, affiliations (NCA, VVTP, NAPA, NCP as checkboxes), and an approval flag. These fields are stored against the user object using ACF’s user_{$user_id} key format.

Task 4 and 7: Frontend Registration and Admin Approval

The registration page at /aanmelden-casting-director used a custom HTML form handled by a WordPress action hook on init. On submission, the handler creates a new user with the casting_director role and immediately sets account_approved to false in user meta. A confirmation message is shown to the registrant explaining that their account is pending review.

The approval gate uses the wp_authenticate_user hook, which fires after credentials are verified but before the login session is created. If the user’s account_approved meta value is not true, the hook returns a WP_Error that blocks the login and shows an appropriate message:

add_filter("wp_authenticate_user", "block_unapproved_casting_directors", 10, 2);
function block_unapproved_casting_directors($user, $password) {
    if (is_wp_error($user)) return $user;

    if (in_array("casting_director", (array) $user->roles)) {
        $approved = get_user_meta($user->ID, "account_approved", true);
        if ($approved !== "true" && $approved !== true) {
            return new WP_Error(
                "account_pending",
                "Your account is pending approval. You will be notified when access is granted."
            );
        }
    }
    return $user;
}

Admin approval is done manually: the admin opens the user’s profile in wp-admin/user-edit.php, scrolls to the Casting Director meta section, and sets the Goedgekeurd (Approved) ACF field to true. On next login attempt the user passes through.

Task 8 and 9: Casting Management Interface

Casting Directors needed to create tom_project posts and see both their own castings and the actors who had applied. The capability assignment in the role registration handled the creation side. For the overview, a shortcode renders a filtered list of tom_project posts authored by the currently logged-in Casting Director, with each casting expandable to show its list of applicants stored in post meta.

Debugging the Actor Avatar in the Mobile Header

One issue that came up during implementation was an actor profile photo not appearing in the mobile navigation header. The image was stored correctly in ACF (confirmed by a debug snippet that rendered the URL and a preview image in the page footer), but was not displaying in the menu area. The root cause was that the avatar was being passed to a JavaScript variable (userImg) which was then expected to set a background-image on a DOM element — but the element selector in the existing script did not match the updated HTML structure. Adding an explicit injection for the correct element resolved it.

Admin Meta Viewer

To make all ACF and custom field values inspectable during development without installing a debug plugin, a small snippet was added via WPCode that appends a read-only table of all user meta key/value pairs to the bottom of wp-admin/user-edit.php, visible only to administrators. This was useful throughout the project for verifying that form submissions were storing data under the expected keys and that ACF field names matched what the template code was reading.

Summary

TaskSolution
YouTube thumbnail 404JS snippet strips ?si= from data-bg before lazy load runs
Mobile menu for actorswp_nav_menu_args filter swaps menu for subscribers
Casting Director roleCustom role registered with targeted capabilities
Frontend registration formCustom HTML form + init handler, stores to ACF user fields
Admin approval gatewp_authenticate_user hook checks account_approved meta
CTA buttons on Top of MindShortcode or direct HTML insertion in page template
ACF fields for CD profileACF field group scoped to casting_director role
Create and manage castingstom_project capabilities on role + frontend shortcode
Admin meta debug viewWPCode snippet appends all user meta to user-edit.php

This article is part of a case study series:

WordPress Custom Role System for a Dutch Actor Platform

Read the full case study →