PHP Unserialize Online

Paste the output of PHP's serialize() from a database column, a session file or a WordPress wp_options row and read it as JSON, print_r, var_export or var_dump. The parser counts string lengths in UTF-8 bytes like PHP does, so s:5:"café" works, and when a search-and-replace has left the lengths wrong it finds the real ends and offers to fix them. The parser is written in JavaScript and runs in this tab, so no PHP executes and nothing is uploaded.

Serialized PHP
Result

1 PHP Unserialize Online example

serialize() output

O:5:"Order":5:{s:2:"id";i:1042;s:8:"customer";s:12:"Zoë Müller";s:5:"items";a:1:{i:0;a:3:{s:3:"sku";s:8:"CAFÉ-01";s:3:"qty";i:2;s:5:"price";d:4.5;}}s:4:"paid";b:1;s:4:"note";N;}

As JSON

{
  "__class": "Order",
  "id": 1042,
  "customer": "Zoë Müller",
  "items": [
    {
      "sku": "CAFÉ-01",
      "qty": 2,
      "price": 4.5
    }
  ],
  "paid": true,
  "note": null
}

Look at s:12:"Zoë Müller": 10 characters, 12 bytes, because ë and ü take two bytes each in UTF-8. A parser that counts characters gets this wrong and either fails or cuts the string short.

2 Each token names a type

Token Meaning
N;null
b:1;boolean, 1 or 0
i:42;integer (64-bit, kept exact even past JavaScript's 2^53)
d:0.5;float, also INF, -INF and NAN. JSON has no way to write those, so they come out as strings
s:5:"café";string, length in bytes
a:2:{...}array with 2 key/value pairs. Keys are i: or s:
O:5:"Order":3:{...}object of class Order with 3 properties. Shown with a "__class" key
C:11:"ArrayObject":21:{...}class with its own Serializable format. The payload is shown raw under "__serialized"
E:11:"Suit:Hearts";enum case (PHP 8.1+)
r:3; R:3;reference to the 3rd value parsed (object reuse, and PHP &references). Resolved in the output; a loop prints *RECURSION*

Property names carry their visibility in NUL bytes: \0*\0email is protected, and \0Customer\0tier is private to Customer. The output uses print_r's convention, email:protected and tier:Customer:private. Some database clients drop those NUL bytes when you copy serialized data, which breaks each affected length by two. The repair below handles that, although the visibility information is lost.

3 Broken lengths and WordPress

The most common way to break serialized data is a plain SQL REPLACE() during a domain move: s:19:"http://example.com/" becomes s:19:"https://example.org/", a 20-byte string with a declared length of 19. unserialize() then returns false with an "Error at offset" warning, and WordPress falls back to default widget and theme settings without telling you why.

When a length is off, this tool looks for the nearest "; that is followed by something valid, shows the result with a warning, and enables Fix lengths, which rewrites only the wrong numbers in your input. Check the result before writing it back, since a string that itself contains "; can fool the guess. For a real migration, use a serialization-aware tool such as wp search-replace.

WordPress also serializes values that are already serialized strings, so a field can hold s:30:"a:1:{...}". Tick Expand nested to decode those inner layers in place. Base64-wrapped input (common in cookies and some cache drivers) is detected and decoded first.

How to use the PHP Unserialize Online tool

  1. Paste serialize() output into the left panel. Base64-wrapped input is detected and decoded automatically.
  2. Choose the Output format: JSON, print_r, var_export or var_dump. var_dump shows byte lengths, which helps when debugging.
  3. If a string length is wrong, the note bar says which ones and Fix lengths rewrites them in your input. Errors show the byte offset and the text around it.
  4. Tick Expand nested to decode string values that contain serialized data themselves, as WordPress often stores them.
  5. Switch Mode to Serialize (JSON in) to go the other way. Swap moves the current result across, and Copy Result copies it.

How to unserialize PHP data in PHP, JavaScript and Python

PHP

<?php
$input = 'a:2:{s:4:"name";s:5:"café";s:4:"tags";a:2:{i:0;s:1:"a";i:1;s:1:"b";}}';

// allowed_classes => false: objects come back as __PHP_Incomplete_Class
// instead of running a class's __wakeup()/__destruct(). Do this for any
// input you did not produce yourself.
$data = unserialize($input, ['allowed_classes' => false]);

// false is also a valid serialized value (b:0;), so check both
if ($data === false && $input !== serialize(false)) {
    throw new RuntimeException('Invalid serialized data');
}
echo json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE), PHP_EOL;

// The other direction. strlen() counts bytes, which is what s:N: uses.
echo serialize(['name' => 'café']), PHP_EOL; // a:1:{s:4:"name";s:5:"café";}

// WordPress: get_option() already unserializes; maybe_unserialize()
// is for values you read straight from the database.

unserialize() returns false and raises an E_WARNING (E_NOTICE before PHP 8.3) on malformed input.

JavaScript

// Minimal serialize() for JSON-shaped data. The one rule people get
// wrong: s:N: is the length in UTF-8 bytes, not string.length.
const enc = new TextEncoder();
const isIntKey = (k) => /^(0|-?[1-9]\d*)$/.test(k);

