There seems to be various JavaScript+browser specific ways of decompressing this, but isn't there some way to transform jsonlz4 files to something unlz4 will read?


7 Answers 7


I was able to unpack the jsonlz4 by using lz4json:

apt-get install liblz4-dev
git clone https://github.com/andikleen/lz4json.git
cd lz4json
./lz4jsoncat ~/.mozilla/firefox/*/bookmarkbackups/*.jsonlz4
  • 1
    The andikleen solution is also good for .json.mozlz4 files e.g. as shown at github.com/andikleen/lz4json/issues/1#issuecomment-336729026 (note to self: remember, remember, gmake on FreeBSD …). Oct 15, 2017 at 18:02
  • 5
    Also: bugzilla.mozilla.org/show_bug.cgi?id=1209390#c4 (2016-05-13) under Mozilla bug 1209390 - Use standard lz4 file format instead of the non-standard jsonlz4/mozlz4 draws attention to avih/dejsonlz4: Decompress Mozilla Firefox bookmarks backup files Oct 19, 2017 at 1:40
  • 1
    FWIW, andikleen's tool failed to compile, with the error "undefined reference to LZ4_decompress_safe_partial" (I did install liblz4-dev prior to building it). avih's tool, OTOH, worked perfectly for me.
    – waldyrious
    Nov 6, 2017 at 17:48
  • 3
    Isn't it ironic that an open-web org is using a proprietary compression format for user's data, making it non-trivial to examine your own data?!
    – cnst
    May 8, 2018 at 4:42
  • @Graham-Perrin : dejsonlz4 worked very well for me. It doesn't "transform jsonlz4 files to something unlz4 will read" as requested but directly decompresses them. It would be good to make it a real answer to make it more visible.
    – mivk
    Jun 18, 2018 at 18:26

Save this script in a file, e.g., mozlz4:

#!/usr/bin/env python
from sys import stdin, stdout, argv, stderr
import os
    import lz4.block as lz4
except ImportError:
    import lz4

stdin = os.fdopen(stdin.fileno(), 'rb')
stdout = os.fdopen(stdout.fileno(), 'wb')

if argv[1:] == ['-c']:
    stdout.write(b'mozLz40\0' + lz4.compress(stdin.read()))
elif argv[1:] == ['-d']:
    assert stdin.read(8) == b'mozLz40\0'
    stderr.write('Usage: %s -c|-d < infile > outfile\n' % argv[0])
    stderr.write('Compress or decompress Mozilla-flavor LZ4 files.\n\n')
    stderr.write('\t%s -d < infile.json.mozlz4 > outfile.json\n' % argv[0])
    stderr.write('\t%s -c < infile.json > outfile.json.mozlz4\n' % argv[0])
  • 1
    I had to change import lz4 to import lz4.block as lz4, but it still didn't work. Some bytes vs string related error. OTOH this script worked with the import change: gist.github.com/Tblue/62ff47bef7f894e92ed5
    – user31389
    Apr 19, 2018 at 11:45
  • 1
    @user31389: I updated the script. Does it work now? Apr 20, 2018 at 21:44
  • 2
    Wasn't working for me until I did $ pip install lz4.
    – Daniel
    Jun 14, 2019 at 23:51
  • in macOS, I tried to install lz4 by installing pip from homebrew first, to no avail
    – knocte
    Oct 4, 2021 at 8:58
  • Could you please explain why you are doing what you are doing in the script (maybe add explaining comments)? What is the line stdout.write(b'mozLz40\0' + lz4.compress(stdin.read())) doing? It looks like it is compressing something rather than decompression; why do you do that when the goal was to decompress jsonlz4 files? Jan 21, 2023 at 13:18

There are now simpler solutions for both Linux and Windows, with command-line programs to decompress these annoying .jsonlz4 or .mozlz4 files.

For Debian/Ubuntu, there is now the lz4json package:

sudo apt install lz4 lz4json

It provides lz4jsoncat :


lz4jsoncat - decompress tool for mozilla lz4json format


lz4jsoncat somefile.mozlz4 > somefile.json


lz4jsoncat can unpack lz4json files as generated by Firefox's bookmark backups and session restore. The data is dumped to stdout.

For Windows (or Linux), there is also mozlz4. There are binaries available at the releases page.

  • I think lz4json is enough. I don't have lz4 installed and lz4jsoncat still decompresses the file.
    – Daniel
    Oct 31, 2022 at 11:45
  • @Daniel: yes, it seems that lz4json depends on liblz4 anyway, at least in Debian/Ubuntu. So that should be enough.
    – mivk
    Oct 31, 2022 at 16:49

Sufficiently persistent Googling for this turns up a lot of solutions, but most of them seem to be either (a) broken by subsequent changes to underlying libraries, or (b) unnecessarily complex (at least to my personal taste), making them clunky to drop into existing code.

The following appears to work at least on Python 2.7 and 3.6 using a recent version of the Python LZ4 bindings:

def mozlz4_to_text(filepath):
    # Given the path to a "mozlz4", "jsonlz4", "baklz4" etc. file, 
    # return the uncompressed text.
    import lz4.block
    bytestream = open(filepath, "rb")
    bytestream.read(8)  # skip past the b"mozLz40\0" header
    valid_bytes = bytestream.read()
    text = lz4.block.decompress(valid_bytes)
    return text

Of course this does not attempt to validate inputs (or outputs), is not intended to be secure, etc., but if one just wants to be able to parse one's own FF data, it gets the basic job done.

Command line version here, which could be saved in the relevant directory and invoked from the command line as:

chmod +x mozlz4.py
./mozlz4.py <file you want to read> <file to save output to>

Actually almost all Firefox profile lz4 files are mozlz4 files. It means they have the same "file format header". Except one file. I talk about webext.sc.lz4 file. It has mozJSSCLz40v001\0 file header and maybe some sc packaging to pack group of files to on byte stream.

There is a Firefox addon to read or compress .mozlz4 text files mozlz4-edit

  • It does have a different header, but the format's the same: magic (this time it's 15 characters not 8)+extracted size in 4 bytes LE (uint32). It's not "some sc packaging", but a startup cache file for the extensions installed in the browser (sc=startup cache). I don't think it's good for anything other than forensic use. You can check the original Mozilla code here: hg.mozilla.org/mozilla-central/rev/09a4282d1172
    – CoolKoon
    Apr 23, 2022 at 21:19

The accepted solution doesn't work on any non-Linux systems; I forked to https://github.com/cnst/lz4json to make it compile cleanly on all other UNIX® systems, including the BSDs; here's a sample for Mac OS X:

Mac OS X UNIX® w/ MacPorts

sudo port install lz4
git clone https://github.com/cnst/lz4json.git
cd lz4json
./lz4jsoncat ~/Library/Application\ Support/Firefox/Profiles/CHANGE\
THIS.default/sessionstore-backups/recovery.jsonlz4 \
| python -m json.tool | fgrep :textarea | more

I also fixed compilation issues on FreeBSD and NetBSD w/ pkgsrc at https://github.com/cnst/lz4json; adding support for other systems like Fink or Homebrew would be trivial in my version, too; I welcome pull requests for any other UNIX systems (the author of the original tool is only interested in GNU/Linux support, refusing a pull request to add non-Linux support).

Also, here's the solution that's using Firefox directly: https://superuser.com/a/1363748/180573.


Python 3 solution.
Needs lz4: pip install lz4

Save the code as .py file. Then you can drag & drop files on it or use the command line to access it.

It only accepts one file path. If that file has a Mozilla LZ4 extension (.lz4, .mozlz4, .jsonlz4, .baklz4) it will decode, otherwise it will try to encode the file (always to the same folder the original file was in). You will get an overwrite warning/question in case the output file already exists.

from sys import argv, exit
import os

    import lz4.block as lz4
except ImportError:
    print("Please install lz4 via   'pip install lz4'.")

EXTENSIONS = {"1": ".lz4", "2": ".mozlz4", "3": ".jsonlz4", "4": ".baklz4"}

def check_overwrite(path):
    if os.path.exists(path):
        i = input(f"File {path} already exists. Overwrite? y/n\n")
        if i.lower() == "y":
            print("Overwriting file.")
            return True
        return True

def print_info():
    print(f"Usage: {argv[0]} <infile>")
    print("Decompress Mozilla-LZ4 encoded <infile> or compress <infile> to Mozilla-LZ4.")
    print("Output file will be put in same folder as input file.")

def choose_ext():
    print(f"Please choose file extension:")
    for k, v in EXTENSIONS.items():
        print(f"{k}: {v}")
    if (i := input()) in EXTENSIONS.keys():
        return EXTENSIONS[i]
        print("Invalid extension.\nExiting")

def main():
    if len(argv) != 2:

        if not os.path.exists(argv[1]):
            in_full_name = os.path.basename(argv[1])
            in_folder_path = os.path.dirname(argv[1])
            in_name, in_ext = os.path.splitext(argv[1])  # "search.json", ".mozlz4"
            in_file_handle = open(argv[1], "rb")

            if in_ext.lower() in EXTENSIONS.values():
                ''' Decompress '''
                print(f"Trying to decompress {in_full_name}")
                out_file = os.path.join(in_folder_path, f"{in_name}")
                if check_overwrite(out_file):
                    with open(out_file, "wb") as f:
                        assert in_file_handle.read(8) == b"mozLz40\0"

                ''' Compress '''
                print(f"Trying to compress {in_full_name}")
                ext = choose_ext()
                out_file = os.path.join(in_folder_path, f"{in_full_name}{ext}")
                if check_overwrite(out_file):
                    with open(out_file, "wb") as f:
                        f.write(b"mozLz40\0" + lz4.compress(in_file_handle.read()))

if __name__ == '__main__':

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .