Redis values are bytes: a byte-exact key editor
How SQLly's Redis key view pages, edits and decodes values without turning 00014 into 14: exact commands first, KEEPTTL, hex edits, codecs and live tests.
The short version
A Redis value is bytes. Everything SQLly shows you on top of those bytes is presentation.
Opening a Redis key in SQLly now opens a key view: a header strip with the key's type, TTL,
encoding, memory and length, the value in pages, and the edits that type allows. Every edit
shows the exact command before it runs. Under all of it is one rule: the bytes that come out
of Redis are the bytes that go back in. 00014 stays 00014, an empty
string stays empty, and a value that isn't text is shown and edited as hex.
Why one rule
Redis has no column types. GET, HGETALL and LRANGE all
return bulk strings, and whether those bytes are a zip code, a gzip blob or a MessagePack
document is up to the application that wrote them.
A GUI that reads 00014 as a number has changed your data the moment it writes it
back or exports it as 14.
A reported issue against Redis Insight,
#6269, describes hash field
values with leading zeros, such as 00014, being displayed as numbers in its Browser
view. Any tool that infers types from text can make that mistake. We designed SQLly so it can't,
and then wrote tests to prove it.
The key view
Double-click a key in the explorer, or right-click it and choose Open Value. The view opens on the key's own server and logical database, whichever tab is active. Redis Key View… in the command palette opens the same view with a box for a key name.
The header strip
The strip comes from one virtual command, SQLLY.KEYINFO <key>, which is made
only of read-only probes: TYPE, TTL and OBJECT ENCODING
pipelined, then MEMORY USAGE … SAMPLES 5, then the length command for the type
. A server or ACL user that
can't run MEMORY USAGE gets not measured, not an error. A key that has
disappeared answers with type none, so the view can say the key is gone instead of
failing.
Paged readers and "showing N of M"
A hash with 48,213 fields should not become a 48,213-row surprise, and it shouldn't be silently cut off at some fixed range either. The view reads a hash, list, set, sorted set or stream 1,000 fields, elements, members or entries at a time, and the title says how much you're looking at: showing 1,000 of 48,213 fields. Load more appends the next page, and Refresh starts again from the beginning. Strings longer than 1 MB load in 1 MB windows and report their full length. A JSON document loads whole.
Each page comes from a paged reader (SQLLY.HPAGE, ZPAGE and so on) that
pairs a bounded read such as HSCAN or ZRANGE … WITHSCORES with the
key's length. Values, fields, members and scores stay bulk strings: text when they are valid
UTF-8, bytes otherwise. Nothing is parsed into a number.
redis-key-view-hash-paged.webp — a hash key in the key view with the header strip, the "showing 1,000 of N fields" title and Load more, and one field selected so its value shows under the gridEditing, one exact command at a time
Each type offers the edits it allows, and every form shows the command it will run, updated as you type. Nothing runs until you press Run.
| Type | Buttons | What runs |
|---|---|---|
| string | Edit value… | SET key value KEEPTTL (KEEPTTL ticked by default when the key has an expiry) |
| hash | Add field…, Edit selected…, Delete selected… | HSET key field value / HDEL key field |
| list | Set selected…, Push to head…, Push to tail…, Remove selected… | LSET key index value / LPUSH / RPUSH / LREM key 1 value |
| set | Add member…, Remove selected… | SADD key member / SREM key member |
| sorted set | Add member…, Edit score of selected…, Remove selected… | ZADD key score member / ZADD key XX score member / ZREM key member |
| JSON | Edit document… | JSON.SET key $ document |
| stream | Entries, Groups, Consumers and Pending tabs | see the streams post |
KEEPTTL by default. A plain SET clears a key's expiry. Editing a
cached value in a GUI shouldn't quietly make it permanent, so when the key has a TTL the string
form ticks Keep the key's expiry (KEEPTTL) for you. Untick it and the form says
"Without KEEPTTL a SET also clears the key's expiry." The preview reads
SET k 00014 KEEPTTL or SET k 00014, whichever you'll actually get.
LREM removes by value. Redis has no "delete at index", so Remove
selected runs LREM key 1 value, and the form says it removes by value, not
position.
Hex for values that aren't text
A value that isn't valid UTF-8 is shown as hex, and its edit form switches to hex mode, so you edit the bytes themselves. You can tick Value is hex (bytes, e.g. ff 00 0a) yourself for any value. The command preview writes binary arguments as redis-cli escapes, the same spelling you would type:
HSET session:9f2c token "\xff\x00\x0a"
The reviewed write path
The key view carries the same guards as a query tab:
- A connection marked read-only refuses every edit and says why.
- A production connection whose writes are locked refuses too, until you unlock writes from the status-bar chip. The lock is read again right before the command runs, because an unlock can expire while the view sits open.
- When the connection's safety settings would make a query tab confirm a data change (an unlocked production connection, or Confirm data changes on the connection), Run becomes Confirm Run and needs a second click on the command shown.
After a successful edit the key reloads, so you see what the server holds now.
redis-key-view-edit-keepttl.webp — the Edit value form on a string key with an expiry, KEEPTTL ticked, the value 00014 typed, and the preview SET … 00014 KEEPTTL above a Confirm Run buttonDisplay is a lens: the value codecs
Below the grid, the selected value has a format picker, and the value viewer has the full menu: Auto, UTF-8, JSON, Hex, MessagePack, Base64, Protobuf, gzip, zstd, LZ4, deflate, PHP, Java and Python pickle. Whatever you pick, the bytes in the key never change. Formats only change what you see.
- Compression (gzip, zstd, LZ4, deflate) decompresses, then shows the result as whatever it contains, so a gzip-wrapped JSON document opens as a JSON tree. One layer is unwrapped per pick. zstd isn't available in the browser edition.
- Base64 (standard or URL-safe, padded or not) decodes, then the decoded bytes get the same treatment.
- Protobuf without a schema: field numbers become keys, and each value shows as a number, text, a nested message or bytes. Field names need the schema, so you don't get them.
- PHP
serialize()output decodes to JSON, objects included. - Java and Python pickle are recognised and summarised: classes, opcodes, globals and strings. They are never rebuilt. Rebuilding a pickle means running its instructions, and a database viewer has no business doing that.
- MessagePack decodes to JSON, with timestamps as
{"$timestamp": "…"}and colliding map keys kept as{"$map": […]}so no entry is lost.
Auto sniffs leading bytes and structure (compression magic numbers, Java and
pickle headers) and names what it found beside the menu, for example "Auto: gzip → JSON (1,234 → 5,678 bytes)". The structural guesses are
conservative on purpose. The whole value must parse, and Base64 is only guessed for 16 or more
characters that decode to something readable, so user:42 or a hex id stays text.
Every decoder treats the value as untrusted input. Decompressed output is capped at 16 MB, and a gzip trailer, zstd frame header or LZ4 prefix that already claims more than that is refused before decompression starts. Protobuf, PHP and MessagePack nesting stops at 128 levels. Every length inside a value is checked against the bytes actually present, and a truncated stream is reported as not decodable instead of being shown half-decoded.
The choice is remembered per key across restarts (the 2,000 most recently chosen keys), so a compressed payload reads correctly every time. Choosing Auto forgets it. Details are on Value formats.
redis-value-formats-gzip-auto.webp — the value viewer on a gzip-compressed JSON value with Format set to Auto and the note "Auto: gzip → JSON (… → … bytes)" beside the menu, the JSON tree belowProving it: the byte-exact suite
"We never coerce values" is easy to say. So we wrote a live test suite around the values that break tools that do:
00014, a number with leading zeros- the empty string
- a single NUL byte
0xff 0xfe, which is not valid UTF-8- a 40-digit integer, far past any 64-bit type
1e309, a float that overflows to infinity if anything parses it- a 100 KB string and a 2 MB string
Each value is written with SET through the script path, quoted the way the editor
and dialogs quote it. It is read back through the streaming path and the materialized path, and
then with a raw GET to prove the server holds exactly those bytes. Every
cell must come back as text (valid UTF-8) or bytes (anything else), never as an integer, a float
or a null. The same values are used as key names in the key browser listing, and as field names and
values through HGETALL, the empty field name included. They also go through the Pub/Sub viewer,
where they arrive byte-exact up to the viewer's 64 KiB stored cap. Above the cap, the payload is
stored as the exact 64 KiB prefix, the full length is recorded and the truncation is marked.
On 2026-10-19 the suite passed live against Redis 7.4.11, Redis Stack, Valkey 8.1.10, KeyDB,
DragonflyDB df-v2.0.2 and a TLS-only Redis. In exports, from a Redis string column, 00014, the 40-digit integer, 1e309 and
1e5 export as quoted text to JSON and SQL scripts, as the literal digits to CSV, and
as inline strings in Excel. The rule is the column's type, not what the digits look like.
You can try the classic case in a query tab on any Redis 7 server:
HSET zip:test code 00014
HGET zip:test code
SET bin:test "\x00\xff"
STRLEN bin:test
HGET shows 00014, text in the grid. STRLEN says
2, and opening bin:test shows the hex 00 ff.
Rename, copy and export, still byte for byte
The key menu also has Rename… (RENAMENX, or RENAME if
you tick Replace), Copy to Database… (COPY key key DB n,
Redis 6.2 and later) and Copy to Connection… (DUMP on this
connection, RESTORE on the one you pick, expiry included). Each one shows the exact
command and respects read-only and production locks on both ends.
For whole folders, Export Folder as Command File… (or Export as Command
File… on a database) writes the commands that rebuild the keys: SET,
HSET, RPUSH, SADD and ZADD in lines of up to
500 members, one XADD per stream entry with its original id, JSON.SET,
then PEXPIRE for keys with a TTL. Binary key names and values are written as quoted
"\xHH" escapes, so Import Command File… on an empty database gives
back the same bytes and the same number of keys. A few lines of an export look like this
(illustrative):
SET "bin:\xff\x01" "\x00\xff"
HSET zip:test code 00014
PEXPIRE zip:test 86400000
The export only reads, so it works on read-only connections. It stops at the key limit you choose
(10,000 by default) and at 8 MB, the import's file limit, so any export can be imported again. A
stopped export ends with a # partial line, and types the import can't rebuild are
listed as # skipped comments rather than left out silently.
redis-export-command-file-binary.webp — a .redis file produced by Export Folder as Command File… opened in a text editor, showing a binary key and value written as "\xHH" escapes and the closing completeness lineWhat isn't done
- Strings over 1 MB aren't edited in the view. Read them in windows, and change them with
SETorSETRANGEin a query tab. - There is no editor for vector sets yet.
- Java and pickle payloads are summarised, not decoded into field values, and Protobuf has no field names without a schema.
- Stream consumer groups aren't exported, only the entries. Importing on top of existing keys appends to lists and streams, so import into an empty database.
The full reference is on Key view, Value formats and Bulk actions. To try it on your own keys, download SQLly, connect to a Redis or Valkey server, and double-click a key.