function phpSerialize(v) {
    if (v === null) return "N;";
    if (typeof v === "boolean") return `b:${v ? 1 : 0};`;
    if (typeof v === "number") return Number.isInteger(v) ? `i:${v};` : `d:${v};`;
    if (typeof v === "string") return `s:${enc.encode(v).length}:"${v}";`;
    const entries = Array.isArray(v) ? v.map((x, i) => [String(i), x]) : Object.entries(v);
    return `a:${entries.length}:{` + entries.map(([k, x]) =>
        (isIntKey(k) ? `i:${k};` : phpSerialize(k)) + phpSerialize(x)).join("") + "}";
}

console.log("café".length, enc.encode("café").length); // 4 5
console.log(phpSerialize({ name: "café", tags: ["a", "b"], price: 4.5 }));
// a:3:{s:4:"name";s:5:"café";s:4:"tags";a:2:{i:0;s:1:"a";i:1;s:1:"b";}s:5:"price";d:4.5;}

Integers above 2^53 and floats in exponent form need more care; this covers JSON-shaped data.

Python

# pip install phpserialize
import json
import phpserialize

raw = 'a:2:{s:4:"name";s:5:"café";s:4:"tags";a:2:{i:0;s:1:"a";i:1;s:1:"b";}}'

# phpserialize works on bytes; decode_strings gives you str back
data = phpserialize.loads(raw.encode("utf-8"), decode_strings=True)
print(data)  # {'name': 'café', 'tags': {0: 'a', 1: 'b'}}

# PHP arrays come back as dicts; turn list-like ones into lists
print(json.dumps(phpserialize.dict_to_list(data["tags"])))  # ["a", "b"]

# Objects need object_hook, e.g. phpserialize.phpobject
print(phpserialize.dumps({"name": "café"}).decode("utf-8"))
# a:1:{s:4:"name";s:5:"café";}

phpserialize is a third-party package on PyPI.

Common errors and how to fix them

Warning: unserialize(): Error at offset 40 of 44 bytes
(A Notice before PHP 8.3.) The cause is almost always a wrong s:N: length, from a search-and-replace, a charset conversion, or copying from a client that dropped NUL bytes. Paste the data here to see which string is off, then click Fix lengths.
unserialize() returns __PHP_Incomplete_Class objects
The class named in O:N:"Class" is not loaded, or allowed_classes excluded it. Load or autoload the class before unserializing, or read the properties by casting to array.
WordPress settings reset to defaults after a domain migration
Someone replaced the old URL in serialized options with plain SQL, which broke the lengths, so WordPress treated the value as invalid. Use wp search-replace, which re-serializes each value, or repair the affected rows here with Fix lengths.
Private properties come out with odd names like \0Customer\0tier
That is how serialize() marks visibility: \0*\0 for protected and \0ClassName\0 for private. This tool shows them as tier:Customer:private; in PHP, cast the object to array to see the raw keys.

Good to know

serialize() is PHP-specific and records types exactly: integers stay integers, floats keep their precision, and objects keep their class name. That class name is also why it is dangerous with untrusted input, since unserialize() can instantiate the class. For data that crosses a trust boundary or another language, use json_encode() instead.

Classes that implement __serialize() (PHP 7.4+) or the older Serializable interface write their own payload. ArrayObject is one example. The tool shows those payloads as they are, which may not match what print_r prints for the live object.

Specifications and references

This tool follows the specifications below. Read them when you need the exact normative wording, for example to settle an edge case between two implementations.

Frequently Asked Questions

Why does unserialize() fail with "Error at offset"?

Usually a string length no longer matches its content, for example after a search-and-replace changed a URL inside serialized data. Paste the data here, and the tool finds the real string ends and enables Fix lengths to rewrite the numbers.

Is the s: length in characters or bytes?

Bytes. s:5:"café" is correct because é takes two bytes in UTF-8. A serializer that counts characters breaks on any non-ASCII text, which makes it the most common bug in hand-written serializers.

How are PHP objects shown in JSON?

As a JSON object with a "__class" key. Protected properties appear as name:protected and private ones as name:Class:private, the same convention print_r uses.

Is it safe to unserialize data from users?

Not in PHP. unserialize() on untrusted input can instantiate arbitrary classes and trigger object injection. Use json_decode(), or at least pass ['allowed_classes' => false]. Parsing it here is safe because no PHP code runs.

Can it serialize JSON back to PHP format?

Yes. Switch Mode to Serialize: JSON objects become PHP arrays with byte-correct lengths, numeric keys become integers like json_decode(..., true) does, and "__class" objects can be rebuilt as O: entries.

Working with PHP data

Once it is JSON, the JSON formatter can validate and fold it. For PHP array literals instead of serialize() output, use PHP array to JSON or go the other way with JSON to PHP array. If the value arrived Base64-encoded, the Base64 decoder shows what is inside.

More Data Converters

Browse all data converters

Copied to clipboard!