Discuss your project

Why WordPress Still Uses Serialization (And Not JSON)?

/* by - February 23, 2026 */
Why WordPress Still Uses Serialization (And Not JSON)

If you’ve ever peeked inside a WordPress database — even just out of curiosity — you’ve probably stumbled across something that looks like this:

a:2:{s:5:"color";s:3:"red";s:4:"size";s:5:"large";}

You probably recognized it right away — that’s PHP serialization. And it’s hiding in WordPress databases everywhere — quietly doing its job while most developers never give it a second thought.

But Why WordPress Still Uses Serialization? Is it actually a good idea? And should you be using it in your own code?

Let’s dig in.


So, What Even Is Serialization?

When WordPress needs to save something complex to the database — like an array of settings, or a nested configuration object — it can’t just dump it in as-is. Databases store text. PHP arrays are not text.

So WordPress converts the PHP value into a flat string using PHP’s built-in serialize() function. When it needs to read it back, it runs unserialize() and gets the original PHP value back, intact.

Here’s a quick example:

$data = [
    'color' => 'red',
    'size'  => 'large',
];

$serialized = serialize($data);
// Stores: a:2:{s:5:"color";s:3:"red";s:4:"size";s:5:"large";}

$original = unserialize($serialized);
// Gets back: ['color' => 'red', 'size' => 'large']

Simple enough. But here’s the question worth asking — why not just use JSON?


Why Not JSON? (Fair Question)

You’d think in 2026, WordPress would just use json_encode() and json_decode(). It’s readable, it’s universal, and every developer on the planet knows it.

The honest answer? It’s a historical accident.

WordPress was built in the early 2000s. Back then, json_encode() wasn’t even a reliable built-in PHP function — it only became stable in PHP 5.2, released in 2006. By that time, WordPress already had serialization baked deep into its core.

So the team went with serialize() — the PHP-native solution available at the time — and it worked. The problem is, it worked so well that millions of plugins, themes, and sites were built on top of it. And now, changing it would break the internet. (Or at least, a significant chunk of it.)

WordPress is famously committed to backwards compatibility. That’s actually one of its superpowers — old plugins from 10 years ago still work. But that commitment also means living with some older decisions, like this one.


Okay But Does Serialization Offer Anything JSON Doesn’t?

Actually, yes — a few things.

1. It handles PHP-specific types

JSON doesn’t know what a PHP object is. If you serialize an object, PHP remembers its class name and can reconstruct it perfectly:

class ThemeSettings {
    public string $layout = 'grid';
    public bool $darkMode = true;
}

$settings = new ThemeSettings();
$serialized = serialize($settings);

// Later...
$restored = unserialize($serialized);
// $restored is still a ThemeSettings object, not a plain stdClass

With JSON, you’d get a generic stdClass back — the class name is lost. For WordPress core internals that pass around typed objects, this matters.

2. It preserves exact scalar types

Serialization knows the difference between true (boolean), 1 (integer), and "1" (string). JSON can be ambiguous in some edge cases. For strict type preservation, serialize() wins.


Where Does WordPress Actually Use It?

More places than you might expect. It’s not just the wp_options table.

The pattern shows up across pretty much every table that has a meta_value column:

  • wp_options — the most well-known one. Things like active plugins list, widget settings, theme mods.
  • wp_usermeta — user roles and capabilities are serialized here. That’s why you see a:1:{s:13:"administrator";b:1;} if you look up a user’s capabilities.
  • wp_postmeta — when plugins store arrays as post meta (think WooCommerce product attributes, ACF field groups, etc.), they end up serialized.
  • wp_commentmeta, wp_termmeta, wp_sitemeta — same pattern, different tables.

The WordPress Meta API (add_post_meta, update_user_meta, etc.) handles the serialization transparently. You pass in a PHP array, it serializes it. You get it back, it’s already unserialized. You rarely have to think about it.

// Saving - WordPress serializes automatically
update_post_meta($post_id, 'product_options', [
    'color'    => 'blue',
    'material' => 'cotton',
    'sizes'    => ['S', 'M', 'L', 'XL'],
]);

// Retrieving - WordPress unserializes automatically
$options = get_post_meta($post_id, 'product_options', true);
// $options is already a PHP array, ready to use

Clean, right? The complexity is hidden from you.


So What’s the Catch?

Now for the part that trips up developers (and has probably caused more than a few late-night headaches).

Problem 1: Direct SQL queries don’t work like you’d expect

Serialized data is opaque to SQL. If you store an array and try to search for a value inside it with a plain WHERE clause, you’re going to have a bad time:

-- This WON'T work as expected
SELECT * FROM wp_postmeta
WHERE meta_key = 'product_options'
AND meta_value LIKE '%blue%';

It might work accidentally for simple cases, but it’s fragile. The right approach is to either use WP_Query with meta_query, or restructure your data so each searchable value has its own meta key.

Problem 2: Database migrations are a nightmare

This is the big one. If you’ve ever migrated a WordPress site and done a search-replace on the database — swapping the old URL for the new one — you’ve encountered this problem, even if you didn’t realize it.

Here’s why. Serialized strings store the byte length of each value:

s:22:"https://old-domain.com";

The s:22 means “this string is 22 characters long.” If you do a naive find-replace and swap old-domain.com with new-domain.com (same length, fine) — no problem. But if the lengths differ:

// After find-replace (wrong!)
s:22:"https://new-longer-domain.com";
// The count says 22, but the actual string is now 30 characters. BROKEN.

WordPress will silently fail to unserialize that value. Your settings disappear. Your widget configurations vanish. And you spend hours wondering what went wrong.

This is why tools like WP-CLI’s search-replace command and plugins like Better Search Replace exist — they’re serialization-aware and update the byte counts correctly.

# The safe way to do a URL migration with WP-CLI
wp search-replace 'https://old-domain.com' 'https://new-domain.com' --all-tables

Problem 3: Security considerations

PHP’s unserialize() can trigger magic methods like __wakeup() and __destruct() on objects. If you ever unserialize untrusted user input, you’re opening a potential security hole. WordPress handles this safely in its own core, but it’s worth being aware of if you’re writing custom code.


What Should You Do in Your Own Code?

If you’re building a plugin or theme today, here are some practical guidelines:

Use the Meta API for simple storage — it handles serialization transparently and you don’t need to think about it.

Store JSON yourself for complex queryable data — if you need to query inside your data with SQL, storing a JSON string manually (via wp_json_encode) and parsing it yourself gives you more flexibility:

// Storing as JSON manually
update_post_meta($post_id, 'my_settings', wp_json_encode($data));

// Retrieving
$raw     = get_post_meta($post_id, 'my_settings', true);
$settings = json_decode($raw, true);

Never run find-replace on serialized data manually — always use WP-CLI or a serialization-aware tool.

Never unserialize untrusted input — if data is coming from a user or an external source, validate and sanitize it properly before any unserialization.


The Bottom Line

WordPress uses serialize() because it was the right tool at the right time, and it’s stayed because changing it would break too much. It has genuine advantages for PHP-specific data types, and WordPress’s Meta API hides most of the complexity from you.

But it comes with real trade-offs: opaque SQL queries, fragile migrations, and a bit of security surface area to be mindful of.

Understanding why it works the way it does makes you a better WordPress developer. You stop being surprised by the weird strings in your database, you know to use the right search-replace tools during migrations, and you make smarter decisions about when to lean on serialization versus rolling your own JSON storage.

Next time you see a:2:{s:5:"color";s:3:"red"...} in your database, you’ll know exactly what’s going on — and more importantly, you’ll know how to work with it safely.