When ACF Pro Custom Tables Met WordPress Multisite: Building a Network-Level Learner System
Introduction
While working on an eCoach project built with WordPress Multisite, I faced a database architecture challenge that looked small at first but became an important lesson about plugin limitations, data ownership, and designing scalable solutions.
The project requirement was to use ACF Pro Custom Tables because the client wanted to manage custom fields through the familiar ACF interface. However, the database structure they required was different from the way ACF Pro handles tables in a WordPress Multisite environment.
The client wanted a learner management system where the data was shared across the entire network. The expected database structure was:
wp_learnerwp_learner_meta
Instead of creating separate tables for every website:
wp_1_learnerwp_2_learnerwp_3_learner
The challenge was not creating a database table. The challenge was making the data architecture match the business requirement while still keeping the ACF workflow that the client wanted.
The First Question: Who Owns This Data?
When working with WordPress Multisite, the most important database question is not: "What table name should I create?" The real question is: "Who owns this data?" Does the data belong to one website, or does it belong to the entire network? This decision affects everything.
A WordPress Multisite installation is not simply multiple WordPress websites sharing the same codebase. It is a network where some information belongs to individual sites and some information belongs globally.
Website-specific data could include pages, posts, site settings, theme options, and local content. This data should remain separated per site. However, some data is naturally network-level: shared learners, global reports, network-wide records, and cross-site relationships. This data should exist only once.
In the eCoach project, learners belonged to the entire network. A learner could interact with different sites, so creating a separate learner record for every website would create duplication and synchronization problems. The correct architecture was:
Network Data
wp_learnerwp_learner_meta
Site Data
wp_1_postswp_2_postswp_3_posts
Understanding WordPress Multisite Database Prefixes
The reason this problem happened comes from the way WordPress handles database prefixes. WordPress provides two important values through the $wpdb object:
global $wpdb;
The two values are $wpdb->prefix and $wpdb->base_prefix. They look similar, but they have different purposes.
$wpdb->prefix: The Current Site Prefix
$wpdb->prefix represents the current website's database prefix. For example, if the current site is Site ID 1:
echo $wpdb->prefix; // wp_1_
Creating a table like:
$table = $wpdb->prefix . 'learner';
produces wp_1_learner. If we switch to Site ID 2, the same code produces wp_2_learner. This behavior is correct because WordPress assumes the data belongs to that specific website.
$wpdb->base_prefix: The Network Prefix
$wpdb->base_prefix represents the original network prefix.
echo $wpdb->base_prefix; // wp_
It does not include the site ID. Creating:
$table = $wpdb->base_prefix . 'learner';
produces wp_learner - a table that belongs to the entire network.
The ACF Pro Custom Table Challenge
ACF Pro Custom Tables are a powerful feature because they allow developers to store field data in dedicated database tables instead of relying only on wp_postmeta. For large applications, this can provide better database organization and performance.
However, ACF Pro follows WordPress Multisite rules. When ACF creates a custom table inside a multisite environment, it uses the current site's database context. That means the generated table follows the site prefix:
wp_1_custom_table
instead of:
wp_custom_table
From ACF's perspective, this makes sense because custom fields normally belong to the current WordPress site. But my project requirement was different. The learner data was not site-specific. It belonged to the entire network.
Investigating ACF Hooks and Possible Solutions
My first approach was to investigate whether ACF hooks could change the behavior of table creation. The idea was simple: "Can I tell ACF to use $wpdb->base_prefix instead of $wpdb->prefix?"
After testing, I realized the problem was deeper than just changing a table name. ACF Custom Tables are designed around the idea that the current site owns the data. The internal workflow expects:
Current Site
|
Custom Table
But my requirement was:
WordPress Network
|
Shared Learner Table
These are two different database ownership models. Changing the prefix alone would not completely solve the problem.
Could I Modify ACF Plugin Code?
Technically, it would be possible to modify ACF's internal code and replace $wpdb->prefix with $wpdb->base_prefix. However, this would not be a practical solution. The changes would be lost during plugin updates, and maintaining a modified version of ACF would create long-term problems.
The issue was not that ACF was wrong. ACF was working exactly as designed. The issue was that my project required a different storage architecture.
The Solution: ACF as the Interface, Custom Storage as the Database Layer
After testing the available options, I decided not to fight against ACF's architecture. Instead, I kept ACF as the interface - because it provided the editing experience the client wanted - and created my own storage layer underneath it.
My custom plugin creates the network-level tables (wp_learner and wp_learner_meta) using $wpdb->base_prefix, so the database structure remains shared across all sites in the network.
The bridge between ACF and the custom tables is built with ACF hooks. When an editor opens the field group, acf/load_value reads the data from the shared learner table. When changes are saved, acf/save_post writes the updated values back to the same shared table. The client continues managing learner fields through the normal ACF interface, so their workflow does not change at all.
Here is the pattern in simplified form:
// Load field value from the network learner table
add_filter( 'acf/load_value/name=learner_status', function ( $value, $post_id, $field ) {
global $wpdb;
$table = $wpdb->base_prefix . 'learner';
// read the value from the shared table here
return $value;
}, 10, 3 );
// Save changes back to the shared table
add_action( 'acf/save_post', function ( $post_id ) {
global $wpdb;
$table = $wpdb->base_prefix . 'learner';
// write updated field values here
}, 20 );
The final solution is a hybrid approach: ACF manages the fields and the admin experience, while my plugin controls where and how the data is stored. The client received exactly what they needed - an ACF-powered workflow with a true WordPress Multisite network-level learner system.
What I Learned From This Experience
The biggest lesson was that database prefixes are not just table naming rules. They represent ownership.
- Using
$wpdb->prefixmeans: "This data belongs to this website." - Using
$wpdb->base_prefixmeans: "This data belongs to the entire network."
Before creating any custom table in WordPress Multisite, it is important to define:
- Who owns this data?
- Should this data be shared?
- Should it be duplicated?
- What happens when the network grows?
- How will reporting work?
These questions should be answered before choosing a plugin feature or database structure.
Final Thoughts
This experience changed the way I approach WordPress Multisite projects. A plugin limitation does not always mean the project requirement is impossible. Sometimes it means we need to separate responsibilities.
In this case:
- ACF was responsible for the editing experience.
- My custom plugin was responsible for the storage architecture.
The final result was a solution that respected both the client's workflow and the technical requirements of a scalable multisite platform. The important lesson is simple: Choose your database architecture based on who owns the data, not only based on what a plugin can create by default.
I'm a full-stack PHP developer in the UK building WordPress and WooCommerce platforms. Find me on LinkedIn and GitHub.
Comments
No comments yet. Start the discussion.