Thursday, June 25, 2026

WordPress Null Handling Confusion in Custom Fields

I recently built a WordPress plugin that was going to handle custom fields. The functionality was similar to the plugin ACF (Advanced Custom Fields). However, I struggled with understanding how the null handling was made.
After some discussions with ChatGPT it seemed that WordPress treated null as "nothing existing in the database".
To complicate things further, I also used the postmeta table in WordPress which has a EAV type structure, which is used to create a flexible way of adding custom fields.
The structure of the table is: meta_id, post_id, meta_key and meta_value, where meta_key are the name of a custom field added (a so called meta field) and meta_value is the value the user puts in that field.
Simple enough.

So by going by the way, allegedly, WordPress sees null, if nothing is stored in the custom field, neither meta_key nor meta_value is stored and WordPress reads this as null.

This was my initial understanding of null in WordPress and I figured I would adhere to this logic. However, when saving from the Gutenberg editor and not from the front-end posting, a discrepancy was noted.
When posts where submitted from the front-end, it behaved like a WordPress "null": neither meta_key or meta_value was saved, if nothing had been put in that custom field.
However, from Gutenberg, when saving posts there it would save a meta_key and nothing in the meta_value field. The value was however not a database null, but likely an empty string.

This was the behavior I noticed in ACF.
So in the case of a post where many custom fields had been added, when saving from Gutenberg it saved multiple empty fields in the database. After some research I figured that was mostly a space concern which may not be so important, but as it had some type of exponential growth it could easily start to fill up this table with many empty values.

That didn't seem great but as I was writing code with some requirements, I simply followed the logic I thought was necessary. But I also thought, intuitively that maybe deleting both meta_key and meta_value would be a good thing and created a "nullable option" for the custom fields.

This followed the WordPress null logic, where nothing in the database meant "nullable => true" and when keeping meta_key:s, then "nullable => false".

The problem was that this caused some confusion, both for myself and the team that were using the code.

After an extra check, I realized that this was not how ACF handled it when their nullable option was set ("allow null"). To ACF "allow null" meant rather converting empty strings to null values in PHP. And when stored in the database, null was converted to empty string.

Something like this simple schematic:

Database <- '' <- null 
'' -> null -> PHP

Now, ACF used their own function to handle this feature, as a layer in PHP. It was called get_field().
It turns out WordPress has its own function that gives the same result: get_post_meta()
where the null handling however, looks a little bit different:

When using

get_post_meta($post_id, 'fieldname' , true) it returns the first value in a array of values: [value*]

and when using
get_post_meta($post_id, 'fieldname' , false) it returns all of the values in a array of values: [value1, value2]

* value = for example: 1, or "string"

So when the field is empty, it will return [] (nullable, or null)
or [''] when the field contains an empty string ('')

(Whereas ACF seem to do (convert): [value] => value, [''] = null, [] = null.)

Now this functionality wasn't requested and it turned out that deleting both meta_key and meta_value was the requested behavior, hence my code worked using "nullable => true".
But of course, the confusion prevailed until I changed the name of the option from nullable to "remove_when_empty".

Semantically this made a lot more sense and since null handling wasn't requested, no further adjustments needed to be done at that point.

What can also be added, however, is that when creating these options, I had to handle the empty values, and normalize them to empty strings (' ') if no value was added.

When I write this writeup I realize it is still somewhat confusing and by writing this text I try to create some clarity also for myself, besides telling a story about this mysterious null issue.

Maybe it is not so mysterious but I think that I begin to understand the issues that has been raised about "null hell". In this case, I think it stemmed from a question of definition. What is null, really?
How should null be defined?

Going back to theory, it could be conceived as:  null => 'nothing' was ever inserted at all,
while empty string (' '), or ('') means the same as 0.
The difference could for example be a user field, where either "no users has been added", or "users has been added, but was removed".

It is a slight difference in these cases but enough to create a lot of confusion.

tl; dr: I implemented a null that wasn't null, so eventually the function was renamed to remove_when_empty.

No comments:

Post a Comment

WordPress Null Handling Confusion in Custom Fields

I recently built a WordPress plugin that was going to handle custom fields. The functionality was similar to the plugin ACF (Advanced Custom...