# Go-Tools > Free, privacy-first browser tools for developers. All processing runs locally — no data uploads, no tracking. Go-Tools provides fast, secure online utilities for developers: JSON formatting, UUID generation, Base64 encoding, hash generation, and more. Every tool runs 100% in the browser with zero server-side processing. ## Tools ### AES Decryption Tool — OpenSSL & CryptoJS Compatible URL: https://go-tools.org/tools/aes-decrypt Decrypt AES online — GCM/CBC/CTR, passphrase or raw key, auto-detects OpenSSL & CryptoJS "U2FsdGVkX1" format. 100% in-browser, keys never leave the page. #### How AES Decryption Works AES decryption is the exact inverse of encryption: the same symmetric key that scrambled the data runs the cipher's rounds in reverse to recover the original bytes. Because AES is symmetric, there is no separate 'decrypt key' — you must supply the identical key or passphrase, mode, and IV that were used to encrypt. Get the parameters right and the plaintext comes back byte-for-byte; get one wrong and you get an error or garbage. To decrypt correctly, four things must match the encryption exactly: the key (or the passphrase plus its key-derivation settings), the mode of operation (GCM, CBC, or CTR), the IV or nonce, and — for GCM — the authentication tag. Miss any of them and the result is either a hard error (a GCM authentication failure, a CBC padding failure) or silent garbage (CTR, or CBC with the wrong IV). This tool reads the salt, IV, and tag out of the ciphertext for you when they are packed in, and diagnoses the failure when something does not line up. Ciphertext arrives in a few different shapes, and this tool understands three. Format 1 is our self-contained passphrase layout: a 16-byte salt, then the IV, then the ciphertext (with the GCM tag appended in GCM mode) — decrypt it with only the passphrase. Format 2 is the raw-key layout: the IV prepended to the ciphertext, or 'bare' ciphertext where you supply the IV separately. Format 3 is the OpenSSL layout, produced by the openssl enc command and by CryptoJS. That OpenSSL format is worth knowing by sight. It begins with the 8 ASCII bytes Salted__, followed by an 8-byte salt and then the ciphertext, and when the whole thing is Base64-encoded those first bytes always render as the prefix U2FsdGVkX1. So if a ciphertext you were handed starts with U2FsdGVkX1, it was almost certainly produced by openssl enc or a CryptoJS AES.encrypt(text, passphrase) call. CryptoJS derives its key with the legacy EVP_BytesToKey function using a single MD5 pass, while modern openssl uses PBKDF2 — which is why picking the right key-derivation function is the whole game when decrypting these. If your input is Base64 and you just want to see the raw bytes, our Base64 decoder will show them. To create ciphertext in these formats, use the AES encryption tool. ``` // AES-256-GCM decrypt with a passphrase (PBKDF2-HMAC-SHA256, 600,000 iterations). // Identical code runs in the browser and in Node.js 20+ via Web Crypto. async function aesGcmDecrypt(base64, passphrase) { const enc = new TextEncoder(); const packed = Uint8Array.from(atob(base64), (c) => c.charCodeAt(0)); const salt = packed.slice(0, 16), iv = packed.slice(16, 28); // salt(16) | iv(12) const ct = packed.slice(28); // ciphertext + tag const baseKey = await crypto.subtle.importKey( 'raw', enc.encode(passphrase), 'PBKDF2', false, ['deriveKey']); const key = await crypto.subtle.deriveKey( { name: 'PBKDF2', salt, iterations: 600000, hash: 'SHA-256' }, baseKey, { name: 'AES-GCM', length: 256 }, false, ['decrypt']); const plain = await crypto.subtle.decrypt( { name: 'AES-GCM', iv }, key, ct); // throws if the tag fails return new TextDecoder().decode(plain); // recovered plaintext } ``` #### FAQ **Q: Can you decrypt AES without the key?** A: No — and be wary of anyone who says otherwise. AES has no known practical weakness, so without the key or passphrase the only option is brute force, and the numbers make that hopeless: AES-256 has 2^256 possible keys, a space so large that even astronomically fast guessing would take far longer than the age of the universe to make a dent. There is no 'forgot my password' recovery for AES, and no legitimate service that decrypts arbitrary AES ciphertext for you. If you have lost the key, the data is gone; if you have it, this tool will decrypt in your browser. When decryption fails despite having the key, the culprit is almost always the wrong mode, IV, or key-derivation function — not a broken cipher. **Q: How do I decrypt openssl enc output?** A: Paste the Base64 ciphertext (it starts with U2FsdGVkX1) and accept the tool's prompt to switch to OpenSSL mode, or turn on OpenSSL-compatible mode manually with Mode CBC and Key type Passphrase. Enter the passphrase, then choose the key-derivation function openssl used: PBKDF2 if the file was made with -pbkdf2 (set the same iteration count — the default is 10,000), or EVP-SHA256 for a plain OpenSSL 1.1+ file with no -pbkdf2. The tool shows the exact openssl enc -d command it mirrors. For example, echo 'U2FsdGVkX18AESIzRFVmd1PBwxIFQpF+VgIhTK0aDHQ=' | openssl enc -d -aes-256-cbc -pbkdf2 -iter 10000 -pass pass:correct-horse -base64 -A prints Attack at dawn!. **Q: What does U2FsdGVkX1 at the start of a ciphertext mean?** A: It is the fingerprint of the OpenSSL Salted__ format. OpenSSL and CryptoJS prepend the 8 ASCII bytes Salted__ followed by an 8-byte salt to their ciphertext, and when that is Base64-encoded the leading bytes always come out as U2FsdGVkX1. Seeing it tells you two things: the data was produced by the openssl enc command or a CryptoJS AES.encrypt(text, passphrase) call, and the key was derived from a passphrase — so you will need the passphrase plus the right key-derivation function, not a raw key. Paste it in and this tool detects the format automatically and offers to switch you into the matching flow. **Q: How do I decrypt CryptoJS ciphertext?** A: CryptoJS's AES.encrypt(text, passphrase) uses the OpenSSL Salted__ format, but with a key derived by the legacy EVP_BytesToKey function using MD5 and just one iteration. So paste the ciphertext, switch to OpenSSL mode, and choose the EVP-MD5 key-derivation function — this is the setting almost everyone misses, and it is why CryptoJS ciphertext appears to 'not decrypt' when it is tried with PBKDF2 or SHA-256. Enter the same passphrase and your plaintext appears. On the command line the equivalent decryption needs -md md5. **Q: GCM vs CBC — why does my ciphertext decrypt one way and not the other?** A: The mode has to match how the data was encrypted; the two are not interchangeable. GCM includes a 128-bit authentication tag and will refuse to decrypt (authentication failure) if you pick the wrong mode, key, or IV. CBC has no tag, so choosing it wrongly can silently produce garbage or a padding error rather than a clear failure. If GCM authentication keeps failing, confirm the data really is GCM; if CBC gives padding errors, recheck the passphrase and the key-derivation function. This tool's diagnosis panel points you at the most likely cause instead of a generic error. **Q: Is my ciphertext or key uploaded when I decrypt here?** A: No. Decryption runs entirely in your browser through the Web Crypto API (crypto.subtle) — the same audited engine your browser uses for HTTPS. Your ciphertext, passphrase, and key are never sent anywhere; you can watch the Network tab stay empty, or disconnect from the internet and decrypt offline. That local-only design is what makes it reasonable to paste sensitive ciphertext here, though you should still avoid handling production secrets in any online tool. **Q: What is the difference between a passphrase and a raw key when decrypting?** A: It decides how you enter the secret. If the data was encrypted from a passphrase (including anything in the U2FsdGVkX1 format), choose Passphrase and let the tool derive the key with the matching KDF and salt. If it was encrypted with an exact 128/192/256-bit key, choose Raw key and paste those bytes as hex or Base64 — and you may also need to supply the IV, either prepended to the ciphertext or entered separately. Trying to decrypt passphrase data with a raw key, or vice versa, is a common reason decryption fails. To create ciphertext in either shape, use the AES encrypt tool. --- ### AES Encryption Tool — GCM, CBC & CTR URL: https://go-tools.org/tools/aes-encrypt Free online AES encryption — AES-128/192/256, GCM/CBC/CTR, passphrase (PBKDF2) or raw key. Runs 100% in your browser; nothing is uploaded. #### What Is AES Encryption? AES (the Advanced Encryption Standard) is a symmetric block cipher standardized by NIST in FIPS 197 in 2001, based on the Rijndael design by Joan Daemen and Vincent Rijmen. It encrypts data in fixed 128-bit blocks using a 128-, 192-, or 256-bit key, and the same key both encrypts and decrypts. It is the workhorse of modern cryptography, protecting everything from HTTPS traffic to disk encryption. A raw block cipher only scrambles one 16-byte block, so AES is always run inside a mode of operation that chains blocks together. This tool offers three, all provided natively by the browser's Web Crypto API: GCM, CBC, and CTR. GCM (Galois/Counter Mode, NIST SP 800-38D) is the recommended default because it is authenticated — it produces a 128-bit authentication tag alongside the ciphertext, so any tampering is detected when you decrypt. CBC and CTR provide confidentiality only; on their own they cannot tell you whether the ciphertext was altered, which is why TLS 1.3 (RFC 8446) dropped every CBC cipher suite in favor of authenticated modes like GCM. You will notice there is no ECB mode here, and that is deliberate. ECB encrypts every identical plaintext block to the same ciphertext block, so large-scale structure leaks straight through — the famous 'ECB penguin' image is still visibly a penguin after encryption. The Web Crypto API omits ECB for exactly this reason (it implements only AES-CBC, AES-CTR, and AES-GCM), and so do we. If you need to interoperate with a legacy system that used ECB, treat that as a reason to migrate it, not to reproduce the weakness. Because most people type a passphrase rather than a 32-byte random key, the tool derives the AES key from your passphrase with PBKDF2-HMAC-SHA256 at 600,000 iterations and a 16-byte random salt, matching the current OWASP Password Storage guidance (and NIST SP 800-132, which requires a salt of at least 128 bits). That makes brute-forcing a weak passphrase slow, but it is not magic: this is a tool for learning modes, debugging ciphertext, and one-off personal data — not for protecting production secrets, which belong in a dedicated key-management system. For a strong passphrase, generate one with our random password generator; for a real random key, use the secret key generator. ``` // AES-256-GCM with a passphrase (PBKDF2-HMAC-SHA256, 600,000 iterations). // Identical code runs in the browser and in Node.js 20+ via Web Crypto. async function aesGcmEncrypt(plaintext, passphrase) { const enc = new TextEncoder(); const salt = crypto.getRandomValues(new Uint8Array(16)); const iv = crypto.getRandomValues(new Uint8Array(12)); const baseKey = await crypto.subtle.importKey( 'raw', enc.encode(passphrase), 'PBKDF2', false, ['deriveKey']); const key = await crypto.subtle.deriveKey( { name: 'PBKDF2', salt, iterations: 600000, hash: 'SHA-256' }, baseKey, { name: 'AES-GCM', length: 256 }, false, ['encrypt']); const ct = new Uint8Array(await crypto.subtle.encrypt( { name: 'AES-GCM', iv }, key, enc.encode(plaintext))); const packed = new Uint8Array([...salt, ...iv, ...ct]); // salt(16) | iv(12) | ct+tag return btoa(String.fromCharCode(...packed)); // self-contained Base64 } ``` #### FAQ **Q: Is it safe to encrypt text online?** A: With this tool the encryption itself happens entirely in your browser — your text, passphrase, and key never travel to a server, which you can confirm by watching the Network tab. That makes it safe for learning, debugging, and one-off personal data. It is not the right place for production secrets, regulated data, or anything under long-term key management, and that is true of any online tool: a browser page cannot give you audited key storage, rotation, or access control. Encrypt throwaway and personal things here, and keep real secrets in a dedicated system. **Q: Can AES be decrypted without the key?** A: No. AES has no known practical break, so without the key or passphrase there is no shortcut — an attacker is reduced to trying keys one by one. AES-256 has 2^256 possible keys; even at a trillion trillion guesses per second you would still need vastly longer than the age of the universe to search a meaningful fraction. The realistic risks are never the cipher itself: they are a weak passphrase, a reused IV, a leaked key, or a padding oracle in a badly built system. Choose a strong passphrase and none of those apply. Anyone advertising key-free 'AES recovery' is selling a scam. **Q: GCM vs CBC — which mode should I use?** A: Use GCM. It gives you confidentiality plus a built-in 128-bit authentication tag, so if a single byte of the ciphertext is altered, decryption fails instead of silently returning corrupted data. CBC only hides the data; on its own it cannot detect tampering and has a long history of padding-oracle vulnerabilities (Vaudenay, EUROCRYPT 2002), which is why TLS 1.3 (RFC 8446) removed every CBC cipher suite. The only good reason to pick CBC here is interoperability with an existing system — for example OpenSSL's enc command, which has no GCM support. **Q: AES-128 vs AES-256 — is 256 worth it?** A: Both are considered secure; AES-128 is not broken and is slightly faster. AES-256 has a larger key and a bigger security margin, and it is the size approved under the NSA's CNSA 2.0 suite for information up to TOP SECRET, which is why it is a sensible default. The cost is minor — a few extra rounds per block. Unless you are optimizing a very hot path, encrypt with AES-256; the extra security headroom is essentially free in practice. **Q: Is my data uploaded when I encrypt here?** A: No. All encryption runs locally through the browser's Web Crypto API (crypto.subtle), the same audited implementation your browser uses for HTTPS. Nothing you type is sent anywhere — you can open your developer tools' Network panel, encrypt something, and watch zero requests fire, or disconnect from the internet entirely and the tool still works. SubtleCrypto is only available in secure (HTTPS) contexts, which is part of why this guarantee holds. **Q: What is the difference between a passphrase and a key?** A: A key is exactly 128, 192, or 256 bits of random data — for AES-256 that is 32 raw bytes, usually written as hex or Base64. A passphrase is human-typed text of any length and is not itself a key: it must be stretched into one by a key-derivation function. This tool does that automatically in passphrase mode with PBKDF2-HMAC-SHA256, 600,000 iterations, and a random salt. Mixing the two up — pasting a passphrase into the raw-key field, or vice versa — is one of the most common reasons two systems fail to agree on a ciphertext. (For signed tokens rather than encryption, see the JWT encoder.) **Q: Can I encrypt so OpenSSL or CryptoJS can decrypt it?** A: Yes. Turn on OpenSSL-compatible mode (with AES-CBC and a passphrase) and the tool emits the Salted__ format that the openssl enc command and CryptoJS understand, and it shows you the exact equivalent openssl command. Pick the matching key-derivation function: PBKDF2 for modern OpenSSL (the openssl -pbkdf2 default is 10,000 iterations), EVP-SHA256 for OpenSSL 1.1+ without -pbkdf2, or EVP-MD5 for CryptoJS and OpenSSL 1.0.2 and earlier. To go the other way and read someone else's ciphertext, use the AES decrypt tool. --- ### ASCII Table and Converter URL: https://go-tools.org/tools/ascii-table The full ASCII table: 128 characters in decimal, hex, octal and binary, plus a two-way text-to-ASCII converter. Control characters come with escape sequences, caret notation and where you actually meet them. #### What is ASCII? ASCII is a **7-bit** character encoding — 128 code points, numbered 0 to 127, and no more. It was first published as ASA X3.4-1963 and its current form is ANSI X3.4-1986; `RFC 20` describes its use on the internet and was elevated to Internet Standard STD 80 in 2015. The first version had no lowercase letters at all; those arrived in the 1967 revision. Everything above 127 belongs to some other encoding, not to ASCII. ``` 0x00-0x1F 32 C0 control characters 0x20 1 space (a graphic character, not a control) 0x21-0x7E 94 printable graphic characters 0x7F 1 DEL (a control character, but not part of C0) --- 128 total ``` #### FAQ **Q: How many characters are in ASCII?** A: 128. ASCII is a 7-bit code, so it defines exactly the code points 0 through 127: 32 C0 control characters, 95 graphic characters including the space, and DEL. Articles titled "complete ASCII table (256 characters)" are showing you some specific 8-bit code page — usually CP437 or Windows-1252 — not ASCII. **Q: What is extended ASCII?** A: Nothing, strictly speaking. "Extended ASCII" is not the name of any standard; it is a loose term for a whole family of 8-bit encodings that keep 0-127 the same and define 128-255 differently. Byte `0xE9` is e-acute in ISO-8859-1, the Greek letter Theta in CP437, and a Cyrillic letter in CP866. Without naming the code page, the question has no answer. **Q: Is Windows-1252 the same as ISO-8859-1?** A: They are identical from `0xA0` to `0xFF`, and completely different from `0x80` to `0x9F`. ISO-8859-1 leaves that range to invisible C1 control characters; Windows-1252 puts 27 printable characters there — the euro sign, curly quotes, en and em dashes — and leaves 5 positions undefined. This is why a curly quote copied out of a word processor turns into an invisible control character when it goes through something that really is ISO-8859-1. **Q: Is DEL a control character?** A: Yes. Unicode classifies U+007F as `Cc`, and C's `iscntrl(127)` is true. It just is not part of the C0 set, which is strictly 0x00-0x1F. It sits at the end of the table because of paper tape: `0x7F` is seven holes punched, and once you have punched a hole you cannot un-punch it, so "all holes punched" was the only way to mark a character as deleted. **Q: Is the space character a control character?** A: No, and plenty of ASCII charts get this wrong. Unicode classifies the space (0x20) as `Zs`, a space separator, which is a graphic character. Only 0x00-0x1F and 0x7F are control characters. **Q: Why does my binary column show 8 bits if ASCII is 7-bit?** A: Because a byte is the unit everything is actually stored in — the top bit is simply always 0. That padding is not cosmetic: precisely because ASCII never uses the eighth bit, UTF-8 could claim it as the "this is part of a multi-byte sequence" flag, which is what makes UTF-8 backward compatible with ASCII. **Q: Does every language write control characters the same way?** A: No, and the differences bite. `\a` (bell) is standard in C and Python, but JavaScript has no such escape — `'\a'` silently becomes the letter `a`, with no error even in strict mode. Java rejects both `\a` and `\v`. Go rejects `\0` because its octal escapes must be exactly three digits, so you write `\000`. And `\e` for escape is a GNU extension, not ISO C. **Q: What does ^M mean in a diff or in vim?** A: It is a carriage return, byte 13. Caret notation writes a control character by XOR-ing its value with `0x40`, so CR (0x0D) becomes `^M` and ESC (0x1B) becomes `^[`. Seeing `^M` at the end of every line means the file has Windows CRLF endings; seeing `bad interpreter: /bin/bash^M` means a shell script picked them up. --- ### Number Base Converter — Binary, Hex, Decimal & Octal URL: https://go-tools.org/tools/base-converter Convert between binary, hex, decimal, octal and any base (2-36) instantly. Free, private — all processing in your browser. #### What Is a Number Base Converter? A number base converter is a tool that translates values between different positional numeral systems, each defined by its radix (as described in Knuth's *The Art of Computer Programming*, Vol. 2, "Seminumerical Algorithms") — the number of unique digits used. Every numeral system is positional, meaning a digit's value depends on its position within the number. The radix determines the multiplier for each position: in base 10, positions represent powers of 10; in base 16, powers of 16. A base converter automates the arithmetic of translating a value from one radix to another. The four most common bases in computing are binary (base 2), octal (base 8), decimal (base 10), and hexadecimal (base 16). Binary is the native language of processors, representing data as sequences of 0s and 1s. Octal maps neatly to 3-bit groups and is used for Unix file permissions. Decimal is the human-readable default. Hexadecimal compresses 4 bits into a single character, making it the standard for memory addresses, CSS color codes, and byte-level data inspection. Other bases appear in specialized contexts — for instance, base 64 is used in data encoding (see our Base64 encoder for that use case). Hexadecimal is the dominant representation format in modern computing. Memory debuggers display addresses in hex (e.g., 0x7FFF5FBFF8C0), CSS and graphic design tools express colors as hex triplets (e.g., #FF5733), network MAC addresses are written as six hex-separated octets, and binary file formats embed hex signatures called magic numbers that identify file types. The reason is straightforward: each hex digit maps exactly to 4 binary bits (a nibble), so a full byte is always two hex digits — compact, unambiguous, and easy to read. This tool supports any integer base from 2 to 36, using digits 0-9 and letters A-Z for bases above 10. It is powered by JavaScript's BigInt, enabling arbitrary-precision arithmetic with no upper limit on digit count. For floating-point representations, the IEEE 754 standard defines how binary and hexadecimal formats map to the internal representation used by virtually all modern processors. All processing runs entirely in your browser — no data is transmitted to a server, ensuring complete privacy for sensitive values like cryptographic keys or proprietary identifiers. Base conversion is also fundamental to understanding the output of cryptographic tools — for example, MD5 and SHA hash generators produce hexadecimal output, and UUID identifiers are formatted as 32 hex digits in the 8-4-4-4-12 pattern. ``` // Convert decimal 255 to other bases console.log((255).toString(2)); // → '11111111' (binary) console.log((255).toString(8)); // → '377' (octal) console.log((255).toString(16)); // → 'ff' (hexadecimal) // Parse binary/hex strings back to decimal console.log(parseInt('11111111', 2)); // → 255 console.log(parseInt('ff', 16)); // → 255 // JavaScript code literals (same value, different syntax) const bin = 0b11111111; // 255 (binary literal) const oct = 0o377; // 255 (octal literal) const hex = 0xff; // 255 (hex literal) ``` #### FAQ **Q: What is a number base (radix) and why does it matter in programming?** A: A number base (or radix) defines how many unique digits are used in a positional numeral system. Base 10 (decimal) uses digits 0-9; base 2 (binary) uses 0-1; base 16 (hexadecimal) uses 0-9 and A-F. In programming, binary represents raw machine data, octal is used in Unix file permissions (e.g., chmod 755), and hexadecimal is standard for memory addresses, color codes (#FF5733), and byte-level data inspection. Understanding bases is essential for debugging, networking, and low-level programming. **Q: How do I convert between number bases manually?** A: To convert from any base to decimal: multiply each digit by the base raised to the power of its position (right to left, starting from 0), then sum the results. For example, binary 1011 = 1×2³ + 0×2² + 1×2¹ + 1×2⁰ = 8+0+2+1 = 11. To convert from decimal to another base: repeatedly divide by the target base and collect remainders in reverse order. For example, decimal 255 to hex: 255÷16 = 15 remainder 15, giving FF. **Q: Is my data safe when using this base converter?** A: Yes, completely. All conversions run locally in your browser using JavaScript. No data is sent to any server — there are no network requests, no cookies, no analytics on your input, and zero data storage. Your numbers never leave your device. This tool is ideal for converting sensitive data like memory addresses or proprietary byte sequences. **Q: What is the base 36 number system and where is it used?** A: Base 36 is the largest alphanumeric base, using digits 0-9 and letters A-Z (where A=10 through Z=35). It is widely used in URL shorteners (e.g., YouTube video IDs), compact unique identifiers, database primary keys, and encoding large numbers into short human-readable strings. For example, the decimal number 1,000,000 becomes LFLS in base 36 — much shorter and easier to share. Base 36 is especially popular in web development for generating slug-friendly identifiers that are both compact and case-insensitive, making them ideal for URLs and short codes. **Q: What is the difference between signed and unsigned number representation?** A: Unsigned numbers represent only non-negative values (0 and positive). Signed numbers can represent both positive and negative values, typically using two's complement encoding in computers. In two's complement, the most significant bit indicates the sign: 0 for positive, 1 for negative. For example, in 8-bit unsigned, the range is 0-255; in 8-bit signed (two's complement), the range is -128 to 127. **Q: Why do programmers use hexadecimal instead of binary?** A: Hexadecimal is a compact representation of binary data: each hex digit maps exactly to 4 binary bits (a nibble). This makes hex much easier to read and write than long binary strings. For example, the binary value 11111111 00001010 is simply FF0A in hex. Hex is the standard in memory addresses, color codes (CSS #FF5733), MAC addresses (00:1A:2B:3C:4D:5E), and UUID formatting. **Q: Can this tool handle very large numbers?** A: Yes. This tool uses JavaScript's BigInt for arbitrary-precision integer arithmetic, so there is no upper limit on the number of digits. You can convert numbers with hundreds or even thousands of digits between any bases from 2 to 36 without losing precision. JavaScript's native Number type is limited to 53-bit integers (up to 9,007,199,254,740,991), but BigInt removes this limitation entirely. Whether you're working with cryptographic hashes, large database IDs, or scientific computations, this tool handles them all accurately. **Q: How do I convert binary to hexadecimal manually?** A: The simplest method is the 4-bit grouping technique. Starting from the rightmost bit, split the binary number into groups of 4 digits (called nibbles). Pad the leftmost group with leading zeros if needed. Then use this lookup table to convert each group: 0000=0, 0001=1, 0010=2, 0011=3, 0100=4, 0101=5, 0110=6, 0111=7, 1000=8, 1001=9, 1010=A, 1011=B, 1100=C, 1101=D, 1110=E, 1111=F. For example, binary 10101111 splits into 1010 and 1111, which map to A and F, giving hex AF. This works because 16 is a power of 2 (16 = 2⁴), so each hex digit represents exactly 4 binary digits. **Q: How do I convert a negative number between bases?** A: Negative numbers in computers are typically represented using two's complement. In this system, the most significant bit (MSB) acts as the sign bit: 0 for positive and 1 for negative. To find the two's complement of a number, invert all bits (change 0s to 1s and vice versa) and add 1. For example, to represent -5 in 8-bit binary: start with 5 (00000101), invert to get 11111010, add 1 to get 11111011. This means -5 in 8-bit two's complement is 11111011 in binary or FB in hexadecimal. The range of an n-bit two's complement number is -2^(n-1) to 2^(n-1)-1. This tool converts the magnitude of the number; for signed representations, you would apply two's complement manually. **Q: What is the difference between hexadecimal and decimal?** A: Decimal (base 10) uses ten digits (0-9) and is the everyday number system humans are most familiar with. Hexadecimal (base 16) uses sixteen symbols (0-9 and A-F) and is the preferred format in computing. The key difference is place value: in decimal, each position represents a power of 10 (1, 10, 100, 1000...), while in hexadecimal each position represents a power of 16 (1, 16, 256, 4096...). For example, the decimal number 255 is FF in hex because 15×16 + 15×1 = 255. Hexadecimal is favored in programming because it maps cleanly to binary — each hex digit represents exactly 4 bits — making it ideal for memory addresses, color codes, and byte-level data. **Q: Why do computers use binary instead of decimal?** A: Computers use binary (base 2) because their fundamental building blocks — transistors — operate as electronic switches with two states: on (1) and off (0). This maps perfectly to binary digits. Representing decimal digits would require circuits that reliably distinguish between 10 different voltage levels, which is far more complex and error-prone than distinguishing just 2 states. Binary also aligns naturally with Boolean logic (true/false), which forms the foundation of all computer operations. While early computers experimented with ternary (base 3) and decimal systems, binary won out because it offers the best combination of simplicity, reliability, and noise tolerance in electronic circuits. **Q: Why are Unix file permissions represented in octal?** A: Unix file permissions use three categories — owner, group, and others — each with three permission bits: read (r=4), write (w=2), and execute (x=1). Since 3 bits can represent values 0-7, each category maps perfectly to a single octal digit. For example, permission 755 means: owner has rwx (7 = 4+2+1), group has r-x (5 = 4+0+1), and others have r-x (5 = 4+0+1). Octal is the natural choice because each digit encodes exactly one permission group. In binary, 755 is 111 101 101, which directly shows the rwx bit pattern. This elegant 3-bit-to-1-digit mapping is why chmod uses octal notation. **Q: How are hexadecimal colors used in web development?** A: In web development, colors are commonly specified in the #RRGGBB hex format, where each pair of hex digits represents one color channel: red, green, and blue. Each channel ranges from 00 (0, no intensity) to FF (255, full intensity). For example, #FF5733 means red=FF (255), green=57 (87), blue=33 (51), producing a vibrant orange-red. There is also a shorthand notation — #F00 expands to #FF0000 (pure red). Modern CSS additionally supports #RRGGBBAA for alpha transparency, where AA ranges from 00 (fully transparent) to FF (fully opaque). Hexadecimal is used because two hex digits perfectly represent one byte (0-255), making it a compact and readable format for color values. **Q: What are the practical applications of base conversion in networking?** A: Base conversion is essential in networking for working with IP addresses, subnet masks, and MAC addresses. IPv4 addresses like 192.168.1.1 are written in decimal, but subnet calculations require binary. For example, a /24 subnet mask is 11111111.11111111.11111111.00000000 in binary, which is 255.255.255.0 in decimal. Network engineers use a bitwise AND on the IP address and subnet mask in binary to determine the network address. If you would rather skip the binary arithmetic by hand, our subnet calculator derives the network address, broadcast address, and usable host range straight from a CIDR block. MAC addresses use hexadecimal notation (e.g., 00:1A:2B:3C:4D:5E) because each hex pair represents one byte. Understanding base conversion helps you calculate subnets, troubleshoot routing, and analyze packet captures. **Q: How does this tool compare to using programming language built-in conversion functions?** A: Programming languages offer built-in conversion functions — JavaScript has parseInt() and toString(), Python has bin(), oct(), hex(), and int(). However, this tool provides several advantages: it converts to all common bases simultaneously with real-time updates, requires no coding setup, supports any base from 2 to 36 in one interface, and uses BigInt for arbitrary precision beyond what some language defaults offer. It is ideal for quick lookups, verifying your code's output, learning base conversion concepts visually, and working with bases not directly supported by language built-ins. For production code, use your language's native functions; for exploration and debugging, this tool is faster and more convenient. --- ### Base64 Decoder & Encoder URL: https://go-tools.org/tools/base64-decode-encode Decode and encode Base64 online for free. Real-time conversion with full UTF-8 and emoji support. 100% private — runs in your browser. No signup needed. #### What is Base64? Base64 is a binary-to-text encoding scheme defined by RFC 4648 that converts arbitrary binary data into a safe ASCII string representation using a 64-character alphabet. It is one of the most widely deployed encodings on the internet, underpinning everything from email attachments to JSON Web Tokens and TLS certificates. "The Base 64 encoding is designed to represent arbitrary sequences of octets in a form that need not be humanly readable." — RFC 4648, Section 1 Base64 divides the input into groups of 3 bytes (24 bits), then splits those 24 bits into four 6-bit groups, each mapped to one of 64 printable characters: A-Z (0-25), a-z (26-51), 0-9 (52-61), + (62), and / (63). The = character pads the output when the input length is not a multiple of 3. Base64 encoding increases data size by approximately 33% (RFC 4648) — a deliberate trade-off to make binary data safe for text-only channels. Base64 was originally designed to safely transmit binary data over channels that only support text, such as email (MIME) and early HTTP. Today it is ubiquitous: data URIs embed images in HTML, JWT tokens encode claims, PEM certificates wrap keys, and APIs transport binary payloads in JSON. A Base64 encoder converts raw bytes to this safe ASCII representation, while a decoder reverses the process. All encoding and decoding in this tool runs entirely in your browser — your data is never uploaded to any server, making it safe to use with API keys, tokens, and other sensitive values. Use this free online Base64 converter to decode Base64 to text or encode text to Base64 instantly in your browser. Base64 is often used alongside other encoding and data tools. For example, you might need to format JSON data before Base64-encoding it for an API payload, URL-encode a Base64 string before placing it in a query parameter, or verify a file checksum with MD5 or SHA-256 after decoding a Base64-encoded download. New to Base64? Read our beginner-friendly Base64 guide. For advanced topics like MIME, data URLs, and performance optimization, see the advanced Base64 guide. Need to put binary data into a QR code? Base64-encode the bytes first, then paste into the QR Code Generator — QR works best with text payloads. ``` // Encode plain text to Base64 const encoded = btoa('Hello, World!'); console.log(encoded); // → 'SGVsbG8sIFdvcmxkIQ==' // Decode Base64 back to text const decoded = atob('SGVsbG8sIFdvcmxkIQ=='); console.log(decoded); // → 'Hello, World!' // UTF-8 safe encode (handles Chinese, emoji, any Unicode) function encodeBase64(str) { const bytes = new TextEncoder().encode(str); // to UTF-8 bytes return btoa(String.fromCharCode(...bytes)); } console.log(encodeBase64('你好')); // → '5L2g5aW9' ``` #### FAQ **Q: What is Base64 encoding?** A: Base64 is a binary-to-text encoding scheme that represents binary data as a string of printable ASCII characters. It converts every 3 bytes of input into 4 ASCII characters from the set A-Z, a-z, 0-9, +, and /. The '=' character is used for padding when the input length is not a multiple of 3. Base64 is defined in RFC 4648 and is widely used in email (MIME), data URIs, JSON Web Tokens (JWT), and HTTP authentication. **Q: Is my data safe when using this tool?** A: Yes, completely. All encoding and decoding happens locally in your browser using JavaScript's native btoa(), atob(), TextEncoder, and TextDecoder APIs. Your data never leaves your device — there are no server requests, no cookies, no analytics on your input, and no data storage of any kind. **Q: How does this tool handle non-ASCII characters like Chinese or emoji?** A: This tool first converts the input text to UTF-8 bytes using the TextEncoder API, then Base64-encodes those bytes. This ensures characters outside the ASCII range — including Chinese (你好), Japanese (こんにちは), Arabic, and emoji (🎉) — are encoded correctly. When decoding, the tool reverses the process: Base64 → bytes → UTF-8 text using TextDecoder. To see the exact code point and UTF-8 bytes behind a single character, or to move a string between the escape syntaxes different languages expect, use the Unicode converter. **Q: Is Base64 encryption?** A: No. Base64 is an encoding, not encryption. It does not provide any security — anyone can decode a Base64 string back to the original data instantly. Base64 is designed for data transport (making binary data safe for text-only channels), not for protecting secrets. If you need to protect data, use proper encryption (AES, RSA) before optionally Base64-encoding the result. **Q: Where is Base64 commonly used?** A: Base64 is used extensively in: (1) Data URIs — embedding images directly in HTML/CSS as 'data:image/png;base64,...', (2) Email — MIME encoding for attachments and non-ASCII content, (3) JWT — JSON Web Tokens encode header and payload as Base64URL, (4) HTTP Basic Auth — credentials are sent as Base64-encoded 'username:password', (5) APIs — transmitting binary data in JSON payloads, (6) Certificates — PEM format wraps DER-encoded certificates in Base64. **Q: What is the difference between Base64 and URL-safe Base64?** A: Standard Base64 uses '+' and '/' characters, which have special meaning in URLs — + represents a space and / is a path separator. URL-safe Base64 (also defined in RFC 4648) replaces '+' with '-' and '/' with '_', making the output safe for use in URLs and filenames without additional percent encoding. If you need to use standard Base64 in a URL, you can percent-encode it with our URL Encoder. This tool uses standard Base64. To convert to URL-safe, simply replace + with - and / with _ in the output. **Q: Why does Base64 increase data size?** A: Base64 encodes 3 bytes of input into 4 characters of output, resulting in approximately 33% size increase. This is because Base64 uses only 64 printable ASCII characters (6 bits each) to represent 8-bit bytes. For example, the 13-character string 'Hello, World!' becomes the 20-character Base64 string 'SGVsbG8sIFdvcmxkIQ=='. This trade-off is acceptable because the encoded data is safe to transmit through text-only protocols. **Q: How do I encode a file to Base64?** A: On macOS or Linux, use the command line: base64 < myfile.png > myfile.b64. On Windows, use PowerShell: [Convert]::ToBase64String([IO.File]::ReadAllBytes('myfile.png')). In JavaScript (Node.js), use fs.readFileSync('myfile.png').toString('base64'). In Python, use import base64; base64.b64encode(open('myfile.png','rb').read()). This browser tool handles text input; for large binary files, command-line tools are more efficient. **Q: Can I use Base64 in HTML and CSS?** A: Yes. Base64 is commonly used in data URIs to embed small assets directly in HTML or CSS, eliminating extra HTTP requests. In HTML: . In CSS: background-image: url('data:image/svg+xml;base64,PHN2Zy...'). This is ideal for small icons and SVGs (under ~5 KB). For larger files, separate file references are more efficient because Base64 adds 33% size overhead and bypasses browser caching. **Q: What is the maximum input size?** A: This browser-based tool efficiently handles text up to about 5 MB. For very large files or binary data, consider using command-line tools like 'base64' (available on macOS and Linux) or 'openssl base64'. The Base64 standard itself has no size limit. **Q: What characters are in the Base64 alphabet?** A: The standard Base64 alphabet (RFC 4648) consists of 64 characters: uppercase letters A-Z (values 0-25), lowercase letters a-z (values 26-51), digits 0-9 (values 52-61), plus sign + (value 62), and forward slash / (value 63). The equals sign = is used for padding. The URL-safe variant (Base64URL) replaces + with - and / with _ to avoid conflicts with URL-reserved characters. **Q: I need to embed a small image in my HTML email template — should I use Base64 data URIs or host the image externally?** A: For HTML email, Base64 data URIs are actually the recommended approach for small images like logos and icons. Many email clients (Outlook, Gmail) aggressively block externally hosted images by default, requiring users to click "Display images" to see them. Embedding images as Base64 data URIs (data:image/png;base64,...) bypasses this problem entirely — the image is part of the email itself. The tradeoff is email file size: Base64 adds ~33% overhead, so a 10 KB PNG becomes ~13 KB in the email. Keep embedded images under 20 KB for best compatibility. For larger images or backgrounds, hosting externally is more practical. Use this tool to encode your image file's bytes to Base64 for embedding. **Q: Why does my Base64 encoded string have + and / characters that break my URL parameters?** A: Standard Base64 uses + and / as two of its 64 characters, and both have special meaning in URLs (+ means space, / is a path separator). When you include a standard Base64 string in a URL query parameter without encoding, these characters corrupt the value. The solution is to use URL-safe Base64 (also called Base64URL, defined in RFC 4648), which replaces + with - and / with _. This variant is used in JWT tokens, OAuth flows, and any Base64 value that appears in URLs. To convert standard Base64 to URL-safe, simply replace all + with - and / with _ in the output, and optionally remove the = padding characters. **Q: I'm trying to decode a JWT token — how does Base64URL decoding work and how is it different from standard Base64?** A: A JWT (JSON Web Token) consists of three parts separated by dots: header.payload.signature. The header and payload are each encoded with Base64URL — not standard Base64. Base64URL differs from standard Base64 in two ways: it uses - instead of + and _ instead of /, and it omits the = padding characters. To decode a JWT manually, split the token by dots, take the first or second segment, replace - with + and _ with /, add = padding if needed to make the length a multiple of 4, then Base64-decode. Most JWT debugging is easier with a dedicated JWT decoder, but understanding the Base64URL encoding helps when implementing token handling in code or debugging raw token values. --- ### Base64 to Image Converter URL: https://go-tools.org/tools/base64-to-image Decode a Base64 string or data URI back into an image in your browser. Preview, read dimensions & MIME, then download as PNG, JPG, GIF, SVG. No upload. #### What is Base64 to Image Decoding? Base64 to image decoding is the reverse of encoding: it takes a string of printable ASCII characters from the Base64 alphabet (A–Z, a–z, 0–9, + and /) and reconstructs the original binary image the string represents. Every group of four Base64 characters maps back to three bytes, and one or two trailing = characters indicate padding. The result is the exact file that was originally encoded — a PNG comes back as a PNG, a JPEG as a JPEG — with no loss, recompression, or resizing. These strings show up wherever an image has been inlined as text. A data URI in a stylesheet (background-image: url(data:image/png;base64,…)), an img src in HTML, a thumbnail field in a JSON API response, an embedded logo in HTML email, or an asset bundled into a config file are all Base64 images waiting to be decoded. When you are debugging, auditing, or extracting such an asset, you need to see what the opaque string actually is and pull it out as a real file — which is exactly what this decoder does. The operation is purely mechanical and requires no key, because Base64 is an encoding rather than encryption. That also means it offers no security: anyone with the string can recover the image instantly. Base64 exists solely to let binary data pass through channels designed for text — HTML, JSON, URLs, email headers — without being corrupted by control characters or delimiters. Decoding simply undoes that text-safe packaging and hands you back the original bytes. This tool performs the entire decode locally in your browser. It tolerates the messiness of real-world strings — missing data: prefixes, line wrapping at 76 characters, stray whitespace from copy-paste — and infers the image format from the data's magic bytes when the MIME type is not declared. To create these strings in the first place, see the companion Image to Base64 encoder. ``` // A Base64 PNG payload (no prefix) iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg== // The decoder infers the format from the leading bytes: // iVBORw0KGgo → PNG // /9j/ → JPEG // R0lGOD → GIF // UklGR → WebP // PHN2Zy → SVG (Image to Base64 encoder. **Q: Is my Base64 data uploaded anywhere?** A: No. Decoding happens completely client-side. The string is turned into binary with the browser's built-in atob, rebuilt into a Blob, and rendered from a local object URL — no server, no network request, no logging. You can confirm this in your browser's Network tab: pasting a string and downloading the image triggers zero requests. That makes the tool safe for decoding strings that contain confidential or unreleased imagery pulled from a config file, an API response, or a stylesheet you are debugging. **Q: Do I need to include the data: prefix?** A: No. You can paste either a full data URI (data:image/png;base64,iVBORw0KGgo…) or just the raw Base64 payload (iVBORw0KGgo…). When the prefix is present, the tool uses its declared MIME type. When it is absent, the decoder reads the first few characters — which map directly to the image's magic bytes — and infers the format: iVBORw0KGgo means PNG, /9j/ means JPEG, R0lGOD means GIF, UklGR means WebP, and PHN2Zy or PD94bWw means SVG. Either way you get a correct preview and a download with the right file extension. **Q: What image formats can it decode?** A: Any format the browser can render from a data URI: PNG, JPEG/JPG, GIF (including animated), WebP, SVG, ICO, BMP, and AVIF where supported. Because the tool reconstructs the original bytes rather than re-encoding, transparency, animation, and vector scalability are all preserved exactly. The downloaded file is byte-for-byte the image that was originally encoded — decoding and then re-encoding is a lossless round trip. **Q: Why does my Base64 string fail to decode?** A: The usual culprits are a truncated string that lost its trailing = padding, characters accidentally deleted or altered during copy-paste, a string that is actually text or some other binary rather than an image, or a wrong MIME type that prevents the browser from rendering otherwise-valid bytes. This decoder strips whitespace and tolerates a missing prefix, so those common issues are handled automatically — if it still cannot render, the data itself is incomplete or is not an image. Re-copy the entire value, including any trailing == padding, and try again. **Q: How do I save the decoded image as a PNG or JPG?** A: Once the preview appears, click Download. The tool rebuilds the binary from the Base64 payload and saves it with the extension that matches the detected MIME type — .png for image/png, .jpg for image/jpeg, .svg for image/svg+xml, and so on. The download is reconstructed locally from the exact decoded bytes, so it is identical to the original file that was encoded. There is no format conversion: a Base64-encoded PNG downloads as a PNG, not a re-rendered copy. **Q: Is decoding Base64 the same as decrypting it?** A: No. Base64 is an encoding, not encryption, and decoding requires no key or password — it simply reverses the 4-character-to-3-byte mapping. Anyone who has the string can recover the original image, which is exactly what this tool does. Base64 provides no confidentiality whatsoever; it exists only to let binary data travel safely through text-based channels like HTML, JSON, and email. If a string was genuinely encrypted before being Base64-encoded, decoding here will yield the encrypted bytes, not a viewable image. **Q: Can it handle very long Base64 strings?** A: Yes. Because everything is processed locally, there is no upload size limit — the practical ceiling is how much text your browser can hold and decode comfortably, which is well into the multi-megabyte range on a modern machine. Very large strings (a high-resolution photo encoded as Base64 can be hundreds of kilobytes of text) take a moment to render but decode correctly. If you find yourself routinely decoding huge strings, that is often a sign the image should have been served as a normal file rather than inlined in the first place. **Q: Where do these Base64 image strings come from?** A: You will most often encounter them embedded in CSS (background-image: url(data:image/png;base64,…)), in HTML img src attributes, inside JSON API responses, in email source, in SVG sprite sheets, and in configuration or theme files that bundle assets inline. Developers paste them here to see what an opaque data URI actually contains, to extract an asset that has no separate file, or to verify that a string a build tool produced is valid. The companion Image to Base64 tool produces exactly these strings. **Q: Does decoding lose any quality?** A: No. Base64 is a lossless, exact representation of the original bytes — decoding returns precisely the file that was encoded, with no quality change, recompression, or resizing. If the source image was a compressed JPEG, you get that same JPEG back; if it was a lossless PNG, you get the identical PNG. The only thing that changes is the container (text string versus binary file). Any quality loss you see would have existed in the original image before it was ever encoded. --- ### Bcrypt Hash Generator & Verifier URL: https://go-tools.org/tools/bcrypt-generator Generate and verify bcrypt password hashes online — adjustable cost, $2b$/$2a$/$2y$ prefixes. 100% in your browser; your password is never uploaded. #### What Is Bcrypt? Bcrypt is a password-hashing function designed specifically for storing passwords safely. Instead of keeping a password in plaintext, a server stores a one-way bcrypt hash; when a user logs in, the server hashes the submitted password the same way and checks that the two hashes match. Bcrypt is built on the Blowfish cipher and was designed by Niels Provos and David Mazières in 1999, with one deliberate feature that sets it apart from general-purpose hashes like SHA-256: it is slow on purpose, and you can make it slower over time with an adjustable cost factor as hardware gets faster. A bcrypt hash is a single, self-describing 60-character string — for example $2b$12$dUSFKqT1FCMYZ6hcQfsxuONizEqcX8IGK8snfVSowP5Uu.TDJoPUq. It packs four parts: the version ($2b$), the cost (12, a logarithmic work factor), a 22-character Base64 salt, and the 31-character Base64 digest. Because the salt is random and embedded in the hash, the same password produces a different hash every time — which defeats rainbow tables and hides the fact that two users picked the same password. Verification reads the salt and cost back out of the stored hash and re-hashes the candidate, so bcrypt never needs to (and cannot) reverse a hash to recover the password. This tool runs entirely in your browser using a bundled bcrypt implementation — no password or hash is ever uploaded. Use it to generate a hash with a chosen cost and prefix, to verify a password against an existing hash, and to read a hash's anatomy. It pairs naturally with other security tools: protect a directory with HTTP Basic Auth using our htpasswd Generator (which can emit bcrypt entries directly), mint a strong password to hash with our Random Password Generator, and reach for our SHA-256 Generator when you need a fast general-purpose checksum rather than a slow password hash. If you are deciding which algorithm to store passwords with, compare the options in bcrypt vs Argon2 vs scrypt. ``` // Node.js — bcryptjs / bcrypt (emits $2b$) const bcrypt = require('bcrypt'); const hash = await bcrypt.hash('correct horse battery staple', 12); // -> $2b$12$dUSFKqT1FCMYZ6hcQfsxuONizEqcX8IGK8snfVSowP5Uu.TDJoPUq const ok = await bcrypt.compare('correct horse battery staple', hash); // true # Python — bcrypt import bcrypt hashed = bcrypt.hashpw(b'correct horse battery staple', bcrypt.gensalt(12)) bcrypt.checkpw(b'correct horse battery staple', hashed) # True # PHP — password_hash (emits $2y$) $hash = password_hash('correct horse battery staple', PASSWORD_BCRYPT, ['cost' => 12]); password_verify('correct horse battery staple', $hash); // true # Apache htpasswd CLI — bcrypt entry to stdout (-B bcrypt, -b inline, -n stdout) htpasswd -Bbn admin 'correct horse battery staple' # -> admin:$2y$12$dUSFKqT1FCMYZ6hcQfsxuONizEqcX8IGK8snfVSowP5Uu.TDJoPUq ``` #### FAQ **Q: Is an online bcrypt generator safe to use?** A: With this one, yes — because nothing you type ever leaves your browser. The password, the generated hash, and the verification all run locally in JavaScript on your own device. There are no network requests, no logging, and no storage: you can confirm it by opening your browser's Developer Tools (F12 → Network tab) while you generate a hash and watching for zero outgoing requests, or by disconnecting from the internet and seeing the tool keep working. That is the opposite of a sketchy generator that POSTs your password to a server. As a habit, still prefer a throwaway test password over a real production one whenever you are just experimenting. **Q: How do I generate a bcrypt hash online?** A: Open the Generate tab, type a password (or click Random password to mint a strong one), pick a cost factor — 12 is the modern default — and choose a version prefix: $2b$ for most modern stacks, $2y$ for PHP and Apache, or $2a$ for the original identifier. The bcrypt hash is computed instantly in your browser with a fresh random salt and appears as a single 60-character $2b$12$... string you can copy with one click. Nothing is uploaded: the password and hash never leave your device. Generate again any time to get another valid hash for the same password, since each one carries a different random salt. **Q: Can a bcrypt hash be decrypted or reversed?** A: No. bcrypt is a one-way password hashing function, not encryption, so there is no key and no decrypt operation that turns a hash back into the original password. The only way to learn the password from a hash is to guess candidates and hash each one until it matches — which is exactly what bcrypt's adjustable cost factor is designed to make slow and expensive. That is why you verify a password against a hash rather than decrypt it: the tool re-hashes your candidate with the salt and cost stored in the hash and checks whether the result is identical. **Q: What cost factor (work factor) should I use?** A: Cost 12 is the modern default and a sensible balance of security and speed. The cost is a logarithmic work factor: each increment doubles the number of internal rounds, so cost 13 takes roughly twice as long to compute and verify as cost 12, and cost 11 takes half as long. Higher costs slow down attackers brute-forcing leaked hashes, but they also add latency to every legitimate login, so do not raise it past the point where authentication feels sluggish on your real hardware. Cost 10 is acceptable for low-risk endpoints; 12–14 suits anything sensitive. The valid range is 4 to 31, and this tool lets you pick 4 to 15. **Q: What's the difference between $2a$, $2b$, and $2y$?** A: They are version prefixes for the same bcrypt algorithm, and the differences trace back to historical bug fixes in how some implementations handled string length and high-bit characters. $2a$ is the original widely used identifier; $2b$ is the corrected current version that the bcryptjs library and most modern implementations emit; and $2y$ is the identifier PHP and Apache's htpasswd use. For verification they are interchangeable — a hash you generate here with any prefix will validate correctly across libraries, because they all run the same core function. Pick the prefix your stack expects if you need byte-for-byte compatibility. **Q: How do I verify a password against a bcrypt hash?** A: Switch to the Verify tab, paste the stored bcrypt hash (the full $2b$12$... string) and the candidate password, and the tool tells you instantly whether they match. It works by extracting the salt and cost embedded in the hash, re-hashing the candidate password with those exact parameters, and comparing the new digest to the stored one — there is no decryption involved. This is how a login system checks a password: it never recovers the plaintext, it only confirms that re-hashing the submitted password reproduces the stored hash. **Q: bcrypt vs Argon2 vs scrypt — which should I use?** A: All three are deliberately slow, salted password-hashing functions, and all are far better than a bare SHA-256 for storing passwords. bcrypt is the most widely supported and battle-tested, with a simple tunable cost; its main limits are a 72-byte password cap and that it is CPU-bound only. scrypt adds memory hardness, making large-scale GPU/ASIC attacks costlier. Argon2 (specifically Argon2id) is the current recommendation from the Password Hashing Competition and OWASP, tuning time, memory, and parallelism independently. If you are choosing fresh today, Argon2id is the strongest default; bcrypt remains an excellent, safe choice — especially where library support or interoperability matters. We cover the tradeoffs in depth in bcrypt vs Argon2 vs scrypt. **Q: Why is the bcrypt hash different every time?** A: Because bcrypt generates a fresh random salt for every hash, and the salt is mixed in before hashing. The same password therefore produces a completely different 60-character string each time you click Generate — and that is the point: it stops attackers from precomputing rainbow tables or spotting that two users share a password. The salt is not secret; it is stored right inside the hash (the 22 characters after the cost), so verification can read it back out. If you re-roll the hash you simply get another valid hash for the same password, and every one of them will verify successfully. --- ### Case Converter — UPPERCASE, lowercase, camelCase & More URL: https://go-tools.org/tools/case-converter Convert text between UPPERCASE, lowercase, Title Case, Sentence case, camelCase, PascalCase, snake_case, kebab-case, CONSTANT_CASE and 6 more formats instantly. Free, browser-only, no signup. #### What Is a Case Converter? A case converter is a small utility that takes a piece of text and re-renders it in a different letter-case convention. The simplest forms are UPPERCASE and lowercase — flip every letter to one case. The richer forms apply linguistic rules (Title Case capitalizes the first letter of every word, Sentence case capitalizes the first letter of each sentence) or programming-naming rules (camelCase joins words by capitalizing each one after the first; snake_case lowercases everything and joins with underscores). Online case converters have existed for as long as the web has had textareas, because the conversion is mechanically simple but tedious to do by hand for any non-trivial amount of text. The text-case family is the one writers, editors, marketers, and journalists reach for. UPPERCASE and lowercase are useful for matching house style or removing shouty ALL-CAPS from a forwarded email. Title Case is for headings and book titles. Sentence case is the modern web standard for body text, UI microcopy, button labels, and captions — Google, Apple, and Microsoft's style guides all converged on it over the last decade. The mocking variants (iNVERSE cASE, aLtErNaTiNg cAsE, RaNdOm CaSe) come from internet culture, particularly the "spongebob meme" used to sarcastically quote someone; alternating case is the strict deterministic variant, random case is the chaotic one. The programming-case family is the one developers use every single day. camelCase is the standard for JavaScript, Java, Swift, and Kotlin identifiers. PascalCase is the standard for class names in most object-oriented languages and component names in React, Vue, and Angular. snake_case is the standard for Python, Ruby, Rust, and Elixir, plus most database column names. kebab-case is the standard for CSS class names, URL slugs, and HTML attributes. CONSTANT_CASE is the standard for environment variables, top-level constants, and macro names. dot.case is used for namespacing (Java packages, MongoDB field paths). path/case is used for URLs and filesystem paths. Header-Case is the canonical HTTP/1.1 header convention (Content-Type, Access-Control-Allow-Origin). Under the hood, the interesting engineering is the tokenizer that splits an input string into its semantic words. It's easy to split on whitespace; the hard part is recognizing word boundaries that don't have a whitespace separator. The standard convention — used by lodash, the change-case npm package, Python's PEP 8, and most real-world codebases — inserts a boundary at three transitions: lower-to-upper (parseHTML → parse / HTML), upper-to-upper-to-lower (XMLHttpRequest → XML / Http / Request), and letter-to-digit / digit-to-letter (file2x → file / 2 / x). Plus the explicit separators: hyphen, underscore, dot, slash, backslash. With that single tokenizer, you can paste an identifier in any case — camelCase, snake_case, kebab-case, mixed — and convert to any other case cleanly without manual cleanup. The tool you're using runs the tokenizer and all 15 transforms entirely in your browser using JavaScript. There's no network call, no server, no logging, no cookie that records what you type. The output for every case updates on every keystroke with no debounce delay. The Copy button on each card writes only that one case to your clipboard. Re-shuffle re-rolls the random case without disturbing the other outputs. Everything is designed for the speed of actual work — paste, scan, copy, paste somewhere else. For related text tooling, the word counter handles length and reading-time metrics, the text diff compares two pieces of text line by line, and the regex tester verifies pattern matches against sample input. Together they cover most of the text-shaping work a developer or content worker does in a browser. ``` // The tokenizer that powers every programming-case conversion function tokenize(input) { return input .replace(/([a-z0-9])([A-Z])/g, '$1 $2') // lower→upper: parseHTML → parse HTML .replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2') // acronym boundary: XMLHttp → XML Http .replace(/([A-Za-z])(\d)/g, '$1 $2') // letter→digit: file2 → file 2 .replace(/(\d)([A-Za-z])/g, '$1 $2') // digit→letter: 2x → 2 x .replace(/[\s\-_./\\]+/g, ' ') // collapse separators .split(' ').filter(Boolean); } // Then each case is a one-liner over the tokens const camelCase = (s) => tokenize(s).map((t, i) => i === 0 ? t.toLowerCase() : cap(t)).join(''); const snakeCase = (s) => tokenize(s).map(t => t.toLowerCase()).join('_'); const kebabCase = (s) => tokenize(s).map(t => t.toLowerCase()).join('-'); const PascalCase = (s) => tokenize(s).map(cap).join(''); function cap(t) { return t.charAt(0).toUpperCase() + t.slice(1).toLowerCase(); } ``` #### FAQ **Q: What does a case converter do?** A: A case converter takes a piece of text and re-renders it in a different case — UPPERCASE, lowercase, Title Case, Sentence case, or one of the programming-naming cases like camelCase, PascalCase, snake_case, kebab-case, and CONSTANT_CASE. This tool shows all 15 common variants at once so you don't have to pick which conversion you want before pasting; you paste, scan the grid, and copy the one you need. It runs entirely in your browser using JavaScript — no signup, no upload, no server roundtrip, and no analytics on the text you paste. **Q: What's the difference between camelCase, PascalCase, and snake_case?** A: All three are conventions for naming multi-word identifiers in code. camelCase starts with a lowercase letter and capitalizes each subsequent word with no separator: `userProfileImage`. PascalCase capitalizes every word including the first: `UserProfileImage` — used for class names in most languages and component names in React. snake_case lowercases everything and joins words with underscores: `user_profile_image` — the convention for Python, Ruby, Rust, and most database column names. kebab-case is the same idea with hyphens: `user-profile-image` — used for CSS class names, URL slugs, and HTML attributes. CONSTANT_CASE is uppercase with underscores: `USER_PROFILE_IMAGE` — for constants and environment variables. Pick the one that matches your codebase's existing style. **Q: How does the tokenizer handle acronyms like XMLHttpRequest or parseHTML?** A: The tokenizer recognizes the upper-to-upper-to-lower boundary (XMLHttp → XML / Http) and the lower-to-upper boundary (parseHTML → parse / HTML). So `XMLHttpRequest` becomes the tokens `XML`, `Http`, `Request`, and converts cleanly to `xml_http_request`, `xml-http-request`, `XML_HTTP_REQUEST`, or `Xml-Http-Request`. This matches the convention used by lodash, the change-case npm package, and Python's PEP 8 — the de facto standard for acronym handling across languages. The one tradeoff: when converting back to PascalCase, the acronym becomes title-cased (`XMLHttpRequest` round-trips to `XmlHttpRequest`), which is also the standard convention to avoid ambiguity in re-tokenization. **Q: What is Title Case versus Sentence case?** A: Title Case capitalizes the first letter of every word, leaving everything else lowercase: `Hello World Example`. This tool uses the naive variant — every word capitalized — which is what most people mean by "title case" in casual usage. Some style guides (APA, Chicago, AP) recommend lowercasing short articles and prepositions like `a`, `an`, `the`, `of`, `in`, `for`; those variants are different enough that they belong in a separate "headline" tool. Sentence case capitalizes only the first letter of each sentence (and the very first letter of the input): `Hello world example. This is a sentence.` Use Title Case for headings and book titles, Sentence case for descriptions, captions, and body text. **Q: Is my text uploaded anywhere?** A: No. Every case transform runs 100% in your browser using JavaScript. Your text is never transmitted, never stored on any server, never logged, and never analyzed by humans or AI. You can verify in your browser's Network tab — typing in the editor or clicking Copy triggers zero network requests. This makes the tool safe for unannounced product names, internal variable schemes, draft legal text, journalist source notes, and any other confidential material. The tool also uses no cookies for the input text. **Q: How do I convert text to camelCase from any other case?** A: Paste your text into the editor above and copy the camelCase output card. It works from any starting format: a sentence with spaces (`hello world` → `helloWorld`), snake_case (`hello_world` → `helloWorld`), kebab-case (`hello-world` → `helloWorld`), PascalCase (`HelloWorld` → `helloWorld`), CONSTANT_CASE (`HELLO_WORLD` → `helloWorld`), or even a mixed acronym (`XMLHttpRequest` → `xmlHttpRequest`). The smart tokenizer recognizes all the common boundaries automatically, so you don't have to pre-clean the input. **Q: Does the tool support Unicode and non-English letters?** A: Yes. The case transforms use the JavaScript Intl-aware `toLocaleLowerCase()` and `toLocaleUpperCase()` methods, which correctly handle Turkish dotted/dotless `İ`/`ı`, German `ß` (which uppercases to `SS` in standard handling), Greek final-sigma, and other locale-sensitive case mappings. Tokenization uses Unicode-aware regex patterns that recognize letters from any script (`\p{L}`). For programming-case outputs (camelCase, snake_case, etc.), the tokenizer treats only ASCII letters and digits as identifier characters by default — which matches the constraints of most programming languages — so non-Latin letters in the input pass through unchanged inside tokens. **Q: What's the difference between dot.case and path/case?** A: Both are lowercase, separator-joined identifiers — the only difference is the separator. `dot.case` uses periods: `hello.world.example`. It's common for namespacing (Java packages, Lodash methods, MongoDB field paths) and config-file keys (TOML, INI). `path/case` uses forward slashes: `hello/world/example`. It's the convention for URL paths, filesystem paths, and Git refs. Both are produced from the same tokenization, so converting between them is just a separator swap. Use dot.case when the identifier represents a hierarchical key inside data; use path/case when it represents a literal location. **Q: Why does the tokenizer split on numbers (file2x → file, 2, x)?** A: Numbers as token boundaries are the convention most modern codebases follow — `parseUTF8` should round-trip to `parse_utf_8` (or `parseUtf8` in PascalCase), not `parseutf_8`. The tokenizer treats every letter-to-digit and digit-to-letter transition as a boundary, so `file2x` becomes `file / 2 / x`. If you'd prefer to keep digits glued to the preceding letters, paste a manually-tokenized version (`file 2x` with a literal space) and the tokenizer will respect the space. This convention matches the change-case package and PEP 8 for Python. **Q: How is alternating case different from random case?** A: Alternating case (aLtErNaTiNg cAsE) flips between lowercase and uppercase deterministically — every odd letter is uppercase, every even letter is lowercase, regardless of word boundary. The result is the same every time for the same input. Random case (RaNdOm CaSe) flips each letter independently with a coin-flip, so every paste produces a different result. Click Re-shuffle to re-roll the random output without clearing the editor. Both are mocking-text formats (the so-called "spongebob meme"); alternating is the strict variant, random is the chaotic one. Other case outputs aren't affected by Re-shuffle. **Q: Does this convert HTTP header names?** A: Yes — use the Header-Case output. It capitalizes every token and joins with hyphens, producing canonical HTTP header spellings like `Content-Type`, `Access-Control-Allow-Origin`, and `X-Forwarded-For`. Paste a camelCase JS property name (`accessControlAllowOrigin`) and you get the exact header spelling the HTTP/1.1 spec uses, ready to drop into a `fetch()` options object or a server-side response. Note that HTTP/2 prefers lowercase header names (use kebab-case for that variant); HTTP/1.1 is case-insensitive but the Header-Case spelling is the conventional human-readable form. **Q: Can I convert a whole paragraph at once?** A: Yes — for the text-case transforms (UPPERCASE, lowercase, Title Case, Sentence case, iNVERSE, aLtErNaTiNg, RaNdOm), the tool preserves all whitespace, line breaks, and punctuation by design, so you can paste an entire paragraph or even a multi-page document. The programming-case transforms (camelCase, snake_case, etc.) deliberately strip punctuation since they produce identifiers; pasting a paragraph into camelCase will collapse it into one giant identifier, which is technically the correct transform but rarely useful. For document-length text, use the text-case outputs only; for identifier conversion, paste one identifier at a time. **Q: How accurate is this versus lodash, change-case, or other case libraries?** A: The tokenizer and case transforms produce byte-identical output to the change-case npm package (`change-case` v5+) for all common inputs — same handling of acronyms, same number-as-boundary rule, same Unicode letter recognition. lodash's `_.camelCase`, `_.snakeCase`, `_.kebabCase`, and `_.startCase` use a slightly different tokenizer (it splits on more characters and treats some Unicode classes differently), but for ASCII inputs the outputs match for the common cases. The Title Case in this tool is the naive variant (every word capitalized); lodash's `_.startCase` does the same. If you need APA or Chicago title-case rules (lowercase short prepositions), use a dedicated title-case library — this tool optimizes for the case most people search for. **Q: Why are there both Sentence case and Title Case if they look similar?** A: They diverge as soon as the input has more than one word. Sentence case lowercases everything and capitalizes only the first letter of each sentence: `hello world. this is a sentence.` becomes `Hello world. This is a sentence.` Title Case capitalizes every word: `Hello World. This Is A Sentence.` Sentence case is the convention for body text, captions, and UI microcopy in most modern style guides (Google, Microsoft, Apple). Title Case is the convention for headings, page titles, book titles, and dialog window titles in classical typography. Modern web style increasingly prefers Sentence case for everything except primary headlines. --- ### chmod Calculator — Linux File Permissions URL: https://go-tools.org/tools/chmod-calculator Convert Linux file permissions between octal (755, 644) and rwx symbols. Get chmod commands, spot risky settings like 777 — free, right in your browser. #### What Is chmod? chmod — change mode — is the Unix and Linux command that sets who may read, write or execute a file. Every file carries nine permission bits in three groups (owner, group, others), and chmod accepts them either as an octal number in numeric (absolute) mode, like chmod 755, or as letters in symbolic mode, like chmod u=rwx,go=rx. Both describe exactly the same bits; this calculator translates between them live. The octal shorthand works because each permission is a power of two: read is 4, write is 2, execute is 1, and one digit per class is simply their sum. So 7 is everything (4+2+1), 5 is read plus execute, 6 is read plus write, and 0 is nothing at all. Read rwxr-xr-x left to right in threes and you get 7, 5, 5. A fourth, leading digit encodes the special modes — setuid (4), setgid (2) and the sticky bit (1) — which is how 4755 or 1777 arise; their presence shows up in the symbolic string as s or t in place of x. Permissions are the first layer of Unix security, and most day-to-day permission problems reduce to a handful of patterns: a script that will not run needs execute (u+x), a directory you cannot enter is missing its x bit, a web asset should be 644 with 755 directories above it, and a private key must be 600 before OpenSSH will touch it. The mistakes cluster the same way — most infamously chmod 777, which "fixes" access errors by handing write access to every account on the machine. This page keeps the arithmetic, the symbols, the ready-to-run commands and the risk warnings in one place, and since octal is just base 8, you can explore the underlying positional arithmetic with the number base converter. ``` # Make a script executable, then verify with ls -l $ ls -l deploy.sh -rw-r--r-- 1 jack staff 512 Jul 17 10:00 deploy.sh $ chmod 755 deploy.sh $ ls -l deploy.sh -rwxr-xr-x 1 jack staff 512 Jul 17 10:00 deploy.sh # The symbolic equivalent of 755 $ chmod u=rwx,go=rx deploy.sh # Read the current mode as a number $ stat -c '%a' deploy.sh # Linux → 755 $ stat -f '%Lp' deploy.sh # macOS → 755 ``` #### FAQ **Q: What does chmod 777 mean?** A: It grants read, write and execute to everyone — owner, group and all other users alike. Each digit is a sum of read (four), write (two) and execute (one) for one class of user, and the maximum value in every position means nobody is restricted in any way. In symbolic form it is rwxrwxrwx. There are legitimate uses, but they are rare and almost always directories rather than files; when a tutorial tells you to chmod 777 something to "make it work", the actual problem is nearly always ownership, and chown is the correct fix. **Q: Is chmod 777 dangerous?** A: On anything reachable by other users or by a web server, yes: 777 means any account on the system — including a compromised service account — can replace the file's contents or drop new files into the directory. Web-server document roots set to 777 are a classic path to defaced sites and injected malware. The exception that proves the rule is 1777: adding the sticky bit, as /tmp does, lets everyone create files while preventing users from deleting each other's — this calculator flags plain 777 as a risk but recognises 1777 as the standard shared-directory pattern. **Q: What is the difference between chmod 755 and 644?** A: The execute bit. 755 (rwxr-xr-x) lets everyone execute or traverse, which is what directories, scripts and binaries need; 644 (rw-r--r--) is the same thing without any execute permission, which is right for regular files like HTML pages, images and config files. The classic pairing is 755 for every directory and 644 for every file inside — the files-only and directories-only commands this calculator generates produce exactly that split with find -type d and find -type f. **Q: How do I fix "Permissions 0644 for 'id_rsa' are too open"?** A: Run chmod 600 ~/.ssh/id_rsa and connect again. OpenSSH refuses to use a private key that any other account could read: it prints the banner WARNING: UNPROTECTED PRIVATE KEY FILE!, the line Permissions 0644 for '/home/user/.ssh/id_rsa' are too open., states that private key files must NOT be accessible by others, and then ignores the key — which surfaces as a sudden password prompt or Load key ... bad permissions. Mode 600 (rw-------) satisfies the check; 400 (r--------) also works and additionally guards against accidental overwrites. See the OpenSSH manual for the key-file requirements. **Q: What is the difference between chmod and chown?** A: chmod changes what the existing owner, group and others may do with a file — its permission bits. chown changes who the owner and group actually are. They solve different problems: if a web server cannot write its own upload directory, the fix is usually chown www-data:www-data on the directory, not loosening permissions for the whole world with chmod. A good habit when something is unexpectedly unreadable: check ownership first with ls -l, decode the mode (paste the line into this calculator), and only then decide which of the two commands the situation calls for. **Q: What does chmod +x do?** A: It adds the execute bit for the user classes not masked by your umask — with the common umask of 022 that means everyone, so chmod +x script.sh is usually equivalent to chmod a+x script.sh and turns a file the shell refuses to run into an executable one. Under a stricter umask like 077, however, +x only affects the owner. If you want a specific outcome regardless of umask, say it explicitly: u+x for just the owner, a+x for everyone. Directories need the same bit to be entered at all, which is why they are 755 rather than 644 by default. **Q: How do I chmod all files and folders recursively?** A: Avoid a blanket chmod -R 755: it makes every regular file executable, which is noise at best and a risk at worst. The clean way is to treat directories and files separately — find dir -type d -exec chmod 755 {} + for directories and find dir -type f -exec chmod 644 {} + for files, both of which this calculator generates for your chosen mode. GNU chmod's capital X does the same in one line (chmod -R u+rwX,go+rX dir): it adds execute only to directories and to files that are already executable. One Linux subtlety: a plain numeric chmod like 755 leaves an existing setuid/setgid bit on directories in place — clear it explicitly with 00755 or u-s,g-s if that is what you intend. **Q: Is my data uploaded when I use this calculator?** A: No. Every conversion happens locally in your browser with plain JavaScript bit arithmetic — there is no server call, no analytics event tied to what you type, and nothing to retain. You can open your browser's developer tools and watch the network panel stay silent while you toggle permissions, or go offline entirely and keep working. The Copy link button encodes the current mode in the URL fragment, and fragments are never sent to a server either. --- ### Color Converter — HEX, RGB, HSL & OKLCH URL: https://go-tools.org/tools/color-converter Convert HEX to RGB, HSL, OKLCH, OKLAB and CMYK in your browser — copy any format with one click. Free, no signup, your colors never leave the page. #### What Is a Color Converter? A color converter is a small utility that translates a single color value between the formats your toolchain, your design system, and your browser actually understand — HEX, RGB, HSL, HSV, HWB, CMYK, OKLCH, OKLAB, and the 148 CSS named colors. Online converters have been a staple of web tooling since the early 2000s, back when the answer was almost always a simple HEX-to-RGB textbox built for a Geocities-era stylesheet. What separates a modern converter from those legacy tools is three things: a unified-field UX where every format is simultaneously editable instead of a one-direction dropdown, an OKLCH source-of-truth that holds the canonical value internally so round-trips stay bit-stable, and perceptual math grounded in W3C CSS Color 4 instead of the gamma-tangled HSL arithmetic the 2003 generation shipped. Different color spaces exist because different problems need different representations, and no single space is good at all of them. RGB is hardware-native — it maps directly to the red, green, and blue subpixels of an LCD or the phosphors of a CRT, with each channel encoded as an 8-bit integer from 0 to 255. HEX is just RGB in base-16, packed into a six-character string for terse CSS and Figma copy-paste. HSL, HSV, and HWB are designer-cognitive spaces — cylindrical reshapes of RGB that let you rotate hue, lighten, or darken with intuitive sliders. HSL was published in 1978 alongside HSV by Alvy Ray Smith; HWB was added in 1996 as a cleaner mental model (Hue + amount of White + amount of Black). CMYK is a print-process abstraction — a subtractive ink stack (Cyan, Magenta, Yellow, Key=black) that models how ink absorbs light on paper rather than how a screen emits it. OKLCH and OKLAB are perceptual spaces — they're designed so that equal numeric distance corresponds to equal perceived distance, which makes them indispensable for design-system ramps and accessibility math. Named colors are CSS legacy: the 148 SVG/CSS3 names like `tomato`, `rebeccapurple`, and `slategray` that ship with every browser. For more than twenty years sRGB was "good enough" — a 1996 IEC standard built around the phosphor primaries of the CRT monitors of the day. It quietly defined the upper bound of what a web color could mean. Then wide-gamut displays broke the assumption. Apple's Display P3 covers roughly 50% more of the visible spectrum than sRGB and now ships in every iPhone, iPad, and MacBook from 2017 onward. Rec.2020 covers even more and is the broadcast standard for HDR TV. HSL embedded sRGB's gamma quirks deep in its definition, which is why an HSL ramp looks visually uneven on a wide-gamut display — a green at L=50% looks brighter than a blue at L=50%, because HSL's L is geometric, not perceptual. In 2020 Björn Ottosson published OKLAB, a perceptually-uniform color space derived from CIE-LAB with corrected lightness response and cleaner behavior at high saturation. OKLCH is its polar form (Lightness / Chroma / Hue), the same shape as HSL but with the perceptual math fixed. CSS Color 4 added `oklch()` and `oklab()` syntax in 2022; Chrome 111 shipped support in March 2023, Safari 15.4 already had it as of March 2022, and Firefox 113 landed in May 2023. Tailwind v4, released in 2025, made OKLCH its default color token format; shadcn/ui followed shortly after. This tool reflects that shift by making OKLCH the internal source of truth — every conversion routes through OKLCH, so a HEX → RGB → OKLAB → HEX round-trip never accumulates float drift, and editing the L channel of OKLCH directly updates every other field exactly. Which space you reach for depends entirely on what you're doing. **HEX** is the right call for web embedding, copy-paste between design tools and code, and anywhere terse identifiers matter — `#3b82f6` fits in a CSS variable comfortably and most front-end developers can read it on sight. The dedicated hex to RGB converter handles the single most common direction one-step; the reverse RGB to hex converter covers the case where you have separate channel integers from a designer or image-pixel-math pipeline. **RGB** is for direct hardware addressing — anywhere you need 0-255 integers (canvas APIs, image manipulation, hardware LED strips, OpenGL color attributes). **HSL** is the legacy designer-cognitive space — rotate hue, lighten, darken — and still useful when you need a quick CSS color tweak in a project that hasn't migrated to OKLCH. The single-direction hex to HSL converter is the right shortcut when that's all you need. **HSV and HWB** are designer color-picker spaces. HSV (Hue, Saturation, Value) matches the saturation-value square most picker UIs draw, so it's what Photoshop, Figma, and Sketch report when you click the eyedropper. HWB is the cleaner mental model — pick a pure hue, then add white to lighten or black to darken — and CSS Color 4 added native `hwb()` support across all evergreen browsers. **CMYK** is for print preparation. A blunt disclaimer: our CMYK output is a naive sRGB-based approximation using the standard `K = 1 - max(R,G,B); C = (1-R-K)/(1-K)` formula. Real print accuracy requires ICC profile conversion against the specific press, ink, and paper — typically US Web Coated SWOP v2 or Fogra39 — which can shift channels by 5-15%. Treat our CMYK as a starting estimate, not a deliverable. The single-direction hex to CMYK converter applies the same formula with the same caveat. **OKLCH** is the default choice for new code in 2025 and forward — modern design systems, accessibility-aware palette generation, anywhere perceptual uniformity matters. The single-direction hex to OKLCH converter exists for quick legacy-palette migration. **OKLAB** is the rectangular cousin used for palette math: mid-points between two colors, distance calculations, color-blindness simulation matrices, and other operations that need linear-axis arithmetic. **Named colors** are for documentation, code comments, mocks, and prose — the 148 CSS named colors are a fixed dictionary, and the tool finds the closest named color for any input via ΔE distance in OKLAB. The conversion graph at the heart of all this is well-defined and surprisingly clean. sRGB and linear-sRGB are related by a piecewise gamma curve specified in W3C CSS Color 4 §11.2 (roughly a 2.4 exponent with a small linear segment near zero). Linear-sRGB and CIE XYZ D65 are related by a fixed 3×3 matrix from CSS Color 4 §15.1. XYZ D65 and OKLAB are related by two matrices and a cube-root step (the LMS cone-response stage, per Ottosson 2020). OKLAB and OKLCH are related by a Cartesian-to-polar transform — `C = sqrt(a² + b²); H = atan2(b, a)`. HEX is just sRGB serialized as `#RRGGBB` base-16. RGB ↔ HSL, RGB ↔ HSV, RGB ↔ HWB are direct geometric transforms within sRGB, defined in CSS Color 4 §5-7. CMYK is the naive sRGB-based formula above. The whole pipeline is a directed graph rooted at OKLCH internally; every other format is computed from it on every keystroke. Beyond the core conversion, this tool ships features the legacy generation didn't. **Display P3 and Rec.2020 gamut detection** — three badges flag whether the current color falls inside each space's reproducible range, with a one-click **Snap to sRGB** button that uses binary chroma reduction (per CSS Color 4's informative algorithm) to shrink the color until it fits. **WCAG 2 + APCA Lc dual contrast badges** — both metrics displayed in one row so you can pass the regulatory standard today and sanity-check with the forward-looking perceptual metric. **8 color-blindness simulations** — protanopia, deuteranopia, and tritanopia via the Brettel-Viénot-Mollon 1997 dichromacy matrices; protanomaly, deuteranomaly, and tritanomaly via Machado-Oliveira-Fernandes 2009 anomalous-trichromacy matrices at severity 0.66; achromatopsia and partial achromatomaly via rec601 luminance weights. **OKLCH-uniform palette generation** — tints, shades, tones, and harmonies built by stepping the L channel in equal increments while holding C and H fixed (the same construction as Tailwind v4's default palette). **CSS / Tailwind v4 / SwiftUI / Compose / Flutter code snippets** — paste-ready output for the five platforms most cross-team teams target. **EyeDropper API integration** on Chromium browsers (Chrome, Edge, Brave, Opera) for picking colors anywhere on screen including outside the browser. **URL hash state** — the current color encodes into the URL as `#hex=...` or `#oklch=...` so you can share a live link to the exact color with one copy. Everything in this tool runs locally in your browser. Your color values are never uploaded, never logged, never analyzed, never persisted on a server. Zero network requests as you type — open the browser DevTools Network tab and watch: typing in any field triggers no traffic at all. This makes the tool safe for unannounced brand palettes, internal design-token systems, draft mockups, and any other confidential color work. No cookies record what you paste; no analytics fire on color changes. The same posture extends to the URL hash: the `#hex=...` fragment lives only in your address bar and is never transmitted to the server (browsers don't include the fragment in HTTP requests), so even a shared link doesn't leak the color to any third party other than the recipient you sent it to. For teams handling pre-launch brand work, embargoed campaigns, or client palettes under NDA, this matters more than the convenience headline suggests. For a deeper dive into why OKLCH became the design-system standard in 2024–2026, read our companion guide: OKLCH color space explained — why Tailwind v4 adopted it. ``` // sRGB → linear → XYZ D65 → OKLAB → OKLCH // References: W3C CSS Color 4 §11-15, Ottosson 2020 (https://bottosson.github.io/posts/oklab/) // Worked example: #3b82f6 (Tailwind blue-500) const srgb = [0x3b, 0x82, 0xf6].map(v => v / 255); // [0.231, 0.510, 0.965] const toLinear = (v) => v <= 0.04045 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4); const lin = srgb.map(toLinear); // gamma-decoded linear-sRGB // linear-sRGB → XYZ D65 (CSS Color 4 §15.1 matrix) const [lr, lg, lb] = lin; const x = 0.4124564 * lr + 0.3575761 * lg + 0.1804375 * lb; const y = 0.2126729 * lr + 0.7151522 * lg + 0.0721750 * lb; const z = 0.0193339 * lr + 0.1191920 * lg + 0.9503041 * lb; // XYZ D65 → LMS (Ottosson 2020 matrix), cube-root, → OKLAB const l_ = Math.cbrt(0.8189330101 * x + 0.3618667424 * y - 0.1288597137 * z); const m_ = Math.cbrt(0.0329845436 * x + 0.9293118715 * y + 0.0361456387 * z); const s_ = Math.cbrt(0.0482003018 * x + 0.2643662691 * y + 0.6338517070 * z); const L = 0.2104542553 * l_ + 0.7936177850 * m_ - 0.0040720468 * s_; const a = 1.9779984951 * l_ - 2.4285922050 * m_ + 0.4505937099 * s_; const b = 0.0259040371 * l_ + 0.7827717662 * m_ - 0.8086757660 * s_; // OKLAB → OKLCH (Cartesian to polar) const C = Math.sqrt(a * a + b * b); const H = (Math.atan2(b, a) * 180 / Math.PI + 360) % 360; console.log(`oklch(${L.toFixed(3)} ${C.toFixed(3)} ${H.toFixed(1)})`); // → oklch(0.629 0.193 263.4) ``` #### FAQ **Q: How do I convert a hex code to RGB?** A: Split the 6-digit hex into three 2-digit pairs, then read each pair as a base-16 number from 0-255. `#FF5733` becomes R=`FF`=255, G=`57`=87, B=`33`=51, giving `rgb(255, 87, 51)`. 3-digit shorthand (`#F73`) expands by doubling each digit: `#F73` → `#FF7733`. This tool does the conversion live as you type — paste any hex (with or without the `#`, 3-digit or 6-digit, 4-digit or 8-digit with alpha) and the RGB field updates instantly. **Q: Is hex the same as RGB?** A: They encode the same information in different notation. Both describe a color as three channels (red, green, blue) on the 0-255 scale, anchored to the sRGB color space. Hex packs the three channels into a 6-character base-16 string (`#FF5733`); the `rgb()` function spells them out in decimal (`rgb(255, 87, 51)`). They round-trip losslessly. The differences are practical: hex is shorter and fits in a CSS variable comfortably, `rgb()` accepts a separate alpha channel via `rgba()` and supports CSS Color 4 percentage syntax. **Q: How do you read a hex color code?** A: A hex color has 6 hexadecimal digits after the `#`, grouped as **RR GG BB**. Each pair encodes one channel from `00` (none) to `FF` (full, 255 in decimal). `#FF0000` is pure red, `#00FF00` is pure green, `#0000FF` is pure blue. An 8-digit hex (`#FF5733CC`) adds an alpha pair at the end — `CC` = 204/255 ≈ 80% opacity. The 3-digit shorthand (`#F73`) expands each digit by duplicating it: `#F73` is identical to `#FF7733`. **Q: What is the formula for hex to RGB?** A: For each 2-digit hex pair, multiply the first digit by 16 and add the second: `FF` = 15×16 + 15 = 255, `57` = 5×16 + 7 = 87, `33` = 3×16 + 3 = 51. In JavaScript: `parseInt('FF', 16)` returns 255. In CSS the reverse direction is built into the parser — `rgb(255 87 51)` and `#FF5733` are interchangeable anywhere a `` is accepted. There's no rounding loss: 16² = 256, exactly matching the 0-255 byte range each channel occupies. **Q: Why use hex instead of RGB?** A: Three reasons: it's shorter (`#FF5733` vs `rgb(255, 87, 51)`), it's the format every design tool (Figma, Sketch, Photoshop) exports by default, and it's the format your eye learns to recognize over time — most front-end developers can identify `#3b82f6` as Tailwind blue-500 on sight. Use `rgb()` (or the modern space-separated `rgb(R G B / A)` syntax from CSS Color 4) when you need alpha, when you're computing a color from JavaScript, or when you want explicit percentage syntax for readability. **Q: Can hex codes have alpha / transparency?** A: Yes — use 8-digit hex (`#RRGGBBAA`) or 4-digit shorthand (`#RGBA`). The alpha pair follows the same 0-`FF` scale: `#FF573300` is fully transparent, `#FF5733FF` is fully opaque, `#FF573380` is about 50%. CSS 4-digit hex with alpha shipped in all evergreen browsers in 2018. Safari, Chrome, Firefox, and Edge all support it. If you need to target very old browsers, fall back to `rgba()` which has been supported since IE 9. **Q: How many colors can hex represent?** A: 6-digit hex represents exactly **16,777,216** colors — 256 values per channel cubed (256³). With 8-digit hex including alpha, the addressable space is 256⁴ ≈ 4.3 billion, but the color content is still 16.7M; the extra dimension is opacity. The human eye can distinguish around 10 million colors, so 24-bit sRGB has been called "truecolor" since the 1990s. Modern wide-gamut displays (Display P3, Rec.2020) cover more of the visible spectrum, but hex itself is sRGB-bound — use OKLCH or `color(display-p3 ...)` to address wide-gamut values. **Q: What is OKLCH color?** A: OKLCH is a perceptually-uniform color space derived from OKLAB by converting the a/b chroma axes to polar coordinates. Channels are **Lightness** (0-1), **Chroma** (0 to about 0.4 depending on hue), **Hue** (0-360°). Unlike HSL, equal L values look equally bright across all hues, so design-system color ramps stay perceptually consistent. CSS Color 4 added the `oklch()` function in 2022; Chrome 111, Safari 15.4, and Firefox 113 all ship native support. Tailwind v4 and shadcn use OKLCH for their default palettes. **Q: Is OKLCH better than HSL?** A: For design systems, yes — and the difference is measurable. HSL's L (lightness) is geometric, not perceptual: `hsl(60, 100%, 50%)` (yellow) looks visibly brighter than `hsl(240, 100%, 50%)` (blue) even though both report L=50%. OKLCH's L is anchored to the OKLAB perceptual model from Björn Ottosson (2020), so equal L means equal perceived brightness. The practical upshot: an OKLCH ramp produces visually-even steps automatically; an HSL ramp requires manual per-hue lightness tweaking to look right. **Q: What browsers support oklch()?** A: All evergreen browsers as of mid-2023: **Chrome/Edge 111** (March 2023), **Safari 15.4** (March 2022, the earliest), **Firefox 113** (May 2023). Combined caniuse coverage is over 94%. For the remaining IE 11 / old-Safari long tail, use the `@supports (color: oklch(0 0 0))` query to provide a hex fallback, or use a build-time tool like PostCSS `postcss-oklab-function` to inline an sRGB approximation alongside the OKLCH value. **Q: Why use OKLCH in Tailwind v4?** A: Tailwind v4 moved its default palette from HSL-based to OKLCH-based generation because OKLCH gives perceptually-even ramps automatically. The `blue-500` and `red-500` swatches actually look equally bright now — under the v3 HSL system they didn't, which forced designers to hand-tune individual stops. OKLCH also unlocks wider gamuts on modern displays: a Tailwind v4 token like `oklch(0.65 0.25 30)` can address Display P3 reds that no hex code can reach. The build still emits hex fallbacks for older browsers. **Q: Is OKLCH perceptually uniform?** A: Yes — that's the whole point. OKLCH inherits perceptual uniformity from OKLAB, Björn Ottosson's 2020 color space designed specifically to fix the non-uniformities in CIELAB (the previous best perceptually-uniform space). A fixed step in the L channel corresponds to a fixed perceived brightness step. A fixed step in C corresponds to a fixed perceived chroma step. This is why OKLCH ramps look smooth — the math matches human vision. CIELAB approximations break down around very saturated colors; OKLAB/OKLCH stay accurate across the gamut. **Q: How do you read an OKLCH value?** A: `oklch(L C H)` — three numbers, optionally with `/ A` for alpha. **L** is Lightness from 0 (black) to 1 (white); written as a number or percentage (`0.6` and `60%` are equivalent). **C** is Chroma from 0 (gray) up to about 0.4 for the most saturated sRGB colors; there is no upper bound, wide-gamut colors can exceed it. **H** is Hue in degrees from 0-360, same as HSL (0/360 = red, 120 = green, 240 = blue). Example: `oklch(0.629 0.193 263.4)` is Tailwind's blue-500. **Q: What is the difference between gamut and color space?** A: A **color space** is a coordinate system that gives every color a unique address — sRGB, Display P3, Rec.2020, OKLCH are all color spaces. A **gamut** is the subset of visible colors that a particular space (or device) can actually reproduce. sRGB and Display P3 use similar coordinate systems but P3 covers ~50% more of the visible spectrum. OKLCH is unbounded — its coordinate system can address any color, but whether your screen can display it depends on the screen's gamut. The gamut badges in this tool tell you which device families will render the color accurately. **Q: Why is my OKLCH color out of sRGB gamut?** A: OKLCH is gamut-unbounded — you can write `oklch(0.7 0.4 30)` and it's a valid color, but its chroma exceeds what sRGB's 256-per-channel byte space can encode. On an sRGB monitor that color clips to the nearest in-gamut approximation (usually a desaturated version). On a Display P3 monitor (most modern laptops, iPhones, MacBooks) it renders correctly. Click **Snap to sRGB** to reduce chroma until the color fits, then ship the snapped hex as a fallback alongside the original OKLCH for wide-gamut displays. **Q: Should I use WCAG 2 or APCA for contrast?** A: Use **WCAG 2.1** today — it's the regulatory standard (ADA, EAA, Section 508) and what audit tools check. The 4.5:1 ratio for body text and 3:1 for large text are the legal floors. **APCA** (Accessible Perceptual Contrast Algorithm) is the proposed WCAG 3 successor, designed to match perception better — it weighs light-on-dark differently from dark-on-light, which WCAG 2's symmetric formula gets wrong. APCA is still draft. Best practice: pass WCAG 2 to satisfy compliance, then sanity-check with APCA (target `Lc 75`+ for body text) to make sure the result actually looks readable. **Q: What is the difference between HSV and HWB?** A: Both are cylindrical reshapes of RGB that share the same Hue channel. **HSV** (Hue, Saturation, Value) was published by Smith in 1978 — Saturation is the colorfulness, Value is the brightness. Pure red is `hsv(0, 100%, 100%)`. **HWB** (Hue, Whiteness, Blackness) was published by Smith again in 1996 as a more intuitive alternative for artists — you pick a pure hue, then add white to lighten or black to darken. CSS Color 4 added `hwb()` syntax; it ships in all evergreen browsers. HWB is easier to teach ("add white") but HSV remains more common in graphics software like Photoshop and Figma. --- ### CRC Checksum Calculator URL: https://go-tools.org/tools/crc-calculator Paste hex or text and get all 63 CRC-8, CRC-16 and CRC-32 variants at once. Got a checksum you cannot match? Type it in and the tool names the variant — MODBUS, CCITT-FALSE, XMODEM, KERMIT. Runs entirely in your browser. #### What is a CRC? A cyclic redundancy check treats a block of data as the coefficients of a very long binary polynomial, divides it by a fixed generator polynomial using modulo-2 arithmetic, and keeps the remainder. That remainder is the checksum. The construction is popular because the division reduces to shifts and XORs, which costs almost nothing in hardware, and because the algebra gives hard guarantees rather than statistical hope: a well chosen 16-bit polynomial detects every single-bit error, every double-bit error within a useful block length, every odd number of bit flips, and every burst of 16 or fewer consecutive corrupted bits. What makes CRC confusing in practice is that the polynomial is only one of six parameters. Two implementations can agree on the polynomial and still disagree on every result, because they differ in the register's initial value, in whether input bytes and the output register have their bit order reversed, and in the constant XORed into the final value. A CRC variant is that whole parameter set, not the polynomial alone — which is why a name like "CRC-16" identifies almost nothing on its own, and why this page prints the polynomial, the initial value, both reflection flags and the final XOR beside every result, with the width as the group heading. One caveat the table cannot show: a 16-bit polynomial detects every odd number of bit flips only when x+1 divides it. That holds for the two you are most likely to meet, 0x1021 and 0x8005, but not for all of them — CRC-16/T10-DIF and CRC-16/PROFIBUS are among the exceptions. ``` // CRC-16/MODBUS: poly=0x8005, init=0xFFFF, refin/refout=true, xorout=0x0000 // Written in the reflected form, so the polynomial appears bit-reversed as 0xA001. function crc16Modbus(bytes) { let crc = 0xffff; for (const byte of bytes) { crc ^= byte; for (let i = 0; i < 8; i++) { crc = crc & 1 ? (crc >>> 1) ^ 0xa001 : crc >>> 1; } } return crc; } crc16Modbus([0x01, 0x03, 0x00, 0x00, 0x00, 0x0a]); // 0xCDC5 // On the wire Modbus RTU sends the low byte first: ... 0x0A 0xC5 0xCD ``` #### FAQ **Q: Why does my device return a different CRC than this calculator?** A: Almost always because you are comparing two different variants. CRC-16 alone has 31 catalogued parameter sets, and MODBUS, CCITT-FALSE, XMODEM and KERMIT produce four unrelated numbers from identical bytes. Put the device's value in the expected-value box: if any variant reproduces it, the matching row is highlighted and you have your answer. If nothing matches, the data being hashed is not what you think it is — check byte order, and check whether the frame's start and end markers are included in the calculation. **Q: Which CRC-16 does Modbus use?** A: CRC-16/MODBUS: polynomial 0x8005, initial value 0xFFFF, input and output both reflected, no final XOR. The frequent point of confusion is transmission order rather than the algorithm — Modbus RTU sends the CRC low byte first, so a frame whose CRC is 0xCDC5 carries the bytes C5 CD at the end. See the CRC-16 variants guide for a worked frame. **Q: What is the difference between CRC-16/CCITT and CRC-16/CCITT-FALSE?** A: They are different algorithms with confusingly similar names, which is why the RevEng catalogue renamed both. What people call CCITT-FALSE is CRC-16/IBM-3740: initial value 0xFFFF, no reflection. What is usually meant by plain CCITT is CRC-16/KERMIT: initial value 0x0000, input and output reflected. This calculator shows both the formal name and the name your device manual is likely to use. **Q: Can CRC detect that a file has been tampered with?** A: No. CRC is an error-detection code designed for accidental corruption on a noisy channel, and it is linear — anyone can modify a message and adjust it so the CRC still matches. For integrity against a deliberate attacker use a cryptographic hash such as SHA-256, or an authenticated construction like HMAC. CRC is excellent at what it was built for and offers no security at all. **Q: What do refin and refout actually do?** A: refin reverses the bit order within each input byte before it is fed to the register; refout reverses the bit order of the final register. They exist because hardware shift registers and software table implementations clock bits in opposite directions, and the reflected forms let both arrive at the same number. They are not the same thing as byte order — reflection acts on bits inside a byte, while endianness decides the order of the bytes themselves. **Q: I only have the checksum, not the data. Can the tool work backwards?** A: No, and no tool can. A CRC compresses an arbitrarily long message into 8, 16 or 32 bits, so countless different messages share any given value — this is not a matter of effort. The reverse lookup on this page answers a narrower question: given the data **and** a checksum somebody computed from it, which parameter set connects the two. If you captured a device response but not the payload behind it, capture the payload first. On choosing between widths rather than identifying one, see the CRC-16 variants guide. **Q: Why does the calculator show 63 variants when my device only lists one?** A: Because the useful question is usually not "compute a CRC" but "which of these produced the value I am holding". Tools that make you pick a variant first assume you already know the answer. Showing every variant at once turns identification into a single lookup, and the parameter columns let you confirm the match against the specification rather than trusting a name. **Q: Is my data sent anywhere?** A: No. The whole calculation runs in your browser using the same engine that rendered the table on this page — there is no upload, no API call and no logging. You can disconnect from the network and the tool keeps working, which matters because CRC inputs are often production frames or firmware images. --- ### Crontab Generator & Cron Expression Builder URL: https://go-tools.org/tools/crontab-generator Build, validate, and decode cron expressions in your browser. Live next-run preview in local time or UTC. POSIX 5-field syntax, presets, plain-English description. Free, private, no signup. #### What Is a Cron Expression? A cron expression is a five-field string that defines a repeating schedule. From left to right, the fields are minute (0-59), hour (0-23), day-of-month (1-31), month (1-12), and day-of-week (0-6, where 0 and 7 both mean Sunday). Each field accepts a value, a list (`1,3,5`), a range (`1-5`), a wildcard (`*` meaning any value), or a step (`*/15` meaning every 15). The combination defines exactly when the scheduled command will run — `0 9 * * 1-5` for example reads "at minute 0, hour 9, any day-of-month, any month, day-of-week Monday through Friday" — in plain English, "weekdays at 9:00 AM". Cron originated in Unix Version 7 in 1979 and the five-field grammar has remained essentially unchanged for over four decades — a testament to how well-designed the original syntax is. Today cron expressions are used far beyond the Unix crontab file: Kubernetes CronJobs, GitHub Actions workflows, AWS EventBridge rules, GitLab CI scheduled pipelines, Cloudflare Workers Cron Triggers, and serverless platforms across every cloud all accept the same five-field grammar. Learning cron once means knowing how to schedule jobs in every modern infrastructure context. The POSIX standard defines five operators: `*` (any value), `,` (list of values), `-` (range), `/` (step), and named tokens for months (JAN-DEC) and weekdays (SUN-SAT). Most implementations also expand five common shortcuts: `@yearly` (`0 0 1 1 *`), `@monthly` (`0 0 1 * *`), `@weekly` (`0 0 * * 0`), `@daily` (`0 0 * * *`), and `@hourly` (`0 * * * *`). The Quartz scheduler (a Java library) extends this with an optional seconds field and additional operators (`?`, `L`, `W`, `#`) — useful if you're working in Java/Spring, but not portable to standard cron. This tool follows the POSIX five-field standard because it's the dominant variant and the one your Linux server, GitHub Actions runner, and Kubernetes cluster will actually understand. One quirk of POSIX cron deserves special attention: when both day-of-month and day-of-week are restricted (neither is `*`), the schedule runs when EITHER matches — OR semantics, not AND. So `0 0 1 * 5` runs on the 1st of every month AND every Friday, not just Fridays that happen to fall on the 1st. This is the single most common cron surprise; the next-run preview in this tool makes it obvious by showing the actual datetimes the schedule will fire. Verify before deploying. All parsing and next-run computation happens entirely in your browser using JavaScript — no expressions, schedules, or any other data are ever sent to a server. This tool parses any standard POSIX cron expression instantly with a plain-English description and a five-run preview, with complete privacy. Cron expressions are closely related to other developer tools. Cron jobs are commonly debugged by checking Unix timestamps against the expected run times, and complex schedules are often documented as JSON configuration which can be validated with our JSON formatter. For an in-depth guide covering the OR semantics, timezone pitfalls, and common cron variants with examples in Linux, Kubernetes, and GitHub Actions, read our cron schedule reference. ``` # Linux crontab entry — runs every 15 minutes */15 * * * * /usr/local/bin/poll-api.sh # Kubernetes CronJob — weekdays at 9:00 AM UTC apiVersion: batch/v1 kind: CronJob metadata: name: daily-report spec: schedule: "0 9 * * 1-5" timeZone: "UTC" jobTemplate: spec: template: spec: containers: - name: report image: report-runner:1.0 restartPolicy: OnFailure # GitHub Actions workflow — hourly on: schedule: - cron: '0 * * * *' # AWS EventBridge — first of each month ScheduleExpression: cron(0 0 1 * ? *) # (Note: AWS uses the Quartz 6-field form with '?' for day-of-week) ``` #### FAQ **Q: What does this tool do?** A: It parses, validates, and explains cron expressions in your browser, with a live preview of the next five scheduled runs in your timezone or UTC. Type any standard POSIX five-field cron expression — or use the preset chips and per-field inputs to build one without memorizing the syntax — and the tool produces a plain-English description ("Every 15 minutes", "Weekdays at 9:00 AM", etc.) plus the actual datetimes when the job will fire. Wrong expressions are caught instantly with a field-level error message so you don't waste a deploy on a broken schedule. The whole tool runs 100% client-side: nothing is uploaded, logged, or stored — safe for production crontabs and internal schedules with sensitive timing patterns. **Q: What is a cron expression?** A: A cron expression is a five-field string that defines a repeating schedule. The fields are minute (0-59), hour (0-23), day-of-month (1-31), month (1-12), and day-of-week (0-6, where 0 and 7 both mean Sunday). Each field accepts a value, a list (`1,3,5`), a range (`1-5`), a wildcard (`*` = any), or a step (`*/15` = every 15). The combination of all five fields defines exactly when the scheduled command should run. Cron originated in Unix V7 (1979) and remains the de facto language for time-based job scheduling on Linux/Unix, in container orchestration (Kubernetes CronJobs), in CI/CD (GitHub Actions, GitLab CI), and in serverless platforms (AWS EventBridge, Cloudflare Workers Cron Triggers). Despite many alternatives proposed over the decades, no replacement has displaced cron's terse, expressive five-field grammar. **Q: Is my data uploaded anywhere?** A: No. All parsing, validation, and next-run computation runs 100% client-side in your browser using JavaScript. Your expressions are never transmitted, never stored on any server, never logged, and never analyzed. This makes the tool safe for production crontabs, internal scheduling patterns that reveal infrastructure timing, and any sensitive schedules. You can verify this in your browser's Network tab — typing a cron expression triggers zero network requests. The tool uses no cookies for input data and no third-party analytics that would capture what you type. **Q: What's the difference between POSIX cron and Quartz?** A: POSIX cron is the five-field standard used by Unix/Linux crontab, systemd timers, GitHub Actions, GitLab CI, AWS EventBridge, Kubernetes CronJobs, and most schedulers. Quartz is a Java scheduling library that adds a seconds field (six or seven fields total) plus extra operators: `?` (no specific value, used when day-of-month and day-of-week conflict), `L` (last — last day of month, last Friday, etc.), `W` (nearest weekday), and `#` (nth weekday of month, e.g., `2#1` = first Tuesday). This tool implements POSIX cron because it's far more widely used; Quartz operators are reported as syntax errors with a clear "Quartz operators not supported" message. If you need Quartz, popular Java schedulers like Quartz Scheduler and Spring's `@Scheduled` are your target — but the schedule definitions don't port directly to Linux cron. **Q: Why does '0 0 1 * 5' run on every Friday AND the 1st?** A: This is POSIX cron's day-of-month / day-of-week OR semantics, and it's the single most common cron surprise. The rule: when BOTH fields are restricted (neither is `*`), cron runs the job if EITHER condition matches — not both. So `0 0 1 * 5` (day-of-month=1, day-of-week=5) fires on the 1st of every month AND every Friday, not just Fridays that happen to fall on the 1st. If you wanted only the latter (AND semantics — Friday-the-1st), you can't express it in standard cron; you'd need a script that runs every Friday OR the 1st and exits early based on the actual date. Vixie cron (the GNU/Linux default), BSD cron, and AWS EventBridge all follow this OR rule. The next-run preview in this tool makes the actual schedule obvious — paste suspicious expressions and verify before deploying. **Q: How do I run a job every 30 seconds?** A: You can't, with standard POSIX cron. Cron's minimum granularity is one minute — the smallest field is minute (0-59). For sub-minute schedules, your options are: (1) Run two jobs at `* * * * *` and `* * * * *` with `sleep 30 &&` in front of one — crude but works for vixie cron. (2) Use a scheduler with seconds support like Quartz, Kubernetes CronJob with a custom controller, or systemd timers with `OnCalendar: *-*-* *:*:00/30`. (3) Run a long-lived daemon that sleeps 30 seconds between iterations — the right answer for most monitoring needs. (4) Switch to an event-driven trigger (webhook, message queue) if you actually need real-time response. The 30-second cron pattern is almost always a sign that cron is the wrong abstraction. **Q: What timezone does cron use?** A: On a Linux server, vixie cron uses the system timezone — usually set via `/etc/timezone` or the `TZ` environment variable. This is a frequent source of bugs: a 9:00 AM cron on a US East server fires at 14:00 UTC, but on a server set to UTC it fires at 09:00 UTC (i.e., 4:00 AM East). The fix is either to always set your servers to UTC and write all cron expressions in UTC, or to set the `CRON_TZ=America/New_York` variable at the top of the crontab to pin the timezone explicitly (supported by vixie cron 3.0+). Managed schedulers vary: GitHub Actions runs UTC always, AWS EventBridge supports timezone in the schedule definition, Kubernetes CronJob added a `spec.timeZone` field in 1.27+. This tool's UTC/Local toggle lets you preview the schedule in either timezone — flip between them to confirm the run lands where you intended. **Q: What does '*/15' actually expand to?** A: The step operator `*/N` means "every N starting at the lowest valid value of the field". For minute (range 0-59), `*/15` expands to `0,15,30,45` — four runs per hour at the quarter-hour. The step is NOT "every 15 minutes from the current time"; it's anchored to the field's starting value. Same logic for other fields: `*/2` in hour means `0,2,4,...,22` (12 runs); `*/3` in day-of-month means `1,4,7,...,31` (11 runs). For non-wildcard step bases (e.g., `5/15`), the expansion is from the base: `5/15` in minute = `5,20,35,50`. Step values that don't divide the range evenly will skip near the wraparound — this is correct cron behavior, not a bug. The next-run preview makes the actual schedule obvious. **Q: Can I use a six-field expression with seconds?** A: Six-field expressions with a leading seconds field (range 0-59) are a Quartz/Spring/Cron4j extension, not POSIX. This tool accepts six-field expressions when the input has exactly six space-separated tokens — useful if you're targeting Quartz, Spring `@Scheduled(cron=...)`, or Node.js libraries like `node-cron` that support seconds. For standard POSIX schedulers (Linux crontab, GitHub Actions, AWS EventBridge, Kubernetes CronJob), stick to five fields — adding a leading seconds field will silently break the schedule (the scheduler will interpret your minute as seconds, your hour as minute, etc., shifting everything by one). When in doubt, check your target scheduler's docs; if it doesn't explicitly say "six-field with seconds is supported", use five fields. **Q: What's the maximum interval cron can express?** A: Without external state, cron can express up to once-per-year reliably with `0 0 D M *` (e.g., `0 0 1 1 *` = every January 1st at midnight). For "every two years" or longer intervals, cron alone is not enough — you'd need an external date check at the top of your script (e.g., `[ $(($(date +%Y) % 2)) -eq 0 ] && /your-command` to run on even years). For "every 90 days" or other non-aligned multi-day intervals, cron also fails: there's no native modulo-day operator, so you'd write a wrapper that checks the day-of-year against a reference date. If your scheduling needs are this complex, consider a real workflow scheduler (Airflow, Temporal, AWS Step Functions) — cron's grammar is intentionally simple and breaks down for anything beyond regular weekly/monthly patterns. **Q: How do I handle missed runs after downtime?** A: Standard cron has no recovery — if the system was down at the scheduled time, the run is simply skipped. There's no log of "we missed this one". For critical jobs, you have three options: (1) Use `anacron` (or `systemd-cron` with `Persistent=true`), which catches up missed jobs after boot, suitable for laptops and intermittent systems. (2) Switch to a scheduler with built-in retry: Kubernetes CronJobs have `startingDeadlineSeconds` (run if delay is within deadline) and `concurrencyPolicy` (avoid overlapping runs); AWS EventBridge supports retry policies. (3) Build idempotency into the job itself: instead of "run report at 9 AM", have the job query "has today's report been generated?" and produce it if not — this self-heals after any downtime length. Option 3 is the most robust and works with any scheduler. **Q: Why is my GitHub Actions cron not running on time?** A: GitHub Actions schedules are best-effort: they can fire up to several minutes late under high load on GitHub's infrastructure, and during very high load they may be skipped entirely (especially for short intervals like every five minutes). The same disclaimer applies to most managed schedulers — they trade exact timing for scale and reliability. Practical implications: (1) Don't schedule things that must run at an exact second; cron is for "roughly at this time, daily". (2) For short intervals, prefer a long-lived worker over a scheduled job. (3) For exact-time financial or compliance cutoffs, use a dedicated cron daemon on a server you control, or a stricter scheduler like AWS EventBridge Scheduler with the Standard schedule. (4) GitHub Actions specifically: avoid intervals shorter than 15 minutes; the scheduler will often skip them under load. --- ### CSS Formatter, Beautifier & Minifier URL: https://go-tools.org/tools/css-formatter Format, beautify and minify CSS instantly in your browser. Clean up messy stylesheets or compress them to ship — free, private, and your CSS never leaves your device. #### What is CSS Formatting? CSS formatting (also called beautifying or pretty-printing) rewrites a stylesheet with consistent indentation, line breaks and spacing so its structure is easy to read and review. The styles render identically before and after — only whitespace changes. Minifying does the reverse: it removes comments and collapses the CSS to the smallest possible size so pages load faster. This tool does both, entirely in your browser. #### FAQ **Q: How do I format CSS online?** A: Paste your CSS into the input box and click Format. The tool reindents the stylesheet with consistent line breaks and spacing, then lets you copy it. Everything runs locally in your browser — nothing is uploaded. **Q: How do I minify CSS?** A: Paste your CSS and click Minify. The tool removes comments and collapses whitespace to produce the smallest equivalent stylesheet, and shows how many bytes you saved. The minified CSS renders exactly like the original. **Q: What is the difference between formatting and minifying CSS?** A: Formatting (beautifying) adds indentation and line breaks to make CSS readable. Minifying does the opposite: it strips comments and whitespace to shrink the file for faster loading. Both produce styles that render identically to the original. **Q: Does formatting change how my styles look?** A: No. Formatting and minifying only change whitespace and comments — never selectors, properties or values. The page renders exactly the same before and after. **Q: Is my CSS safe with this tool?** A: Yes. All formatting and minifying happen locally in your browser using JavaScript — your CSS is never sent to any server, logged, or stored. That makes it safe for proprietary or unreleased styles, unlike server-side formatters that receive a copy of everything you paste. **Q: Can it format SCSS or Less?** A: It formats and minifies standard CSS. Plain SCSS/Less that is also valid CSS will usually format fine, but pre-processor-only syntax (nesting, mixins, variables with $ or @) is best handled by your pre-processor's own formatter. **Q: What indentation should I use for CSS?** A: Two spaces is the most common default and keeps diffs compact; four spaces can improve readability for deeply nested rules; tabs let each developer choose their width. Pick one and apply it consistently — this tool supports all three. --- ### CSV to JSON Converter URL: https://go-tools.org/tools/csv-to-json Convert CSV to JSON in your browser. RFC 4180, type inference, header row, big-int safe. 100% private, no upload. #### What is JSON and Why Convert from CSV? JSON (JavaScript Object Notation) is the universal format for API responses, configuration files, and structured data exchange — every modern programming language, every database, and every web framework has first-class JSON support. CSV (Comma-Separated Values), by contrast, is the oldest and most widely supported tabular format — every spreadsheet app, every database export, and every analytics tool can produce it. Converting between them is one of the most common chores in data engineering: you receive a CSV from a spreadsheet, a database dump, or a third-party export, and you need JSON to feed an API, hydrate a frontend, or load into a NoSQL store. This tool is built for that conversion path and handles four scenarios that most online converters botch. This tool has four important differentiators compared to typical online CSV-to-JSON converters: **1. RFC 4180 State-Machine Parser.** CSV looks simple but the quoting rules are subtle: a field wrapped in double quotes can contain commas, embedded newlines, and escaped double quotes (doubled, like ""). Naive split-by-comma parsers break on real-world data — addresses with commas, multiline text fields, and quoted values containing quotes. This tool implements a proper state-machine parser following RFC 4180 (the IETF spec for CSV), correctly handling quoted fields, embedded delimiters, embedded line endings, and escaped quotes in every direction. The output is round-trippable through Python's csv module, PostgreSQL COPY, AWS S3 SELECT, and any compliant parser. **2. Type Inference with Big-Integer Safety.** With Infer types on, numeric strings become numbers, true/false become booleans, empty cells become null. But the inference pipeline has two important guards: leading-zero strings (007, 0123) are kept as strings because leading zeros indicate identifiers — converting to a number would silently strip them. And integers above 2^53 - 1 (9007199254740991) are also kept as strings to avoid IEEE 754 precision loss. Twitter snowflake IDs, Discord IDs, MongoDB Long fields, and K8s resourceVersion all stay exact instead of being silently rounded. ISO date strings are intentionally kept as strings — JSON has no native date type. **3. Header Autonames or Use First Row.** With Header on (the default), the first row is treated as column names and each subsequent row becomes a JSON object keyed by those names. With Header off, the parser auto-names columns col1, col2, col3 — useful for raw data dumps without a header line. The Delimiter chip row covers the four most common separators: comma (RFC 4180 default), semicolon (Excel-EU locales), tab (TSV from Unix tools and data warehouses), and pipe (high-comma fields). Pick the chip and parse — no manual configuration needed for typical real-world CSVs. **4. 100% Browser-Based Privacy.** Your CSV data — which often contains user PII, internal database exports, customer records, and production exports — never leaves your browser. No data is sent to any server, no logging, no analytics that capture input. You can verify this in your browser's Network tab. This is the only safe way to handle sensitive data in an online tool. See the reverse direction by clicking Swap or use our companion JSON to CSV Converter when CSV is your target. Need to validate the JSON output before consuming it? Try our JSON Formatter. JSON's strengths are precise types, native nesting, and a strict spec that parses identically everywhere — the right format whenever a machine consumes the data. CSV's strengths are universality and human-readability — the right format whenever a human opens a spreadsheet. The right tool depends on the consumer: human reading a spreadsheet → CSV, machine consuming an API → JSON. This converter handles the bridge in both directions. ``` // Input CSV (comma + LF, header on, infer types on) id,name,active,score 1,Alice,true,98.5 2,Bob,false,87 3,Carol,true, // Output JSON [ { "id": 1, "name": "Alice", "active": true, "score": 98.5 }, { "id": 2, "name": "Bob", "active": false, "score": 87 }, { "id": 3, "name": "Carol", "active": true, "score": null } ] // Same input with Header off (no first-row keys) 1,Alice,true,98.5 2,Bob,false,87 // Becomes [ { "col1": 1, "col2": "Alice", "col3": true, "col4": 98.5 }, { "col1": 2, "col2": "Bob", "col3": false, "col4": 87 } ] ``` #### FAQ **Q: What does this tool do?** A: It converts CSV to JSON directly in your browser, with bidirectional support: click Swap direction to convert JSON back to CSV in the same panel. Paste CSV in the input area and the tool produces JSON output instantly — no upload, no signup, nothing leaves your machine. The parser is RFC 4180 compliant, handles delimiter chips for comma, semicolon (Excel-EU), tab (TSV), and pipe, and the Infer types option converts numeric strings to numbers, true/false to booleans, and empty cells to null. The tool also handles big-integer IDs that would otherwise lose precision through JSON.parse, embedded commas inside quoted fields, escaped double quotes (doubled), and headerless data with autonamed columns (col1, col2, col3). **Q: Is my data uploaded anywhere?** A: No. All conversion runs 100% client-side in your browser using JavaScript. Your CSV data is never transmitted, never stored on any server, never logged, and never analyzed. This makes the tool safe for spreadsheet exports containing PII, internal database CSV dumps, customer records, and any sensitive data. You can verify this in your browser's Network tab — pasting CSV triggers zero network requests. The tool uses no cookies for input data and no third-party analytics that would capture what you paste. **Q: How does Type Inference work?** A: With Infer types on, each parsed cell is run through a small detection pipeline before being placed in the JSON: numeric strings (1, 42, -3.14) become numbers, true/false become booleans, empty strings and the literal null become JSON null, and everything else stays as a string. There are two important guards. First, leading-zero strings like 007 or 0123 are kept as strings even though they look numeric — leading zeros indicate the value is an identifier (zip codes, phone codes, sequence IDs) and converting to a number would silently strip the zeros. Second, integers above 2^53 - 1 (9007199254740991) are also kept as strings to avoid IEEE 754 precision loss. ISO date strings (2026-05-09T10:00:00Z) are intentionally left as strings — JSON has no native date type, so coercing them would produce a JavaScript Date object that doesn't survive serialization. **Q: Why are big integers kept as strings?** A: JavaScript's Number type uses IEEE 754 double-precision and can only represent integers exactly up to 2^53 - 1 (9007199254740991). Real-world identifiers — Twitter snowflake IDs, Discord IDs, MongoDB Long fields, K8s resourceVersion — are 64-bit integers that exceed this safe range. If the parser called Number() on these, the result would silently round (9007199254740993 becomes 9007199254740992). The Infer types pipeline detects values above the safe-integer boundary and keeps them as strings instead, so the digits survive intact. A warning banner appears below the output listing the affected fields. To convert back precisely in code, use BigInt("9007199254740993") on the JSON string value. **Q: My CSV uses semicolons — how do I parse it?** A: European Excel locales (Germany, France, Spain, Italy, etc.) emit semicolon-delimited CSVs because the comma is reserved for the decimal separator. Click the `;` chip on the Delimiter row (or open the full Options panel and pick `;`) and the parser switches to semicolon-mode immediately. Numeric values with comma decimals (1234,56) inside such files are kept as strings by Type Inference because European decimal notation is locale-specific — convert them in code if you need numeric values. The parser still applies RFC 4180 quoting rules with the new delimiter, so quoted fields containing semicolons inside them are handled correctly. **Q: Does it handle TSV (tab-delimited)?** A: Yes. Click the Tab chip on the Delimiter row and the parser splits on tab characters instead of commas. TSV is the cleanest format for cross-locale CSV sharing because tab is unlikely to appear inside text fields, eliminating most quoting edge cases. It is the default output of Unix tools (cut, awk), data warehouses (BigQuery, Snowflake), and is well-supported by Excel in any locale. Paste your .tsv or .tab file content directly — the rest of the parser (header autonames, type inference, big-integer detection) works identically. **Q: What if my CSV has no header row?** A: Toggle Header off in the Options panel. The parser will treat the first line as data instead of column names and auto-generate keys: col1, col2, col3, … one per column. The output JSON is an array of objects with these synthetic keys. This is useful for raw exports from databases that omit the header, fixed-format flat files, and machine-generated CSVs. If you want different key names, convert with autonames first then rename keys in your downstream pipeline (jq, JavaScript map, etc.). The tool does not infer keys from data heuristics — Header off always produces col1, col2, col3. **Q: Can it handle quoted fields with embedded commas?** A: Yes. The parser is a proper RFC 4180 state machine: when it sees an opening double quote, it switches to QuotedField state and treats everything until the next unescaped double quote as a single field, including delimiters and embedded line endings (CR/LF). Escaped double quotes (doubled, like "") are correctly collapsed to a single quote. This means `"Smith, Jr."` parses as one field containing `Smith, Jr.`, and `"He said ""hi"""` parses as `He said "hi"`. Naive split-by-comma parsers break on this real-world data; this tool does not. **Q: Why are my dates being kept as strings?** A: By design. JSON has no native date type — only strings, numbers, booleans, null, arrays, and objects. ISO 8601 date strings (2026-05-09T10:00:00Z) are kept verbatim as strings in the JSON output, which is the correct, lossless representation. If the parser coerced them to JavaScript Date objects, serializing the resulting JSON would produce different output (an object with no useful round-trip representation, or a numeric timestamp). Keep dates as strings in JSON and parse them at the point of use with new Date(value) or your date library of choice. This matches the behavior of every major JSON-from-CSV pipeline: Pandas, jq, and the Python csv + json modules. **Q: What happens if rows have different lengths?** A: Mixed-shape rows (some with more or fewer columns than the header) are filled to match the header length. Extra cells beyond the header count are dropped, and missing cells are set to empty string (or null when Infer types is on and the parser sees an empty value). A Schema notes warning appears below the output so you know the rows were normalized. This is usually fine for downstream tools that union keys, but verify the output if your consumer expects strict row-shape consistency. The most common cause is trailing commas in some rows or quoted fields with embedded line endings being mis-counted by upstream exporters. **Q: How big a file can I paste?** A: Above 100,000 characters or 2,000 rows, live conversion automatically switches to manual mode: a Convert button appears in an info banner and conversion only runs when you click it. This prevents the browser's main thread from blocking on every keystroke during heavy parsing. For output above 5 MB or 50,000 rows, the tool truncates the on-screen JSON preview to the first 500 rows and shows a Showing the first 500 of N rows banner — but the Download button still produces the full file with every row included. Hard upper limit is 10 MB of input; above that the tool shows an error and asks you to reduce the input. **Q: Can I round-trip JSON → CSV → JSON?** A: Yes, when the JSON is flat (no nested objects or arrays). For nested data, the reverse direction (JSON → CSV) needs Stringify mode to keep arrays and objects as JSON inside a single cell — which then round-trips losslessly through this CSV → JSON converter when Infer types is on. Click Swap direction at the top of the panel to flip into JSON-to-CSV mode and verify the round-trip. Flatten mode in the reverse direction is one-way: it emits dotted keys (customer.address.city) that cannot be perfectly reconstructed from CSV. See our JSON to CSV converter for the reverse direction with full Stringify support. --- ### cURL Command Generator & Builder URL: https://go-tools.org/tools/curl-builder Build curl commands in your browser — set method, headers, auth, and body, get a copy-ready command instantly. Presets for Bearer, POST JSON, file upload. Free, private, no signup. #### What Is a curl Command? curl is a command-line tool for transferring data over HTTP and dozens of other protocols. A curl command is the binary name, a set of flags, and a URL — for example `curl -X POST https://api.example.com/users -H 'Content-Type: application/json' -d '{"name":"Ada"}'`. Because curl ships on virtually every Linux, macOS, and modern Windows machine, it's the universal way to test an API, reproduce a request from documentation, or health-check a service from inside a CI script. The terseness that makes it powerful also makes it hard to remember, which is exactly why a visual builder helps. Every curl command has the same anatomy. The method (`-X GET`, `-X POST`, …) sets the HTTP verb, defaulting to GET. The URL is the endpoint, with query parameters appended after a `?`. Headers (`-H 'Key: Value'`, repeatable) carry metadata like `Accept` and `Content-Type`. Authentication is just a special header — `-H 'Authorization: Bearer …'` for a token, `-u user:pass` for Basic auth, or a custom `-H 'X-API-Key: …'`. The body (`-d` for raw or form data, `-F` for multipart and file uploads) carries the payload. Finally, option flags like `-L` (follow redirects), `-i` (include response headers), and `-v` (verbose) shape the behavior. This tool lays out each of those parts as a form field and rebuilds the command live. Reach for a curl command generator when you'd otherwise be fumbling with quoting, forgetting the `Content-Type` header on a JSON POST, or hand-assembling a multipart upload. The builder gets the shell-safe single-quote escaping right, encodes your query string, and attaches the correct headers for each body type — then you copy a command you can trust. For an exhaustive flag-by-flag reference with 40+ runnable examples, read the companion curl cheat sheet; to encode tricky query-string values you can use our URL encoder. Everything happens in your browser. Your tokens, credentials, URLs, and request bodies are assembled with client-side JavaScript and never sent anywhere — so you can build commands against real production endpoints with real API keys and nothing leaves your device. ``` # Plain GET — curl defaults to GET curl https://api.example.com/users # GET with a Bearer token (auth is just a header) curl https://api.example.com/me \ -H 'Authorization: Bearer YOUR_TOKEN' # POST a JSON body — note the Content-Type header curl -X POST https://api.example.com/users \ -H 'Content-Type: application/json' \ -d '{"name":"Ada","role":"admin"}' # Multipart file upload — '@' reads the file from disk curl -X POST https://api.example.com/upload \ -F 'file=@report.pdf' \ -F 'title=Q3 report' ``` #### FAQ **Q: What does this tool do?** A: It builds a ready-to-run curl command from a simple form. You pick the HTTP method, type a URL, add query parameters and headers, choose an authentication scheme (None, Bearer, Basic, or API-key header), set a request body (JSON, form, multipart, or file upload), and toggle option flags like follow-redirects or verbose. As you fill the form, the curl command rebuilds live at the bottom of the page — copy it, export it as a `.sh` script, or switch between single-line and multi-line output. It's a curl command generator built for the everyday workflow of testing a REST API, reproducing a request from documentation, or dropping a reproducible command into a bug report. For a deeper reference on the flags themselves, see our curl cheat sheet. **Q: Is my data (tokens, URLs) uploaded anywhere?** A: No. The entire command is assembled in your browser with JavaScript. Your tokens, credentials, URLs, headers, and request bodies are never transmitted, stored, or logged on any server — you can confirm this in your browser's Network tab, where building a command triggers zero network requests. This is why the tool is safe to use with real production endpoints and live API keys: nothing you type leaves your device. The command only does something when you copy it and run it yourself in your own terminal. **Q: How do I send a POST request with JSON in curl?** A: Set the method to POST and choose the JSON body type; the tool produces `curl -X POST -H 'Content-Type: application/json' -d '{…}'`. Two parts matter: the `-d` (or `--data`) flag carries the body, and the `Content-Type: application/json` header tells the server how to parse it — omit the header and many frameworks ignore or misread the payload. Modern curl (7.82+) also offers a `--json` shortcut that sets the header and body together. After the call, paste the response into our JSON formatter to pretty-print and validate it. **Q: How do I add a Bearer token to a curl request?** A: Add an `Authorization` header with the value `Bearer YOUR_TOKEN`, or pick the "GET with Bearer" preset and paste your token. The generated command is `curl -H 'Authorization: Bearer YOUR_TOKEN'`. The word `Bearer`, a single space, then the token — that exact format is required by the OAuth 2.0 Bearer spec (RFC 6750). To inspect a token's claims and expiry before sending it, decode it with our JWT decoder. **Q: How do I upload a file with curl?** A: Use the Multipart body type and toggle a field to File: the tool emits `curl -X POST -F 'file=@/path/to/file'`. The `@` prefix tells curl to read the file from disk and send it as `multipart/form-data`; you can add more `-F` fields for both files and plain text values in the same request. Don't set the `Content-Type` header yourself for multipart — curl generates the boundary and sets the header automatically, and overriding it breaks the upload. For a single-file PUT instead, curl uses `-T file `. **Q: Can I import a command copied from browser DevTools ("Copy as cURL")?** A: Not yet — that's the Convert tab, shipping in the next release. It will parse a pasted curl command (including the one DevTools generates with "Copy as cURL") and translate it into JavaScript fetch, Python requests, Go, PHP, Ruby, and Node.js. For now the tool is a builder: recreate the request by filling in the URL, headers, auth, and body fields manually. The DevTools output is verbose but readable — copy each `-H` header into the headers section and the URL into the URL field. **Q: How is curl on Windows different?** A: The curl binary behaves the same; the shell quoting differs. In `cmd.exe` you use double quotes (`"`) around values and the caret (`^`) for line continuation, and single quotes have no special meaning. In PowerShell, `curl` is an alias for `Invoke-WebRequest` unless you call `curl.exe` explicitly, and quoting rules differ again. The most painless option is Git Bash or WSL, where the Unix-style single-quote commands this tool generates run unchanged. If you must use `cmd`, switch the generated command to single-line and replace the `'…'` quoting with `"…"`. **Q: What's the difference between -d, --data-raw, and --data-binary?** A: `-d` (alias `--data`) sends a body and strips newlines and carriage returns from the data — fine for `key=value` form pairs, risky for JSON that spans lines. `--data-raw` is identical but does not treat a leading `@` as a filename, so it's the safe choice when your data could literally start with `@`. `--data-binary` sends the bytes exactly as given with no newline stripping at all — the correct flag for uploading a file's raw contents (`--data-binary @file.json`) or any payload where whitespace is significant. All three imply POST unless you override the method. **Q: How do I send cookies with curl?** A: Use `-b` to send cookies and `-c` to save them. `-b 'name=value'` sends a literal cookie string, while `-b cookies.txt` reads cookies from a Netscape-format file. To capture cookies a server sets (for example after a login), add `-c cookies.txt` to write them to a jar, then reuse that jar with `-b cookies.txt` on the next request. This builder focuses on headers, auth, and body; for a cookie header you can also just add a `Cookie: name=value` header in the headers section. **Q: How do I follow redirects?** A: Toggle "Follow redirects (-L)" or add the `-L` flag manually. By default curl prints the 301/302 response and stops; `-L` tells it to follow the `Location` header to the final destination. This is essential for download links behind CDNs, shortened URLs, and APIs that redirect HTTP to HTTPS. Combine it with `-o filename` to save a downloaded file after the redirect resolves. **Q: How do I set a request timeout?** A: Use the "Connect timeout" option to add `--connect-timeout `, which caps how long curl waits to establish the connection. For a ceiling on the entire transfer — connection plus download — add `--max-time ` to the generated command manually. Pinning timeouts is a best practice in any script or CI smoke test: without them a hung endpoint can block your pipeline indefinitely. Pair with `--retry N` if you want curl to retry transient failures. --- ### .env to JSON Converter URL: https://go-tools.org/tools/env-to-json Paste a .env file, get JSON instantly. Your database passwords, API keys and tokens never leave your browser — 100% private, no upload, free dotenv parser. #### What is a .env File and Why Convert it to JSON? A .env file (dotenv file) is a plain-text list of KEY=VALUE pairs used to keep configuration and secrets out of source code. It is the de facto standard for environment variables in Node.js, Vite, Next.js, Python, Ruby, Docker Compose and almost every modern framework — the dotenv library and its ports load the file and inject each pair into the process environment. Because the file commonly contains database passwords, API keys, OAuth client secrets and access tokens, it is almost always git-ignored and treated as sensitive. Converting a .env file to JSON is a frequent task: you need to feed configuration into a tool that reads JSON, validate it against a JSON Schema, import it into a secrets manager, generate typed config objects, or simply inspect a long .env at a glance as structured data. This converter turns the flat list of pairs into a single JSON object, one property per key. This tool is built around a few deliberate decisions: **1. Strings by default, types on demand.** dotenv never coerces types — at runtime every process.env value is a string. The default output honours that exactly, so the JSON matches what your app actually sees. When you want typed JSON, the optional Infer types switch promotes unquoted numbers, booleans and null values, while quoted values stay strings because the quotes are an explicit signal. **2. Faithful dotenv parsing.** Comments, blank lines, the export prefix, single vs. double quotes, escape sequences, multi-line double-quoted values and inline comments on unquoted values are all handled the way the dotenv library handles them — no surprises when you compare the JSON against what your application loads. **3. Duplicate-key safety.** When a key is defined twice the later value wins, and a warning tells you which keys were duplicated so an accidentally shadowed secret never slips by unnoticed. **4. 100% browser-based privacy.** Your .env data never leaves the browser. There is no upload, no server round-trip and no logging — you can verify zero network requests in the DevTools Network tab. This is the only responsible way to convert a real .env online, since the file is essentially a list of credentials. After converting, you can pretty-print or validate the result with the JSON Formatter, escape it for embedding in another string with JSON Escape, or go the other way with the companion JSON to .env Converter. If your configuration lives in YAML instead, try YAML to JSON. ``` // Parse a .env file to a JSON object in Node.js using dotenv import { parse } from 'dotenv'; const envText = `# Database DATABASE_URL=postgres://user:pass@localhost:5432/mydb DEBUG=true`; // dotenv.parse returns a plain object of string values const parsed = parse(envText); // Every value is a string, just like process.env const json = JSON.stringify(parsed, null, 2); console.log(json); // { // "DATABASE_URL": "postgres://user:pass@localhost:5432/mydb", // "DEBUG": "true" // } ``` #### FAQ **Q: How do I convert a .env file to JSON online?** A: Paste the contents of your .env file into the input field above. The tool parses it to JSON instantly in your browser — no button click needed. Each KEY=VALUE line becomes a JSON property. You can choose 2 or 4 space indentation from the Options panel, then click Copy to grab the JSON or Download to save it as a .json file. Everything runs locally, so your secrets never leave your device. **Q: Are values converted to numbers and booleans, or kept as strings?** A: By default every value is kept as a string, exactly matching the dotenv standard. dotenv itself never coerces types — process.env values are always strings — so this default keeps the JSON faithful to what your application actually sees at runtime. If you would rather have typed JSON, turn on the Infer types option. With it enabled, unquoted values that look like numbers become JSON numbers, true and false become booleans, and an empty or null value becomes JSON null. Quoted values are always kept as strings even with Infer types on, because the quotes signal an explicit string. **Q: What happens if the same key appears twice in my .env file?** A: Following dotenv behavior, the last occurrence wins — the later value overwrites the earlier one. Because a silently dropped value is a common source of misconfiguration, the tool also shows a non-blocking warning listing exactly which keys were duplicated, so you can confirm which value ended up in the JSON. The output is still a valid JSON object with one entry per key. **Q: How are quotes, escapes and multi-line values handled?** A: Double-quoted values process escape sequences: \n becomes a newline, \t a tab, \r a carriage return, \\ a backslash and \" a literal double quote. A double-quoted value may also span multiple lines until the closing quote — handy for PEM private keys and certificates. Single-quoted values are treated literally with no escape processing, just like the shell. Unquoted values run to the end of the line, with trailing whitespace removed and inline comments stripped (a space followed by # starts a comment). This matches how the dotenv library parses files. **Q: Is this parser consistent with the dotenv library?** A: Yes. The parsing rules follow the widely used dotenv conventions: # comment lines and blank lines are ignored, the optional export prefix is removed, keys are split on the first = sign, single quotes are literal, double quotes process escapes and allow multi-line values, unquoted values strip surrounding and trailing whitespace, and all values default to strings. The optional Infer types switch is an extra convenience layered on top — it is off by default precisely so the output matches what dotenv would load. **Q: Can it handle nested or grouped configuration?** A: .env files are intentionally flat — a list of key/value pairs with no nesting. This converter produces a single flat JSON object that mirrors that structure one-to-one. If your application reads grouped config (for example DB_HOST and DB_PORT), the keys stay flat in the JSON; you can reshape them in code afterwards. If you need true nested structures, a format like YAML to JSON is a better fit, and you can pretty-print the result with the JSON Formatter. **Q: Is my .env data sent to a server?** A: No. All parsing happens entirely in your browser with JavaScript. Your .env contents — which typically hold database passwords, API keys, OAuth secrets and access tokens — are never transmitted, never stored on any server, and never logged. You can confirm this by opening your browser's Network tab and watching that pasting the file triggers zero requests. This is what makes it safe to convert a real production .env, not just a sanitized sample. --- ### Hex to CMYK Converter URL: https://go-tools.org/tools/hex-to-cmyk Convert HEX colors to CMYK in your browser. Naive sRGB-based approximation for print previews. Free, no signup, your colors stay local. #### What Is a Hex to CMYK Converter? A hex to CMYK converter is a small utility that turns a hex color code (`#FF5733`) into the four-channel CMYK percentages (`cmyk(0%, 66%, 80%, 0%)`) that approximate the same color in print ink coverage. Hex is the terse base-16 string designers and developers paste between Figma, Sketch, Photoshop, brand-guideline PDFs, and CSS stylesheets — three 8-bit sRGB channels packed into a 6-character `#RRGGBB` form, anchored to the IEC 61966-2-1 sRGB specification. CMYK is the process-color model for printing — four channels representing the percentage of each ink (Cyan, Magenta, Yellow, Key=black) applied to a paper substrate, anchored to whatever ICC profile the printing setup uses (typically US Web Coated SWOP v2, Fogra39, or Japan Color 2011 Coated). Developers and designers convert HEX → CMYK for print preparation: vendor proofs, packaging design, business cards, brochures, and any other work that has to leave the screen and become ink on paper. **The CMYK format deep dive + ICC caveat.** CMYK is a process-color model for printing. Each channel represents the percentage of ink applied to a substrate — 0% means no ink, 100% means full coverage. K (Key) is black ink, separated out from CMY so dark colors don't require muddy CMY overprints that produce a brown-tinged near-black; pure K gives crisp typography, deep shadows, and tighter total ink coverage. The 4-channel notation is `cmyk(76%, 47%, 0%, 4%)` for Tailwind blue-500 — 76% cyan, 47% magenta, 0% yellow, 4% black. **The honest disclaimer the entire tool is built around**: our converter uses the naive textbook formula `K = 1 - max(R, G, B); C = (1-R-K)/(1-K); M = (1-G-K)/(1-K); Y = (1-B-K)/(1-K)`. This treats CMYK as a direct sRGB inversion. It ignores ink absorption curves (each ink has its own non-linear response), paper substrate (cream stock shifts the white point, glossy coated stock holds saturation better than uncoated), dot gain (ink dots spread as they hit absorbent paper, increasing apparent coverage by 10-30% per channel), total ink limit (offset presses cap total coverage around 280-320% to avoid set-off and drying issues, newsprint caps around 220%), and the actual color-management chain (the full ICC pipeline from source profile through device link to output profile). **Real print accuracy requires ICC profile conversion** against the specific press: typically US Web Coated SWOP v2 for North American offset (SWOP-certified presses on coated stock), Fogra39 for European offset (per ISO 12647-2, with Fogra51 for newer premium coated and Fogra52 for uncoated), Japan Color 2011 Coated for Japanese offset. Plus the paper stock characteristics and the ink set. Use this tool for estimation, not deliverables. **When the naive approximation is "close enough" vs. when it isn't.** Close-enough cases: digital short runs on tightly-managed digital presses (HP Indigo, Kodak NexPress, Xerox iGen), where the RIP applies its own color management and the naive output tracks the actual print more closely; proof-of-concept mockups for internal design review; vendor briefs where you'll send the ICC-correct spec separately and the naive CMYK just communicates intent; blog post illustrations and documentation that demonstrate "roughly this color in CMYK"; budget and scope conversations with print providers before commissioning the actual job. NOT close-enough cases: offset press production runs where the press operator needs press-ready files; packaging design where Pantone spot-color matching matters (spot colors override CMYK entirely — the press uses a pre-mixed ink rather than building the color from process channels); brand-critical work where a 5-15% channel shift could produce a visibly wrong color on the final piece; any job where the deliverable is the printed artifact itself rather than a screen approximation. The tool surfaces the disclaimer prominently in every output so you stay honest about which case you're in. **Why CMYK exists at all.** Printers can't emit light — they apply ink that subtracts wavelengths from the white reflectance of the paper. Combining Cyan, Magenta, and Yellow at full strength theoretically yields black (each ink absorbs one third of the visible spectrum), but in practice produces a muddy dark brown because real inks aren't ideal Lambertian absorbers — they reflect some light at every wavelength, the inks don't perfectly stack on the substrate, and dot gain on absorbent paper increases the overlap area beyond what the CMY math predicts. K solves three problems at once: pure black ink produces a clean crisp black for typography and shadows; letting the formula reduce CMY when K is present cuts total ink coverage (saving cost on ink, speeding drying time, avoiding paper saturation that causes wrinkles); and giving the press a dedicated black plate keeps registration cleaner because text only needs one plate to line up instead of three. This tool's HEX → CMYK workflow is one direction of a 5-spoke family that all share the same underlying unified color converter. The dedicated unified color converter is the hub — it shows all 9 formats simultaneously editable and is the right tool when your workflow needs more than just hex and CMYK. The single-direction spokes target specific Google search intents: the hex to RGB converter for the canvas-and-hardware direction, the RGB to hex converter for the inverse, the hex to HSL converter for the legacy designer-cylindrical space, and the hex to OKLCH converter for modern Tailwind v4 and shadcn/ui design systems. All five spokes and the hub share the same parsing engine and the same conversion math — including the same naive CMYK approximation with the same ICC caveat. Every conversion runs locally in your browser; your hex codes are never uploaded, never logged, and zero network requests fire as you type. Verify in DevTools. ``` // sRGB hex → naive CMYK approximation // NOTE: This is the textbook K = 1 - max(R,G,B); C = (1-R-K)/(1-K) formula. // It is NOT an ICC-profile conversion. Real print accuracy requires color // management against the specific press (US Web Coated SWOP v2, Fogra39, // Japan Color 2011 Coated, etc.), ink set, and paper substrate. Channel // values can drift 5-15% from this naive output once ICC conversion runs. // Use as estimate for proofs, briefs, and digital press — never as a press- // ready deliverable. function hexToCmyk(hex) { const h = hex.trim().replace(/^#/, ''); const [r, g, b] = [0, 2, 4].map(i => parseInt(h.slice(i, i + 2), 16) / 255); const k = 1 - Math.max(r, g, b); // Pure black short-circuit: max RGB = 0 ⇒ K = 1, C/M/Y undefined (0/0) if (k === 1) return { c: 0, m: 0, y: 0, k: 100 }; const c = (1 - r - k) / (1 - k); const m = (1 - g - k) / (1 - k); const y = (1 - b - k) / (1 - k); return { c: Math.round(c * 100), m: Math.round(m * 100), y: Math.round(y * 100), k: Math.round(k * 100) }; } console.log(hexToCmyk('#3b82f6')); // → { c: 76, m: 47, y: 0, k: 4 } console.log(hexToCmyk('#FF5733')); // → { c: 0, m: 66, y: 80, k: 0 } console.log(hexToCmyk('#000000')); // → { c: 0, m: 0, y: 0, k: 100 } ``` #### FAQ **Q: How do I convert hex to CMYK?** A: The naive textbook formula: parse `#RRGGBB` to three 0-255 sRGB integers, normalize to 0-1, then compute `K = 1 - max(R, G, B); C = (1 - R - K) / (1 - K); M = (1 - G - K) / (1 - K); Y = (1 - B - K) / (1 - K)`. Output channel percentages by multiplying each by 100. This tool runs that pipeline live as you type — paste any hex (with or without `#`, 3-digit, 6-digit, or 8-digit) and the CMYK percentages update instantly. **Caveat**: this is not the same as a proper ICC-profile conversion; treat it as a ballpark, not a deliverable. **Q: Why is CMYK from hex an approximation?** A: Hex encodes sRGB — an additive light-emission model anchored to a specific display white point. CMYK encodes subtractive ink absorption on paper, and every press, ink, and substrate combination has its own characteristic absorption curve. The naive textbook formula treats CMYK as a direct sRGB inversion, ignoring ink dot gain, paper substrate, total ink limit, and the actual color-management chain. Real print accuracy requires an ICC profile conversion against the specific press setup. The naive output can drift 5-15% per channel from the ICC-correct value; for some saturated hues, the difference is larger because the source color falls outside CMYK's printable gamut entirely. **Q: What ICC profile should I use for print?** A: Depends on the press and region. **North American offset**: US Web Coated SWOP v2 is the long-standing default for SWOP-certified presses on coated stock. **European offset**: Fogra39 (and the newer Fogra51 for premium coated, Fogra52 for uncoated) per the ISO 12647-2 standard. **Japanese offset**: Japan Color 2011 Coated. **Digital presses** (HP Indigo, NexPress, Xerox iGen): the press vendor's own ICC profile shipped with the RIP. Always confirm with the print shop before final conversion — many shops have a custom press-specific profile tuned to their machine, paper, and ink combination that supersedes the generic standards. **Q: Does my printer support hex codes?** A: Not directly. Hex is a web format; commercial printers and prepress software work in CMYK process colors or named spot colors (Pantone, HKS, RAL). When you send a file to a print shop, the prepress team converts any RGB or hex values to CMYK via their ICC pipeline before sending plates or digital-press jobs. For desktop inkjet and laser printers, the printer driver does the conversion internally — you can send an RGB document and the driver will produce CMYK ink output, but the conversion quality varies wildly by driver. For brand-critical work, hand off the source hex to the print shop and let them produce the press-ready CMYK. **Q: Why does CMYK look different from RGB on screen?** A: Two reasons. **Gamut mismatch**: sRGB and Display P3 can reach saturated colors (pure reds, deep blues, vivid greens) that CMYK ink on paper simply cannot reproduce — the printable CMYK gamut is a smaller, irregularly-shaped volume inside the visible color space. **Substrate and ink physics**: screens emit light, paper absorbs and reflects it. The same color appears warmer or cooler depending on paper whiteness, brighter or duller depending on ink density and dot gain. Even within CMYK's gamut, the same `cmyk(40%, 60%, 0%, 10%)` looks different on glossy coated stock versus uncoated newsprint. Always budget time for a wet proof on the actual stock before committing to a production run. **Q: Can I trust online hex-to-CMYK converters for print?** A: Not for production. Any online converter — including this one — that does not load an ICC profile and apply press-specific color management is producing the same naive `K = 1 - max(R,G,B); C = (1-R-K)/(1-K)` approximation. It's useful for ballpark estimates, vendor briefs, and proof-of-concept work, but it can drift 5-15% per channel from what an ICC-aware workflow produces. For production, send the source hex (or better, the source CMYK derived from an ICC conversion in Photoshop, Illustrator, or InDesign against the target press profile) directly to the print shop, and confirm with a wet proof. Treat the online value as an estimate, never as a press-ready spec. **Q: What is the K in CMYK?** A: K stands for **Key** — the keyplate in traditional offset printing, which historically carried the black ink and the alignment registration marks that the other plates (Cyan, Magenta, Yellow) keyed off. Today it just means black ink, but the term stuck. K is separated out from CMY for two practical reasons. First, combining CMY at full strength theoretically produces black, but in practice yields a muddy brown because real inks aren't ideal absorbers — pure black ink gives crisp text, shadows, and dark areas. Second, separating K lets the press use less total ink for any dark color (lower total ink coverage saves money, dries faster, and avoids paper saturation), and gives prepress operators a single channel to push for sharp typography. **Q: How accurate is sRGB-based CMYK?** A: Accurate enough for ballparks and vendor briefs, not for production. The naive formula treats CMYK as a direct sRGB inversion, which ignores the actual physics of ink-on-paper: dot gain (ink dots spread as they hit absorbent stock), substrate color (cream paper shifts the white point), ink absorption curves (each ink has its own non-linear response), and total ink limit (offset presses cap total coverage around 280-320% to avoid set-off and drying problems). A proper ICC profile conversion against US Web Coated SWOP v2, Fogra39, or Japan Color 2011 Coated accounts for all of these and can shift channels by 5-15% from the naive output. For brand-critical or high-saturation colors, the gap is larger; for neutral mid-tones, it's smaller. --- ### Hex to HSL Converter URL: https://go-tools.org/tools/hex-to-hsl Convert any hex color to HSL in your browser — 3-digit, 6-digit, 8-digit alpha all supported. Free, instant, no signup, your colors never leave the page. #### What Is a Hex to HSL Converter? A hex to HSL converter is a small utility that turns a hex color code (`#3b82f6`) into the cylindrical Hue / Saturation / Lightness triple that encodes the same sRGB color (`hsl(217 91% 60%)`). Hex codes are the terse base-16 string designers and developers paste between Figma, Sketch, Photoshop, brand-guideline PDFs, and CSS stylesheets — three 8-bit channels packed into a 6-character `#RRGGBB` form, anchored to the sRGB color space defined by IEC 61966-2-1 in 1996. HSL is a cylindrical reshape of that same color space onto three more-designer-friendly axes: a hue angle on the color wheel, a chromatic saturation percent, and a lightness percent. Developers convert HEX → HSL constantly: to define a brand color as a CSS variable and then compose lighter or darker shades by adjusting only L, to feed a color-picker UI that displays Hue and SL as separate controls, to generate tint and shade ramps for a design system, or to perform runtime CSS variable math via `hsl(from var(--primary) h s calc(l + 10%))` for derived theme tokens. This tool runs the conversion live as you type, with no "Convert" button to click, and surfaces every other common color format (RGB, OKLCH, OKLAB, HSV, HWB, CMYK, plus the 148 CSS named colors) alongside the HSL output for free. **The HSL format itself deserves a closer look.** HSL = Hue (0-360°), Saturation (0-100%), Lightness (0-100%). Hue is angular position on the color wheel — 0° is red, 60° is yellow, 120° is green, 180° is cyan, 240° is blue, 300° is magenta, and 360° wraps back to red. Saturation is chromatic intensity from 0% (achromatic gray) to 100% (fully chromatic with no gray content). Lightness is brightness from 0% (pure black, regardless of hue or saturation) through 50% (the pure hue at full chroma) to 100% (pure white, regardless of hue or saturation). Alvy Ray Smith published the original derivation in 1978 as part of the early computer-graphics push to give designers coordinate systems closer to their cognitive model of color than raw RGB channel addressing. The model has been in CSS since CSS3 (2010) and ships in every browser back to IE 9. The original CSS syntax used commas: `hsl(217, 91%, 60%)` for opaque, `hsla(217, 91%, 60%, 0.5)` for alpha-bearing. CSS Color 4 (W3C Candidate Recommendation since 2022) added a modern space-separated form: `hsl(217 91% 60%)` and `hsl(217 91% 60% / 0.5)` with slash-prefixed alpha — same syntax shape as the other CSS Color 4 functional notations (`rgb()`, `lab()`, `oklch()`, `color()`). Hue can also be expressed in turns (`hsl(0.6turn 91% 60%)`) or radians (`hsl(3.787rad 91% 60%)`), all equivalent to the canonical degree form. Every evergreen browser parses every syntactic flavor; the tool emits the modern space-separated form by default. The conversion math goes both directions cleanly. **HEX → HSL** is a two-step pipeline. First, parse the 6-digit hex `#RRGGBB` as three 2-digit base-16 numbers via `parseInt(hex.slice(1, 3), 16)` etc. to get integer RGB channels in 0-255. Second, normalize each channel to 0-1 by dividing by 255, then compute `max = Math.max(r, g, b)`, `min = Math.min(r, g, b)`, `delta = max - min`. Lightness is the average of max and min: `L = (max + min) / 2`. Saturation is conditional on lightness: when L ≤ 0.5, `S = delta / (max + min)`; when L > 0.5, `S = delta / (2 - max - min)`. Equivalently in the CSS Color 4 §6.4 form, `S = delta / (1 - |2L - 1|)` (with S = 0 when delta = 0). Hue is piecewise on which channel is max: when R is max, `H = ((G - B) / delta) % 6`; when G is max, `H = (B - R) / delta + 2`; when B is max, `H = (R - G) / delta + 4`; multiply by 60 to scale to degrees, add 360 if negative. The inverse (HSL → HEX, via RGB) uses the helper `f(n) = L - a * max(-1, min(k-3, 9-k, 1))` where `a = S * min(L, 1-L)` and `k = (n + H/30) mod 12`, applied with n = 0, 8, 4 to produce R, G, B respectively, then scaled to 0-255 and hex-encoded. **Why HSL is still useful.** Intuitive sliders — adjusting L predictably brightens or darkens without breaking hue identity, while adjusting an RGB channel produces a less predictable color shift. Runtime CSS math — modern browsers support `hsl(from var(--primary) h s calc(l + 10%))` to derive theme tokens at render time. Designer cognition — designers raised on HSV color pickers reason about color in hue + chroma terms even when the file ships hex. **HSL's problem** is that its Lightness axis is not perceptually uniform — a green at L=50% looks visibly brighter than a blue at L=50% because HSL inherits sRGB's gamma quirks and treats every hue as equivalent on the L scale. When you need perceptual uniformity (palette generation where every step should look equally bright, dark-mode token computation that doesn't accidentally make blue text harder to read than green text), reach for OKLCH instead — the same tool surfaces both, so the choice is one glance away. This tool's HEX → HSL workflow is one direction of a 5-spoke family that all share the same underlying unified color converter. The dedicated unified color converter is the hub — it shows all 9 formats simultaneously editable and is the right tool when your workflow needs more than just hex and HSL. The single-direction spokes target specific Google search intents: the hex to RGB converter for the canvas-and-hardware direction, the RGB to hex converter for the inverse, the hex to OKLCH converter for modern perceptually-uniform design systems (Tailwind v4 and shadcn both default to OKLCH now), and the hex to CMYK converter for print-prep approximations. All five spokes and the hub share the same parsing engine and the same conversion math, so the results are guaranteed identical across the family. Every conversion runs locally in your browser — your hex codes are never uploaded, never logged, and zero network requests fire as you type. Verify in DevTools. ``` // Convert a hex color string to {h, s, l, alpha} per CSS Color 4 §6.4 // h in 0-360, s and l in 0-1, alpha in 0-1. function hexToHsl(input) { let h = input.trim().replace(/^#/, ''); if (h.length === 3 || h.length === 4) h = h.split('').map(c => c + c).join(''); const r = parseInt(h.slice(0, 2), 16) / 255; const g = parseInt(h.slice(2, 4), 16) / 255; const b = parseInt(h.slice(4, 6), 16) / 255; const alpha = h.length === 8 ? parseInt(h.slice(6, 8), 16) / 255 : 1; const max = Math.max(r, g, b), min = Math.min(r, g, b), delta = max - min; const L = (max + min) / 2; const S = delta === 0 ? 0 : delta / (1 - Math.abs(2 * L - 1)); let H = 0; if (delta !== 0) { if (max === r) H = ((g - b) / delta) % 6; else if (max === g) H = (b - r) / delta + 2; else H = (r - g) / delta + 4; H = (H * 60 + 360) % 360; } return { h: H, s: S, l: L, alpha }; } console.log(hexToHsl('#3b82f6')); // { h: 217, s: 0.91, l: 0.60, alpha: 1 } ``` #### FAQ **Q: How do I convert hex to HSL?** A: First convert the hex to RGB integers via `parseInt(hex, 16)`, then normalize each channel to 0-1 by dividing by 255, then compute `max`/`min`/`delta` across the three channels and apply the CSS Color 4 §6.4 piecewise trig: lightness = `(max + min) / 2`, saturation = `delta / (1 - |2L - 1|)` (zero when delta is zero), hue = piecewise on which channel is max (60° per step around the wheel). `#3b82f6` parses to `rgb(59 130 246)` then converts to `hsl(217 91% 60%)`. This tool runs the full pipeline live as you type. **Q: What is HSL color?** A: HSL is a cylindrical reshape of the sRGB color space into three perceptually-meaningful axes: Hue (0-360°, angular position on the color wheel — 0° red, 120° green, 240° blue), Saturation (0-100%, chromatic intensity — 0% gray, 100% fully chromatic), and Lightness (0-100%, brightness — 0% black, 50% pure hue, 100% white). Alvy Ray Smith published the derivation in 1978 to give designers a coordinate system closer to how they think about color than raw RGB channel addressing. HSL has been in CSS since 2010 (CSS3) and ships in every browser. **Q: What is the difference between HSL and HSV?** A: Both are cylindrical reshapes of sRGB with identical hue axes, but they treat the third axis differently. HSL's Lightness goes from black at 0% through pure hue at 50% to white at 100% — symmetric, so `hsl(0 100% 50%)` is pure red and `hsl(0 100% 100%)` is white. HSV's Value goes from black at 0% to pure hue at 100% — asymmetric, so `hsv(0 100% 100%)` is pure red and white only appears when saturation drops to 0. HSL is more useful for design-system tint/shade ramps because the 50% midpoint marks the pure-color reference; HSV is more useful for color pickers because the saturation/value square maps cleanly to an SV picker UI. **Q: Why use HSL over RGB?** A: Three reasons. First, intuitive sliders — moving L from 60% to 70% predictably produces a lighter shade of the same color; moving R from 130 to 150 produces a less predictable color shift. Second, palette generation — `hsl(217 91% 60%)`, `hsl(217 91% 70%)`, `hsl(217 91% 80%)` is a tonally-coherent tint ramp generated by changing one number; the same in RGB needs three coordinated edits. Third, runtime CSS math — modern CSS lets you compute `hsl(from var(--primary) h s calc(l + 10%))` to derive a lighter variant from a base token without precomputing every step. RGB has no such cylindrical-axis convenience. **Q: How do I read an HSL value?** A: HSL has three parts in order: Hue, Saturation, Lightness. `hsl(217 91% 60%)` means hue = 217° (a clean blue, just past 240° pure-blue territory and back toward cyan), saturation = 91% (highly chromatic, almost no gray), lightness = 60% (a notch brighter than the pure-hue midpoint). Hue is the only axis without a percent suffix because it's expressed in degrees — values wrap at 360°, so `hsl(370 ...)` is identical to `hsl(10 ...)`. The slash-prefixed value at the end (if present) is alpha in the 0-1 range: `hsl(217 91% 60% / 0.5)` is the same color at 50% opacity. **Q: Does CSS support HSL?** A: Yes — HSL has been in CSS since CSS3 in 2010 and ships in every browser, including IE 9. The original syntax used commas: `hsl(217, 91%, 60%)` for opaque and `hsla(217, 91%, 60%, 0.5)` for alpha-bearing. CSS Color 4 (W3C Candidate Recommendation since 2022) added the modern space-separated form: `hsl(217 91% 60%)` and `hsl(217 91% 60% / 0.5)` with slash-prefixed alpha. The hue can also be expressed in turns or radians (`hsl(0.6turn 91% 60%)` is identical to `hsl(217 91% 60%)`). Both legacy and modern syntaxes are interchangeable in all evergreen browsers. **Q: What does the L in HSL stand for?** A: Lightness. The 0-100% axis that controls how bright the color appears, with 0% mapping to pure black and 100% to pure white. The midpoint (50%) is where the pure hue lives — `hsl(0 100% 50%)` is pure red, while `hsl(0 100% 25%)` is a darker red and `hsl(0 100% 75%)` is a lighter pink. Lightness is the symmetric counterpart of HSV's asymmetric Value. Note that HSL lightness is *not* perceptually uniform — a green at L=50% looks visibly brighter than a blue at L=50% because HSL inherits sRGB's gamma quirks; for perceptual uniformity, reach for OKLCH instead. **Q: How precise is hex to HSL conversion?** A: The HEX → RGB step is bit-exact (`parseInt(hex, 16)` returns integers with no float involvement). The RGB → HSL step involves trig and division, so the output is a float that the tool rounds to integer degrees and integer percent for display. A round-trip HEX → HSL → HEX recovers the original hex within 1 channel unit (the rounding error from displaying H as an integer degree). For lossless work, OKLCH is a better internal format — this tool actually holds OKLCH as the source-of-truth internally, then derives HSL on display, so the round-trip stability is better than naive HSL-pivot converters. --- ### Hex to OKLCH Converter URL: https://go-tools.org/tools/hex-to-oklch Convert HEX to OKLCH for Tailwind v4 design tokens. Live perceptually-uniform output with Display P3 gamut warnings. Free, browser-only. #### What Is a Hex to OKLCH Converter? A hex to OKLCH converter is a small utility that turns a hex color code (`#3b82f6`) into the perceptually-uniform Lightness / Chroma / Hue triple that encodes the same color in OKLCH space (`oklch(0.629 0.193 263.4)`). Hex codes are the terse base-16 strings designers and developers paste between Figma, Sketch, Photoshop, brand-guideline PDFs, and CSS stylesheets — three 8-bit channels packed into a 6-character `#RRGGBB` form, anchored to the sRGB color space defined by IEC 61966-2-1 in 1996. OKLCH is the polar form of OKLAB, Björn Ottosson's 2020 perceptually-uniform color space, added to CSS via the `oklch()` syntax in CSS Color 4 (W3C Candidate Recommendation since 2022). Channels are Lightness (0-1, also writable as 0-100%), Chroma (0 to about 0.4 for the most saturated sRGB colors, unbounded above for wide-gamut colors), and Hue (0-360°, the same angular axis HSL uses). Browser support landed across all evergreens between March 2022 (Safari 15.4) and May 2023 (Firefox 113), with Chrome 111 in the middle (March 2023) — combined caniuse coverage is over 94%. Example: Tailwind blue-500 is `oklch(0.629 0.193 263.4)`. **Perceptual uniformity — why it matters.** In OKLCH (and its rectangular cousin OKLAB), equal numeric distance in the L channel corresponds to equal perceived distance in brightness — across every hue, every chroma level, every region of the color space. In HSL, equal L values look unevenly bright across hues because HSL inherits sRGB's gamma quirks: a green at `hsl(120 100% 50%)` looks visibly brighter than a blue at `hsl(240 100% 50%)`, even though both report L=50%. The structural cause is that HSL derives L geometrically (average of channel max and min in gamma-encoded sRGB), while OKLCH derives L from a perceptually-anchored model that linearizes first and routes through an LMS cone-response stage. The practical upshot: holding L constant across hues in OKLCH produces visually-equivalent brightness — a green at `oklch(0.7 0.2 130)` and a blue at `oklch(0.7 0.2 250)` look equally bright on screen. This is why Tailwind v4 migrated its default palette to OKLCH-based ramps in 2025 — every shade step (50, 100, 200, …, 900, 950) hits the same perceived lightness difference, so brand colors feel consistent across hues without per-color hand-tuning. **Tailwind v4 and the design-token revolution.** Tailwind v4 (released January 2025) replaced its HSL-based color generation with an OKLCH-based system. shadcn/ui followed shortly after, adopting OKLCH for its CSS-variable theme; Radix Colors v3 also adopted it. Why now: design systems need shades that look evenly-spaced across the entire palette, and they need that property to hold automatically as the palette grows. With HSL, designers had to manually correct each color step — bumping L by 5% extra on the dark end of the blue ramp to match the dark end of the green ramp, then re-bumping when the brand evolved. With OKLCH, a single formula (step L by 0.1, hold C and H constant) produces consistent ramps automatically. Real example: in Tailwind v3, `red-500` and `blue-500` had visibly different perceived weights despite identical HSL L%; in v4, `red-500` and `blue-500` look balanced because both sit at the same OKLCH L. This matters for accessibility (consistent contrast against shared backgrounds means component states feel uniform across the palette), brand consistency (visual hierarchy holds across palettes — a `primary` button and an `accent` button at the same L feel like the same hierarchy level), and developer ergonomics (one mental model instead of dozens of hand-tuned exceptions buried in the design-token spec). **Wide-gamut implications.** OKLCH is unbounded — it can represent colors outside sRGB, including everything Display P3 and Rec.2020 can reproduce. This makes it the natural choice for modern wide-gamut displays. Most Apple devices since 2017 (iPhone 7 onward, MacBook Pro 2016 onward, every iPad Pro) render Display P3 natively, and many modern Android devices and laptop screens do too. The tradeoff: not every OKLCH triple maps to a valid sRGB color. The tool shows three gamut badges — sRGB, Display P3, Rec.2020 — so you can see immediately whether the current OKLCH will display correctly on a given target. When the color is sRGB-only, the **Snap to sRGB** button uses binary chroma reduction (per CSS Color 4 §13's informative gamut-mapping algorithm) to shrink the color into gamut while preserving L and H — giving you a hex fallback you can ship via `@supports not (color: oklch(0 0 0))` alongside the original OKLCH value for the wider-gamut clients. **The HEX → OKLCH conversion math.** The pipeline is well-defined and grounded in two primary sources: W3C CSS Color 4 for the sRGB and XYZ stages, Ottosson 2020 for the OKLAB stage. Step one: parse `#RRGGBB` to three 8-bit integer sRGB channels via `parseInt(hex.slice(1, 3), 16)` per channel. Step two: normalize each channel to 0-1 by dividing by 255. Step three: gamma-decode to linear-sRGB via the CSS Color 4 §11.2 piecewise function (`v <= 0.04045 ? v/12.92 : ((v+0.055)/1.055)^2.4`). Step four: multiply by the §15.1 3×3 matrix to get CIE XYZ D65 coordinates. Step five: multiply by Ottosson's LMS matrix (from his 2020 reference implementation) and take the cube root of each channel. Step six: multiply by Ottosson's OKLAB matrix to get L / a / b. Step seven: Cartesian-to-polar — `C = sqrt(a² + b²); H = atan2(b, a) * 180 / π`, wrap H into 0-360°. The full pipeline runs in microseconds — every keystroke re-renders the OKLCH output instantly with no debounce. This tool's HEX → OKLCH workflow is one direction of a 5-spoke family that all share the same underlying unified color converter. The dedicated unified color converter is the hub — it shows all 9 formats simultaneously editable and is the right tool when your workflow needs more than just hex and OKLCH. The single-direction spokes target specific Google search intents: the hex to RGB converter for the canvas-and-hardware direction, the RGB to hex converter for the inverse, the hex to HSL converter for the legacy designer-cylindrical space still used in many Tailwind v3 codebases, and the hex to CMYK converter for print-prep approximations. All five spokes and the hub share the same OKLCH source-of-truth internally and the same Ottosson 2020 matrices, so the results are guaranteed identical across the family. Every conversion runs locally in your browser — your hex codes are never uploaded, never logged, and zero network requests fire as you type. Verify in DevTools. For a deeper dive into why OKLCH became the design-system standard in 2024–2026, read our companion guide: OKLCH color space explained — why Tailwind v4 adopted it. ``` // sRGB hex → OKLCH per W3C CSS Color 4 + Ottosson 2020 // References: https://www.w3.org/TR/css-color-4/#color-conversion-code // https://bottosson.github.io/posts/oklab/ // Worked example: #3b82f6 (Tailwind blue-500) → oklch(0.629 0.193 263.4) function hexToOklch(hex) { const h = hex.trim().replace(/^#/, ''); const srgb = [0, 2, 4].map(i => parseInt(h.slice(i, i + 2), 16) / 255); // sRGB → linear-sRGB (CSS Color 4 §11.2 piecewise gamma) const lin = srgb.map(v => v <= 0.04045 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4)); const [lr, lg, lb] = lin; // linear-sRGB → XYZ D65 (CSS Color 4 §15.1 matrix) const x = 0.4124564 * lr + 0.3575761 * lg + 0.1804375 * lb; const y = 0.2126729 * lr + 0.7151522 * lg + 0.0721750 * lb; const z = 0.0193339 * lr + 0.1191920 * lg + 0.9503041 * lb; // XYZ D65 → LMS (Ottosson 2020), cube-root, → OKLAB const l_ = Math.cbrt(0.8189330101 * x + 0.3618667424 * y - 0.1288597137 * z); const m_ = Math.cbrt(0.0329845436 * x + 0.9293118715 * y + 0.0361456387 * z); const s_ = Math.cbrt(0.0482003018 * x + 0.2643662691 * y + 0.6338517070 * z); const L = 0.2104542553 * l_ + 0.7936177850 * m_ - 0.0040720468 * s_; const a = 1.9779984951 * l_ - 2.4285922050 * m_ + 0.4505937099 * s_; const b = 0.0259040371 * l_ + 0.7827717662 * m_ - 0.8086757660 * s_; // OKLAB → OKLCH (Cartesian to polar) const C = Math.sqrt(a * a + b * b); const H = (Math.atan2(b, a) * 180 / Math.PI + 360) % 360; return `oklch(${L.toFixed(3)} ${C.toFixed(3)} ${H.toFixed(1)})`; } console.log(hexToOklch('#3b82f6')); // → oklch(0.629 0.193 263.4) ``` #### FAQ **Q: What is OKLCH color?** A: OKLCH is the polar form of OKLAB, a perceptually-uniform color space published by Björn Ottosson in 2020. Channels are Lightness (0-1, also writable as 0-100%), Chroma (0 to about 0.4 depending on hue and L, unbounded above), and Hue (0-360°, identical conceptually to HSL's hue). It's derived from CIE-LAB by routing through an LMS cone-response stage with a cube-root step. CSS Color 4 added `oklch()` syntax in 2022. Tailwind v4 standardized on OKLCH for its default palette in 2025. Example: `oklch(0.629 0.193 263.4)` is Tailwind blue-500. **Q: Is OKLCH better than HSL?** A: For design systems, yes. HSL's L (lightness) is geometric — derived by averaging RGB max and min — and inherits sRGB's gamma curve, so `hsl(60 100% 50%)` (yellow) looks visibly brighter than `hsl(240 100% 50%)` (blue) despite both reporting L=50%. OKLCH's L is perceptual, anchored to the OKLAB model from Ottosson 2020. The practical upshot: an OKLCH ramp at uniform L looks visually even across every hue; an HSL ramp needs hand-tuned per-hue corrections to look even. This is why Tailwind v4 migrated its default palette from HSL-based to OKLCH-based generation. **Q: What browsers support oklch()?** A: All evergreen browsers as of mid-2023: Chrome and Edge 111 (March 2023), Safari 15.4 (March 2022, the earliest landing), Firefox 113 (May 2023). Combined caniuse coverage is over 94%. For the long tail — IE 11, old Safari, Android Chrome on legacy hardware — wrap your tokens in `@supports (color: oklch(0 0 0))` and provide a hex or `hsl()` fallback in the alternate branch. Build-time tools like PostCSS `postcss-oklab-function` can also inline an sRGB approximation alongside the OKLCH value at compile time. **Q: Why use OKLCH in Tailwind v4?** A: Tailwind v4 (released January 2025) moved its default palette from HSL-based to OKLCH-based generation specifically because OKLCH gives perceptually-even ramps automatically. Under v3's HSL system, `red-500` and `blue-500` had visibly different perceived weights despite identical HSL L%, which forced designers to hand-tune individual stops; under v4, both look balanced because both sit at the same OKLCH L. OKLCH also unlocks Display P3 wide-gamut colors that no hex code can encode — a Tailwind v4 token like `oklch(0.65 0.25 30)` can address P3 reds that exceed sRGB. The build still emits hex fallbacks for older browsers. **Q: Is OKLCH perceptually uniform?** A: Yes — that's the design intent. OKLCH inherits perceptual uniformity from OKLAB, Björn Ottosson's 2020 color space designed specifically to fix the non-uniformities in CIE-LAB (the previous best perceptually-uniform space). A fixed step in the L channel corresponds to a fixed perceived brightness step. A fixed step in C corresponds to a fixed perceived chroma step. CIELAB approximations break down around very saturated colors; OKLAB and its polar form OKLCH stay accurate across the gamut, which is why every modern design-system tool (Tailwind v4, shadcn/ui, Radix Colors v3) standardized on it. **Q: How do you read an OKLCH value?** A: `oklch(L C H)` — three numbers, optionally with `/ A` for alpha. L is Lightness from 0 (black) to 1 (white); the number form and percent form are equivalent (`0.6` and `60%`). C is Chroma from 0 (gray) up to roughly 0.4 for the most saturated sRGB colors; there is no hard upper bound, wide-gamut colors can exceed it. H is Hue in degrees from 0 to 360, same as HSL (0/360 = red, 120 = green, 240 = blue). Example: `oklch(0.629 0.193 263.4)` is Tailwind blue-500 — bright, highly chromatic, in the blue arc. **Q: What is the difference between OKLCH and LCH?** A: Both are polar forms (Lightness / Chroma / Hue) of a CIE-LAB-family color space. LCH is the polar form of CIE-LAB, the 1976 perceptually-uniform space. OKLCH is the polar form of OKLAB, Ottosson's 2020 update. The difference: CIE-LAB's perceptual uniformity breaks down around highly-saturated blues and purples (a documented weakness in the model), so an LCH ramp through saturated colors looks subtly uneven. OKLAB fixes this by re-deriving the matrices from a corrected LMS cone-response stage. Both ship in CSS Color 4 (`lch()` and `oklch()` syntax); for new design-system work in 2025, prefer OKLCH. **Q: How do I convert hex to OKLCH?** A: The pipeline is: parse hex `#RRGGBB` to integer sRGB channels via `parseInt(hex, 16)`, normalize to 0-1, gamma-decode to linear-sRGB via the CSS Color 4 §11.2 piecewise function, multiply by the §15.1 matrix to get CIE XYZ D65, multiply by Ottosson's LMS matrix and cube-root each channel, multiply by Ottosson's OKLAB matrix to get L/a/b, then Cartesian-to-polar: `C = sqrt(a² + b²); H = atan2(b, a) * 180 / π`. The full pipeline runs in microseconds. This tool runs it live as you type — `#3b82f6` lands as `oklch(0.629 0.193 263.4)` instantly. --- ### Hex to RGB Converter URL: https://go-tools.org/tools/hex-to-rgb Convert any hex color code to RGB in your browser — 3-digit, 6-digit, and 8-digit alpha hex all supported. Free, instant, no signup, your colors never leave the page. #### What Is a Hex to RGB Converter? A hex to RGB converter is a small utility that turns a hex color code (`#FF5733`) into the three integer channel values it represents (`rgb(255 87 51)`). Hex and RGB are the two formats every web stylesheet, design tool, and image-pixel pipeline has been built around since the late 1990s, and the conversion between them is the single most common operation in color tooling. Hex is the terse copy-paste format that Figma, Sketch, Photoshop, and every brand-guidelines PDF export by default — a 6-character base-16 string that fits in a CSS custom property comfortably and reads at a glance once your eyes learn the patterns. RGB is the channel-addressed format that hardware APIs, canvas drawing calls, image-buffer manipulation, OpenGL color attributes, and most graphics SDKs expect — three separate 0-255 integers (or 0-1 normalized floats) that map directly to the red, green, and blue subpixels of an LCD or the phosphors of a CRT. Converting between them is mechanical: split the hex into three 2-digit pairs and read each pair as a base-16 number. This tool runs that conversion live as you type, with no "Convert" button to click, and surfaces every other common color format (HSL, OKLCH, OKLAB, HSV, HWB, CMYK, plus the 148 CSS named colors) alongside the RGB output for free. **The RGB format itself deserves a closer look.** Standard 24-bit sRGB encodes each channel as an 8-bit unsigned integer from 0 to 255 — 256 values per channel, 16,777,216 colors total (256³). The reference standard is IEC 61966-2-1, the 1996 sRGB specification anchored to the CRT phosphor primaries that dominated displays at the time. CSS exposes RGB through the `rgb()` function in three syntactic flavors. The original CSS 1 form uses comma separators: `rgb(255, 87, 51)`. CSS Color 4 (W3C Candidate Recommendation since 2022) added a modern space-separated form: `rgb(255 87 51)`, with an optional alpha channel after a slash: `rgb(255 87 51 / 0.5)`. Both forms are interchangeable and ship in every evergreen browser. RGB also accepts percentage channels: `rgb(100% 33% 20%)` is equivalent to `rgb(255 87 51)`, sometimes preferred in hand-written stylesheets for readability. Alpha specifically has a separate `rgba()` function for legacy support — `rgba(255, 87, 51, 0.5)` is the canonical form that works everywhere down to IE 9. CSS Color 4 also added a `color(srgb 1 0.341 0.2)` syntax for explicit sRGB addressing, and parallel `color(display-p3 ...)` and `color(rec2020 ...)` functions for wide-gamut values that hex can't encode. The conversion math goes both directions cleanly. **HEX to RGB**: parse the 6-digit hex `#RRGGBB` as three 2-digit base-16 numbers via `parseInt(hex.slice(1, 3), 16)`, `parseInt(hex.slice(3, 5), 16)`, `parseInt(hex.slice(5, 7), 16)`. For 3-digit shorthand `#RGB`, expand each digit by duplicating it (`#F73` → `#FF7733`) before parsing — this is *not* a left-pad. For 8-digit alpha `#RRGGBBAA`, parse the trailing pair the same way and divide by 255 to get the 0-1 alpha float. For 4-digit alpha shorthand `#RGBA`, expand each digit first (`#F738` → `#FF773388`). **RGB to HEX** is the inverse: for each channel, call `value.toString(16).padStart(2, '0')` to get the 2-digit hex pair (the `padStart` matters — without it, channel value 5 would serialize as `'5'` instead of `'05'`, producing invalid hex), then concatenate. Both directions are bit-exact in either direction: 16² = 256, exactly matching the 0-255 byte range each channel occupies, so a HEX → RGB → HEX round-trip produces the original input verbatim with no float drift. **Why hex versus RGB?** Hex is shorter, design-tool-native, and the format your eye learns over time — most front-end developers can identify `#3b82f6` as Tailwind blue-500 at a glance. RGB is explicit-channel-addressing, easier to compute against in JavaScript, and the only of the two that accepts alpha and percentages cleanly. The two formats coexist because they solve different problems. Web stylesheets and brand guidelines lean hex because copy-paste cost dominates. Canvas drawing calls, image processing, hardware-LED APIs, and any code that does per-channel arithmetic lean RGB because indexing into a tuple beats slicing a string. The shift between them happens dozens of times in a typical web project — paste a hex from Figma, convert to RGB integers for a `ctx.fillStyle = ...` call, convert back to hex for a CSS variable definition. This tool's HEX → RGB workflow is one direction of a 5-spoke family that all share the same underlying unified color converter. The dedicated unified color converter is the hub — it shows all 9 formats simultaneously editable and is the right tool when your workflow needs more than just hex and RGB. The single-direction spokes target specific Google search intents: the reverse RGB to hex converter for the inverse direction, the hex to HSL converter for the legacy designer-cognitive space, the hex to OKLCH converter for modern perceptually-uniform design systems (Tailwind v4 and shadcn both default to OKLCH now), and the hex to CMYK converter for print-prep approximations. All five spokes and the hub share the same parsing engine and the same conversion math, so the results are guaranteed identical across the family. Every conversion runs locally in your browser — your hex codes are never uploaded, never logged, and zero network requests fire as you type. Verify in DevTools. ``` // Parse any hex shape (3/4/6/8-digit) into an RGB tuple [r, g, b, a] // All channels in 0-255 range; alpha in 0-1. function parseHex(input) { let h = input.trim().replace(/^#/, ''); // Expand 3-digit and 4-digit shorthand by duplicating each digit if (h.length === 3 || h.length === 4) { h = h.split('').map(c => c + c).join(''); } if (!/^[0-9a-fA-F]+$/.test(h) || (h.length !== 6 && h.length !== 8)) { throw new Error(`Invalid hex: ${input}`); } const r = parseInt(h.slice(0, 2), 16); const g = parseInt(h.slice(2, 4), 16); const b = parseInt(h.slice(4, 6), 16); const a = h.length === 8 ? parseInt(h.slice(6, 8), 16) / 255 : 1; return [r, g, b, a]; } console.log(parseHex('#FF5733')); // [255, 87, 51, 1] console.log(parseHex('#F73')); // [255, 119, 51, 1] console.log(parseHex('#FF573380')); // [255, 87, 51, 0.5019607843137255] ``` #### FAQ **Q: How do I convert a hex code to RGB?** A: Split the 6-digit hex into three 2-digit pairs and read each pair as a base-16 number from 0-255. `#FF5733` becomes R=`FF`=255, G=`57`=87, B=`33`=51, giving `rgb(255 87 51)`. 3-digit shorthand `#F73` expands by duplicating each digit to `#FF7733` before splitting. This tool does the conversion live as you type — paste any hex (with or without the `#`, 3-digit, 6-digit, 4-digit, or 8-digit with alpha) and the RGB field updates instantly with the matching `rgb()` value. **Q: Is hex the same as RGB?** A: They encode the same information in different notation. Both describe a color as three channels (red, green, blue) on the 0-255 scale, anchored to the sRGB color space. Hex packs the three channels into a 6-character base-16 string (`#FF5733`); the `rgb()` function spells them out in decimal (`rgb(255 87 51)`). They round-trip losslessly — the same color goes hex → RGB → hex without drift. Hex is shorter for CSS variables; `rgb()` supports an alpha channel via `rgba()` and CSS Color 4 percentage syntax. **Q: How do you read a hex color code?** A: A hex color has 6 hexadecimal digits after the `#`, grouped as **RR GG BB**. Each pair encodes one channel from `00` (none, 0 in decimal) to `FF` (full, 255 in decimal). `#FF0000` is pure red, `#00FF00` is pure green, `#0000FF` is pure blue. An 8-digit hex (`#FF5733CC`) adds an alpha pair at the end — `CC` = 204/255 ≈ 80% opacity. The 3-digit shorthand `#F73` expands each digit by duplicating it: `#F73` is identical to `#FF7733`. **Q: What is the formula for hex to RGB?** A: For each 2-digit hex pair, multiply the first digit by 16 and add the second: `FF` = 15×16 + 15 = 255, `57` = 5×16 + 7 = 87, `33` = 3×16 + 3 = 51. In JavaScript: `parseInt('FF', 16)` returns 255 directly. In CSS the reverse direction is built into the parser — `rgb(255 87 51)` and `#FF5733` are interchangeable anywhere a `` is accepted. There's no rounding loss in either direction: 16² = 256, exactly matching the 0-255 byte range each channel occupies. **Q: Why use hex instead of RGB?** A: Three reasons: it's shorter (`#FF5733` vs `rgb(255, 87, 51)`), it's the default export from every design tool (Figma, Sketch, Photoshop), and it's the format front-end developers learn to recognize on sight — most can identify `#3b82f6` as Tailwind blue-500 without looking it up. Reach for `rgb()` (or the modern space-separated `rgb(R G B / A)` syntax from CSS Color 4) when you need alpha transparency, when you're computing a color from JavaScript channel values, or when explicit percentage syntax improves readability in a stylesheet. **Q: Can hex codes have alpha?** A: Yes — use 8-digit hex (`#RRGGBBAA`) or 4-digit shorthand (`#RGBA`). The alpha pair follows the same 0-`FF` scale: `#FF573300` is fully transparent, `#FF5733FF` is fully opaque, `#FF573380` is roughly 50%. CSS 4-digit and 8-digit hex with alpha shipped natively in all evergreen browsers in 2018 (Chrome 62, Firefox 49, Safari 9.1, Edge 79). For older parsers and legacy CSS preprocessors that silently truncate the alpha pair, fall back to `rgba(255, 87, 51, 0.5)`, which has been supported since IE 9. **Q: How many colors can hex represent?** A: 6-digit hex represents exactly **16,777,216** colors — 256 values per channel cubed (256³). With 8-digit hex including alpha, the addressable space is 256⁴ ≈ 4.3 billion, but the color content is still 16.7M; the extra dimension is opacity. The human eye can distinguish roughly 10 million colors, so 24-bit sRGB has been marketed as "truecolor" since the 1990s. Modern wide-gamut displays (Display P3, Rec.2020) cover more of the visible spectrum, but hex itself is sRGB-bound — use OKLCH or `color(display-p3 ...)` for wide-gamut values. **Q: How do I convert RGB to hex?** A: Reverse the formula: divide nothing, just convert each channel integer to its 2-digit base-16 representation and concatenate. In JavaScript: `[255, 87, 51].map(v => v.toString(16).padStart(2, '0')).join('')` returns `'ff5733'`, then prepend `#`. The `padStart(2, '0')` matters — without it, single-digit values like `5` would serialize as just `'5'` instead of `'05'`, producing an invalid hex. For the reverse direction in this tool's family, use the dedicated RGB to hex converter. --- ### HMAC Generator & Signature Verifier URL: https://go-tools.org/tools/hmac-generator Free online HMAC generator & verifier — compute or verify HMAC-SHA256/SHA1/384/512 with Text, Hex or Base64 keys and Hex/Base64/Base64URL output. 100% in your browser; your secret key never leaves the page. #### What Is an HMAC? An HMAC (Hash-based Message Authentication Code) is a short, fixed-length tag that proves a message is both unmodified and authentic — that it was produced by someone who holds a shared secret key. Defined in RFC 2104 and FIPS 198-1, HMAC combines any cryptographic hash function with a secret key in a specific nested construction, written as HMAC(K, m) = H((K ⊕ opad) ‖ H((K ⊕ ipad) ‖ m)). The inner hash binds the key to the message; the outer hash wraps the result, which is what makes HMAC resistant to length-extension attacks that affect the raw SHA-1 and SHA-256 functions. HMAC is everywhere in modern web infrastructure. It signs webhooks so you can confirm an incoming request really came from GitHub, Stripe, Slack, or Twilio and was not forged. It signs API requests (AWS Signature Version 4 is built on HMAC-SHA256) so a server can authenticate the caller without sending a password over the wire. It is the S in HS256: a JWT signed with HS256 carries an HMAC-SHA256 over its header and payload, which you can inspect with the JWT encoder. It also underpins TLS key derivation (HKDF), one-time-password algorithms (HOTP/TOTP), and message integrity in countless internal services. This tool computes HMAC entirely in your browser using crypto.subtle.sign('HMAC', ...) from the Web Crypto API — the same primitive browsers use during TLS handshakes. Your secret key and message are never uploaded, so it is safe for production signing secrets. Because the same secret can be expressed as raw text, hex, or base64, the tool lets you choose the Key encoding explicitly, and because different providers expect the tag in different forms, you can output Hex, Base64, or Base64URL. The Verify tab lets you check a signature you received, using a constant-time comparison so the check itself does not leak timing information. ``` const crypto = require('crypto'); // HMAC-SHA256 with a UTF-8 text key, hex output const hmac = crypto .createHmac('sha256', 'my-secret-key') .update('Hello, World!') .digest('hex'); console.log(hmac); // → 'cf3141611e22ea26a9cac6fe41d941274dd6653622c83cba13972d177bd69699' // Verify a signature in constant time function verify(message, key, expectedHex) { const actual = crypto.createHmac('sha256', key).update(message).digest(); const expected = Buffer.from(expectedHex, 'hex'); return actual.length === expected.length && crypto.timingSafeEqual(actual, expected); } ``` #### FAQ **Q: What is HMAC?** A: HMAC (Hash-based Message Authentication Code) is a way to prove both the integrity and the authenticity of a message using a shared secret key. You feed a message and a secret key into a hash function (here SHA-1, SHA-256, SHA-384, or SHA-512) in the specific nested construction defined by RFC 2104, and you get a fixed-length tag. Anyone who knows the secret can recompute the tag and confirm the message was not altered and came from someone who holds the key. It is the standard mechanism behind webhook signatures, signed API requests, and the HS256 family of JWT tokens. **Q: How is HMAC different from a plain hash like SHA-256?** A: A plain hash such as SHA-256 only proves integrity: anyone can recompute it, so anyone can also forge a matching hash for a tampered message. HMAC mixes in a secret key, so only parties holding that key can produce or verify a valid tag — that adds authenticity on top of integrity. HMAC also uses a nested two-pass construction (inner and outer hashing with key-derived pads) that makes it immune to the length-extension attacks that affect raw SHA-1 and SHA-256. In short: use a hash to detect accidental corruption, use HMAC to detect deliberate tampering by an attacker who does not know your key. **Q: How do I verify an incoming webhook signature?** A: Take the raw request body exactly as received (do not re-serialize the JSON — even reordered keys break the signature), select the same algorithm your provider uses (usually HMAC-SHA256), enter your signing secret with the correct Key encoding, and set the Output format to match the header (Hex for GitHub's sha256= prefix, Base64 for Stripe/Twilio-style headers). Then open the Verify tab, paste the signature from the header (such as X-Hub-Signature-256), and the tool reports match or mismatch using a constant-time comparison. Only trust the payload if it matches. **Q: Which key and output encoding does my server use?** A: There is no universal answer — it depends on your provider, which is exactly why this tool exposes both as explicit choices. Common patterns: GitHub uses a UTF-8 text secret and lowercase hex output with a sha256= prefix; Stripe uses a text secret and base64; AWS Signature V4 uses derived binary keys and hex; many internal systems hand out base64 or hex secrets that must be decoded to raw bytes before signing. If your HMAC does not match, the encoding is almost always the culprit — try the same key under Text, Hex, and Base64 to find the interpretation the server expects. **Q: Is HMAC-SHA256 secure?** A: Yes. HMAC-SHA256 is widely considered secure and is the recommended default for message authentication. Its security comes from the HMAC construction plus a strong underlying hash, and it remains safe even though collision attacks exist against the bare hash — HMAC does not rely on collision resistance the way a digital signature does. The real-world risks are operational, not algorithmic: a weak or leaked secret key, logging the key, or using a non-constant-time comparison. Use a long random key and compare tags in constant time and HMAC-SHA256 will hold up. **Q: Why no HMAC-MD5 or HMAC-SHA-3 here?** A: By design. This tool exposes only SHA-1, SHA-256, SHA-384, and SHA-512 because those are the hash functions the browser's native Web Crypto API supports, which keeps everything fast and entirely client-side with no extra libraries. HMAC-MD5 is omitted because MD5 is obsolete and you should not start new systems with it. HMAC-SHA-3 is omitted because Web Crypto does not implement SHA-3; adding it would require shipping a JavaScript polyfill. For virtually all modern use cases HMAC-SHA256 is the correct choice anyway. **Q: HS256 (HMAC) vs RS256 (RSA) — which should I use for JWTs?** A: HS256 signs and verifies a JWT with a single shared secret using HMAC-SHA256, while RS256 signs with an RSA private key and verifies with the matching public key. Use HS256 when one party both issues and validates the tokens (a monolith, or trusted internal services that can safely share the secret) — it is simpler and faster. Use RS256 when third parties or many services must verify tokens but must not be able to mint them, since you can distribute only the public key. You can explore the encoded structure of both in the JWT encoder; the HS256 signature is exactly an HMAC-SHA256 over the header and payload. --- ### Free HTML Entity Decoder — Unescape HTML URL: https://go-tools.org/tools/html-entity-decode Decode HTML entities and unescape HTML online — free, no signup, 100% in your browser. Converts named, decimal & hex references back to characters; never uploaded. #### What is HTML entity decoding? HTML entity decoding — also called HTML unescaping — is the process of converting character references back into the characters they represent. Where encoding replaces a literal < with the entity < so a browser displays it as text, decoding does the reverse: it scans a string for references like <, &, <, >, or © and substitutes the actual character (<, &, <, >, ©) for each one. It is the operation you run when you have markup that was stored or transmitted in its escaped form and you need the real text back — to read it, edit it, hand it to another program, or work out why a page is rendering &lt; instead of <. It is worth being precise about what this tool does. It decodes entities into characters; it does not reformat or validate the markup. If you want to take an escaped string and recover its literal characters, this is the right tool. To go the other direction and turn characters into entities, use the HTML Entity Encoder; and to indent and tidy a block of HTML, use the HTML Formatter. Encoding and decoding are exact inverses, so a string sent through the encoder and back through the decoder returns unchanged. There are three kinds of reference the decoder must understand, and it handles all of them. A named reference uses a defined label (< for <, © for ©, — for —); a decimal numeric reference writes the Unicode code point in base 10 (< for <); and a hexadecimal numeric reference writes the same code point in base 16 (< for <), matching the U+XXXX notation of the Unicode standard. A robust decoder accepts any of them, in any mix, because different encoders emit different forms. The table below shows the references you will meet most often and the character each one decodes to: | Entity (named) | Decimal | Hex | Decodes to | |----------------|---------|-----|------------| | &lt; | &#60; | &#x3C; | < | | &gt; | &#62; | &#x3E; | > | | &amp; | &#38; | &#x26; | & | | &quot; | &#34; | &#x22; | " | | &#x27; | &#39; | &#x27; | ' | | &nbsp; | &#160; | &#xA0; | (no-break space) | | &copy; | &#169; | &#xA9; | © | | &reg; | &#174; | &#xAE; | ® | | &trade; | &#8482; | &#x2122; | ™ | | &euro; | &#8364; | &#x20AC; | € | | &pound; | &#163; | &#xA3; | £ | | &mdash; | &#8212; | &#x2014; | — | | &ndash; | &#8211; | &#x2013; | – | | &hellip; | &#8230; | &#x2026; | … | | &#x1F600; | &#128512; | &#x1F600; | 😀 | Two behaviors set a thorough decoder apart. First, it reconstructs astral-plane characters — anything above U+FFFF, including most emoji — from their numeric references rather than producing a broken half-character; 😀 correctly becomes 😀. Second, it follows the browser's lenient parsing for the small set of legacy named entities that historically appeared without a trailing semicolon, so &copy 2026 still decodes to © 2026 even though strict XML would reject it. This tool does both, matching the behavior of the widely used he library so its output agrees with what a real browser would render. A word of caution that belongs with every decoder: decoded text is unescaped by definition. Decoding is the inverse of the escaping that protects pages from cross-site scripting, so a decoded string containing a <script> tag or an event handler is once again live, dangerous markup. Never decode untrusted input and then insert it into a page with innerHTML — that reopens the exact hole encoding was meant to close. Decode when you need the raw characters for reading, editing, or storage; if the result will be rendered back into HTML, re-escape it in its destination context first. And because every byte is processed in your browser, the escaped strings you decode — even a private record or an unpublished draft — never cross the network. For neighbouring conversions, the URL Encoder / Decoder handles percent-encoding and Base64 Encode / Decode handles binary-safe transport. ``` // Decoding is the inverse of escaping. The classic round-trip: // < → < > → > & → & " → " ' → ' // Browser — the safest decoder is the platform itself. Use textarea, NOT innerHTML on a live node, // so the decoded markup is never executed. function decodeHtml(str) { const ta = document.createElement('textarea'); ta.innerHTML = str; // the parser resolves entities into text return ta.value; // .value is plain text — no script runs } decodeHtml('<div> & ©'); // → '
& ©' decodeHtml('<>'); // → '<>' decodeHtml('😀'); // → '😀' decodeHtml('© 2026'); // → '© 2026' (lenient, no semicolon) // --------------------------------------------------------------- // SECURITY: decoded text is unescaped. Never do this with untrusted input: // el.innerHTML = decodeHtml(userInput); // ❌ reopens the XSS hole // If the decoded value must be displayed, re-escape it in its destination context first, // or assign it as text: // el.textContent = decodeHtml(userInput); // ✅ shown as literal text // --------------------------------------------------------------- // Node.js (no DOM) — use a tested library such as he: // import { decode } from 'he'; // decode('<div> & ©'); // → '
& ©' ``` #### FAQ **Q: Is my text sent to your server when I decode it?** A: No. Every entity is resolved entirely in your browser with JavaScript — open DevTools → Network and you will see zero requests fire when you type or paste. Nothing is uploaded, nothing is logged, nothing is written to disk. That privacy matters because the escaped strings people decode are often sensitive: a fragment pulled from a private database, an internal email, a customer record, or markup copied out of an application you do not want leaking. On a server-side decoder every one of those would travel across the network to a machine you do not control; here the text never leaves the tab. This is the whole reason to decode HTML client-side rather than paste it into a website that could, in principle, keep a copy of everything it processes. **Q: What does it mean to decode or unescape HTML?** A: Decoding HTML — also called unescaping — is the reverse of HTML escaping: it takes character references like <, &, <, or © and converts each back into the real character it stands for (<, &, <, ©). You reach for it whenever you have a string that was stored or transmitted in its escaped form and you need the literal text back — to read it, edit it, feed it to another program, or debug why a page is showing &lt; on screen instead of <. If you want to go the other way and turn characters into entities, use the companion HTML Entity Encoder; the two are exact inverses. **Q: Which kinds of entities can this decoder handle?** A: All three forms, in any mix. It resolves named references (<, &, ©, — and the full HTML5 named-entity set), decimal numeric references (<, é), and hexadecimal numeric references (<, é). It also reconstructs astral-plane characters above U+FFFF from their numeric references, so an emoji like 😀 decodes correctly to 😀. And it follows the browser's lenient parsing for a handful of legacy named entities that omit the trailing semicolon — &copy 2026 still decodes to © 2026 — which strict parsers would skip. In short, whatever an encoder produced, this decoder reverses it. **Q: Why does my text show &lt; instead of HTML Entity Encoder again first. **Q: Does decoding handle non-ASCII characters and emoji correctly?** A: Yes. Numeric references can encode any Unicode code point, and the decoder resolves them all — accented letters (é → é), symbols (€ → €), em dashes (— → —), and full-plane emoji (😀 → 😀). For astral characters above U+FFFF it reconstructs the complete code point rather than producing a broken half-character. Raw non-ASCII characters that are already in the input pass through untouched, so a string that mixes real UTF-8 with entities decodes cleanly without corrupting either part. Make sure the page or file you paste the result into is served as UTF-8 so the recovered characters display correctly. **Q: How do I encode text back into entities?** A: Use the companion HTML Entity Encoder. It takes raw characters like <div> & © and escapes them to <div> & ©, with options for named, decimal, or hex output and an "encode all non-ASCII" mode for legacy charsets. Encoding and decoding are exact inverses for the reserved characters, so you can round-trip text through both tools without loss. You can jump straight there with the Swap direction button on this page. **Q: Is this the same as URL decoding or Base64 decoding?** A: No — they are three different encodings for three different jobs, and mixing them up is a common source of bugs. HTML entity decoding turns < back into <. URL (percent) decoding turns %20 back into a space and is for query strings and paths — use the URL Encoder / Decoder for that. Base64 decoding turns a base64 string back into the original bytes and is for binary-safe transport — use Base64 Encode / Decode. A value can be wrapped in more than one of these, so decode them in the reverse order they were applied. This tool handles HTML entities only. --- ### Free HTML Entity Encoder — Escape HTML URL: https://go-tools.org/tools/html-entity-encode Encode HTML entities and escape special characters (< > & " ') online — free, no signup, 100% in your browser. Named, decimal, or hex output; never uploaded. #### What is HTML entity encoding? HTML entity encoding — also called HTML escaping — is the process of replacing characters that have special meaning in HTML with a safe textual representation called an entity, so the browser displays them as literal text instead of interpreting them as markup. The five characters that matter most are the ones HTML uses to structure a document: the angle brackets < and > that open and close tags, the ampersand & that begins an entity, and the quotation marks " and ' that delimit attribute values. When any of these appears in content that should be shown rather than executed, it must be escaped, or the browser will misread the page — at best your text renders wrong, at worst an attacker slips in a <script> tag. It helps to be precise about what this tool does. It encodes text into entities; it does not assemble or pretty-print a document. If you want to read a string of code on a page as plain text, or you are inserting user-supplied input into your HTML and need to neutralise it, this is the right tool. If instead you want to indent and tidy existing markup, that is the job of the HTML Formatter; and to turn entities back into characters, use the HTML Entity Decoder. There are three ways to write any entity, and they are interchangeable. A named reference uses a human-friendly label (< for <, © for ©); a decimal numeric reference writes the character's Unicode code point in base 10 (< for <); and a hexadecimal reference writes the same code point in base 16 (< for <), matching the U+XXXX notation of the Unicode standard. Named entities read best but exist only for characters that have a defined name; numeric entities can represent any code point, which is why they are the safe fallback. The table below lists the entities you will reach for most often: | Character | Named | Decimal | Hex | |-----------|-------|---------|-----| | < | &lt; | &#60; | &#x3C; | | > | &gt; | &#62; | &#x3E; | | & | &amp; | &#38; | &#x26; | | " | &quot; | &#34; | &#x22; | | ' | &#x27; | &#39; | &#x27; | | (space) | &nbsp; | &#160; | &#xA0; | | © | &copy; | &#169; | &#xA9; | | ® | &reg; | &#174; | &#xAE; | | ™ | &trade; | &#8482; | &#x2122; | | € | &euro; | &#8364; | &#x20AC; | | £ | &pound; | &#163; | &#xA3; | | — | &mdash; | &#8212; | &#x2014; | | – | &ndash; | &#8211; | &#x2013; | | … | &hellip; | &#8230; | &#x2026; | | é | &eacute; | &#233; | &#xE9; | Note that the apostrophe is written ' (or ') rather than ': the named ' was only standardised in HTML5 and XML and is unsafe in older HTML4 parsers, so the numeric form — understood everywhere — is the compatible choice. This tool follows the same convention as the widely used he library, which is why the default output for ' is '. The distinction between a character set and an entity is worth holding onto, because it explains the "Encode all non-ASCII" option. A charset (like UTF-8) determines how characters are stored as bytes; an entity is a way to write a character using only the plain ASCII characters & # ; and letters or digits. On a modern UTF-8 page, é, —, and 😀 are valid raw characters and need no entity at all — which is why the default mode leaves them alone. You only force them into entities when the text must pass through a system that cannot handle raw UTF-8, in which case every non-ASCII code point is rewritten as an ASCII-safe numeric or named reference. And because all of this runs in your browser, the markup you escape — even a private template or an unpublished draft — never crosses the network. For related conversions, the JSON Escape and Base64 Encode / Decode tools handle escaping for JavaScript strings and binary-safe transport respectively. ``` // Server-side templates auto-escape, but when you build HTML by hand you must escape yourself. // The five reserved characters and their safe entities: // < → < > → > & → & " → " ' → ' // Node.js — escape untrusted input before inserting it into HTML element content. function escapeHtml(str) { return str .replace(/&/g, '&') // & first, so later entities are not double-escaped .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); // numeric form — safe in HTML4, HTML5 and XML } const userInput = `Tom & Jerry's`; const safe = escapeHtml(userInput); // → <a href="x">Tom & Jerry's</a> document.getElementById('out').innerHTML = `

${safe}

`; // renders as literal text // --------------------------------------------------------------- // In practice, prefer the platform's built-in escaping where it exists: // - React / Vue / Angular escape interpolated text by default // - Use textContent instead of innerHTML when you only need text: // el.textContent = userInput; // the browser escapes for you // - Server frameworks (Jinja, ERB, Blade) auto-escape unless you opt out ``` #### FAQ **Q: Is my text sent to your server when I encode it?** A: No. Every character is encoded entirely in your browser with JavaScript — open DevTools → Network and you will see zero requests fire when you type or paste. Nothing is uploaded, nothing is logged, nothing is written to disk. That privacy matters because the markup people escape is often sensitive: a snippet from a private CMS, an internal email template, a customer support reply, or a draft blog post you have not published. On a server-side encoder every one of those would travel across the network to a machine you do not control; here the text never leaves the tab. This is the whole reason to escape HTML client-side rather than paste it into a website that could, in principle, keep a copy of everything it processes. **Q: What does it mean to escape HTML, and why would I do it?** A: Escaping HTML means replacing characters that the browser would otherwise interpret as markup with their entity equivalents, so they are displayed as literal text instead. The classic case is showing code on a page: if you want a visitor to read the string <strong>bold</strong> rather than see the word "bold" rendered in boldface, you escape the angle brackets to <strong>bold</strong>. The other, more important case is security: when you insert untrusted user input into a page, escaping the five reserved characters (< > & " ') prevents that input from breaking out of its context and injecting a <script> tag — the core defense against cross-site scripting (XSS). Any text that originates from a user and lands in your HTML should be escaped first. **Q: What is the difference between named, decimal, and hex entities?** A: All three produce the same character; they differ only in how the reference is written. A named entity uses a human-readable label — < for <, & for &, © for © — which is easy to read but only works for characters that have a defined name. A decimal numeric entity writes the Unicode code point in base 10, like < for < or é for é. A hexadecimal entity writes the same code point in base 16, like < for < or é for é, mirroring the U+XXXX notation in the Unicode standard. Named entities are the most readable and are the right default for the common reserved characters; numeric entities (decimal or hex) can encode any code point, including ones with no name, which makes them the safe choice when you cannot guarantee the consumer supports a particular named entity. **Q: Why is the apostrophe encoded as ' and not '?** A: Because ' is not safe everywhere. The named entity ' was only introduced in HTML5 and XML — it is not defined in HTML4, so a few older parsers and email clients render it as the literal text "'" instead of an apostrophe. The numeric reference ' (or its decimal twin ') maps to the exact same character, U+0027, and is understood by every conforming parser ever written. Following the behavior of well-tested libraries like he, this tool emits the universally compatible ' for the apostrophe so the output is safe to drop into any HTML, XML, or attribute context without surprises. **Q: Do I need to encode non-ASCII characters like é, — or 😀?** A: Usually no. If your page declares <meta charset="utf-8"> — which essentially every modern page does — then accented letters, em dashes, and emoji are perfectly valid as raw UTF-8 and need no encoding at all. That is why the default "special characters" mode leaves them untouched, keeping your output short and readable. You only need to encode non-ASCII characters when the text will be served or stored in a legacy single-byte charset, or passed through a system that corrupts raw UTF-8. For those cases tick "Encode all non-ASCII characters" and every code point above 0x7F is converted to an ASCII-safe entity. When in doubt, keep the default and make sure your charset declaration is correct. **Q: Does escaping HTML protect me from XSS attacks?** A: Escaping is the foundation of XSS defense, but it is context-dependent, so the honest answer is "yes, when applied correctly." Encoding the five reserved characters before you place untrusted input into HTML element content reliably stops an attacker from injecting tags or scripts — a payload like <script>alert(1)</script> becomes inert text. The caveat is that HTML has several contexts, each with its own escaping rules: inside an attribute value you must escape quotes (which this tool does), inside a <script> block or an inline event handler you need JavaScript escaping instead, and inside a URL you need URL encoding. Use HTML entity encoding for HTML and attribute contexts; for URLs reach for the URL Encoder / Decoder, and for embedding a string in JavaScript or JSON see the JSON Escape tool. Encode at output time, in the context where the data lands. **Q: How do I reverse this — turn entities back into characters?** A: Use the companion HTML Entity Decoder. It takes a string full of entities like <div> & © and converts it back to the real characters <div> & ©, handling named entities, decimal references, hexadecimal references, and even legacy unterminated entities such as &copy without a trailing semicolon. Encoding and decoding are exact inverses for the reserved characters, so you can round-trip text through both tools without loss. If you are debugging why a page shows literal &lt; instead of <, the decoder is the fastest way to see what the entities actually resolve to. **Q: Will encoding change the visible text or break my layout?** A: No — that is the entire point. An entity is just an alternate spelling of a character: when a browser parses < it renders a single < glyph, identical to the raw character. So a correctly escaped page looks exactly the same to a visitor as it would with raw characters; the only difference is that the browser treats the escaped version as text rather than markup. The one thing escaping changes is the length and appearance of the source string, which is why you escape only what needs escaping. If your goal is to clean up and indent messy markup rather than escape it, that is a different job — use the HTML Formatter instead. --- ### HTML Formatter, Beautifier & Minifier URL: https://go-tools.org/tools/html-formatter Format, beautify and minify HTML instantly in your browser. Indent messy markup or compress it to ship — free, private, and your HTML never leaves your device. #### What is HTML Formatting? HTML formatting (also called beautifying or pretty-printing) rewrites markup with consistent nesting, indentation and line breaks so its structure is easy to read and edit. The page renders identically before and after — only whitespace changes. Minifying does the reverse: it removes comments and collapses whitespace — including embedded CSS and JS — so pages load faster. This tool does both, entirely in your browser. #### FAQ **Q: How do I format HTML online?** A: Paste your HTML into the input box and click Format. The tool reindents the markup with proper nesting and line breaks, then lets you copy it. Everything runs locally in your browser — nothing is uploaded. **Q: How do I minify HTML?** A: Paste your HTML and click Minify. The tool removes comments and collapses whitespace — including embedded CSS and JavaScript — to produce the smallest equivalent markup, and shows how many bytes you saved. **Q: What is the difference between formatting and minifying HTML?** A: Formatting (beautifying) adds indentation and line breaks to make markup readable. Minifying strips comments and whitespace to shrink the file for faster loading. Both render identically in the browser. **Q: Does formatting change how my page renders?** A: Formatting only adds whitespace, which is safe for normal markup. Be aware that whitespace-sensitive elements like pre and textarea can be affected by reformatting or aggressive minification — verify those after processing. **Q: Is my HTML safe with this tool?** A: Yes. All formatting and minifying happen locally in your browser using JavaScript — your HTML is never sent to any server, logged, or stored. That makes it safe for proprietary or unreleased markup, unlike server-side tools that receive a copy of everything you paste. **Q: Does it minify inline CSS and JavaScript?** A: Yes. The minifier compresses style and script contents too, so a single pass shrinks your whole document — markup, styles and scripts together. **Q: What indentation should I use for HTML?** A: Two spaces is the most common default and keeps diffs compact; four spaces can help with deeply nested layouts; tabs let each developer choose their width. Pick one and apply it consistently — this tool supports all three. --- ### HTML to Markdown Converter URL: https://go-tools.org/tools/html-to-markdown Convert HTML to clean Markdown in your browser — GFM tables, task lists, and links. Choose ATX/Setext headings and inline or reference links. Great for migrating web content or feeding LLMs. 100% private, no upload. #### What is HTML to Markdown Conversion? HTML to Markdown conversion takes a rendered HTML document — the tags, attributes, and nesting a browser displays — and rewrites it as Markdown, the lightweight plain-text format built for writing and version control. Where Markdown to HTML expands compact text into markup for display, this is the reverse and reductive direction: you start with rich, verbose HTML and distil it down to the small, readable set of conventions Markdown offers. Under the hood the converter parses your HTML into a DOM tree — the same node structure a browser builds — then walks that tree and emits the Markdown equivalent for each node it recognises. An <h2> becomes ## , a <strong> becomes **text**, a <ul> becomes a bulleted list, an becomes a link, a <table> becomes a GFM pipe table. Traversing a real DOM, rather than running regular expressions over the raw string, is what lets it handle nested lists, mixed inline formatting, and tables correctly instead of breaking on edge cases. You reach for this conversion when you are migrating out of HTML, not into it. Content trapped in a CMS, a WYSIWYG editor, an old web page, or a rich-text field is hard to diff, hard to review, and hard to move. Converting it to Markdown frees it into a format that lives happily in a Git repo, a static-site generator, or a notes app — and, increasingly, into a format that large language models read efficiently. The catch, which honest tools state plainly, is that the conversion is lossy: HTML can express things Markdown cannot, so some structure and every styling detail are deliberately discarded in exchange for clean, portable text. The reverse operation — Markdown back to HTML, for when you are ready to publish or preview — is just as useful. Switch to the Markdown → HTML tab or open the dedicated Markdown to HTML converter. ``` HTML in:

Pricing

Plans start at $9/mo. See the details.

PlanPrice
Pro$9
Markdown out: ## Pricing Plans start at **$9/mo**. See the [details](https://example.com/pricing). | Plan | Price | | ---- | ----- | | Pro | $9 | ``` #### FAQ **Q: How are inline vs reference links handled?** A: You choose with the Links radio. Inline style writes each anchor as [text](url) right where it appears — compact and obvious for one or two links per paragraph. Reference style writes [text][1] in the prose and collects all the URLs as [1]: https://… definitions at the bottom of the document, which keeps text with many links readable and lets you reuse a URL by label. Both produce identical rendered output; it is purely a source-readability choice. Images follow the same rule: an <img> becomes ![alt](src) inline or ![alt][1] in reference mode. **Q: ATX vs Setext headings — which should I use?** A: ATX headings prefix the line with hashes — # H1, ## H2, ### H3 — and work for all six levels. Setext headings underline the text instead: a row of = under a line makes it an H1, a row of - makes it an H2. The catch is that Setext only exists for levels 1 and 2, so this converter emits Setext for <h1>/<h2> and automatically falls back to ATX for <h3> and deeper. ATX is the more common, more portable choice and is easier to grep; pick Setext only if a downstream style guide or linter requires it. **Q: What happens to HTML that Markdown can't represent, like <div> and <span>?** A: Markdown has no syntax for generic containers, so structural wrappers such as <div>, <span>, <section>, and <article> are unwrapped — their text and child elements are kept, but the tag itself disappears because there is nothing in Markdown to map it to. Class names, id attributes, inline style attributes, and data-* attributes are dropped for the same reason: Markdown carries no way to express them. When an element genuinely has no Markdown equivalent and dropping it would lose meaning, the converter leaves it as raw inline HTML rather than silently deleting the content. This is by design — see the question on whether the conversion is lossless. **Q: Does it strip <script> and styles?** A: Yes. <script> and <style> elements, along with their contents, are removed entirely — they are code and CSS, not document content, and have no place in Markdown. The same goes for <link>, <meta>, and other head-level elements when you paste a whole page. Inline event handlers like onclick and CSS in style attributes are dropped as well. The result is text content only, which is exactly what you want when the Markdown is headed for a docs repo, a static-site generator, or an LLM context window. If you need the styling preserved, Markdown is the wrong target format. **Q: How are nested tables and lists handled?** A: Nested lists convert cleanly: each level of <ul>/<ol> nesting becomes two spaces of indentation, and ordered lists are renumbered from 1. Tables are trickier. GitHub Flavored Markdown pipe tables are flat by specification — a table cell cannot contain another table, and it cannot contain block elements like lists or multiple paragraphs. So a simple <table> converts to a clean pipe table, but a table with a nested table inside a cell, or with block content in cells, degrades: the converter flattens what it can and falls back to leaving the complex parts as raw HTML so no data is lost. Deeply nested layout tables from legacy pages are the worst case — consider simplifying the HTML first. **Q: Is HTML to Markdown lossless?** A: No, and it is important to be honest about that. HTML is far more expressive than Markdown: it has hundreds of elements and arbitrary attributes, while Markdown covers a small, deliberate set — headings, emphasis, lists, links, images, code, blockquotes, and (with GFM) tables, task lists, and strikethrough. Anything outside that set has no representation: colspans, custom attributes, inline styles, <div>/<span> structure, and most semantic wrappers are dropped or preserved only as raw HTML. Converting HTML → Markdown → HTML will not reproduce the original byte-for-byte. The conversion is lossy on purpose — the goal is clean, portable, human-editable text, not a faithful round-trip. To go back the other way, use our Markdown to HTML converter. **Q: Can I feed the Markdown to an LLM or ChatGPT?** A: Yes — this is one of the best modern uses. Raw HTML wastes tokens on tags, attributes, scripts, and styling that a model does not need, and the noise can degrade retrieval quality in a RAG pipeline. Converting a page to Markdown strips that overhead while keeping the structure a model reads well: headings become hierarchy, lists stay lists, tables stay tables, and links stay links. The output is typically a fraction of the original HTML's token count, so you fit more real content in the context window. Paste a scraped page here, copy the Markdown, and drop it into your prompt, embedding step, or document store. **Q: Are my files uploaded to a server?** A: No. The conversion runs entirely in your browser: the HTML is parsed into a DOM and serialised to Markdown locally with JavaScript, and nothing is transmitted, stored, or logged. You can confirm it by opening your browser's Network tab — converting triggers zero network requests. That makes the tool safe for internal CMS exports, unpublished pages, customer content, and anything under NDA. There is no upload step and no size limit beyond what your browser can comfortably hold in memory. **Q: Does it work offline?** A: Yes, once the page has loaded. The DOM parser and the Markdown serialiser both run in the browser with no server round-trip, so you can convert with your network disconnected — on a plane, behind a strict firewall, or any time you would rather a page never left your machine. This falls straight out of the privacy-first design: because nothing is sent anywhere, there is nothing the tool needs the network for after the initial load. **Q: Can I convert Markdown back to HTML?** A: Yes. Switch to the Markdown → HTML tab, or open the dedicated Markdown to HTML converter, paste your Markdown, and get rendered HTML with a live preview, full GFM support, and fragment, full-document, or email-inline output. The two directions pair up: use HTML → Markdown to pull existing web content into a Markdown workflow, and Markdown → HTML to publish or preview it. If the source HTML is messy, our HTML Formatter can tidy it before you convert. --- ### htpasswd Generator — bcrypt, Apache MD5 (apr1) & Basic Auth URL: https://go-tools.org/tools/htpasswd-generator Generate htpasswd entries with bcrypt, Apache MD5 (apr1), SHA-1 & more. Get ready-to-paste Apache, nginx & Docker config. 100% in your browser — no upload. #### What Is an htpasswd File? An .htpasswd file stores the credentials used by HTTP Basic Authentication. Each line is a single username:hash pair, where the hash is a one-way digest of the password — the plaintext is never stored. Web servers read this file to decide who may access a protected URL. On Apache, a .htaccess file (or a <Directory> block) references the .htpasswd file and prompts the browser for a username and password before serving the page. The hash format depends on which algorithm produced it. Apache's htpasswd tool can emit several: bcrypt (lines starting with $2y$) is the strongest and is recommended for Apache, Docker Registry, and Caddy; apr1 (Apache MD5, starting with $apr1$) is the most portable and the safe default for nginx; SHA-1 (starting with {SHA}) is unsalted and considered insecure; crypt (traditional DES) is legacy and truncates at 8 characters; and plain stores the password in cleartext, which should never be used in production. This generator runs entirely in your browser — no username, password, or hash is ever uploaded. If you need a strong password to go with your entry, use our Random Password Generator. To build the Authorization: Basic header by hand, the credential is just base64(user:password), which you can produce with our Base64 Encoder. And once your endpoint is protected, test it from the command line with our cURL Command Builder. ``` # Apache htpasswd CLI equivalents (apache2-utils / httpd-tools) # bcrypt entry, printed to stdout (recommended; -B = bcrypt, -n = no file, -b = password on CLI) htpasswd -Bbn admin 's3cret' # → admin:$2y$10$N9qo8uLOickgx2ZMRZoMye... # apr1 (Apache MD5) entry, portable for nginx — no apache2-utils needed printf "admin:$(openssl passwd -apr1 's3cret')\n" # → admin:$apr1$k3l4Hj9.$qN8... # Append a user to an existing file from the shell htpasswd -B /etc/apache2/.htpasswd alice # Note: nginx delegates bcrypt to the system crypt(); on Alpine/musl or old # glibc that fails — prefer apr1 for nginx to stay portable. ``` #### FAQ **Q: bcrypt vs apr1 — which should I choose?** A: Use bcrypt for Apache, Docker Registry, Caddy, and Traefik — it's a strong, salted, adaptive hash and is the modern standard. Use apr1 (Apache MD5) for nginx, because nginx hands bcrypt off to the system crypt() and that fails on many builds, while apr1 is implemented internally and works everywhere. If you control the runtime and know bcrypt is supported, bcrypt is always the stronger choice; apr1 is about portability, not security. **Q: Does nginx support bcrypt?** A: Only indirectly, and not reliably. nginx doesn't hash passwords itself — for $2y$ entries it delegates verification to the C library's crypt() function, so support depends entirely on your libc. Alpine's musl and older glibc builds don't include the blowfish (bcrypt) scheme, so authentication silently fails. For portable nginx setups, use the apr1 format instead, which nginx verifies internally on every platform. **Q: How do I fix the nginx error `crypt_r() failed (22: Invalid argument)`?** A: That error means nginx tried to verify a bcrypt ($2y$) hash on a libc that doesn't support the blowfish scheme — typically Alpine/musl or an older glibc. The fix is to regenerate the entry as apr1 (Apache MD5) instead of bcrypt, which nginx verifies internally on any platform. Alternatively, switch to a base image whose libc includes bcrypt support, but apr1 is the simpler, portable solution. **Q: Where should I put the .htpasswd file and what permissions?** A: Store the .htpasswd file outside the web document root so it can never be served as a static file and exposed. A common location is /etc/apache2/.htpasswd or /etc/nginx/.htpasswd. Set permissions to 640 (chmod 640) and make it owned by the user the web server runs as (for example www-data or nginx), so the server can read it but other accounts cannot. **Q: How do I configure Basic Auth in .htaccess / nginx?** A: For Apache, this tool generates a .htaccess block with AuthType Basic, AuthName, AuthUserFile pointing at your .htpasswd path, and Require valid-user. For nginx, it generates a location block with auth_basic "Restricted"; and auth_basic_user_file /path/.htpasswd;. Copy the config block that matches your server, adjust the file path, and reload — the snippets are ready to paste. **Q: Are my passwords uploaded anywhere?** A: No. Every hash is computed entirely in your browser using JavaScript — no username, password, or generated hash is ever sent over the network. You can confirm this by opening your browser's Developer Tools (F12 → Network tab) while generating: there are zero outgoing requests. Nothing is stored or logged on any server, so it's safe to generate real production credentials here. **Q: What's the difference between $2a$, $2b$, and $2y$ in bcrypt?** A: They are version prefixes for the same bcrypt algorithm and produce equivalent hashes; the differences trace back to historical bug fixes in how certain implementations handled high-bit characters and string length. Apache's htpasswd emits $2y$. Modern bcrypt libraries treat $2a$, $2b$, and $2y$ as interchangeable for verification, so a $2y$ entry generated here will validate correctly in Apache, Caddy, Traefik, and Docker Registry. **Q: What bcrypt cost should I use?** A: Cost 12 is the modern default and a good balance of security and speed. The cost is a work factor: each increment doubles the time to compute and verify the hash, which slows down brute-force attacks but also adds latency to every login. Cost 10 is acceptable for low-traffic or low-risk endpoints; 12–14 is recommended for anything sensitive. Avoid going so high that legitimate authentication becomes noticeably slow. **Q: htpasswd vs the Authorization: Basic header — what's the difference?** A: They sit on opposite ends of the same exchange. The .htpasswd file holds the server-side stored hash — a one-way digest the server uses to verify credentials. The Authorization: Basic header is the client-side request credential: the literal base64 of username:password the browser or curl sends on each request. The server base64-decodes the header, then checks the password against the stored hash. One is storage, the other is transport. **Q: I don't have apache2-utils installed — how do I generate an htpasswd entry?** A: You don't need it — this tool generates valid bcrypt, apr1, and SHA-1 entries entirely in your browser. If you prefer the command line, OpenSSL ships on almost every system: run openssl passwd -apr1 to produce an apr1 hash, then prefix it with username: to form the line. On Debian/Ubuntu you can also install the htpasswd binary via apt install apache2-utils, or httpd-tools on RHEL/CentOS. **Q: What do the htpasswd flags -B, -Bbn, -bnB mean?** A: Each letter is an independent flag: -B selects bcrypt, -n prints the result to stdout instead of writing a file, and -b takes the password as a command-line argument (rather than prompting). The order doesn't matter, so -Bbn and -bnB are identical. -Bbn is the common combination for piping a bcrypt entry into a Docker Registry htpasswd file. **Q: Why does Docker Registry require bcrypt?** A: The Docker Registry's htpasswd authentication backend only accepts bcrypt-formatted entries; apr1, SHA-1, and crypt hashes are rejected and login will fail. Generate the entry with htpasswd -Bbn user password (or use the bcrypt option here), mount the file into the registry container, and point REGISTRY_AUTH_HTPASSWD_PATH at it. Always pair this with TLS, since Basic Auth credentials are otherwise readable in transit. **Q: Is Basic Auth secure?** A: Only over HTTPS. HTTP Basic Auth sends credentials as base64(username:password) on every request, and base64 is reversible encoding — not encryption — so anyone who can read the traffic can recover the password instantly. Over TLS the header is encrypted in transit and Basic Auth is acceptable for simple gating. Never use it on plain HTTP, and prefer stronger schemes for high-value applications. --- ### IEEE 754 Floating-Point Converter URL: https://go-tools.org/tools/ieee-754-converter Convert floats to IEEE 754 hex & binary, or decode hex to float — FP16, FP32, FP64 and bfloat16. See the exact stored value, rounding error and bit layout. 100% in your browser. #### What Is IEEE 754 Floating Point? IEEE 754 is the standard that defines how computers store real numbers in binary. Every value is packed into three fields: a sign bit, an exponent (stored with a bias so it can represent both large and tiny magnitudes), and a mantissa holding the significant digits. Almost every CPU, GPU, and programming language uses it, which is why the same rounding surprises appear in JavaScript, Python, C, and SQL alike. The key insight is that binary floating point can only represent numbers of the form m × 2ⁿ. Decimal fractions like 0.1 are infinite repeating fractions in base 2 — 0.000110011001100… — so the format stores the nearest representable neighbor instead. In single precision that neighbor is 0.100000001490116119384765625; in double precision it is 0.1000000000000000055511151231257827021181583404541015625. Neither is 0.1. Every downstream oddity — 0.1 + 0.2 ≠ 0.3, sums that drift, equality checks that fail — follows from this single fact, and this converter makes it visible by printing the exact stored value rather than a rounded-back approximation. The same drift shows up whenever floats round-trip through JSON — inspect the payload with the JSON formatter and the bits here. The standard also reserves bit patterns for special values. An all-ones exponent encodes ±Infinity (mantissa zero) or NaN (mantissa non-zero); an all-zeros exponent encodes signed zero (mantissa zero) or subnormal numbers (mantissa non-zero), which fill the underflow gap next to zero at reduced precision. The four formats this tool covers — binary16/FP16, bfloat16, binary32/FP32, binary64/FP64 — differ only in how many bits they give each field: more exponent bits mean more range, more mantissa bits mean more precision. bfloat16, the machine-learning favorite, is simply FP32 with the bottom 16 mantissa bits cut off: same range, much coarser precision. A useful mental model is that representable floats form a grid on the number line whose spacing — one unit in the last place, or ULP — doubles at every power of two. Near 1.0 a double's grid spacing is about 2.22 × 10⁻¹⁶; near 2⁵³ it is a whole integer, which is why doubles cannot count beyond 2⁵³ reliably. The neighbors panel in this tool shows that grid directly: the previous and next representable values around whatever you type, with the exact gap between them. ``` // Float ↔ hex through the raw IEEE 754 bits (works in any browser / Node.js) const buf = new DataView(new ArrayBuffer(8)); function floatToHex32(value) { buf.setFloat32(0, value); // rounds to nearest even return '0x' + buf.getUint32(0).toString(16).toUpperCase().padStart(8, '0'); } function hexToFloat32(hex) { buf.setUint32(0, parseInt(hex, 16)); return buf.getFloat32(0); } floatToHex32(0.1); // '0x3DCCCCCD' floatToHex32(3.14159); // '0x40490FD0' hexToFloat32('3DCCCCCD'); // 0.10000000149011612 ``` #### FAQ **Q: Why is 0.1 + 0.2 not equal to 0.3?** A: Because 0.1, 0.2 and 0.3 have no exact binary representation — a double stores the nearest representable value instead. 0.1 and 0.2 are each stored slightly high, so their sum lands one bit above the stored 0.3: bit pattern 0x3FD3333333333334 versus 0x3FD3333333333333, which is why 0.1 + 0.2 == 0.3 is false in JavaScript, Python, Java, C, Rust and Go. The double behind 0.1 is exactly 0.1000000000000000055511151231257827021181583404541015625, and the sum stored for 0.1 + 0.2 is 0.3000000000000000444089209850062616169452667236328125 — type 0.1 into this converter to read the stored value digit for digit. **Q: What is IEEE 754?** A: IEEE 754 is the technical standard for binary floating-point arithmetic used by virtually every modern CPU, GPU, and programming language. It defines how a number is packed into bits — one sign bit, an exponent field, and a mantissa (significand) — plus the rounding rules and the special values Infinity, NaN, signed zero, and subnormals. The formats you will meet in practice are binary32 (float, FP32), binary64 (double, FP64), binary16 (half, FP16), and the related bfloat16 truncation used in machine learning. This converter shows all four at the bit level. **Q: What is the difference between FP16 and bfloat16?** A: Both are 16-bit formats, but they split their bits differently. FP16 (IEEE binary16) uses 5 exponent bits and 10 mantissa bits: more precision, but a tiny range — the largest finite value is 65504, so overflow to Infinity is a constant hazard. bfloat16 keeps FP32's 8 exponent bits and only 7 mantissa bits: the full ±3.4 × 10³⁸ range of a float, with much coarser precision. That is why ML training, where gradients can spike far beyond 65504, standardized on bfloat16, while FP16 suits storage and inference where values are tamed. Compare them here: type 0.1 and switch formats — FP16 stores 0.0999755859375 (0x2E66), bfloat16 stores 0.10009765625 (0x3DCD). **Q: What are subnormal (denormal) numbers?** A: When the exponent field is all zeros, IEEE 754 drops the implicit leading 1 and lets the mantissa shrink gradually toward zero — these are subnormal (older term: denormal) numbers. They fill the gap between zero and the smallest normal number, so the difference of two unequal floats can never round to zero (gradual underflow). The cost is reduced precision and, on many CPUs, slower arithmetic. In FP16 the smallest subnormal is 2⁻²⁴ = 0.000000059604644775390625; click the Min subnormal chip in any format to inspect its bit pattern — exponent bits all zero, mantissa 0…001. **Q: Should I use float or double?** A: Default to double (FP64) unless you have a measured reason not to. A double carries about 15–16 significant decimal digits versus roughly 7 for a float, and most languages (JavaScript numbers, Python floats) are double-only anyway. Choose float (FP32) when memory bandwidth or storage dominates — large arrays, GPU pipelines, graphics — and you have confirmed 7 digits are enough. For money, use integers (cents) or a decimal type instead: no binary format stores 0.1 exactly, as the stored-value panel of this tool demonstrates. If you need to inspect integer bases instead, see the number base converter. **Q: How do I convert a float to hex by hand?** A: Take the sign (0 for positive, 1 for negative). Write the absolute value in binary and normalize it to 1.xxx × 2ⁿ. Add the format's bias to n (127 for FP32, 1023 for FP64) and write that in the exponent bits. Drop the leading 1 and keep the next 23 (or 52) bits of the fraction as the mantissa, rounding to nearest even at the cut. Concatenate sign, exponent, mantissa and group each 4 bits into one hex digit. For 3.14159 in FP32 that yields 0 | 10000000 | 10010010000111111010000 → 0x40490FD0 — the same steps with bias 1023 and 52 mantissa bits turn a double to hex — or skip the arithmetic and let this converter do each step visibly. **Q: Is my data uploaded when I use this converter?** A: No. Every conversion runs locally in your browser using plain JavaScript — DataView and BigInt arithmetic, no server round-trip, no third-party libraries. You can open your browser's developer tools, watch the network panel stay silent while you type, or disconnect from the internet entirely and keep converting. The Copy link button encodes the bit pattern into the URL fragment, which is likewise never sent to any server. --- ### Compress Images Online — JPEG, PNG & WebP URL: https://go-tools.org/tools/image-compressor Compress JPEG, PNG, WebP & AVIF up to 80% smaller — in your browser, no upload. Batch 20 images, resize, compare before & after, download as ZIP. Free & private. #### What Is Image Compression? Image compression reduces file size by removing redundant or imperceptible visual data, enabling faster page loads and reduced bandwidth consumption. According to the HTTP Archive Web Almanac, images account for approximately 50% of total page weight on average — making image optimization one of the highest-impact performance improvements available to web developers. As Google's web performance guidance notes, optimizing images is consistently among the top recommendations from Lighthouse and PageSpeed Insights, directly improving Core Web Vitals metrics such as Largest Contentful Paint (LCP). The WebP specification (Google, 2010) demonstrated that modern compression algorithms can reduce image file sizes by 25–35% compared to JPEG at equivalent visual quality, a finding that has since driven widespread adoption of next-generation formats (HTTP Archive, WebP specification). There are two main compression approaches: **Lossy compression** discards some image data to achieve smaller file sizes. JPEG and WebP use lossy compression by default — a quality setting of 75% typically reduces file size by 60–80% with minimal visible difference. The tradeoff is irreversible: once data is discarded, it cannot be recovered from the compressed file. **Lossless compression** reduces file size without discarding any data. PNG uses lossless compression by default — the decompressed image is bit-for-bit identical to the original. The compression ratio is lower (typically 10–30%), but image quality is perfectly preserved. This tool compresses your images entirely in your browser — your images are never uploaded to any server, at any point. For JPEG and WebP files, the quality slider directly controls the lossy compression level through the Canvas API. PNG files come back as PNG: because the Canvas API cannot encode a lossy PNG, the tool compresses them with palette quantization instead, which trims the number of distinct colors and keeps transparency intact, including soft anti-aliased edges. If you want a different container, the output format selector lets you request PNG or WebP for any input. Every compression operation stays on your device, giving you the performance gains without the privacy cost. For embedding small compressed images directly in HTML or CSS, you can Base64-encode the output to create data URIs — a common technique for icons and logos under 5 KB. For a deeper comparison of browser-based vs Node.js compression solutions — including Squoosh, Sharp, and Imagemin — read our image compression guide. ``` // Compress a JPEG image in the browser using the Canvas API async function compressImage(file, quality = 0.75) { const img = await createImageBitmap(file); // decode the image const canvas = document.createElement('canvas'); canvas.width = img.width; canvas.height = img.height; canvas.getContext('2d').drawImage(img, 0, 0); // quality: 0.0 (smallest file) → 1.0 (original quality) return new Promise((resolve) => canvas.toBlob(resolve, 'image/jpeg', quality) ); } // file comes from an or drag-and-drop const blob = await compressImage(file, 0.75); console.log(`Original: ${file.size} bytes`); console.log(`Compressed: ${blob.size} bytes`); // → Original: 2100000 bytes // → Compressed: 672000 bytes (~68% reduction) ``` #### FAQ **Q: Is it safe to compress images online?** A: Yes — this tool is completely safe because it processes images entirely in your browser. Your images are never uploaded to any server. The compression uses the browser's built-in Canvas API, and all data stays on your device. You can verify this by opening your browser's Network tab in Developer Tools — you will see zero network requests during compression. When you close or refresh the page, all image data is cleared from memory. **Q: What is the difference between lossy and lossless compression?** A: **Lossy compression** permanently removes some image data to achieve smaller file sizes. JPEG and WebP use lossy compression — a quality setting of 75 typically reduces file size by 60–80% with minimal visible difference, but the removed data cannot be recovered. **Lossless compression** reduces file size without removing any data. The decompressed image is bit-for-bit identical to the original. PNG uses lossless compression. The tradeoff is that lossless compression achieves smaller reductions (typically 10–30%). For web use, lossy compression at quality 75–85 is almost always the right choice — the file size savings are dramatic and the quality difference is imperceptible to most viewers. **Q: Do my PNG files stay PNG?** A: Yes. A PNG you upload comes back as a PNG. The browser's Canvas API cannot encode a lossy PNG, so rather than change the format, this tool quantizes the image to a color palette — it reduces the number of distinct colors and writes a real PNG back out, with transparency preserved, including soft anti-aliased edges. If you do want a WebP, pick **WebP** in the output format selector above the file list. **Same as input** is the default and leaves every file in its own format; **PNG** forces PNG output even for JPEG or WebP input. At quality 100 the PNG is re-encoded losslessly, so no pixels change and the size reduction depends entirely on how efficiently your original was already encoded — it can be zero. Whenever the result is not smaller than the file you uploaded, the tool discards it and keeps your original. **Q: What quality setting should I use?** A: It depends on your use case: - **Quality 85–95**: Visually indistinguishable from the original. Use for professional photography, portfolio sites, or anywhere image quality is critical. Typical reduction: 30–50%. - **Quality 70–85**: Excellent quality with significant size savings. The recommended range for most web use. Typical reduction: 50–75%. - **Quality 50–70**: Good quality with aggressive compression. Suitable for thumbnails, social media, and images viewed at small sizes. Typical reduction: 70–85%. - **Quality below 50**: Noticeable artifacts. Only use when file size is more important than quality (e.g., email constraints, very low bandwidth). Use the Compare button to find the lowest quality that looks acceptable for your specific image. **Q: Can I compress images without losing quality?** A: Technically, yes — set the quality slider to 100 for lossless compression. However, the file size reduction will be minimal (0–10% for most images) because lossless compression can only remove redundant encoding data, not image data. In practice, quality 80–85 is effectively "no visible quality loss" for most images. The human eye cannot distinguish between quality 85 and quality 100 in typical viewing conditions. The Compare slider lets you verify this for your specific image. For maximum file size reduction without visible quality loss, start at quality 75 and use the Compare button to check. If you see artifacts, increase the quality in increments of 5 until the result looks acceptable. **Q: How many images can I compress at once?** A: You can compress up to 20 images in a single batch. Each image can be up to 10MB in size. All processing happens in your browser, so performance depends on your device's CPU and available memory. For large batches of high-resolution images, compression may take a few seconds. The tool processes all images and shows a per-file progress indicator and the total space saved. **Q: What happens if the compressed file is larger than the original?** A: This can happen with images that are already well-optimized, or when compressing at very high quality settings (90–100). The tool then keeps your original file and shows "0% saved", with a tooltip explaining why nothing was replaced. If this happens, the original image was likely compressed with an advanced encoder (like mozjpeg, cjpeg, or pngquant) that is more efficient than the browser's built-in encoder. In this case, your original file is already optimally compressed — no further action is needed. **Q: Does compression change my image dimensions?** A: By default no — pixel dimensions are preserved. A 4000×3000 image stays 4000×3000 after compression and only the file size changes. If you want to resize, set a max width in pixels and keep the **Keep aspect ratio** checkbox enabled. The image is downscaled (never upscaled) and height is derived from the original aspect ratio. Resizing and compression compound — a 4000 px photo dropped to 1600 px max width before quality 75 compression often shrinks 5–10× total. **Q: What image formats are supported?** A: This tool supports four formats: - **JPEG** (.jpg, .jpeg): The most common format for photographs. Supports lossy compression with the quality slider. - **PNG** (.png): Best for graphics with transparency. Compressed by palette quantization and saved as PNG, with transparency preserved. - **WebP** (.webp): Modern format with the best compression efficiency. Supports both lossy compression and transparency. - **AVIF** (.avif): Next-generation format with the best compression ratios in 2026. AVIF input is supported on every modern browser; AVIF encode (re-saving as AVIF) requires Chrome 85+ — on browsers without AVIF encode, AVIF input is transparently re-encoded as WebP. Other formats (GIF, SVG, HEIC, TIFF) are not currently supported. **Q: How does this compare to TinyPNG or Squoosh?** A: The main difference is **privacy**: this tool processes images entirely in your browser — your files never leave your device. TinyPNG uploads images to their servers for processing. **TinyPNG** uses server-side compression with advanced algorithms (pngquant for PNG, mozjpeg for JPEG) that can produce slightly smaller files than browser-based compression. However, your images must be uploaded to their servers, and the free tier limits you to 20 images per day at 5MB each. **Squoosh** (by Google) also processes images in the browser using WebAssembly, offering more codecs and finer control. This tool is simpler and faster for the common case of batch-compressing JPEG, PNG, and WebP files with a single quality setting. Choose this tool when privacy is a priority, you need quick batch compression, and you don't need advanced codec options. --- ### Image to Base64 Converter URL: https://go-tools.org/tools/image-to-base64 Convert images to Base64 data URIs in your browser — PNG, JPG, GIF, WebP, SVG, ICO. Copy HTML, CSS, Markdown & JSON, with the exact size increase. 100% private, no upload. #### What is a Base64 Image (Data URI)? A Base64 image is a picture whose binary bytes have been re-encoded as a string of printable ASCII characters using the Base64 alphabet (A–Z, a–z, 0–9, + and /). Wrapped in the data: URI scheme — data:image/png;base64,iVBORw0KGgo… — that string can appear anywhere a URL is expected: an HTML img src, a CSS background-image, an email body, or a field inside a JSON payload. The browser decodes it on the fly and displays the image with no separate network request. This is why Base64 images are sometimes called "inline" or "embedded" images. The encoding exists for a simple reason: many systems were built to carry text, not arbitrary binary. HTML, JSON, email headers, and URLs all expect characters, and raw image bytes would include control codes and delimiters that break them. Base64 maps every 3 binary bytes onto 4 safe text characters, guaranteeing the data survives transport intact. The cost is size: the text representation is about 33% larger than the original binary, and it cannot be cached independently of the document that contains it. That trade-off defines when Base64 images make sense. For a tiny icon used in one stylesheet, inlining removes a round trip and the size penalty is negligible — a clear win. For a 200 KB hero photo reused across every page, inlining bloats every page, defeats the browser cache, and costs CPU to decode on each load — a clear loss. The modern, HTTP/2-era guidance is to inline only small, stable assets and serve everything else as ordinary cached files. This tool surfaces the exact numbers for your image and a traffic-light recommendation so the decision is grounded in data, not folklore. The reverse operation — turning a Base64 string back into a viewable, downloadable image — is equally useful when you are debugging a data URI from a stylesheet, inspecting an API response, or recovering an asset embedded in a config file. Switch to the Base64 → Image tab or open the dedicated Base64 to Image decoder. ``` pixel /* CSS */ .badge { background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=="); } ![pixel](data:image/png;base64,iVBORw0KGgo…) // JSON { "mime": "image/png", "data": "iVBORw0KGgo…" } ``` #### FAQ **Q: What does this Image to Base64 converter do?** A: It reads an image you drop, paste, or select and encodes its bytes as a Base64 string — entirely inside your browser. You get the raw Base64, a ready-to-use data URI (data:image/png;base64,…), and copy-paste snippets for HTML , CSS background-image, Markdown, and JSON. A metadata bar reports the original file size, the encoded size, the exact percentage increase (Base64 is about 33% larger), the pixel dimensions, and the MIME type. Nothing is uploaded: the encoding runs locally via the FileReader API, so the same tool is safe for screenshots, internal assets, and unreleased artwork. To go the other way, use the Base64 → Image tab or our Base64 to Image decoder. **Q: Are my images uploaded to a server?** A: No. Every step happens client-side in your browser using the FileReader API and JavaScript string encoding. Your image is never transmitted, never stored, and never logged. You can confirm this by opening your browser's Network tab — encoding an image triggers zero network requests. This makes the tool safe for sensitive material: product screenshots before launch, internal diagrams, customer assets, and anything under NDA. There is no file-size cap imposed by an upload limit, only the practical limit of how large a Base64 string your browser and target system can comfortably handle. **Q: How much bigger does Base64 make an image?** A: Base64 encodes every 3 bytes of binary data as 4 ASCII characters, so the encoded string is roughly 33% larger than the original file (plus a few bytes of padding and the data: prefix). A 9 KB PNG becomes about 12 KB of text. This overhead is the single most important reason not to Base64 large images: you ship more bytes and, because the string is embedded in your HTML or CSS, those bytes are re-downloaded every time the containing file changes and cannot be cached independently. The tool shows the exact increase for your specific file in the metadata bar so you can make the call with real numbers. **Q: When should I use a Base64 image instead of a normal file?** A: Base64 (as a data URI) is a good fit for small, rarely-changing assets where avoiding a separate HTTP request matters more than caching: tiny icons and logos inlined in CSS, images embedded in HTML email (many clients block external images but render data URIs), single-file widgets or bookmarklets that must be self-contained, SVG sprites, and images stored inside JSON/API payloads. A practical rule of thumb: under about 2 KB and used on one or two pages, inlining usually wins. The advice badge in this tool encodes exactly that heuristic — green under 2 KB, amber up to 10 KB, red above. **Q: When should I NOT use Base64 images?** A: Avoid Base64 for anything large or reused across pages. Four concrete reasons: (1) the ~33% size increase means more bytes over the wire; (2) an inlined image cannot be cached on its own — it is re-downloaded with every change to the HTML or CSS that contains it, and repeated on every page that embeds it; (3) decoding a large data URI costs CPU and battery, which is noticeable on mobile; and (4) you lose responsive images (srcset/sizes) and lazy-loading. Since HTTP/2 multiplexes many small requests cheaply, the original reason to inline — cutting request count — rarely applies anymore. For photos, hero images, or anything over ~10 KB, a normal cached file almost always loads faster. If the goal is a smaller file, run it through our Image Compressor first. **Q: How do I use the Base64 output in HTML and CSS?** A: For HTML, switch to the HTML tab and paste the generated element: …. For CSS, use the CSS tab, which wraps the data URI in background-image: url("data:image/png;base64,…"). Both work anywhere a URL is accepted — img src, CSS background, mask-image, even favicon link tags. The data: scheme is supported by every modern browser. One caveat: very long data URIs in inline HTML can hurt readability and, in CSS, bloat the stylesheet that ships to every visitor, so reserve inlining for genuinely small assets. **Q: Which image formats are supported?** A: PNG, JPEG/JPG, GIF (including animated), WebP, SVG, ICO, and BMP are all supported, plus AVIF where the browser can decode it. Because the tool encodes the raw bytes rather than re-rendering the image, animated GIFs stay animated, transparent PNGs keep their alpha channel, and SVGs remain fully scalable. The MIME type is read from the file itself and, when you paste raw Base64 into the decoder, inferred from the data's magic bytes. There is no format conversion during encoding — the output represents exactly the file you provided. **Q: Why is SVG a special case?** A: SVG is XML text, not binary, so Base64 actually makes it larger and harder to read for no benefit. For inlining SVG in CSS or HTML, URL-encoding the markup (percent-encoding a handful of characters like #, <, >, and quotes) is usually smaller than Base64 and keeps the source legible and gzip-friendly. This tool still offers Base64 SVG output because some pipelines require it, but if you are hand-optimizing CSS, prefer a URL-encoded data URI. Our URL Encoder/Decoder helps with that approach. **Q: Is Base64 the same as encryption?** A: No. Base64 is an encoding, not encryption — it is fully reversible by anyone with no key required. It exists to represent binary data using a safe set of printable ASCII characters so the data survives transport through systems that only handle text (HTML, JSON, email headers, URLs). Anyone can decode a Base64 string back to the original image in seconds, including with the Base64 → Image tab here. Never treat Base64 as a way to hide or protect sensitive image content; it provides zero confidentiality. **Q: Can I embed a Base64 image in an email?** A: Yes, and it is one of the better uses of the technique. Many email clients block externally hosted images by default for privacy, which breaks layouts that rely on remote logos. Embedding small images as data URIs ensures they render immediately without a server fetch. The trade-offs: some older clients (notably certain versions of Outlook) have spotty data-URI support, and large embeds inflate the message size that every recipient downloads. Keep embedded images small — logos and icons, not photographs — and test across your target clients. **Q: Why does my Base64 image not render?** A: The most common causes: a missing or wrong MIME type in the data: prefix (use image/png for PNG, image/jpeg for JPG, image/svg+xml for SVG), whitespace or line breaks accidentally inserted into the string, a truncated copy that dropped the trailing padding (= or ==), or pasting only the raw Base64 without the data:…;base64, prefix where a URL is expected. The decoder in this tool is tolerant — it strips whitespace, accepts input with or without the prefix, and infers the MIME from the image's magic bytes — so pasting your string into the Base64 → Image tab is the fastest way to confirm whether the data itself is valid. --- ### JavaScript Formatter & Minifier URL: https://go-tools.org/tools/js-formatter Format, beautify and minify JavaScript instantly in your browser. Clean up messy code or compress it with Terser to ship — free, private, and your code never leaves your device. #### What is JavaScript Formatting? JavaScript formatting (also called beautifying or pretty-printing) rewrites code with consistent indentation, spacing and line breaks so it's easy to read and review. The code behaves identically before and after — only whitespace changes. Minifying does the reverse: it shortens names, drops comments and collapses whitespace to produce the smallest bundle that runs the same. This tool does both, entirely in your browser. #### FAQ **Q: How do I format JavaScript online?** A: Paste your code into the input box and click Format. The tool reindents it with consistent spacing and line breaks, then lets you copy it. Everything runs locally in your browser — nothing is uploaded. **Q: How do I minify JavaScript?** A: Paste your code and click Minify. The tool runs Terser to rename locals, remove comments and collapse whitespace into the smallest equivalent script, and shows how many bytes you saved. **Q: What is the difference between formatting and minifying JavaScript?** A: Formatting (beautifying) adds indentation and spacing to make code readable. Minifying shortens names and strips whitespace and comments to shrink the bundle for faster loading. Both run with the same behavior as the original. **Q: Does minifying change what my code does?** A: No. Terser preserves behavior — it only renames local variables and removes whitespace, comments and unreachable code. The minified script runs the same as the source. **Q: Is my code safe with this tool?** A: Yes. All formatting and minifying happen locally in your browser using JavaScript — your code is never sent to any server, logged, or stored. That makes it safe for proprietary or unreleased code, unlike server-side tools that receive a copy of everything you paste. **Q: Why did minify report an error?** A: Terser needs syntactically valid JavaScript. If you paste an incomplete snippet or TypeScript/JSX, parsing fails — format works on a best-effort basis, but minification requires valid JS. Fix the syntax or transpile first, then try again. **Q: What indentation should I use for JavaScript?** A: Two spaces is the most common default in modern JavaScript and keeps diffs compact; four spaces and tabs are also widely used. Pick one and apply it consistently — this tool supports all three when beautifying. --- ### JSON Diff & Compare URL: https://go-tools.org/tools/json-diff Compare two JSON files instantly in your browser. Side-by-side highlighting, RFC 6902 JSON Patch output, ignore noisy fields like timestamps and IDs. 100% private, no upload. #### What is JSON Diff? JSON Diff is a structural comparison of two JSON documents that respects JSON's data model — keys are unordered, types are strict, and arrays may be ordered or keyed. Unlike a text diff (which compares lines and reports key reorders or whitespace as differences), a JSON diff produces semantically meaningful results. The canonical machine-readable form is JSON Patch (RFC 6902), an ordered ops array (add, remove, replace, move, copy, test) that transforms one document into another. Paths use JSON Pointer (RFC 6901). Closely related: JSON Merge Patch (RFC 7396) — simpler but cannot distinguish 'remove key' from 'set key to null'. This tool outputs RFC 6902. Deep equality on JSON in JavaScript is harder than it looks. JSON.stringify(a) === JSON.stringify(b) fails on key reorder, misleads on -0 vs 0 (both stringify to "0"). A correct diff must walk both trees in parallel using key-set union, distinguish null from missing via the 'in' operator, and decide what 'equal' means for numbers (Object.is by default, epsilon for tolerance). This tool runs entirely in your browser. Inputs never leave your machine. Safe for API responses, internal schemas, and proprietary configs. Working with adjacent JSON tools? Format with JSON Formatter; convert with JSON to YAML, YAML to JSON, JSON to CSV, and CSV to JSON. See our guide for advanced timestamp/ID filtering patterns. Need to validate the structure (not just diff it)? See our JSON Schema validation guide. ``` // Two JSON documents that look different but are semantically equal const a = '{"a":1,"b":2}'; const b = '{"b":2,"a":1}'; // Naive comparison — wrong JSON.stringify(JSON.parse(a)) === JSON.stringify(JSON.parse(b)); // → false (key order differs) // JSON Diff (this tool) — correct: key order is irrelevant // → 0 differences // JSON Patch (RFC 6902) for { "a": 1 } → { "a": 2 } // [{ "op": "replace", "path": "/a", "value": 2 }] ``` #### FAQ **Q: Why does my diff show everything changed when I only changed one field?** A: Three usual suspects: (1) different key order — JSON Diff treats key order as equivalent, but text diff tools don't; (2) timestamps/UUIDs/auto-IDs that mutate on every request — add them to Ignore paths; (3) array order, when by-index comparison shouldn't apply — switch Array mode to 'Match by key'. **Q: How do I ignore timestamps and IDs in JSON diff?** A: Use the Ignore paths input above. Click the 'Timestamps' or 'IDs' preset for one-click filtering of /createdAt, /updatedAt, /*Id, /*At, /requestId. You can also paste your own Extended JSON Pointer patterns — one per line — for advanced filtering. **Q: What's the difference between JSON Patch and a visual diff?** A: Visual (side-by-side) diff is for humans — review changes by eye. JSON Patch (RFC 6902) is for machines — a structured ops array (add/remove/replace) you can apply with fast-json-patch or rfc6902 npm packages. Same diff, two outputs. **Q: Does JSON diff treat null and missing keys the same?** A: No. {"a":null} and {} differ — the first has an explicit null, the second has no key. Real systems behave differently for the two; this tool keeps them distinct. **Q: How are arrays compared — by index or by key?** A: By index (Sequential) by default. Switch to 'Match by key' and provide a key field (commonly id) to align elements regardless of order. Use this for K8s envs, package-lock entries, or any list that's logically a set. **Q: Can I export the diff as RFC 6902 JSON Patch?** A: Yes. The JSON Patch tab outputs a valid RFC 6902 ops array. If Ignore paths are set, the patch is filtered (the tab shows '(filtered: excludes N ignored paths)') and will not round-trip the originals exactly. Clear Ignore paths for a complete patch. **Q: Is JSON Patch the same as JSON Merge Patch (RFC 7396)?** A: No. RFC 6902 (JSON Patch) is an ordered ops array — explicit and reversible. RFC 7396 (Merge Patch) is a single merge document — simpler but cannot represent removal differently from setting null. JSON Diff outputs RFC 6902. **Q: How do I compare two large JSON files (>10 MB)?** A: Files over ~5 MB exceed practical browser memory. Live mode disables at 200 KB; for multi-megabyte files, use command-line jq or fast-json-patch in Node. **Q: Does the tool send my JSON to a server?** A: No. All comparison runs locally in your browser. Your JSON inputs are never written to disk, network, localStorage, or URL parameters. Only your preferences (ignore paths, array mode, numeric tolerance, active tab) are stored in localStorage so they persist across sessions. Refreshing the page clears the JSON inputs. The Share Link button writes only your config (Array mode, Ignore paths) — never your data. **Q: Why does 42 differ from "42" in the diff?** A: JSON Diff is type-strict: number 42 and string "42" are not equal. This catches backend serialization drift (some endpoints return numeric IDs, others return strings) — the diff labels it as 'type' modification. **Q: Can I diff JSON with comments (JSONC) or trailing commas?** A: Standard JSON (RFC 8259) does not allow comments or trailing commas. This tool uses native JSON.parse, which rejects both. Strip comments first using JSON Formatter. **Q: How do I compare nested arrays of objects by a key like id?** A: Set Array mode to 'Match by key' and enter id. Diff aligns by id values. v1 applies the same key field at every array depth; inner arrays without that field fall back to sequential and emit a warning chip. **Q: Does the diff handle floating-point precision (0.1 + 0.2)?** A: Yes, with Numeric tolerance. Default is 0 with Object.is — so -0 vs +0 are flagged. Set tolerance to a small epsilon (e.g. 1e-9) and 0.1 + 0.2 will compare equal to 0.3. Tolerance applies only to numeric leaves. --- ### JSON Escape URL: https://go-tools.org/tools/json-escape Escape any text or JSON into a valid JSON string literal in your browser. Handles quotes, newlines, tabs, Unicode, and slashes. 100% private, no upload, instant. #### What is JSON Escaping and When Do You Need It? JSON escaping is the process of converting a raw string into a form that is safe to embed inside a JSON document. JSON has a small set of characters that carry structural meaning — the double quote delimits strings, the backslash starts an escape sequence — plus control characters (newlines, tabs) that are not allowed to appear literally inside a string. Escaping replaces each of these with a safe two-character sequence (\", \\, \n, \t) or a \uXXXX Unicode escape, so the resulting string parses cleanly anywhere. You reach for JSON escaping more often than you might think. The most common case is JSON-in-JSON: a webhook envelope, a message-queue payload, or an audit log stores a request body as a string field, which means the inner JSON must be escaped before it can be assigned. Another is hand-authoring JSON config: pasting a multi-line shell script, SQL query, or code snippet into a single JSON value requires turning every newline into \n. A third is building REST request bodies by hand in tools like curl, where a quoted JSON string must be escaped to survive the shell and the HTTP layer. This tool has three differentiators over a naive escaper. First, it is built on the exact JSON specification rules — the same logic a compliant serializer uses — so output round-trips losslessly: escape here, parse anywhere, get your bytes back. Second, the optional ASCII-safe mode converts every non-ASCII character (including astral emoji, handled as surrogate pairs) to \uXXXX for systems that cannot be trusted with UTF-8. Third, everything runs 100% in your browser — your payloads, which often contain PII, tokens, and secrets, never touch a server. To reverse the process, use our JSON Unescape tool; to validate JSON first, see the JSON Formatter. ``` // Input text She said "hi" then left. // Escaped (Wrap on) — identical to JSON.stringify(input) "She said \"hi\"\nthen left." // Escaped (Wrap off) — just the body, for hand-built JSON She said \"hi\"\nthen left. // JSON-in-JSON {"a":1} -> "{\"a\":1}" -> {"payload": "{\"a\":1}"} ``` #### FAQ **Q: What does this JSON escape tool do?** A: It converts any text — a JSON object, a code snippet, a log line, or plain prose — into a valid JSON string literal, entirely in your browser. Special characters that would break a JSON document are escaped: double quotes become \", backslashes become \\, newlines become \n, tabs become \t, carriage returns become \r, and other control characters become \uXXXX. The result is a string you can safely paste as a value inside a JSON document, a REST request body, a configuration file, or a database column. Nothing is uploaded — the conversion runs 100% client-side, so it is safe for payloads containing PII, secrets, or internal data. **Q: What is the difference between JSON escape and JSON stringify?** A: They describe the same core operation from two angles. JSON.stringify() in JavaScript takes a value and produces its JSON text representation; when the value is a string, that means wrapping it in double quotes and escaping the special characters inside — which is exactly JSON escaping. This tool does precisely that: with Wrap in double quotes on, the output equals JSON.stringify(yourText); with it off, you get the escaped body without the surrounding quotes, which is what you need when you are building the JSON by hand and already typed the quotes. So if you searched for json stringify online, this is the tool — it gives you both the quoted and unquoted forms. **Q: Is my data uploaded anywhere?** A: No. All escaping runs entirely in your browser using JavaScript — your text is never transmitted, stored, logged, or analyzed on any server. This makes the tool safe for API payloads with PII, authentication tokens, internal configuration, and production secrets. You can verify it in your browser's Network tab: typing or pasting triggers zero network requests. There are no cookies for your input and no third-party analytics that capture what you paste. **Q: When do I need the \uXXXX (escape non-ASCII) option?** A: JSON allows raw UTF-8, so by default an é stays an é and an emoji stays an emoji — perfectly valid and more readable. Turn on Escape non-ASCII only when a downstream system cannot be trusted with UTF-8: old SOAP/XML gateways, some logging pipelines, email headers, or source files that must stay pure ASCII. With it on, every character above U+007F becomes a \uXXXX sequence (astral characters like emoji become a surrogate pair, e.g. 😀 → \ud83d\ude00). The escaped output is byte-for-byte ASCII and decodes back to the original Unicode in any compliant JSON parser. **Q: How do I embed a JSON object inside another JSON string (JSON-in-JSON)?** A: Paste the inner JSON into the input, keep Wrap in double quotes on, and copy the result — it is now a single escaped string you can assign to a key in the outer document. For example {"a":1} becomes "{\"a\":1}", which you place after a colon: {"payload": "{\"a\":1}"}. This double-encoding is common in webhook envelopes, message-queue payloads, and audit logs that store a request body as a string. To reverse it and read the inner object, use our JSON Unescape tool. **Q: What does the Escape forward slash (\/) option do?** A: The forward slash / is a normal character in JSON and does not require escaping, so it is left alone by default. The option exists for one specific case: embedding JSON inside an HTML <script> tag, where the sequence </script> would prematurely close the tag. Escaping / to \/ turns </script> into <\/script>, which is still valid JSON but no longer a tag terminator. Enable it only when you are inlining JSON into HTML; for every other use, leave it off for cleaner output. **Q: Does it handle newlines, tabs, and control characters correctly?** A: Yes. The tool is built on the JSON specification's exact escaping rules: newline → \n, carriage return → \r, tab → \t, backspace → \b, form feed → \f, double quote → \", backslash → \\, and any remaining control character below U+0020 → \uXXXX. This is identical to what a compliant JSON serializer produces, so the output round-trips losslessly: escape it here, parse it anywhere, and you get your original text back byte for byte. --- ### JSON Formatter & Validator URL: https://go-tools.org/tools/json-formatter Format, validate and beautify JSON instantly in your browser. Free online tool with syntax validation, error detection, minify and one-click copy. 100% private. #### What is JSON? JSON (JavaScript Object Notation) is a lightweight, text-based data interchange format that is easy for humans to read and write, and easy for machines to parse and generate. Standardized as RFC 8259 and ECMA-404, JSON has become the universal standard for data exchange across virtually all programming languages, APIs, and web services. As Douglas Crockford, the creator of JSON, wrote on json.org: "JSON's design goals were for it to be minimal, portable, textual, and a subset of JavaScript." This deliberate simplicity is exactly why JSON won out over XML and became the lingua franca of the modern web. JSON supports six data types: strings (in double quotes), numbers, booleans (true/false), null, arrays (ordered lists), and objects (key-value pairs) (RFC 8259). Its simplicity and readability have made it the preferred format over XML for most modern web applications, REST APIs, and configuration files. JSON is the most popular data format for APIs, used by 86% of developers (Postman State of API Report 2023). A JSON formatter transforms raw or minified JSON into a well-structured, indented format that makes the data hierarchy immediately visible. This is essential for debugging API responses, inspecting configuration files, and understanding complex nested data structures. Unlike XML, JSON does not support comments, attributes, or namespaces — it focuses purely on data representation (ECMA-404). This tool runs entirely in your browser — your JSON data never leaves your device. Unlike server-based formatters, there are no uploads, no logging, and no data retention. Safe to use with API keys, production configs, and proprietary data. JSON is frequently used with other developer tools. When debugging APIs, you may need to decode Base64-encoded JSON payloads (such as JWT tokens), or generate UUIDs for use as unique identifiers within JSON data structures. Working with JSON5 or JSONC config files? See our JSON5 and JSONC formatting guide for syntax differences, tooling support, and best practices. For a deep dive on YAML's Norway problem and JSON ↔ YAML conversion, read our YAML Norway problem & JSON-YAML differences guide, or convert directly with JSON to YAML and YAML to JSON. Need to compare two JSON documents and find what changed? Try our JSON Diff. To move tabular JSON into a spreadsheet or import a CSV export back into JSON, use JSON to CSV and CSV to JSON. ``` // Format (pretty-print) JSON with 2-space indentation const raw = '{"name":"Alice","age":30,"active":true}'; const parsed = JSON.parse(raw); // parse string → object const formatted = JSON.stringify(parsed, null, 2); console.log(formatted); // → { // "name": "Alice", // "age": 30, // "active": true // } // Minify JSON (strip all whitespace) const minified = JSON.stringify(parsed); console.log(minified); // → '{"name":"Alice","age":30,"active":true}' ``` #### FAQ **Q: How do I format JSON online?** A: Paste your raw or minified JSON into the input field above and click "Format JSON." The tool instantly parses your data, validates the syntax, and displays a properly indented version with 2-space indentation. You can then copy the result to your clipboard with one click. Everything runs locally in your browser — no data is sent to any server. **Q: How do I validate JSON?** A: Paste your JSON into the input field and click "Format JSON." If the JSON contains syntax errors, the tool displays a detailed error message showing what went wrong and where. If the JSON is valid, it will be formatted and displayed in the output area. This tool validates against RFC 8259, the current JSON standard. **Q: How do I minify JSON?** A: Paste your JSON into the input field and click "Minify JSON." The tool removes all unnecessary whitespace, line breaks, and indentation to produce the most compact representation. Minified JSON is ideal for API responses, configuration files in production, and anywhere file size or bandwidth matters. **Q: Is my JSON data safe when using this tool?** A: Yes, completely. All processing happens locally in your browser using JavaScript's native JSON.parse() and JSON.stringify() — your data never leaves your device. There are no server uploads, no cookies, no analytics tracking on your input, and no data storage of any kind. This makes it safe to use with API keys, credentials, and proprietary data. **Q: How do I fix "Unexpected token" errors in JSON?** A: An "Unexpected token" error means the JSON parser found a character that doesn't belong at that position. The most common causes are: a missing comma between elements ({"name": "Alice" "age": 30}), a trailing comma after the last element ({"name": "Alice",}), or extra characters after the JSON ends. Paste your JSON into this tool to see the exact error location, then check the characters around that position. **Q: Why does my JSON have a "trailing comma" error?** A: JSON does not allow a comma after the last element in an object or array. This is one of the most common errors because JavaScript and many other languages permit trailing commas. For example, {"name": "Alice", "age": 30,} is invalid JSON — remove the comma after 30 to fix it. If you frequently copy data from JavaScript code, always check for trailing commas before using it as JSON. **Q: Can I use single quotes in JSON?** A: No. JSON requires double quotes for all strings and property keys. Single quotes are valid in JavaScript and Python, but they are not part of the JSON specification (RFC 8259). For example, {'name': 'Alice'} is invalid — it must be {"name": "Alice"}. If you have data with single quotes, this tool will report a syntax error and show you the exact position to fix. **Q: Can I add comments to JSON?** A: No, standard JSON does not support comments of any kind — no //, /* */, or # syntax. This was an intentional design decision to keep JSON simple and parseable. If you need comments in configuration files, consider JSONC (JSON with Comments, used by VS Code and TypeScript), JSON5, or YAML. To use commented files as standard JSON, strip the comments before parsing. **Q: Why is my JSON not parsing correctly?** A: The most common reasons JSON fails to parse are: (1) trailing commas after the last element, (2) single quotes instead of double quotes, (3) unquoted property keys, (4) comments in the data, (5) missing or extra brackets/braces, (6) unescaped special characters like backslashes or newlines inside strings. Paste your JSON into this tool — it will pinpoint the exact error type and location so you can fix it quickly. **Q: What is the difference between JSON and YAML?** A: Both JSON and YAML are data serialization formats, but they differ in design philosophy. JSON uses braces, brackets, and double quotes with a strict syntax — making it ideal for machine parsing and APIs. YAML uses indentation and minimal punctuation — making it more human-readable and popular for configuration files (Docker Compose, Kubernetes, GitHub Actions). JSON is a subset of YAML, so any valid JSON is also valid YAML, but not vice versa. **Q: What is JSON Schema?** A: JSON Schema is a separate standard (not part of JSON itself) that defines the expected structure, types, and constraints of JSON data. For example, you can specify that a field must be a string, a number must be between 1 and 100, or an array must contain at least one element. JSON Schema is widely used for API request/response validation, form generation, and documentation. This tool validates JSON syntax, not JSON Schema — for schema validation, use a dedicated JSON Schema validator. For end-to-end validation patterns in Node, Python, and the browser, see our complete JSON Schema validation guide. **Q: What is the difference between JSON and JSON5?** A: JSON5 is an extension of JSON that adds features developers frequently request: single and double quotes, trailing commas, comments (// and /* */), unquoted keys, multiline strings, and hexadecimal numbers. JSON5 is often used in configuration files where human editing is common. Standard JSON parsers cannot read JSON5 — you need a JSON5 parser. This tool works with standard JSON (RFC 8259) only. **Q: What is the maximum size of a JSON file?** A: The JSON specification itself has no file size limit. Practical limits depend on the parser and environment: browsers typically handle JSON up to 500 MB–1 GB before running into memory issues, while server-side parsers (Node.js, Python, Java) can handle larger files with streaming parsers. This online tool efficiently handles JSON up to about 10 MB. For very large JSON files, consider using command-line tools like jq or streaming parsers. **Q: I have a large API response that's completely minified — what's the fastest way to make it readable for debugging?** A: Paste the minified JSON into this tool and click Format JSON. It will instantly parse and pretty-print the data with 2-space indentation, making nested objects and arrays immediately visible. For very large responses (5-10 MB), this browser-based tool is often faster than VS Code or command-line jq because it uses the browser's native JSON.parse() with zero startup overhead. You can also use the keyboard shortcut Ctrl+V to paste and the result appears instantly. For programmatic formatting, use JSON.stringify(data, null, 2) in JavaScript or python -m json.tool from the command line. **Q: I keep getting JSON parse errors when copying data from my JavaScript code — what am I doing wrong?** A: The most common cause is that JavaScript object literals are not valid JSON. Three key differences trip people up: (1) JavaScript allows single quotes ('name') but JSON requires double quotes ("name"); (2) JavaScript allows trailing commas ({"a": 1,}) but JSON does not; (3) JavaScript allows unquoted keys ({name: "Alice"}) but JSON requires quoted keys ({"name": "Alice"}). Additionally, JavaScript comments (// or /* */) are not valid in JSON. Paste your data into this tool — it will pinpoint the exact error type and position so you can fix it quickly. If you frequently need to convert JS objects to JSON, consider using JSON5 format as an intermediate step. --- ### JSON Schema Validator URL: https://go-tools.org/tools/json-schema-validator Validate JSON against any JSON Schema instantly in your browser. Supports Draft 2020-12, 2019-09, and Draft-07 with path-precise error messages. 100% private — no upload, no account, free. #### What is a JSON Schema Validator? A JSON Schema validator is a program that takes two JSON documents — a data document and a schema document — and reports whether the data conforms to the schema's contract. The schema declares field types, required keys, value ranges, allowed enum values, regex patterns, and structural rules using a fixed vocabulary (type, properties, required, items, enum, oneOf, allOf, $ref, format). The validator walks both documents in parallel and emits zero or more errors, each pinned to a JSON Pointer path inside the data. Validation runs at runtime, at the boundary between untrusted input and your code. TypeScript types vanish at compile time and cannot help with JSON arriving from a webhook, a third-party API, or a user paste — that gap is exactly what JSON Schema fills. Pair it with TypeScript (or Pydantic in Python) and you get compile-time guarantees inside your codebase plus runtime guarantees at the boundary. Draft 2020-12 is the current spec and what you should pick for new projects in 2026. Earlier drafts (2019-09, Draft-07, Draft-06, Draft-04) survive in legacy codebases — Draft-07 is still common in Helm charts, VS Code settings, and older Ajv configs. OpenAPI 3.1 uses Draft 2020-12 natively; OpenAPI 3.0 uses a Draft 4 subset. This tool runs entirely in your browser. Your JSON, your schema, and the validation output never leave your machine — safe for proprietary API contracts and sensitive payloads. Internal $ref pointers resolve automatically; external HTTP refs are intentionally disabled to preserve privacy. Working with adjacent JSON tools? Format the JSON with JSON Formatter before pasting; compare two JSON documents with JSON Diff; convert with JSON to YAML and YAML to JSON. For end-to-end validation in Node, Python, and the browser, see our JSON Schema validation guide. ``` // A 5-line schema that catches three real bugs const schema = { "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "object", "properties": { "id": { "type": "integer", "minimum": 1 }, "email": { "type": "string", "format": "email" }, "age": { "type": "integer", "minimum": 0, "maximum": 150 } }, "required": ["id", "email"], "additionalProperties": false }; // Three bugs the schema catches: const bad = { "id": "42", "age": 200 }; // /id → type: expected integer, got string // /email → required: missing // /age → maximum: 200 > 150 // In Node: new Ajv().compile(schema)(bad) // false; ajv.errors has the paths // In Python: jsonschema.validate(bad, schema) // In the browser: this tool — same errors, same paths, no install ``` #### FAQ **Q: What is JSON Schema validation?** A: JSON Schema validation checks whether a JSON document matches a contract written in JSON Schema syntax. The schema declares field types, required keys, allowed values, and structural rules; the validator walks both documents in parallel and reports any path that violates the contract. It runs at the boundary between untrusted input (API request, webhook, config file, form payload) and your business logic — catching shape errors before they corrupt downstream code. See our complete guide to JSON Schema validation for end-to-end Node, Python, and browser examples. **Q: Which JSON Schema drafts does this validator support?** A: Draft 2020-12 (default and recommended), Draft 2019-09, and Draft-07. The validator auto-detects the draft from the schema's $schema URI when present and falls back to your selection from the dropdown otherwise. For new projects in 2026 use Draft 2020-12 — it is what OpenAPI 3.1 uses natively and what Ajv defaults to. Draft-07 remains common in legacy codebases (older AJV setups, Helm charts, VS Code settings). **Q: How do I validate JSON against a schema?** A: Paste your JSON data into the left panel and your JSON Schema into the right panel. The validator runs instantly as you type — green check means valid, red list means errors. Each error includes a JSON Pointer path (for example /user/email), the failing keyword (type, required, pattern, minimum), and a human-readable message. Click any error to jump to the offending line. No upload, no signup. **Q: What's the difference between JSON Schema validation and JSON syntax validation?** A: JSON syntax validation only confirms the document parses — no extra commas, no missing braces. JSON Formatter handles that. JSON Schema validation runs after parsing and checks whether the parsed structure matches the contract: required fields present, types correct, values within range. You typically run both — format first to confirm parseable, then validate against the schema. **Q: Why is my schema rejecting JSON that looks correct?** A: Five usual suspects: (1) additionalProperties: false — your data has a key the schema didn't declare, often a typo or a new field; (2) type: "integer" vs "number" — JSON Schema treats 1.0 as a number, not an integer; (3) format keywords (email, uri, uuid) reject malformed strings even when they look fine; (4) required at the wrong nesting level — required must appear next to properties, not inside one; (5) the JSON has a string "42" where the schema expects integer 42. The error path will pinpoint which one. **Q: Does this support $ref and remote schema references?** A: Internal $ref pointers (#/$defs/foo, #/properties/bar) work out of the box. Remote $ref to external URLs is intentionally disabled — fetching external schemas would leak your validation activity to third parties and break the privacy model. To validate against a multi-file schema, inline the referenced definitions into a single document using $defs, or run validation in your own CI with Ajv where remote refs are appropriate. **Q: What does additionalProperties: false do?** A: additionalProperties: false rejects any key that isn't declared in properties. It's the single most useful keyword for tightening contracts — without it, schemas are open by default and silently accept misspelled or malicious fields. Always set additionalProperties: false on input contracts (request bodies, config files, queue messages). Leave it true (or omit it) only when the schema is a partial description of a larger document. **Q: How do I validate JSON against a schema in Node.js or Python?** A: Node: install Ajv (npm i ajv ajv-formats), call new Ajv().compile(schema), then validate(data). Python: install jsonschema (pip install jsonschema), call jsonschema.validate(data, schema). For TypeScript, generate types from the schema using json-schema-to-typescript so compile-time and runtime stay in sync. Our JSON Schema validation guide has copy-paste recipes for Node, Python, and browser. **Q: What's the difference between oneOf, anyOf, and allOf?** A: allOf — must match every subschema (intersection, used for composition). anyOf — must match at least one (union, fast-fail). oneOf — must match exactly one (discriminated union, slower but stricter). Use oneOf for discriminated unions like webhook events tagged by type; use anyOf for permissive unions; use allOf to extend a base schema with extra constraints. oneOf is the slowest because it tests every branch — prefer anyOf when exactly-one isn't required. **Q: Does it support OpenAPI schemas?** A: OpenAPI 3.1 uses Draft 2020-12 natively, so any OpenAPI 3.1 schema component pastes in directly. OpenAPI 3.0 uses a Draft 4 subset that's mostly compatible — you may hit edge cases around nullable: true (3.0 syntax) which Draft 2020-12 expresses as type: ["string", "null"]. For full OpenAPI document validation (paths, operations, security), use a dedicated OpenAPI linter like Spectral; this tool focuses on the schema portion. **Q: Why does the validator say my JSON Schema is itself invalid?** A: JSON Schema is a JSON document that must be valid JSON before it can be a valid schema. Common causes: trailing comma in a properties object, single quotes instead of double, $schema set to a non-existent draft URL, or required listed as a string instead of an array. Format the schema in JSON Formatter first to surface syntax issues, then paste it back here for semantic validation. **Q: Does the tool send my JSON or schema to a server?** A: No. All parsing and validation runs locally in your browser. Your JSON, your schema, and the validation results never leave your machine — no upload, no localStorage of inputs, no analytics on what you paste. Safe for proprietary API contracts, internal config files, and sensitive payloads. Only your draft selection persists in localStorage so it survives a refresh; clear browser data to wipe it. **Q: Can I validate JSON Lines (NDJSON) or multiple documents?** A: This tool validates one document per run. For JSON Lines, validate each line individually or use Ajv in Node with a schema and a stream parser like JSONStream. For batch validation of large datasets, prefer the command-line — ajv-cli or check-jsonschema (Python) handle thousands of files per second with a single schema compile. --- ### JSON to CSV Converter URL: https://go-tools.org/tools/json-to-csv Convert JSON to CSV in your browser. RFC 4180, Excel-EU, TSV, Pipe presets. Flatten nested or stringify. 100% private, no upload. #### What is CSV and Why Convert from JSON? CSV (Comma-Separated Values) is the oldest and most widely supported tabular data format in computing — every spreadsheet app, every database, every analytics tool, and most programming languages have first-class CSV support. JSON, by contrast, is the universal format for API responses, configuration, and structured data exchange. Converting between them is one of the most common chores in data engineering: you receive JSON from an API or a NoSQL database, and you need a CSV to load into Excel for analysis, into a Postgres table via COPY, or into a BigQuery / Snowflake warehouse. This tool is built for that conversion path and handles four scenarios that most online converters botch. This tool has four important differentiators compared to typical online converters: **1. RFC 4180 State-Machine Parser.** CSV looks simple but the quoting rules are subtle: a field wrapped in double quotes can contain commas, embedded newlines, and escaped double quotes (doubled, like ""). Naive split-by-comma parsers break on real-world data — addresses with commas, multiline text fields, and quoted values containing quotes. This tool implements a proper state-machine parser following RFC 4180 (the IETF spec for CSV), correctly handling quoted fields, embedded delimiters, embedded line endings, and escaped quotes in every direction. The output is round-trippable through Python's csv module, PostgreSQL COPY, AWS S3 SELECT, and any compliant parser. **2. Flatten One-Way / Stringify Reversible.** Nested JSON is fundamentally incompatible with CSV's flat tabular shape, and most converters silently corrupt data when they hit a nested object or array. This tool gives you an explicit choice: Flatten mode emits dotted keys (customer.address.city) and indexed keys (items.0.sku) for the cleanest spreadsheet layout — readable in Excel but lossy for round-trips. Stringify mode keeps arrays and objects as JSON inside a single cell — uglier but fully round-trippable: CSV → JSON → CSV produces identical data when paired with Infer types on the reverse. Choose based on your goal: analysis in Excel (Flatten) or pipeline round-trips (Stringify). **3. Big-Integer Detection.** JavaScript's Number type uses IEEE 754 double precision and silently rounds integers above 2^53 - 1 (9007199254740991). This bites real-world JSON: Twitter snowflake IDs, Discord IDs, MongoDB Long fields, and Kubernetes resourceVersion are all 64-bit integers that exceed the safe range. Most browser-based JSON tools silently produce wrong numbers without warning. This tool detects big-integer values during parsing, shows a warning banner listing affected fields, and preserves the original digits as strings in the CSV output so Excel and Google Sheets won't truncate them to scientific notation. **4. 100% Browser-Based Privacy.** Your JSON data — which often contains user PII, internal database exports, API keys embedded in payloads, and production secrets — never leaves your browser. No data is sent to any server, no logging, no analytics that capture input. You can verify this in your browser's Network tab. This is the only safe way to handle sensitive data in an online tool. See the reverse direction by clicking Swap or use our companion JSON to YAML Converter when YAML is your target. Need to validate JSON before converting? Try our JSON Formatter. CSV's strengths are universality and simplicity: every tool reads it, parsers are tiny, and the file format is human-readable in any text editor. Its weaknesses are the lack of type information (everything is a string until you tell the parser otherwise), no native nested-structure support, and locale-specific quirks (Excel-EU semicolons, Windows CRLF vs Unix LF). JSON's strengths are exactly the opposite: precise types, native nesting, and a strict spec that parses identically everywhere. The right tool depends on the consumer: human reading a spreadsheet → CSV, machine consuming an API → JSON. This converter handles the bridge in both directions. ``` // Input JSON [ { "id": 1, "name": "Alice", "role": "admin" }, { "id": 2, "name": "Bob", "role": "editor" } ] // Output CSV (RFC 4180 preset: comma + CRLF + no BOM) id,name,role 1,Alice,admin 2,Bob,editor // Same input with Stringify mode + nested data [ { "id": 1, "tags": ["a", "b"] } ] // Becomes id,tags 1,"[""a"",""b""]" ``` #### FAQ **Q: What does this tool do?** A: It converts JSON to CSV directly in your browser, with bidirectional support: click Swap direction to convert CSV back to JSON in the same panel. Paste JSON in the input area and the tool produces CSV output instantly — no upload, no signup, nothing leaves your machine. The output respects your chosen preset (RFC 4180, Excel, TSV, or Pipe) so you can paste straight into Excel, Google Sheets, a database COPY command, or any data pipeline. The tool handles flat arrays of objects, nested structures (via Flatten or Stringify mode), NDJSON line-delimited input, and big-integer values that would otherwise lose precision in spreadsheet apps. **Q: Is my data uploaded anywhere?** A: No. All conversion runs 100% client-side in your browser using JavaScript. Your JSON data is never transmitted, never stored on any server, never logged, and never analyzed. This makes the tool safe for API responses containing PII, internal database exports, MongoDB dumps, and any sensitive data. You can verify this in your browser's Network tab — pasting JSON triggers zero network requests. The tool uses no cookies for input data and no third-party analytics that would capture what you paste. **Q: What's the difference between Flatten and Stringify mode?** A: Flatten mode emits dotted keys for nested objects and indexed keys for nested arrays (customer.address.city, items.0.sku) so each leaf value lives in its own column. This is the most readable layout for analysis in Excel or BigQuery, but it is lossy for round-trips because the dotted-key structure cannot be perfectly reconstructed. Stringify mode keeps arrays and objects as JSON inside a single cell ({"name":"Alice","city":"Seattle"}) — uglier in a spreadsheet, but fully round-trippable: CSV → JSON → CSV produces identical data. Choose Flatten for analysis, Stringify for round-trip safety. Pick before you convert; switching mid-session re-runs the conversion on the current input. **Q: How does it handle big integers like Twitter IDs or Snowflake keys?** A: Big integers (above 2^53 - 1, or 9007199254740991) are detected during JSON parsing and a warning banner appears below the output. The tool preserves the original digits as strings in the CSV so Excel and Google Sheets won't truncate them to scientific notation. This matters because JavaScript's IEEE 754 double-precision float silently rounds integers above 2^53 — for example, 9007199254740993 becomes 9007199254740992. To preserve precision when generating the JSON upstream, store these IDs as strings ("id": "9007199254740993"). The tool will keep them as strings in the CSV without any precision loss. **Q: Why is Excel showing my CSV in one column?** A: European Excel locales (Germany, France, Spain, Italy, etc.) expect a semicolon delimiter because the comma is reserved for decimal separators. When you open a comma-delimited CSV in Excel-EU, every row collapses into column A. Use the Excel preset on this tool — it switches the delimiter to ;, the line ending to CRLF, and adds a UTF-8 BOM so Excel correctly detects encoding and column boundaries. If you are sharing CSVs across regions, the safer option is TSV (Tab delimiter) which Excel handles consistently in every locale. **Q: Does this support NDJSON or JSON Lines?** A: Yes. NDJSON (.ndjson) and JSONL (.jsonl) are line-delimited formats where each line is one valid JSON value. Paste the file contents directly into the input area — the tool auto-detects the format by looking for multiple top-level JSON values separated by newlines, and treats each line as a row in the output CSV. This is the natural shape for streaming logs, API event exports, and many data lake pipelines. NDJSON does not require a wrapping array, so you do not need to manually merge lines into one JSON document. **Q: What is RFC 4180?** A: RFC 4180 is the IETF specification that codified the de facto CSV format in 2005. It defines the rules for delimiters (typically comma), line endings (CRLF), the optional header row, and most importantly the quoting rules: fields containing the delimiter, double quote, CR, or LF must be wrapped in double quotes, and embedded double quotes are escaped by doubling them (""). The RFC 4180 preset on this tool produces output strictly compliant with the spec: comma delimiter, CRLF line endings, no BOM, double-quote auto-escaping. This is the safest choice for interoperability with parsers in Python (csv module), PostgreSQL COPY, AWS S3 SELECT, and most data pipelines. **Q: Why are some cells wrapped in quotes and others not?** A: The default Quote mode is Auto, which follows RFC 4180: a cell is wrapped in double quotes only when it contains the delimiter, a double quote, a carriage return, or a newline. This produces the cleanest, most human-readable CSV — values like Alice or 42 stay unquoted, while values like Smith, Jr. or Line 1\nLine 2 get wrapped. Switch to Always quote mode to wrap every cell, even simple ones — useful when downstream tools have buggy CSV parsers that misinterpret unquoted values, or when your team's pipeline expects every field to be quoted for consistency. **Q: Can I round-trip CSV → JSON → CSV without data loss?** A: Yes, when the input is flat (no nested objects or arrays). For nested data, you must use Stringify mode — it keeps arrays and objects as JSON inside a single cell, which round-trips losslessly back to the original structure when you reverse with Swap direction and Infer types. Flatten mode is one-way: it emits dotted keys (customer.address.city) that cannot be perfectly reconstructed because the parser cannot distinguish a dotted key from a nested path. The tool detects nested structures and shows a Schema notes warning when round-trip safety is at risk so you can switch modes before exporting. **Q: How do I get a TSV file?** A: Click the TSV preset chip. This switches the delimiter to Tab, the line ending to LF, and disables the BOM — the standard format for tab-separated values used by Unix tools (cut, awk), data warehouses (BigQuery, Snowflake), and most Excel locales without ambiguity. TSV is generally safer than comma-CSV for cross-locale sharing because Tab is unlikely to appear inside text fields, eliminating most quoting edge cases. Save the output with a .tsv or .tab extension and most tools will recognize it automatically. **Q: What happens with very large input?** A: Above 100,000 characters or 2,000 rows, live conversion automatically switches to manual mode: a Convert button appears in an info banner and conversion only runs when you click it. This prevents the browser's main thread from blocking on every keystroke during heavy serialization. For output above 5 MB or 50,000 rows, the tool truncates the on-screen preview to the first 500 rows and shows a Showing the first 500 of N rows banner — but the Download button still produces the full file with every row included. Hard upper limit is 10 MB of input; above that the tool shows an error and asks you to reduce the input. **Q: What encodings are supported?** A: Input and output are both UTF-8. UTF-8 covers every modern character set including emoji, CJK ideographs, Arabic, Hebrew, and combining marks. The only encoding nuance is the optional UTF-8 BOM (Byte Order Mark): Excel on Windows traditionally needs the BOM to detect UTF-8 correctly, otherwise it falls back to the system locale and mangles non-ASCII characters. Toggle BOM on (or use the Excel preset, which enables BOM by default) when you plan to open the CSV in Excel. Leave BOM off for everything else — most modern parsers (PostgreSQL, Pandas, jq, Python csv) will choke or include the BOM as a stray character at the start of the first cell. --- ### JSON to .env Converter URL: https://go-tools.org/tools/json-to-env Paste a JSON object, get a .env file instantly. Generate dotenv from config locally — your keys and secrets never leave your browser. 100% private, no upload. #### What is a .env File and Why Generate One from JSON? A .env file (dotenv file) is a plain-text list of KEY=VALUE pairs that holds environment configuration and secrets outside your source code. It is the de facto standard for Node.js, Vite, Next.js, Python, Ruby and Docker Compose — the dotenv library loads the file and injects each pair into the process environment. Because it commonly stores database passwords, API keys and access tokens, a .env file is treated as sensitive and kept out of version control. Generating a .env file from JSON is the reverse of the common parse-config task: you already have configuration as a JSON object — from an API response, a config export, a secrets-manager dump, or a script that builds settings programmatically — and you need a .env file to drop into a project or hand to a container. This converter walks the top-level keys of your JSON object and writes one correctly quoted KEY=VALUE line per property. This tool is built around a few deliberate decisions: **1. Round-trip-safe quoting.** Numbers and booleans are written bare, null becomes an empty value, and any string that contains a space, newline, # or quote is automatically double-quoted and escaped. The result parses back cleanly through dotenv and through the companion .env to JSON Converter, so a value never changes meaning on the round trip. **2. Honest handling of nesting.** A .env file is flat by definition. Rather than silently dropping nested data, the tool serializes each nested object or array to a compact JSON string and warns you which keys were flattened, so you can decide whether .env is really the right target. **3. Optional key normalization.** Keys are kept verbatim by default to avoid losing information. Turn on Normalize keys to convert camelCase or kebab-case into the UPPER_SNAKE_CASE convention environment variables use, with a warning for any key that still cannot form a valid name. **4. 100% browser-based privacy.** The JSON you paste — usually the very credentials you are about to write into a .env — never leaves the browser. No upload, no server round-trip, no logging; verify zero network requests in the DevTools Network tab. Before converting, you can validate or pretty-print the JSON with the JSON Formatter, or unescape a JSON string with JSON Escape. If your configuration is better expressed with structure, JSON to YAML preserves nesting that a flat .env cannot. ``` // Generate .env lines from a JSON object in Node.js const config = { DATABASE_URL: 'postgres://user:pass@localhost:5432/mydb', PORT: 8080, DEBUG: true, NOTE: 'value with spaces', }; const needsQuotes = (s) => /[\s#"'\n]/.test(s); const env = Object.entries(config) .map(([key, value]) => { if (typeof value === 'string') { return needsQuotes(value) ? `${key}=${JSON.stringify(value)}` : `${key}=${value}`; } return `${key}=${value ?? ''}`; // null -> empty value }) .join('\n'); console.log(env); // DATABASE_URL=postgres://user:pass@localhost:5432/mydb // PORT=8080 // DEBUG=true // NOTE="value with spaces" ``` #### FAQ **Q: How do I convert JSON to a .env file online?** A: Paste a JSON object into the input field above. The tool generates a .env file instantly in your browser — no button click needed. Each top-level property becomes a KEY=VALUE line. You can optionally normalize keys to UPPER_SNAKE_CASE or add an export prefix from the Options panel, then click Copy to grab the result or Download to save it as a .env file. Everything runs locally, so your secrets never leave your device. **Q: What kind of JSON does this accept?** A: The input must be a JSON object (a set of key/value pairs at the top level), because a .env file is fundamentally a flat list of variables. A top-level array or a bare scalar like a string or number cannot map to environment variables, so the tool reports an error asking for an object. Invalid JSON also produces an error with best-effort line and column numbers so you can locate the problem quickly. **Q: How are strings, numbers, booleans and null written?** A: Numbers and booleans are written without quotes (PORT=8080, DEBUG=true). A null value becomes an empty assignment (KEY=), which dotenv loads as an empty string. Plain strings are written as-is, but a string containing spaces, a newline, a #, or a quote character is automatically wrapped in double quotes and escaped so it parses back correctly. This means the output round-trips cleanly through the dotenv parser and through our companion .env to JSON Converter. **Q: What happens to nested objects and arrays?** A: .env files cannot represent nesting — every variable is a flat string. When a value is a nested object or array, the tool serializes it to a compact JSON string with JSON.stringify, wraps it in double quotes, and escapes it. A non-blocking warning lists exactly which keys were flattened this way, so you always know the structure was collapsed. If your data is deeply nested, a format like JSON to YAML preserves the hierarchy far better than .env can. **Q: What does the Normalize keys option do?** A: By default the original JSON keys are kept exactly as written, so no data is lost — and in that mode any key that is not already a valid environment variable name (most shells and loaders only accept names matching [A-Za-z_][A-Za-z0-9_]*) is flagged with a warning so you can rename it. With Normalize keys enabled, keys are converted to UPPER_SNAKE_CASE — the conventional style for environment variables (databaseUrl becomes DATABASE_URL, enable-signup becomes ENABLE_SIGNUP) — which resolves most invalid names automatically. **Q: Is my JSON data sent to a server?** A: No. All conversion happens entirely in your browser with JavaScript. The JSON you paste — which often holds API keys, database credentials and tokens you are about to write into a .env file — is never transmitted, never stored on any server, and never logged. You can confirm this by opening your browser's Network tab and watching that pasting triggers zero requests. That is what makes it safe to generate a real production .env, not just a sample. --- ### JSON to Python Class Converter URL: https://go-tools.org/tools/json-to-python Paste JSON, get Python classes instantly — dataclass, Pydantic v2, or TypedDict. Correct Optional typing, camelCase aliases, nested classes. 100% in your browser, free. #### What is JSON to Python conversion? JSON to Python conversion turns a JSON sample into ready-to-use Python classes — a standard-library dataclass, a Pydantic v2 model, or a TypedDict — so you never hand-write field definitions for an API response or config file. This Python class generator infers correct types (int, float, str, bool, Optional), turns nested objects into named classes, and adds Pydantic aliases for camelCase keys, all 100% in your browser. #### FAQ **Q: How do I convert JSON to a Python class?** A: Paste your JSON into the input box. The converter parses it instantly in your browser and generates Python on the right. Pick dataclass, Pydantic v2, or TypedDict with the toggle, then click Copy — no upload, no account, no waiting. **Q: What is the difference between dataclass, Pydantic, and TypedDict output?** A: dataclass gives you a standard-library @dataclass with no dependencies — great for plain data holders. Pydantic v2 emits BaseModel classes that validate and coerce data at runtime, ideal for parsing untrusted API responses. TypedDict describes the shape of a plain dict for static type checkers (mypy, Pyright) with zero runtime cost. Switch modes to compare the same JSON in each. **Q: How do I generate a Pydantic model from JSON?** A: Choose the Pydantic v2 tab. Fields are converted to snake_case with Field(alias="originalKey") so the model still reads camelCase JSON, and model_config = ConfigDict(populate_by_name=True) lets you construct it by field name too. Parse a payload with Root.model_validate(data) — Pydantic validates types and raises a clear error on bad input. **Q: How does the dataclass output handle camelCase keys?** A: A dataclass has no built-in alias, so to keep Root(**data) working, dataclass mode keeps the original key as the field name when it is a valid Python identifier (publicRepos stays publicRepos). If you want idiomatic snake_case with aliases, use the Pydantic v2 mode instead, which maps snake_case fields back to the exact JSON key. **Q: How are optional and null fields typed?** A: When a key appears in some array items but not others, its type is wrapped in Optional. A field that is only ever null becomes Optional[Any], because JSON null alone carries no type. Paste a representative sample with a filled-in value to get a more specific type than Any. **Q: What Python type does each JSON value map to?** A: Strings map to str, booleans to bool, whole numbers to int, and any number with a decimal point or exponent to float. Because Python integers are arbitrary precision, even huge IDs stay int — there is no 64-bit overflow. Empty or mixed-type arrays become List[Any], and objects become nested classes. **Q: Does it handle nested objects and arrays of objects?** A: Yes. Each nested object becomes its own named class, and identical shapes are deduplicated into a single class reused by every field. Arrays of objects are merged key by key so you get one element class, with keys missing from some items marked Optional. Child classes are always emitted before the classes that use them. **Q: How are Python keywords and non-identifier keys handled?** A: A JSON key that is a Python keyword (class, from, import) gets a trailing underscore (class_). Keys with hyphens, spaces, or a leading digit are sanitized to valid identifiers in dataclass and Pydantic modes. In TypedDict mode, any dict containing a non-identifier key is emitted with the functional TypedDict('Name', {...}) syntax so the exact key like "first-name" is preserved. **Q: How do I use the generated dataclass to parse JSON?** A: For a flat object, json.loads then Root(**data) works directly. For nested structures, dataclass does not recurse automatically — either build the child objects yourself, use a library like dacite or pydantic.dataclasses, or switch this tool to Pydantic v2 mode, where Root.model_validate(json.loads(text)) parses the whole tree in one call. **Q: Is my JSON data private and safe?** A: Yes. Conversion runs 100% in your browser with JavaScript. Your JSON — including tokens, IDs, or customer data — never leaves the page and is never sent to a server. **Q: Is the tool free? Do I need an account?** A: It is completely free with no sign-up, no limits, and no ads cluttering the workspace. It works offline once the page has loaded. --- ### JSON to Rust Struct Converter URL: https://go-tools.org/tools/json-to-rust Paste JSON, get idiomatic Rust serde structs instantly, 100% in your browser. Correct i64/u64/f64 typing, Option for nulls, #[serde(rename)] for camelCase. Free. #### What is JSON to Rust conversion? JSON to Rust conversion turns a JSON sample into ready-to-compile Rust structs with serde's #[derive(Serialize, Deserialize)] macros, so you never hand-write deserialization boilerplate for API responses or config files. This fast Rust struct generator infers correct number types, marks absent fields as Option, and adds #[serde(rename)] for non-snake_case keys — all 100% in your browser. #### FAQ **Q: How do I convert JSON to a Rust struct?** A: Paste your JSON into the input box. The converter parses it instantly in your browser and generates Rust structs with serde derives on the right. Click Copy to grab the result — no upload, no account, no waiting. **Q: Does it generate serde derives? Do I need serde and serde_json?** A: Yes — output uses #[derive(Debug, Clone, Serialize, Deserialize)] by default. Add serde with the derive feature to your Cargo.toml. You only need serde_json as a dependency if the output contains serde_json::Value, which appears for empty or mixed arrays and null-only fields. Turn off the serde toggle to emit plain structs. **Q: How do I use the generated struct to parse JSON?** A: Add serde_json to your Cargo.toml, then deserialize in one line: let root: Root = serde_json::from_str(json)?;. The generated Deserialize derive does the rest — use serde_json::from_slice for a byte slice or from_reader for a file or HTTP body, and serde_json::to_string to serialize back. **Q: How are optional and null fields handled?** A: When a key appears in some array items but not others, it becomes an Option field. A field that is only ever null becomes an optional serde_json::Value. serde treats Option as optional automatically, so no #[serde(default)] attribute is added or required. **Q: How does it handle camelCase keys and Rust keywords?** A: Field names are converted to idiomatic snake_case, and a #[serde(rename)] attribute maps them back to the exact JSON key. Reserved keywords like type or match are emitted as type_ or match_ with a rename, which is more robust than raw identifiers because it also covers self, crate, and super. **Q: Can it use #[serde(rename_all)] instead of per-field renames?** A: The tool emits a per-field #[serde(rename)] because it always works — even when one payload mixes camelCase, snake_case, and irregular keys. If every field in a struct shares one convention, delete those attributes and put a single #[serde(rename_all = "camelCase")] on the struct instead; both deserialize identically. **Q: What Rust number type does it use?** A: Integers map to i64, or u64 when a value exceeds i64::MAX, and fall back to f64 beyond u64 — so large IDs still round-trip. Any number written with a decimal point or exponent (like 1.0 or 2e3) maps to f64, because serde would reject a float into an integer field. **Q: How are dates and timestamps typed?** A: JSON has no date type, so ISO strings like 2011-01-25 or RFC 3339 timestamps come out as String. For real date handling, change the field to a chrono type — DateTime in the Utc time zone, or NaiveDate — and enable chrono's serde feature. serde then parses RFC 3339 automatically. **Q: How do I handle objects with dynamic or unknown keys?** A: When keys vary — for example a map of IDs to values — replace the generated struct with a HashMap keyed by String. To keep a typed struct but still capture extra fields, add a #[serde(flatten)] field that is a HashMap. For fully dynamic values, serde_json::Value is the catch-all type. **Q: Is my JSON data private and safe?** A: Yes. Conversion runs 100% in your browser with JavaScript. Your JSON — including tokens, IDs, or customer data — never leaves the page and is never sent to a server. **Q: Can I generate plain Rust structs without serde?** A: Yes. Turn off the serde toggle to drop the use serde line, the Serialize and Deserialize derives, and all #[serde(rename)] attributes — leaving clean structs. You can also toggle Debug and Clone derives and pub visibility. **Q: Is the tool free? Do I need an account?** A: It is completely free with no sign-up, no limits, and no ads cluttering the workspace. --- ### JSON to TOML Converter URL: https://go-tools.org/tools/json-to-toml Paste JSON, get TOML instantly in your browser. Safe null handling, top-level table checks, Cargo.toml & pyproject.toml-ready output. 100% private, no upload. #### What is TOML and Why Convert from JSON? TOML (Tom's Obvious, Minimal Language) is a configuration file format built to be unambiguous and easy for humans to read and edit. It is the standard config format for the Rust ecosystem (Cargo.toml), modern Python packaging (pyproject.toml), and tools like Hugo, Netlify, Poetry, and Foundry. JSON is the universal machine format that tools and APIs produce. Converting JSON to TOML is common when you have structured data as JSON but need a human-editable TOML config as the destination — generating a Cargo.toml or pyproject.toml, or turning an application's JSON settings into a readable config file. TOML is more structured than JSON, and this tool turns that strictness into guidance rather than cryptic failures: **1. Top-level table enforcement.** Every TOML document is a table at the root — a bare array or scalar is not valid TOML. Instead of emitting broken output for { top-level array } input, this tool detects it and tells you exactly how to wrap your data under a key, so you always get valid TOML or a clear explanation. **2. Safe, transparent null handling.** JSON has null; TOML does not. Most converters either crash or silently discard data. This tool is explicit: object keys with null values are dropped and the removed keys are listed in a warning, and a null inside an array — which has no valid TOML representation — produces a precise, path-pointing error. You are never surprised by missing data. **3. Idiomatic TOML output.** Nested objects become [tables], deeply nested objects become dotted tables ([tool.ruff]), and arrays of objects become arrays of tables ([[section]]) — the shape real Cargo.toml and pyproject.toml files use. The conversion is powered by the zero-dependency, TOML 1.0.0-compliant smol-toml library. **4. 100% browser-based privacy.** Your JSON — which may contain credentials, tokens, or internal service details — never leaves your browser. No upload, no server, no logging. Confirm it in your browser's Network tab. Need the reverse? Use the TOML to JSON Converter. Working with other config formats? Try the JSON to YAML Converter and YAML to JSON Converter, or clean up your JSON input first with the JSON Formatter. Each format has its niche: JSON for machine interchange, TOML for human-edited application and tooling config, and YAML for deeply nested infrastructure manifests. This converter lets you move from JSON to TOML without writing any code. ``` // Convert JSON to TOML in Node.js using the smol-toml library import { stringify } from 'smol-toml'; const data = JSON.parse(`{ "package": { "name": "my-app", "version": "1.0.0" }, "dependencies": { "serde": { "version": "1.0" } } }`); // The top-level value must be an object; null values on objects are dropped. const toml = stringify(data); console.log(toml); // [package] // name = "my-app" // version = "1.0.0" // // [dependencies.serde] // version = "1.0" ``` #### FAQ **Q: How do I convert JSON to TOML online?** A: Paste your JSON into the input field above. The tool parses it and produces TOML instantly in your browser — no button click needed. Once the TOML appears in the output area, click Copy to grab it to your clipboard or Download to save it as a .toml file. Everything runs locally, so your JSON never leaves your device. Two things to know before you start: your JSON's top level must be an object (not an array), and null values have no TOML equivalent — the tool explains both clearly if they come up. **Q: What is TOML and why convert JSON to it?** A: TOML (Tom's Obvious, Minimal Language) is a configuration format designed to be easy for humans to read and write, with unambiguous semantics. It is the config format for Rust's Cargo, Python's pyproject.toml, Hugo, Netlify, Poetry, and more. You convert JSON to TOML when a tool or API gives you JSON but the destination expects TOML — for example, generating a Cargo.toml or pyproject.toml from structured data, or turning an application's JSON settings into a human-editable TOML config file. **Q: Why must the top level of my JSON be an object?** A: TOML documents are always a table (a set of key-value pairs) at the root — the specification does not allow a bare array, string, or number at the top level. So a JSON array like [1, 2, 3] or a lone value like 42 cannot be converted directly. The fix is to wrap it under a key: { "items": [1, 2, 3] } converts cleanly to items = [1, 2, 3]. This tool detects a non-object top level and tells you exactly how to wrap it, instead of producing broken output. **Q: What happens to null values when converting JSON to TOML?** A: TOML has no null type, so null cannot be represented. This tool handles it safely and transparently in two ways. When a null appears as an object value, TOML simply omits that key — and the tool shows a warning listing exactly which keys were dropped, so you are never surprised by silent data loss. When a null appears inside an array, there is no valid TOML output at all (an array cannot hold a gap), so the tool reports an error pointing at the exact path of the offending null. Either way, you know precisely what happened and where. **Q: How are nested objects and arrays converted to TOML?** A: A nested JSON object becomes a TOML table: { "owner": { "name": "Tom" } } becomes [owner] with name = "Tom". Deeply nested objects become dotted tables like [tool.ruff]. An array of objects becomes an array of tables written with double brackets ([[servers]]), which is the idiomatic TOML way to express repeated sections. Arrays of scalars stay inline as arrays (ports = [8001, 8002]). The output follows the TOML 1.0.0 specification. **Q: What happens to floating-point numbers like 1.0?** A: TOML distinguishes integers from floats, and a float whose fractional part is zero (like 1.0) is written as the integer 1. So { "version": 1.0 } becomes version = 1. The tool shows a small warning when this coercion happens, because it changes the value's type on a round-trip. If you need the value to stay a float, that distinction cannot be preserved through TOML for whole numbers — consider whether an integer is actually what you want. **Q: Is my JSON data sent to any server?** A: No. All parsing and conversion happen entirely in your browser using JavaScript. Your JSON is never uploaded, never stored, and never logged. This makes the tool safe for configuration that contains API keys, database credentials, or internal service details. You can verify this by opening your browser's Network tab — pasting JSON triggers zero network requests. **Q: Can I convert TOML back to JSON?** A: Yes. Use the companion TOML to JSON Converter for the reverse direction, or click the Swap direction button at the top of this tool to flip the input and output in place. TOML to JSON has fewer constraints — it accepts any valid TOML — so round-tripping JSON → TOML → JSON is reliable as long as your JSON had no nulls or top-level array to begin with. **Q: How do I convert JSON to TOML on the command line?** A: A popular option is the Go tool 'yj' (yj -jt reads JSON and writes TOML). In Python you can use the third-party 'tomli-w' package: import json, tomli_w; tomli_w.dump(json.load(open('config.json')), open('config.toml','wb')). In Node.js: import { stringify } from 'smol-toml'; const toml = stringify(JSON.parse(text)) — the same library this tool uses. For a quick one-off without installing anything, this browser tool is the fastest path. **Q: How do I convert JSON to TOML in Python, Rust, or Node.js?** A: In Python: import json, tomli_w; tomli_w.dump(json.load(open('config.json')), open('config.toml','wb')). In Rust: use serde_json and toml — let value: serde_json::Value = serde_json::from_str(&text)?; let toml = toml::to_string_pretty(&value)?. In Node.js: import { stringify } from 'smol-toml'; const toml = stringify(JSON.parse(text)) — this is exactly the approach used by this tool. Remember that the top-level value must be an object in all of these. **Q: Does the converter preserve key order?** A: Keys within a table are preserved in their original order, but TOML structure requires that all plain key-value pairs of a table come before any child table headers. So top-level scalars are emitted first, then tables and arrays of tables — the converter reorders only where the TOML grammar demands it. The data is identical; only the textual ordering of table sections shifts to produce valid TOML. **Q: Is there a file size limit for JSON input?** A: There is no hard limit, but inputs over 200KB switch from live conversion to manual mode: a Convert button appears and conversion runs only when you click it, keeping the browser responsive. Typical configuration payloads convert in well under 50 milliseconds. --- ### JSON to TypeScript Converter URL: https://go-tools.org/tools/json-to-typescript Paste JSON, get TypeScript interfaces instantly. 100% in your browser — data never leaves the page. interface or type, nested objects, arrays, optional fields. Free, no sign-up. #### What is JSON to TypeScript conversion? JSON to TypeScript conversion reads a JSON value and generates matching TypeScript interface or type definitions — eliminating hand-written boilerplate for API responses and config files. Paste a payload and get production-ready types in seconds, fully typed for nested objects, arrays, and optional fields. #### FAQ **Q: How do I convert JSON to a TypeScript interface?** A: Paste your JSON into the input box. The converter reads it instantly in your browser and generates a TypeScript interface on the right. Click Copy to grab the result — no upload, no account. **Q: Should I use `type` or `interface` for JSON data?** A: Both work. `interface` is conventional for object shapes and gives slightly better editor errors; `type` is handy for unions and intersections. Use the Output toggle to switch between them and keep whichever your codebase prefers. **Q: How are nested objects and arrays handled?** A: Nested objects become separate, named interfaces (e.g. an `address` field yields an `Address` interface). Arrays of objects are merged into one element interface; primitive arrays become typed arrays like `string[]`. **Q: How are optional and null fields handled?** A: When a key is present in some array items but not others, it is marked optional. Choose `?:` (optional) or `| null` (explicit nullable) with the Optional fields toggle. Literal null values are typed as `null`. **Q: How do I generate TypeScript types from JSON automatically in VSCode?** A: You can install an extension, but you don't have to. This tool runs entirely in your browser — paste, copy, done — with no plugin to install, configure, or keep updated. **Q: Is my JSON data private and safe?** A: Yes. Conversion happens 100% in your browser using JavaScript. Your JSON — including any tokens, IDs, or customer data — never leaves the page and is never sent to a server. **Q: Is the tool free? Do I need an account?** A: It is completely free with no sign-up, no limits, and no ads cluttering the workspace. **Q: Can it detect dates or enums?** A: Date strings are kept as `string` (safer than guessing). String values are typed as `string` rather than literal unions, so the output stays stable as your data changes. --- ### JSON to XML Converter URL: https://go-tools.org/tools/json-to-xml Paste JSON, get XML instantly. Converts objects, arrays, and @_ attributes in-browser — nothing uploaded. Free, private, no signup required. #### What is JSON-to-XML Conversion and How Does It Work? JSON (JavaScript Object Notation) and XML (Extensible Markup Language) are both structured data formats, but they have fundamentally different models: JSON is a tree of objects, arrays, strings, numbers, booleans, and null values with no concept of attributes or document root constraints; XML is a tree of elements that may carry attributes and text content, and the document must have exactly one root element. Converting from JSON to XML requires a set of conventions to bridge this mismatch. This tool uses the most widely adopted convention — the same one used by fast-xml-parser (Node.js), xmltodict (Python), and JAXB (Java) — applied in reverse: **1. Root element normalization.** The single most important difference between JSON and XML is the root constraint. JSON has no root concept; XML requires exactly one. The converter handles four cases automatically. A single-key object uses that key as the XML root: { "config": {...} } → .... A multi-key object wraps in : { "a": 1, "b": 2 } → 12. A top-level array wraps as .... A primitive value wraps as value. **2. @_ prefix → XML attributes.** JSON keys prefixed with @_ become XML attributes on the enclosing element. { "element": { "@_id": "42", "@_class": "primary" } } produces . This prefix is the canonical convention — no valid XML element name starts with @, so there is never a collision with child element names. **3. #text → element text content.** When an element needs both attributes and text content, the text is stored under the #text key: { "price": { "@_currency": "USD", "#text": "29.99" } } → 29.99. Elements with only text content (no @_ keys) convert to plain text elements without this indirection. **4. Arrays → repeated same-named sibling elements.** XML allows multiple child elements with the same name; JSON uses arrays for ordered lists. A JSON array under a key produces repeated child elements that reuse the key name: { "items": ["a", "b"] } produces ab (the two elements are siblings under the parent). When the entire JSON input is a top-level array, a wrapper is added and each element becomes an child — is a fixed fallback name used only in that case. **5. Symmetric with XML-to-JSON.** The @_ and #text conventions used here are exactly the same conventions used by the companion XML to JSON Converter. This means a JSON → XML → JSON round-trip preserves attributes, text content, and element structure — as long as the input JSON follows the @_/#text conventions. **When to convert JSON to XML?** The most common scenarios are: (1) sending data to a legacy SOAP or XML-based web service that requires an XML request body; (2) generating XML configuration files (Spring, Maven, Ant, Android resources) from JSON data; (3) producing sitemap.xml or RSS feed XML from JSON content data; (4) interoperating with enterprise systems (ERP, CRM, EDI) that consume XML; (5) generating SVG or other XML-based graphics formats programmatically from JSON data. For formatting and validating the resulting XML, use the XML Formatter. ``` // Convert JSON to XML in Node.js using fast-xml-parser import { XMLBuilder } from 'fast-xml-parser'; const data = { catalog: { product: { '@_id': 'P01', '@_category': 'electronics', name: 'Wireless Headphones', price: { '@_currency': 'USD', '#text': '79.99' } } } }; const builder = new XMLBuilder({ attributeNamePrefix: '@_', // @_ keys become XML attributes textNodeName: '#text', // #text key becomes element text content ignoreAttributes: false, // process @_ attribute keys format: true, // pretty-print with indentation indentBy: ' ', // 2-space indent }); const xml = builder.build(data); console.log(xml); // // // Wireless Headphones // 79.99 // // ``` #### FAQ **Q: Is my JSON data sent to a server when I use this tool?** A: No. All conversion happens entirely inside your browser using JavaScript. Your JSON is never transmitted over the network, never stored on any server, and never logged or analyzed. This makes the tool safe to use with JSON payloads containing API credentials, database configuration, internal service data, or any other sensitive content. You can verify this by opening your browser's Network tab — you will see zero requests triggered by pasting or converting JSON. **Q: How does the tool decide what the XML root element is?** A: XML requires exactly one root element; JSON has no such constraint. The converter applies these rules: (1) A single-key object uses that key as the root element name — { "user": { ... } } becomes .... (2) A multi-key object (two or more keys at the top level) is wrapped in a element so all keys become children of a single root. (3) A top-level array is wrapped as ..., with each array element becoming an child. (4) A primitive value (string, number, boolean, null) at the top level becomes value. These rules guarantee the output is always well-formed XML with exactly one root. **Q: Why does a multi-key JSON object get wrapped in ?** A: XML is a document format with a strict single-root requirement — a valid XML document must have exactly one top-level element. JSON objects can have any number of top-level keys, so when your JSON has multiple top-level keys (such as { "status": 200, "data": {...}, "meta": {...} }), there is no single key to use as the root. Wrapping in is the safest and most predictable convention. If you want a different root element name, reshape your JSON to a single-key object before converting — e.g. { "response": { "status": 200, "data": {...} } }. **Q: How does a top-level JSON array convert to XML?** A: A top-level array is wrapped as ....... Each array element becomes an child — "item" is a fixed literal name used only for top-level arrays. This is distinct from arrays nested under an object key: if you write { "products": [...] }, each array element becomes a child (reusing the key name), not . If you want custom tag names for a top-level array, wrap it in a named object first: { "products": [...] } gives you repeated elements. **Q: How do I convert JSON keys to XML attributes?** A: Prefix the key with @_ and the converter will emit it as an XML attribute instead of a child element. For example, { "tag": { "@_id": "42", "@_lang": "en", "#text": "Hello" } } produces Hello. The @_-prefix convention is the same one used by fast-xml-parser (Node.js) and xmltodict (Python), making the output round-trip consistently with those libraries. This is also the convention used by the companion XML to JSON Converter. **Q: What is the #text key used for?** A: When an element needs both XML attributes and text content, you cannot simply map the text to a child element — it must be the element's own text node. The #text key in your JSON becomes that text content. Example: { "price": { "@_currency": "USD", "#text": "29.99" } } produces 29.99. If an object has only a #text key and no @_ keys, it still produces a plain text element: { "note": { "#text": "hello" } } becomes hello. **Q: Does indentation affect the XML structure?** A: No. Indentation is purely cosmetic — it changes how the XML is formatted for human readability but does not affect the element structure, attribute values, or text content. Choose 2 spaces for compact output or 4 spaces for more readable output. Both produce semantically identical XML. Most XML parsers treat whitespace-only text nodes between elements as ignorable whitespace, so indented and minified XML are equivalent for parsing purposes. **Q: How does a JSON array nested inside an object convert to XML?** A: A JSON array value under a key produces repeated same-named child elements, reusing the key name for every element. For example, { "items": [1, 2, 3] } produces three siblings — not . Similarly, { "products": [{"name":"A"},{"name":"B"}] } produces two elements, each containing a child. The key name is used as-is for every array element; no singularization occurs. The only place the literal name appears is when the entire JSON input is a top-level array (see above), where is a fixed fallback wrapper name. **Q: How do I convert XML back to JSON?** A: Use the companion XML to JSON Converter. It applies the same @_ and #text conventions in reverse: XML attributes become @_-prefixed JSON keys, element text content paired with attributes becomes a #text key, and repeated same-named sibling elements become a JSON array. The two tools are symmetric for round-trip use cases. **Q: Can I validate or format the XML output?** A: Yes — paste the XML output into the XML Formatter to validate well-formedness, adjust indentation, or minify. The XML Formatter is the right tool for inspecting and polishing the XML once this converter has produced it. **Q: Is there a file size limit for JSON input?** A: There is no hard limit, but inputs larger than 200KB automatically switch from live conversion to manual mode. In manual mode a Convert button appears and conversion runs only when you click it — this keeps the browser responsive during heavy serialization. For very large JSON files (multi-megabyte), consider command-line tools for better performance: node -e "const {XMLBuilder}=require('fast-xml-parser');console.log(new XMLBuilder({attributeNamePrefix:'@_'}).build(JSON.parse(require('fs').readFileSync('in.json','utf8'))))" or an equivalent Python script with xmltodict. **Q: What JSON types are supported?** A: All six JSON types are supported. Objects become XML elements with child elements. Arrays become repeated same-named sibling elements. Strings, numbers, booleans, and null become element text content. Booleans and null are serialized as their literal string representations: true, false, and empty content for null. No type coercion is applied — numbers are written to XML text content exactly as they appear in the JSON, preserving decimals and precision. --- ### JSON to YAML Converter URL: https://go-tools.org/tools/json-to-yaml Paste JSON, get YAML instantly. Live conversion in your browser. K8s/Compose-ready, 2/4-space indent, smart quoting. 100% private, no upload. #### What is YAML and Why Convert from JSON? YAML (YAML Ain't Markup Language) is a human-readable data serialization format designed for configuration files, infrastructure-as-code, and anywhere a human writes data that a machine will read. Its indentation-based syntax requires no braces or brackets, making it far more legible than JSON for complex nested structures. Kubernetes, Helm, Ansible, Docker Compose, GitHub Actions, CircleCI, and virtually every cloud-native tool uses YAML as its primary configuration format. Converting JSON to YAML is therefore one of the most common tasks in DevOps and backend development — you receive a resource definition from an API in JSON, and you need a YAML manifest to commit to version control. This tool has four important differentiators compared to typical online converters: **1. Norway Problem — Auto-Safe Quoting.** The single biggest footgun in JSON-to-YAML conversion is the YAML Norway Problem. In YAML 1.1 (which millions of production parsers still use, including older Kubernetes, PyYAML, Ansible, and Ruby's Psych), the bare strings yes, no, on, off, y, and n are parsed as boolean true/false values. This famously bit the ISO country code for Norway ("NO" → false) and has caused real production outages in Kubernetes configs. YAML 1.2 fixed this, but your parsers may not be on 1.2. This tool's default Auto quote mode uses the eemeli/yaml library with the YAML 1.1 schema, so it automatically wraps any Norway-problem string in quotes, guaranteeing safe round-trips through both YAML 1.1 and 1.2 parsers. Learn more in our companion article at The YAML Norway Problem and JSON-YAML Differences. **2. Key Order Preservation.** Unlike some converters that sort keys alphabetically, this tool preserves the original key insertion order from your JSON — matching the behavior of JSON.parse() in all modern JavaScript engines. This matters for Kubernetes manifests (where apiVersion and kind are expected first by convention), OpenAPI specs (where info appears before paths), and any config where field ordering is meaningful for readability or diffs. **3. Big-Number Precision Caveat.** JSON numbers larger than 2^53 - 1 (9007199254740991) cannot be represented exactly in JavaScript's IEEE 754 double-precision float. When JSON.parse() reads a large integer like a Kubernetes resourceVersion field (which is a 64-bit integer on the server), it silently truncates it. This is a fundamental browser JavaScript limitation that affects every browser-based JSON tool, including this one. The safe workaround is to ensure large integers are stored as strings in your JSON before converting. This tool documents this behavior honestly in the Number Precision Loss common error below. **4. 100% Browser-Based Privacy.** Your JSON data — which often contains API keys, database credentials, internal service configurations, and production secrets — never leaves your browser. No data is sent to any server. You can verify this in your browser's Network tab. This is the only safe way to handle sensitive configuration data in an online tool. See our companion tool for the reverse direction at YAML to JSON Converter, and our JSON Formatter if you need to validate and pretty-print JSON before converting. YAML's human-readable nature comes with a tradeoff: it has more parsing edge cases than JSON. Beyond the Norway Problem, YAML has octal number quirks (0777 is parsed as 511 in YAML 1.1), multiline string syntax (| for literal, > for folded), anchor and alias references (&anchor and *alias), and multiple document support (--- separator). JSON has none of these complexities — it is a strict, minimal format with only six data types. For machine-to-machine data exchange, JSON is almost always the better choice. For human-edited configuration files where readability and comments matter, YAML wins. This converter gives you the best of both: use JSON programmatically, convert to YAML for your infrastructure. Need to compare two JSON documents and find what changed? Try our JSON Diff. If your destination is a spreadsheet rather than YAML, use JSON to CSV (or the reverse CSV to JSON) instead. ``` // Convert JSON to YAML in Node.js using the eemeli/yaml library import { Document } from 'yaml'; const data = JSON.parse('{"apiVersion":"apps/v1","kind":"Deployment"}'); // version: '1.1' ensures Norway-problem strings (yes/no/on/off/y/n) // are automatically quoted in the output for YAML 1.1 parser safety const doc = new Document(data, { version: '1.1' }); const yamlString = doc.toString({ indent: 2, lineWidth: 0, // disable line wrapping defaultStringType: 'PLAIN', // Auto mode: only quote when needed }); console.log(yamlString); // apiVersion: apps/v1 // kind: Deployment ``` #### FAQ **Q: How do I convert JSON to YAML online?** A: Paste your JSON into the input field above. The tool converts it to YAML instantly in your browser — no button click needed. You can adjust indentation (2 or 4 spaces), quoting style (Auto, Double, or Single), and output style (Block or Flow) from the Options panel. Once the YAML appears in the output area, click Copy to grab it to your clipboard or Download to save it as a .yaml file. Everything runs locally — your data never leaves your device. **Q: What is the YAML Norway Problem and how does this tool handle it?** A: The YAML Norway Problem refers to a quirk in YAML 1.1 specification where bare strings like "no", "yes", "on", "off", "y", and "n" are parsed as boolean values (false/true) instead of strings. This caused a famous real-world issue where the ISO country code for Norway ("NO") was misread as the boolean false in Ansible playbooks and Kubernetes configs. In YAML 1.2, this was fixed — bare strings are always strings. However, millions of production parsers (older Kubernetes versions, PyYAML, Ansible, Ruby's Psych) still use YAML 1.1. This tool's Auto quote mode (the default) automatically wraps any Norway-problem strings in quotes so they round-trip safely through both YAML 1.1 and 1.2 parsers. When Norway-problem strings are detected in your input, a warning banner lists exactly which values were auto-quoted. **Q: Why does the Norway Problem matter for Kubernetes and DevOps?** A: Kubernetes YAML manifests, Helm chart values, Ansible playbooks, and GitHub Actions workflows are all parsed by tools that historically used YAML 1.1. If you have a config key with the value "no" (for example, a country code, an enabled flag in string form, or a custom boolean-like field), a YAML 1.1 parser will silently convert it to the boolean false. This can cause service misconfigurations that are extremely difficult to debug because the YAML appears correct when viewed as text but behaves differently when parsed. Always use Auto quote mode when converting JSON for use in Kubernetes or any DevOps toolchain to guarantee safe round-trips. **Q: Should I use 2-space or 4-space indentation for YAML?** A: Use 2-space indentation for Kubernetes manifests, Helm values, Docker Compose files, and GitHub Actions workflows — these tools are designed around 2-space YAML and it is the community convention. Use 4-space indentation for Ansible playbooks (which follow a 4-space convention) and when your team or organization has a style guide mandating it. YAML forbids tabs entirely — all indentation must be spaces. This tool defaults to 2 spaces, which is the correct choice for the vast majority of cloud-native use cases. **Q: How do I use this tool to create a Kubernetes manifest?** A: If you have a Kubernetes resource definition in JSON (from kubectl get deployment my-app -o json, an API response, or a Terraform resource block), paste it into the input field. Select 2 spaces indentation (the default) and Auto quotes (the default, which handles the Norway Problem). The YAML output is immediately ready for kubectl apply -f. You can also click Download to save the file with a .yaml extension and pipe it directly into kubectl apply -f -. The K8s Deployment example above shows a complete deployment manifest you can load and modify. **Q: How do I convert a Docker Compose JSON to YAML?** A: Paste your Docker Compose JSON into the input field. Use 2-space indentation (Docker Compose convention) and Block style. The output YAML is compatible with docker compose up, docker compose config, and Docker Stack. A common scenario is exporting a running stack's configuration with docker inspect and then converting it back to a compose.yaml file. The Docker Compose example above includes service definitions with ports, environment variables, volumes, and depends_on. **Q: Can JSON numbers larger than 2^53 lose precision when converting to YAML?** A: Yes. This is a fundamental JavaScript limitation: the IEEE 754 double-precision float used by JavaScript's Number type can only represent integers exactly up to 2^53 - 1 (9007199254740991). Any integer beyond that — such as Kubernetes resourceVersion fields (which are int64 on the server) — will be silently rounded when parsed by JSON.parse(). For example, the value 9007199254740993 becomes 9007199254740992 in JavaScript, and this truncated number will appear in your YAML output. This affects all browser-based JSON tools, not just this one. The safe workaround is to store large integers as strings in your JSON ("resourceVersion": "9007199254740993") — they will appear as YAML strings without any precision loss. **Q: Does the converter preserve the original key order from my JSON?** A: Yes. The eemeli/yaml library used by this tool preserves insertion order, which matches the behavior of JSON.parse() in all modern JavaScript engines (V8, SpiderMonkey, JavaScriptCore). Keys appear in the YAML output in the same order they appeared in the JSON input. This is important for Kubernetes manifests and OpenAPI specs where field ordering is often meaningful for readability and diffs. **Q: When should I use JSON versus YAML?** A: Use JSON when: you are building APIs and web services (JSON is the universal interchange format), when machine parsing speed matters, when you need strict type safety, or when the consumer is a JavaScript/TypeScript application. Use YAML when: writing configuration files intended for human editing (Kubernetes manifests, CI/CD pipelines, Ansible playbooks, Helm values), when you want comments in your config, or when readability is more important than strictness. A helpful rule: if a machine writes it or reads it first, use JSON; if a human writes it and a machine reads it, use YAML. **Q: How can I convert JSON to YAML on the command line?** A: The most popular approach is combining yq and jq. Install yq (Mike Farah's version, not the Python one): brew install yq on macOS or wget https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64 -O /usr/local/bin/yq for Linux. Then run: cat input.json | yq -P to pretty-print as YAML. Alternatively: yq -o yaml input.json or cat input.json | python3 -c "import sys, json, yaml; yaml.dump(json.load(sys.stdin), sys.stdout, default_flow_style=False)". For Kubernetes specifically: kubectl get deployment my-app -o yaml converts directly from the cluster API. **Q: How do I convert JSON to YAML in Python, Node.js, or Go?** A: In Python: import json, yaml; yaml.dump(json.load(open('input.json')), open('output.yaml', 'w'), default_flow_style=False) using PyYAML, or ruamel.yaml for round-trip fidelity. In Node.js: import { Document } from 'yaml'; const doc = new Document(JSON.parse(input), { version: '1.1' }); const result = doc.toString({ indent: 2, lineWidth: 0 }) — this is the same library and approach used by this tool. In Go: import gopkg.in/yaml.v3; json.Unmarshal(jsonBytes, &data); yaml.Marshal(data) — note that Go YAML v3 uses YAML 1.1 by default, so Norway-problem strings will be auto-quoted. **Q: Is my JSON data sent to any server when I use this tool?** A: No. All conversion happens entirely in your browser using JavaScript. Your JSON data is never transmitted over the network, never stored on any server, and never logged or analyzed. This makes the tool safe to use with API keys, database credentials, internal configuration files, production Kubernetes manifests, and any other sensitive data. The tool uses no cookies for your input data and no third-party analytics that would capture your paste. You can verify this by opening your browser's Network tab — you will see zero requests triggered by pasting JSON. **Q: Is there a file size limit for JSON input?** A: There is no hard file size limit, but large inputs (over 200KB) automatically switch from live conversion to manual mode. In manual mode, a Convert button appears and conversion runs only when you click it — this prevents the browser's main thread from blocking for 200-500ms on every keystroke. For very large JSON files (multi-megabyte), consider using command-line tools like yq or jq for better performance. The tool efficiently handles typical real-world payloads like full Kubernetes namespace dumps, large OpenAPI specs, and multi-service Docker Compose files. --- ### JSON to Zod Schema Converter URL: https://go-tools.org/tools/json-to-zod Paste JSON, get a ready-to-use Zod schema instantly, 100% in your browser. Correct z.number().int(), .optional() and .nullable(), plus a z.infer type. Free. #### What is a Zod schema? A Zod schema is a TypeScript-first description of a value's shape that validates data at runtime and infers a static type at compile time. Generating one from a JSON sample means you never hand-write validation boilerplate for API responses, forms, or config files. This Zod schema generator infers correct types, marks absent keys as .optional(), null values as .nullable(), and hands you a z.infer type — all 100% in your browser. #### FAQ **Q: How do I convert JSON to a Zod schema?** A: Paste your JSON into the box on the left. The converter parses it instantly in your browser and generates a Zod schema on the right, together with a z.infer type alias. Click Copy to grab it — no upload, no account, no waiting. **Q: What is the difference between JSON to Zod and JSON Schema to Zod?** A: This tool takes a sample JSON value — an API response or object — and infers a Zod schema from its shape. JSON Schema to Zod is a different task: it converts an existing JSON Schema document into Zod. If you have raw data, use this tool. If you already have a JSON Schema file, convert that instead. **Q: How do I get a TypeScript type from the schema?** A: Every result includes a z.infer type alias, so you get a fully typed Root without writing it by hand. Keep the schema as the single source of truth and let TypeScript derive the type, so the two never drift apart. You can toggle the z.infer line off in Options if you only want the schema. **Q: How are optional and null fields handled?** A: When a key appears in some array items but not others, its field is marked .optional(). A field that is null in some samples and typed in others becomes .nullable(). A field that is only ever null becomes z.null(). Zod treats optional and nullable independently, so the schema matches how your data actually varies. **Q: What number type does it generate?** A: Whole numbers map to z.number().int(); any value written with a decimal point or exponent maps to z.number(). If a field mixes integers and decimals across samples, it unifies to z.number() so validation never rejects a legitimate value. **Q: Does the output work with Zod 3 and Zod 4?** A: Yes. The generated code uses the common, stable subset — z.object, z.string, z.number, z.array, z.union, .int, .optional, .nullable and z.infer — that behaves the same in Zod 3 and Zod 4. Just make sure the zod package is installed in your project. **Q: How do I validate data with the generated schema?** A: Import the schema and call RootSchema.parse(data) to throw on invalid input, or RootSchema.safeParse(data) to get a typed success or error result without throwing. This is ideal at trust boundaries such as API responses, form input, and environment config. **Q: How are arrays and mixed types handled?** A: An array of one type becomes z.array of that type. An array of objects merges key by key into a single element schema. An array that mixes primitive types becomes a z.union, and anything more ambiguous falls back to z.unknown so you can refine it from a richer sample. **Q: Is my JSON data private and safe?** A: Yes. Conversion runs 100% in your browser with JavaScript. Your JSON — including tokens, IDs, or customer data — never leaves the page and is never sent to a server. **Q: Is the tool free? Do I need an account?** A: It is completely free with no sign-up, no limits, and no ads cluttering the workspace. --- ### JSON Unescape URL: https://go-tools.org/tools/json-unescape Unescape a JSON string back to readable text in your browser. Decodes \n, \t, \", \\, and \uXXXX, with or without surrounding quotes. 100% private, no upload. #### What is JSON Unescaping and When Do You Need It? JSON unescaping is the reverse of JSON escaping: it takes a string full of escape sequences — \n, \t, \", \\, \uXXXX — and turns each one back into the character it represents, recovering the original text. Where escaping makes a string safe to store inside a JSON document, unescaping makes a stored string readable again. The need shows up constantly in debugging and data work. You copy a field out of a structured log and it is full of \n and \" that hide the real message — unescaping reveals the actual multi-line text. An API stored a request body as a string (JSON-in-JSON), and you need to read the inner object — unescaping turns {\"a\":1} back into {"a":1}. A legacy system emitted ASCII-safe output where every accent became \uXXXX — unescaping restores café and résumé. In each case the data is technically intact but unreadable until decoded. This tool is built for that decode path with three advantages. First, it is lenient about the surrounding quotes: paste a full literal or just the escaped body, and it does the right thing — because escaped strings are usually copied out of context. Second, it decodes \uXXXX correctly, combining surrogate pairs into proper astral characters like emoji, identical to a compliant JSON parser, so anything escaped by a serializer round-trips perfectly. Third, it runs 100% in your browser, so the log fields and payloads you decode — which often contain PII or secrets — never reach a server. To re-escape afterward, use our JSON Escape tool; to validate the decoded JSON, see the JSON Formatter. ``` // Escaped input (copied from a log, quotes optional) User said: \"it works!\"\nSession ended. // Unescaped output — readable again User said: "it works!" Session ended. // \uXXXX and surrogate pairs decode too caf\u00e9 \ud83d\ude00 -> café 😀 // JSON-in-JSON {\"a\":1} -> {"a":1} ``` #### FAQ **Q: What does this JSON unescape tool do?** A: It reverses JSON escaping: it takes a JSON-escaped string and decodes the escape sequences back into the characters they represent, entirely in your browser. \n becomes a real newline, \t a tab, \" a double quote, \\ a single backslash, \/ a forward slash, and \uXXXX the corresponding Unicode character (including surrogate pairs for emoji and astral scripts). The result is the original, human-readable text. You can paste the string with or without its surrounding double quotes — the tool detects and handles both. Everything runs client-side, so escaped payloads containing sensitive data never leave your machine. **Q: Do I need to include the surrounding double quotes?** A: No — the tool accepts both forms. If you paste a complete JSON string literal like "hello\nworld" (with the outer quotes), it is parsed directly. If you paste just the escaped body hello\nworld (no outer quotes), the tool wraps it for you before decoding. This is convenient because escaped strings are often copied out of the middle of a larger document, where the surrounding quotes were left behind. Either way you get the same decoded text. **Q: Is my data uploaded anywhere?** A: No. All decoding runs entirely in your browser using JavaScript — the escaped string you paste is never transmitted, stored, logged, or analyzed on any server. This makes the tool safe for decoding log fields, webhook payloads, and config values that may contain PII or secrets. You can confirm it in your browser's Network tab: pasting triggers zero network requests. No cookies capture your input and no third-party analytics read what you paste. **Q: Why do I get an 'invalid escape sequence' error?** A: The error means the input is not a valid JSON-escaped string, so it cannot be decoded unambiguously. The most common cause is a lone backslash followed by a character JSON does not recognize as an escape — for example \q or \x41 (JSON has no \x hex escape; it uses \u). Another cause is an unbalanced or stray double quote inside an unquoted input, which breaks the automatic wrapping. Check that every backslash starts a valid escape (\n \t \r \b \f \" \\ \/ \uXXXX) and that quotes are properly paired. **Q: How do I read a JSON object that was stored as a string (JSON-in-JSON)?** A: Paste the escaped string — for example {\"a\":1} — and the tool decodes it back to the real JSON {"a":1}, which you can then read or copy into a parser. This double-decoding is exactly what you need when a webhook envelope, message-queue record, or audit log stored a request body as an escaped string field. After unescaping, paste the result into our JSON Formatter to pretty-print and validate it. To go the other direction and escape JSON for embedding, use the JSON Escape tool. **Q: Does it correctly decode \uXXXX and emoji?** A: Yes. Each \uXXXX is decoded to its UTF-16 code unit, and consecutive high/low surrogate escapes are combined into the correct astral character — so \ud83d\ude00 becomes 😀 and \u00e9 becomes é. This is the same decoding any compliant JSON parser performs, which means a string escaped by our JSON Escape tool (or any serializer) round-trips back to the exact original here, byte for byte. If the escape you are holding has a different shape — a `U+` code point, an HTML entity, or the bare `u4e2d` that is left over when a log pipeline swallows the backslash — our Unicode converter recognises those too and turns them back into readable text. --- ### Free JSONPath Tester — Evaluate Queries Online URL: https://go-tools.org/tools/jsonpath-tester Test JSONPath expressions against any JSON, 100% private in your browser — no upload, no signup, no eval. RFC 9535 standard engine plus Classic Goessner mode, with Values, Paths and Both result views. #### What is a JSONPath tester? A JSONPath tester is a tool that lets you write a JSONPath expression, paste a JSON document, and see exactly which nodes the expression selects — both the matched values and their precise locations — without writing code or running a script. For developers it shortens the loop from minutes to milliseconds: tweak the path, watch the result change, and ship the query with confidence. JSONPath is a query language for JSON, the JSON analogue of XPath for XML. An expression is built from a small alphabet of selectors. $ is the root of the document. A dot or a bracket steps into a child: $.store or $['store']. The double dot .. is recursive descent — it searches every level of the tree. The wildcard * selects all elements or members. Brackets carry array indices ([0]), slices ([start:end:step]), unions ([a,b]), and filter expressions ([?(@.price < 10)], where @ is the element being tested). With those pieces you can pull a single field out of a deeply nested API response, assert on values in tests, drive data transforms in systems like Kubernetes, AWS Step Functions, and Azure Logic Apps, or extract structured data from irregular JSON — all without imperative traversal code. JSONPath is also famously inconsistent between implementations, which is exactly the problem a good tester surfaces before it reaches production. This tester ships two engines. The default is an RFC 9535 engine: RFC 9535 is the IETF's February 2024 formal specification of JSONPath, the first time the language was precisely standardized after fifteen years of divergent implementations. It defines an exact grammar, the concept of normalized paths for results, and five standard functions — length(), count(), match(), search(), value(). Our RFC 9535 engine is a zero-dependency implementation that uses no eval, so it parses and interprets expressions with its own grammar instead of compiling them to JavaScript. The second engine is Classic (Goessner), the de facto 2007 dialect that most older online tools and libraries implement; switch to it to reproduce results from a tool like jsonpath.com or to run an expression you copied from legacy code. The two dialects agree on common paths but diverge in the edge cases — filter whitespace and quoting, union ordering, how missing members compare, and which functions exist — so being able to flip between them in one place is the fastest way to diagnose why an expression behaves differently than you expected. What the tester surfaces beyond raw values: the result of a JSONPath query is a nodelist, and this tool can show it three ways. The Values view renders the matched nodes as a JSON array, exactly what you would consume in code. The Paths view renders each match's normalized path — a canonical, bracket-quoted location such as $['store']['book'][0]['title'] that uniquely identifies where in the document the value lives, no matter how the expression was written. Two expressions that select the same node produce the same normalized path, which makes the Paths view invaluable for debugging. The Both view shows values and paths side by side. A stats line reports how many nodes matched. Security is a first-class concern here. Many online JSONPath evaluators run on a server, or embed a library that evaluates filter predicates with JavaScript eval — the design that produced remote-code-execution vulnerabilities tracked as CVE-2024-21534 and CVE-2025-1302 in widely used JSONPath packages. This tool uses no eval at all. The RFC 9535 engine has no eval path, and the Classic engine is built on a patched, pinned release of jsonpath-plus with eval explicitly disabled. That closes the RCE class of bugs and lets the tool run under a strict Content-Security-Policy that forbids unsafe-eval. Every evaluation is local: your JSON and your expression never leave the page, are never logged, and are never stored on disk — only your engine and view preferences persist to localStorage. That makes the tool safe for proprietary API payloads, redacted logs, internal config, and any data with a schema you would not paste into a server-backed service. If JSON wrangling is your task, pair this with the other JSON tools on the site: format and pretty-print your input with the JSON Formatter, compare two documents with the JSON Diff, check a payload against a schema with the JSON Schema Validator, or turn a sample response into typed interfaces with JSON to TypeScript. ``` // The expression you build in this tester maps straight onto the // RFC 9535 reference library used under the hood. import { query, paths } from 'jsonpath-rfc9535'; const document = { store: { book: [ { title: 'Sayings of the Century', author: 'Nigel Rees', price: 8.95 }, { title: 'Sword of Honour', author: 'Evelyn Waugh', price: 12.99 }, { title: 'Moby Dick', author: 'Herman Melville', price: 8.99 }, { title: 'The Lord of the Rings', author: 'J. R. R. Tolkien', price: 22.99 } ] } }; // Values: query(document, path) returns the matched values directly. const titles = query(document, '$.store.book[*].title'); // → ['Sayings of the Century', 'Sword of Honour', 'Moby Dick', 'The Lord of the Rings'] // Filter: books cheaper than 10. const cheap = query(document, '$.store.book[?(@.price < 10)].title'); // → ['Sayings of the Century', 'Moby Dick'] // Normalized paths: paths(document, path) returns where each match lives. const authorPaths = paths(document, '$..author'); // → ["$['store']['book'][0]['author']", "$['store']['book'][1]['author']", ...] // RFC 9535 functions like length() are used INSIDE filters, not as a segment. const longTitles = query(document, '$.store.book[?length(@.title) > 15]'); // → the two books whose title is longer than 15 characters ``` #### FAQ **Q: Is my JSON or JSONPath expression sent to your server?** A: No. Every evaluation runs in JavaScript inside your browser. Your JSON document and your JSONPath expression are not uploaded, not logged, not stored on disk, and not sent to any third party. Only your UI preferences — the active engine (RFC 9535 or Classic) and the result view (Values / Paths / Both) — are saved to localStorage so the page remembers them next visit; the JSON and the expression themselves are never persisted. You can verify by opening DevTools → Network: typing in either box fires zero requests. That makes this tool safe for proprietary API payloads, redacted log samples, internal config, and anything else you would not paste into a server-backed evaluator like jsonpath.com. **Q: What is JSONPath and what is it used for?** A: JSONPath is a query language for JSON, the same way XPath is a query language for XML. You write a path expression — for example $.store.book[*].author — and the evaluator returns every value in the document that the path selects. It is used to pull specific fields out of API responses, to assert on values in integration tests, to configure data transforms in tools like Jenkins, Kubernetes, AWS Step Functions, and Azure Logic Apps, and to extract data from large or irregular JSON without writing imperative traversal code. An expression is built from an axis of selectors: $ (the root), . or [] (child access), .. (recursive descent), * (wildcard), [start:end:step] (array slice), [a,b] (union), and [?()] (filter). This tester evaluates the expression live and shows both the matched values and their normalized paths. **Q: What is the difference between RFC 9535 and the classic Goessner syntax?** A: Classic JSONPath is the de facto syntax Stefan Goessner published in 2007. It became widely implemented but was never formally standardized, so subtle behaviors — how filters are written, how unions and the root function work, how absent values compare — diverged across libraries. RFC 9535, published by the IETF in February 2024, is the first formal specification of JSONPath. It nails down a precise grammar, defines normalized paths for results, and adds standard functions (length, count, match, search, value). The two are close but not identical: RFC 9535 is stricter about whitespace and quoting in filters, defines comparison semantics for missing members, and rejects some loose constructs the classic dialect tolerated. This tool defaults to the RFC 9535 engine (a zero-dependency, no-eval implementation) and lets you switch to a Classic (Goessner) engine for backward compatibility. **Q: Why does the same expression return different results in the two engines, and how do I use an expression copied from jsonpath.com?** A: Because RFC 9535 and the classic Goessner dialect have genuinely different rules in the edge cases — filter whitespace and quoting, union ordering, how missing members compare, and which functions exist. An expression written for one engine can match differently (or fail to parse) in the other. If you copied an expression from an older tool such as jsonpath.com, jsonpath-plus, or a Jayway-based service, switch the engine toggle at the top to Classic (Goessner): that mode runs a Goessner-compatible evaluator (built on jsonpath-plus, constructed with eval disabled) and will reproduce the behavior you saw in the source tool. If you are writing a new expression or targeting a system that advertises RFC 9535 compliance, keep the default RFC 9535 engine. The cheat sheet and built-in examples are written to evaluate identically in both engines so you have a known-good starting point. **Q: How do filter expressions [?()] work?** A: A filter selector keeps only the array elements (or object members) for which a predicate is true. Inside the filter, @ refers to the current element being tested. $.store.book[?(@.price < 10)] returns every book whose price member is less than 10. You can compare against literals (@.isbn, @.category == 'fiction'), combine conditions with && and ||, test for the existence of a member (@.isbn selects elements that have an isbn at all), and in RFC 9535 use the function extensions inside the predicate (?(length(@.tags) > 2)). Comparison operators are ==, !=, <, <=, >, >=. RFC 9535 is precise about types: comparing a missing member to a value is well-defined and does not throw. The classic dialect is looser about whitespace, so [?(@.price<10)] and [?(@.price < 10)] are both accepted there; RFC 9535 follows its grammar exactly. **Q: What does recursive descent (..) do?** A: The .. operator searches every level of the document, not just the immediate children. $..author collects every author member wherever it occurs — inside the top-level object, inside arrays, inside nested objects, at any depth. It is the fastest way to extract a field from a deeply nested or irregularly shaped structure when you do not want to (or cannot) spell out the full path. You can follow .. with any selector: $..book[*] finds every element of every book array anywhere in the tree, $..* enumerates every value in the document, and $..['price'] gathers all price members. Recursive descent can match a lot — switch to the Paths view to see exactly where each result came from via its normalized path. **Q: What are the RFC 9535 functions length(), count(), match(), search(), and value()?** A: RFC 9535 defines five standard function extensions, and the key rule is that they are only callable inside a filter expression [?...] — never as a standalone path segment. Writing $.store.book.length() is not valid RFC 9535 and the standard grammar rejects it (that segment-call form is a jsonpath-plus extension, not part of the spec). length() returns the length of a string, array, or object, so you use it to filter by size: $.store.book[?length(@.title) > 15] keeps books whose title is longer than 15 characters. count() returns the number of nodes a nodelist contains, again inside a filter: $.store.book[?(count(@.authors) > 1)]. match() tests whether a string matches a regular expression against the whole value, and search() tests for a match anywhere within the string — both take an I-Regexp pattern. value() converts a single-node nodelist to its value so it can be used in a comparison. These functions are part of the RFC 9535 standard, so they are available in the default engine; the Classic (Goessner) engine does not implement them. If a function-based expression fails, confirm you are calling it inside a filter and that the engine toggle is set to RFC 9535. **Q: How do array slices [start:end:step] work?** A: Slices use the same half-open convention as Python and JavaScript: [start:end] selects from index start up to but not including index end, so [0:2] returns the first two elements (indices 0 and 1). Omit a bound to run to the edge — [2:] from index 2 onward, [:3] for the first three. A negative index counts from the end: [-1:] selects the last element. The optional third field is a step — [::2] takes every other element, [::-1] reverses (in engines that support negative steps). The exclusive end bound is the single most common slice bug; the Paths view shows the exact index of every selected element so you can confirm the boundary at a glance. **Q: What is a union selector and how do I select multiple keys at once?** A: A union selector lists several names or indices inside one bracket and gathers all of them: $['title','author'] selects both members from an object, and $.store.book[0,2] selects the first and third elements of the book array. You can mix it with other selectors — $.store.book[*]['title','price'] pulls the title and price of every book. Unions are handy when you want a fixed projection of a few fields rather than a whole object or a wildcard. The Both view is the clearest way to read a union result because it pairs each selected value with its normalized path, so you can tell which name or index produced each entry. **Q: Can I share a JSONPath query and its JSON via a link?** A: Yes — and the link involves no server roundtrip. Click Copy link in the action bar: the tester encodes the JSON, the expression, the active engine, and the result view into the URL hash. Anyone who opens the link hydrates the page with the same state, locally on their own machine. Because the data lives in the hash fragment, it is never transmitted to the go-tools.org server — browsers do not send the fragment in HTTP requests — and it never appears in our access logs. The link length grows with the size of the JSON, so for large documents share just the expression and let the recipient paste their own data, or use the Upload button to load a file locally. This makes permalinks safe for collaborative debugging without exposing the payload to any backend. **Q: Is there a maximum JSON size?** A: Evaluation is bounded by your browser's memory rather than a hard cap, but the practical sweet spot is documents up to a few megabytes — comfortably larger than almost any single API response. Very large arrays with broad selectors (a $..* recursive wildcard over tens of thousands of nodes) will produce a large result set that takes longer to render; narrow the expression to keep the output readable. The Upload button reads a .json or .txt file entirely in the browser (it is never sent anywhere), and Format JSON re-indents the input so you can read the structure before querying it. For multi-megabyte data pipelines, validate your expression here against a representative slice, then run the same path in your application code or in a CLI tool like jq. **Q: How is this different from jsonpath.com and is it safe — no eval?** A: Four differences. (1) Privacy: jsonpath.com and most online evaluators run on a server or embed a library that evaluates filters with JavaScript eval; this tool runs entirely in your browser and uses no eval at all. The default RFC 9535 engine is a zero-dependency implementation with no eval path, and the Classic (Goessner) engine is built on jsonpath-plus pinned to a patched release with eval explicitly disabled — closing the remote-code-execution class of bugs tracked as CVE-2024-21534 and CVE-2025-1302. That also means the tool works under a strict Content-Security-Policy. (2) Standards: this is one of the few online testers offering a true RFC 9535 engine, not just the legacy Goessner dialect. (3) Dual-engine: you can switch between RFC 9535 and Classic to compare results or to run expressions copied from older tools, side by side. (4) Languages: the interface is available in 15 languages. If you only need quick legacy-syntax checks, jsonpath.com still works; for standards-compliant, private, no-eval evaluation, this is the safer choice. **Q: What do the Values, Paths, and Both views show?** A: The result of a JSONPath query is a nodelist — a set of nodes inside your document. The Values view renders those nodes as a JSON array of the matched values, exactly what you would consume in code. The Paths view renders the normalized path of each match instead — a canonical, bracket-quoted location like $['store']['book'][0]['title'] that uniquely identifies where in the document the value lives, regardless of how your expression was written. Normalized paths are an RFC 9535 concept and are invaluable for debugging: two different expressions that select the same node produce the same normalized path. The Both view shows the two side by side so you can match each value to its location at a glance. Your chosen view persists across sessions via localStorage. **Q: Does this work offline, and what about a Content-Security-Policy?** A: Yes on both counts. Because every evaluation runs in your browser with no network calls, the tool keeps working once the page has loaded even if you go offline. And because neither engine uses eval or the Function constructor to evaluate filter expressions, the tool runs under a strict Content-Security-Policy that forbids unsafe-eval — the policy that many security-conscious organizations enforce and that breaks eval-based JSONPath libraries. The RFC 9535 engine parses and interprets expressions with its own grammar rather than compiling them to JavaScript, and the Classic engine is configured with eval disabled. If you need to evaluate JSONPath inside a hardened internal environment, this tool is designed to run there without policy exceptions. --- ### JWT Decoder URL: https://go-tools.org/tools/jwt-decoder Decode JWT tokens online with our free JWT decoder. Instantly inspect header, payload, signature, expiration, algorithm, and claims. 100% browser-based — your token never leaves your device. No signup, no tracking. #### What is a JWT? A JSON Web Token, or JWT (pronounced 'jot'), is a compact, URL-safe token format for carrying claims between two parties. It is defined in RFC 7519 and is the dominant credential format used by OAuth 2.0 access tokens, OpenID Connect ID tokens, API keys in modern auth providers (Auth0, Okta, Clerk, Supabase, Firebase), and inter-service tokens in microservice architectures. "JSON Web Token (JWT) is a compact claims representation format intended for space-constrained environments such as HTTP Authorization headers and URI query parameters." — RFC 7519, Section 1 A JWT is three Base64URL-encoded JSON objects joined by dots: header.payload.signature. The header describes how the token is signed (the alg claim — for example, HS256 or RS256 — and the typ claim, usually 'JWT'). The payload carries the claims: registered claims like iss, sub, aud, exp, iat, plus whatever custom claims the issuer needs (role, scope, email, tenant ID). The signature is a cryptographic proof, computed over the header and payload with the issuer's secret or private key, that lets the recipient detect tampering. Crucially, a JWT is encoded, not encrypted. Anyone with the token can read its payload — decoding is just Base64URL and JSON parsing. The security guarantee comes from the signature: an attacker can read a JWT, but cannot produce a different JWT that passes signature verification without the signing key. That is why JWTs are safe to pass over the network, but unsafe to fill with secrets. A JWT decoder shows you exactly what a token contains — algorithm, claims, expiration — without touching the signature. It is the fastest way to answer 'is this token expired?', 'what role does this user have?', 'which issuer minted this token?', or 'is this an alg:none token I should reject?'. All decoding in this tool runs locally in your browser, so pasting a live production token is safe. JWT work often pairs with other developer tools. You may need to decode a Base64URL-wrapped segment when debugging a malformed token, URL-decode an Authorization header after capturing it from a proxy, or convert the exp claim to a human date manually. For a deeper walkthrough of how JWTs are signed, verified, and rotated in production, see our Base64 fundamentals guide — Base64URL is the foundation every JWT is built on. ``` // Decode a JWT in the browser — header & payload only function decodeJwt(token) { const [h, p, s] = token.split('.'); const pad = (seg) => seg + '==='.slice((seg.length + 3) % 4); const decode = (seg) => JSON.parse( atob(pad(seg).replace(/-/g, '+').replace(/_/g, '/')) ); return { header: decode(h), payload: decode(p), signature: s }; } const { header, payload } = decodeJwt(token); console.log(header); // → { alg: 'HS256', typ: 'JWT' } console.log(payload); // → { sub: 'user_123', exp: 1999999999, ... } // Expiration check const expired = payload.exp * 1000 < Date.now(); ``` #### FAQ **Q: How do I decode a JWT token online?** A: Paste the full JWT — all three dot-separated segments (header.payload.signature) — into the decoder above. Decoding happens instantly in your browser: the header and payload are Base64URL-decoded to readable JSON, and the signature is displayed as a raw string. A status row surfaces the signing algorithm, issued-at time, and expiration, so you can spot an expired token at a glance. To decode a JWT manually, split the token on dots, Base64URL-decode the first two segments, and parse them as JSON — anyone with the token can read its claims because the payload is encoded, not encrypted. This decoder is safe to use with production tokens because nothing ever leaves your device: no network request, no logging, no tracking. **Q: What is a JWT (JSON Web Token)?** A: A JSON Web Token (JWT) is a compact, URL-safe credential that carries claims between two parties. Defined in RFC 7519, it consists of three Base64URL-encoded sections joined by dots: the header (algorithm and token type), the payload (claims — data about the user and the token itself), and the signature (a cryptographic proof that the token was issued by a trusted party). JWTs are the standard way to represent access tokens in OAuth 2.0 and ID tokens in OpenID Connect. **Q: Is my token safe with this JWT decoder?** A: Yes. All decoding runs in your browser using native JavaScript (atob and TextDecoder). Your token is never sent to a server, never logged, never stored, and never used for analytics. There are no cookies and no tracking. This matters because JWTs can contain live access tokens — pasting them into a remote debugger would be equivalent to handing over a credential. Our tool is safe to use with production tokens. **Q: How does a JWT decoder work?** A: A JWT decoder splits the token by dots into three parts, Base64URL-decodes the header and payload, and parses them as JSON. The signature is left as an opaque Base64URL string because verifying it requires the issuer's secret or public key — something a client-side decoder cannot do safely. This means decoding is instant and reveals the claims, but you must verify the signature server-side with the correct key before trusting anything inside. **Q: Can this tool verify a JWT signature?** A: No, and it intentionally does not. Signature verification requires the issuer's secret (for HMAC) or public key (for RSA/ECDSA/EdDSA), which should never be pasted into a public web tool. Verification must happen on your server, in your auth middleware, or inside an SDK that has access to your JWKS endpoint. This decoder is for inspecting what a token claims — it does not imply the token is authentic or untampered. **Q: What are iat, exp, nbf, iss, aud, sub, and jti?** A: These are the registered claims from RFC 7519. iat (issued at) is the Unix timestamp when the token was created. exp (expiration) is when the token stops being valid — this tool converts it to a human-readable date and marks the token as expired if exp is in the past. nbf (not before) is the earliest time the token can be used. iss (issuer) identifies who created the token. aud (audience) names the intended recipient. sub (subject) identifies the principal — usually a user ID. jti is a unique token ID used to prevent replay. Application-specific claims (role, scope, email, name) live alongside these. **Q: My JWT is expired — why does the decoder still decode it?** A: Decoding is not the same as validating. A JWT decoder reads the content regardless of expiration, so you can inspect an expired or otherwise invalid token to debug why it was rejected. The 'Expired' badge in this tool compares the exp claim against your local clock and flags tokens whose exp is in the past. A real authentication server would reject the token outright — but a decoder that refused to show expired tokens would be useless for debugging. **Q: What is the difference between JWT, JWS, and JWE?** A: JWT is the general concept — a JSON object encoded as a compact token. JWS (RFC 7515) is a signed JWT: the payload is readable by anyone who decodes it, and a signature proves it was not tampered with. This is by far the most common JWT you will encounter. JWE (RFC 7516) is an encrypted JWT: the payload itself is ciphertext and cannot be decoded without the decryption key. This tool decodes JWS tokens. A JWE token will decode only to its header — the encrypted payload is not readable without the key. **Q: Why is alg:none dangerous?** A: A JWT with alg:none has no signature — anyone can construct one, claiming to be any user. Early JWT libraries accepted alg:none by default, leading to a well-known class of authentication bypasses where an attacker would strip the signature, set alg to none, and forge an admin token. Every mature JWT library now rejects alg:none unless explicitly allowed, and you should never accept it for authenticated requests. This decoder will still show you an alg:none token, because inspecting one during debugging is legitimate — but treat any such token received in production as hostile. **Q: Which algorithms does this JWT decoder support?** A: Decoding the header and payload works for every algorithm, because decoding only needs Base64URL and JSON parsing — it is algorithm-agnostic. The tool correctly reads tokens signed with HS256, HS384, HS512 (HMAC), RS256, RS384, RS512 (RSA + SHA), PS256, PS384, PS512 (RSA-PSS), ES256, ES384, ES512 (ECDSA), EdDSA (Ed25519/Ed448), and unsigned tokens (alg:none). Only signature verification is algorithm-specific, and this tool does not perform verification. **Q: Should I store JWTs in localStorage or cookies?** A: Prefer HttpOnly, Secure, SameSite=Strict cookies for session tokens. A token in localStorage is readable by any JavaScript that runs on the page, so a single XSS vulnerability leaks every active session. HttpOnly cookies are invisible to JavaScript, which shrinks the blast radius of XSS to what an attacker can do within a live page — not a stolen token they can replay for days. If you must use localStorage (for example, for cross-domain apps), keep access token lifetimes short (minutes, not hours) and use a separate refresh token in an HttpOnly cookie. **Q: How do I decode a JWT in Node.js, Python, or Go?** A: Node.js: jsonwebtoken.decode(token) for read-only, jsonwebtoken.verify(token, key) for verification. Python: PyJWT.decode(token, options={'verify_signature': False}) to read, pass a key to verify. Go: jwt.ParseUnverified(token, claims) for read-only, jwt.Parse(token, keyFunc) for verification. In every language, never verify with options={'verify_signature': False} in production code — that is what this web tool does deliberately for debugging, and it is only safe when you are inspecting, not authenticating. **Q: What is the maximum size of a JWT?** A: The JWT standard does not impose a hard limit, but headers in most web servers default to around 8 KB. Keep tokens under 4 KB so they fit comfortably in Authorization headers and cookies. If your token is larger than that, you are probably putting too many claims in the payload — move bulky data behind an opaque session ID and fetch it from your backend when needed. Bloated JWTs also get costly on every request because they are sent with every API call. **Q: I pasted my token and got 'Invalid JWT format' — what's wrong?** A: A valid JWT has exactly three parts separated by dots: header.payload.signature. Common causes: (1) you accidentally copied only the payload segment, (2) whitespace or newlines were pasted in the middle, (3) the token was truncated in transit (common with terminal wrapping), (4) the token is a JWE where the format is header.encryptedKey.iv.ciphertext.tag (five segments), or (5) the token was URL-encoded and you need to URL-decode it first. Check the raw value your API returned — most editors show invisible characters on hover. **Q: Can I decode a JWT without the secret key?** A: Yes — the header and payload are Base64URL-encoded, not encrypted. Anyone who has the token can read its claims without any key. This is by design: the payload is meant to be readable so the recipient can make authorization decisions from it. The secret or public key is only required to verify that the token has not been tampered with. This is why you must never put sensitive data (passwords, private keys, PII beyond what the recipient already knows) inside a JWT payload. **Q: My JWT works in Postman but my backend rejects it — how do I debug?** A: Decode the token here and check: (1) exp — is it in the future relative to the server's clock? Server clock skew is a frequent culprit. (2) iss / aud — do they exactly match what your backend expects? A mismatch on aud is the most common false-negative. (3) alg — does your verification code allow that algorithm? An HS256 token will fail against a library configured for RS256 only. (4) kid — if you use key rotation, is the key ID in the header present in your JWKS? (5) signature — have you pasted the right secret/public key? This decoder surfaces (1), (2), (3), and (4) in the header and payload views so you can eliminate them quickly. --- ### JWT Encoder & Generator URL: https://go-tools.org/tools/jwt-encoder Free online JWT generator & encoder. Build the header and payload, sign with HS256, RS256, or ES256 instantly. 100% in-browser — your secret and key never leave your device. #### What is a JWT Encoder? A JWT encoder builds and cryptographically signs a JSON Web Token from a header and a payload of claims. A JWT, defined in RFC 7519, is three Base64URL-encoded sections joined by dots: header.payload.signature. The header names the signing algorithm; the payload carries the claims (who the token is about, what it can do, when it expires); and the signature is a cryptographic proof, computed over the header and payload with a secret or private key, that lets a recipient detect tampering. "JSON Web Token (JWT) is a compact claims representation format intended for space-constrained environments such as HTTP Authorization headers and URI query parameters." — RFC 7519, Section 1 Encoding is the inverse of decoding. A JWT decoder reads an existing token's claims; an encoder takes claims you supply and produces a brand-new signed token. The signing step is what separates a real JWT from arbitrary Base64 — without a valid signature, no verifier will accept the token. This tool signs using the browser's native Web Crypto API across the HMAC (HS), RSA (RS, PS), and ECDSA (ES) families, so the entire operation happens on your device with zero dependencies and zero network calls. Developers reach for a JWT encoder constantly: to mint a token that exercises a protected API endpoint, to reproduce the exact claim shape an OAuth server issues so a bug can be debugged, to build fixtures for integration tests, or to hand a teammate a ready-to-use Bearer token for a curl command. Because the payload is encoded, not encrypted, a JWT is safe to pass over the network but must never carry secrets — anyone with the token can read every claim, and only the signature stops them from changing one. JWT work pairs naturally with other developer tools. After signing, decode the token to confirm its claims, convert exp and iat between Unix time and human dates, or compute a SHA-256 hash when you need the underlying hash function that HS256's HMAC is built on. Because every JWT segment is Base64URL-encoded, a Base64 tool is handy when you inspect a token by hand; for an in-depth look at the encoding, see our Base64 fundamentals guide. ``` // Sign a JWT in the browser with the Web Crypto API (HS256) async function encodeJwt(payload, secret) { const b64url = (bytes) => btoa(String.fromCharCode(...new Uint8Array(bytes))) .replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''); const enc = (obj) => b64url(new TextEncoder().encode(JSON.stringify(obj))); const header = { alg: 'HS256', typ: 'JWT' }; const signingInput = `${enc(header)}.${enc(payload)}`; const key = await crypto.subtle.importKey( 'raw', new TextEncoder().encode(secret), { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']); const sig = await crypto.subtle.sign( 'HMAC', key, new TextEncoder().encode(signingInput)); return `${signingInput}.${b64url(sig)}`; } const token = await encodeJwt({ sub: 'user_123', exp: 1999999999 }, 'my-secret'); // → eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyXzEyMyIsImV4cCI6MTk5OTk5OTk5OX0.... ``` #### FAQ **Q: How do I generate a JWT online?** A: Edit the payload JSON in the box above, choose a signing algorithm (HS256 is the default and needs only a secret), and enter your secret or paste a PKCS8 PEM private key. The signed token appears instantly, with the header, payload, and signature segments color-coded so you can copy the whole thing with one click. Signing runs entirely in your browser using the native Web Crypto API — there is no Generate button to wait on and no request to a server, so it is safe to sign tokens with real keys during development. **Q: What is a JWT generator?** A: A JWT generator is a tool that builds and cryptographically signs a JSON Web Token from a header and a payload of claims, producing a header.payload.signature string you can use as a Bearer token. It is the inverse of a JWT decoder: instead of reading an existing token, it creates a new one signed with your secret (HS256) or private key (RS256/ES256). This generator runs entirely in your browser, so the token is produced instantly and your signing key never leaves your device. **Q: Is this JWT generator free and safe to use?** A: Yes — it is completely free, with no signup, no ads, and no tracking. It is safe because all signing happens locally in your browser via the Web Crypto API: your payload, secret, and private key are never uploaded, logged, or stored, and the tool makes no network requests at all. That makes it suitable even when you are working with sensitive keys, though using disposable test keys is always the safest habit. **Q: Is it safe to enter my secret or private key here?** A: Yes. Signing happens locally in your browser; your secret and private key are never sent to a server, never logged, never stored, and never used for analytics. There are no cookies and no tracking. This matters because a JWT signing key can mint valid credentials — pasting it into a remote tool would be equivalent to handing over the keys to your auth system. Because everything runs client-side, this encoder is safe to use with production keys, but you should still prefer disposable or test keys whenever possible. **Q: What is the difference between HS256 and RS256?** A: HS256 (HMAC-SHA256) uses a single shared secret to both sign and verify. It is simple and fast, but every party that can verify the token can also create one, so the secret must stay on trusted servers only. RS256 (RSA-SHA256) uses a key pair: you sign with a private key and others verify with the public key. This lets you distribute the public key freely — to client apps, partner services, or a JWKS endpoint — without giving anyone the ability to forge tokens. Use HS256 for symmetric, single-owner systems; use RS256 or ES256 when verifiers should not be able to mint tokens. **Q: Which algorithms does this JWT encoder support?** A: It signs with HS256, HS384, HS512 (HMAC with a shared secret), RS256, RS384, RS512 (RSA PKCS#1 v1.5), PS256, PS384, PS512 (RSA-PSS), and ES256, ES384, ES512 (ECDSA on P-256, P-384, and P-521). All of them are produced with the browser's native Web Crypto API, so there are no third-party libraries and nothing leaves your machine. HMAC algorithms take a text or Base64 secret; the RSA and ECDSA families take a PKCS8 PEM private key. **Q: How do I set the exp (expiration) claim?** A: Add an exp claim to the payload as a Unix timestamp in seconds — for example "exp": 1999999999. The quickest way is the exp +1h chip below the payload, which inserts an expiration one hour from now. You can also add iat (issued-at) and nbf (not-before) the same way. Remember that exp is in seconds, not milliseconds, and that verifiers compare it against their own clock, so keep server times in sync to avoid premature rejections. To convert a human date to a Unix timestamp, use our Unix timestamp converter. **Q: How do I get a PKCS8 PEM private key for RS256 or ES256?** A: For RSA: openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out private.pem. For ECDSA P-256: openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -out ec-private.pem. Both commands emit a PKCS8 PEM block beginning with -----BEGIN PRIVATE KEY-----, which is exactly what this tool expects. Paste the whole block, including the header and footer lines. The matching public key — used to verify the token — can be derived with openssl pkey -in private.pem -pubout. If you would rather not touch the command line, our RSA key pair generator produces the same PKCS8 block in your browser, and can switch it to the traditional PKCS1 layout when something else needs that. **Q: How do I verify the token I just generated?** A: Paste it into our JWT decoder to confirm the header and payload decode as expected. To verify the signature, use your server or an SDK with the correct key: jwt.verify(token, secretOrPublicKey, { algorithms: ['HS256'] }) in Node.js, PyJWT.decode(token, key, algorithms=['RS256']) in Python, or jwt.Parse(token, keyFunc) in Go. Never verify with an empty algorithm list or with verify_signature=False in production — always pin the exact algorithm you expect. **Q: What should I put in the payload?** A: Keep it lean. The registered claims from RFC 7519 are iss (issuer), sub (subject — usually a user ID), aud (audience), exp (expiration), nbf (not before), iat (issued at), and jti (token ID). Alongside these you can add application claims like role, scope, or email. Do not put secrets in the payload — a JWT is encoded, not encrypted, so anyone with the token can read every claim. Keep tokens under about 4 KB so they fit in Authorization headers and cookies. **Q: Is a JWT encrypted?** A: No. A standard signed JWT (a JWS) is Base64URL-encoded, not encrypted. The signature proves the token has not been tampered with and was issued by someone holding the key, but the header and payload are fully readable by anyone who has the token. If you need the payload itself to be confidential, you need a JWE (encrypted JWT), which is a different format. This tool produces signed JWS tokens, the kind used for the vast majority of authentication and authorization flows. **Q: Why is my RS256 or ES256 signing failing?** A: The most common causes are: (1) the key is not in PKCS8 format — convert a traditional -----BEGIN RSA PRIVATE KEY----- (PKCS1) key with openssl pkcs8 -topk8 -nocrypt -in old.pem -out pkcs8.pem; (2) the curve does not match the algorithm — ES256 needs a P-256 key, ES384 needs P-384, ES512 needs P-521; (3) you pasted a public key or a certificate instead of the private key; or (4) the key is encrypted with a passphrase, which the Web Crypto API cannot import directly. Decrypt it first with openssl pkey and paste the unencrypted PKCS8 block. **Q: Does this tool support the alg:none unsigned token?** A: No, and deliberately so. An alg:none token has no signature, which means anyone can forge one — it is the root of a classic JWT authentication-bypass vulnerability. Because the entire point of an encoder is to produce a signed token, this tool only offers real signing algorithms. If you are studying alg:none for security research, you can construct one by hand by Base64URL-encoding the header and payload and leaving the signature segment empty — the token still ends with a trailing dot (header.payload.) — but you should never accept such a token in production. **Q: Can I generate a JWT in code instead?** A: Yes. In Node.js: jsonwebtoken.sign(payload, secret, { algorithm: 'HS256', expiresIn: '1h' }). In Python: jwt.encode(payload, key, algorithm='RS256') with PyJWT. In Go: jwt.NewWithClaims(jwt.SigningMethodES256, claims).SignedString(privateKey). This tool is the fastest way to produce a token for a quick test, a curl request, or a fixture — but in application code you should generate tokens server-side with a maintained library and a key loaded from your secrets manager, never hard-coded. --- ### Free JWT Secret Generator — HS256/384/512 URL: https://go-tools.org/tools/jwt-secret-generator Generate a strong, RFC-correct JWT secret for HS256/384/512 — 100% in your browser, never sent to a server. base64url, base64 or hex; copy for .env. #### What is a JWT secret generator? A JWT secret generator produces the random signing key that an HMAC-signed JSON Web Token uses to prove it has not been tampered with. When you sign a token with HS256, HS384, or HS512, the algorithm runs HMAC over the token's header and payload using a single shared secret; the verifier recomputes the same HMAC with the same secret and accepts the token only if the signatures match. The whole security of that scheme rests on the secret being long and unpredictable — which is exactly what this tool creates: a high-entropy random string, generated in your browser, sized correctly for the algorithm you pick. It is worth being precise about what this tool does and does not do. It generates the secret key — the value you put in your JWT_SECRET environment variable — not a finished token. If you want to assemble a header and payload and sign them into an actual JWT, that is the job of the JWT Encoder; to take an existing token apart and verify its signature, use the JWT Decoder. Think of the secret as the key and the encoder as the lock it operates: you generate the key once, store it safely, and reuse it to sign and verify many tokens. How long should the key be? The answer is fixed by the spec, not by preference. RFC 7518 §3.2 — the JSON Web Algorithms standard — requires that an HMAC key be at least as large as the hash output: "A key of the same size as the hash output (for instance, 256 bits for HS256) or larger MUST be used." That gives a clean table the generator follows automatically: | Algorithm | HMAC | Min bytes | Min bits | hex chars | base64 chars | base64url chars | |-----------|------|-----------|----------|-----------|--------------|-----------------| | HS256 | HMAC-SHA-256 | 32 | 256 | 64 | 44 | 43 | | HS384 | HMAC-SHA-384 | 48 | 384 | 96 | 64 | 64 | | HS512 | HMAC-SHA-512 | 64 | 512 | 128 | 88 | 86 | The character counts come from the RFC 4648 encodings of those byte lengths: hex doubles the byte count; base64 expands by 4⁄3 with padding; base64url drops the padding, so a 32-byte key is 43 base64url characters rather than 44. base64url is JWT's native encoding — URL-safe alphabet, no padding — which is why it is the default output here; a secret in base64url can sit in a header, a URL, or a config value with no escaping. Randomness is the part you cannot compromise on. This generator draws its bytes from crypto.getRandomValues, the browser's cryptographically secure pseudo-random number generator, the same primitive that backs Web Crypto key generation. It never uses Math.random, which is fast but predictable and completely unsuitable for a signing key — a predictable RNG means a guessable secret, and a guessable secret means forgeable tokens. Because HMAC verification happens locally with the shared secret, an attacker who captures a token can brute-force a weak key offline with no rate limit; tools such as hashcat (mode 16500) and jwt_tool exist precisely to do this. A full-entropy 32-byte random key, on the other hand, is computationally out of reach. The lesson is blunt: never use a password, a dictionary word, or a hand-typed string as a JWT secret — generate a random one. Finally, generating the key client-side is itself a security property. A signing secret should never be transmitted to a third party, not even the site that helps you create it. Every byte here is produced and encoded in your browser; nothing is uploaded, logged, or stored. When you are ready to ship the key, the Copy for .env button hands you a JWT_SECRET=… line, and if you need it folded into a larger configuration the JSON to .env converter can help. Generate, copy, store it in a secrets manager — and rotate it with a kid header and overlapping validity windows when the time comes. ``` // The secret you generate here goes straight into your signing code. // Node.js with jsonwebtoken — the JWT_SECRET env var holds the key. import jwt from 'jsonwebtoken'; const secret = process.env.JWT_SECRET; // e.g. base64url value from this tool // Sign a token with HS256 (HMAC-SHA-256). const token = jwt.sign({ sub: 'user-42', role: 'member' }, secret, { algorithm: 'HS256', expiresIn: '15m' }); // Verify it — pin the algorithm to a whitelist; never trust the token's alg. const payload = jwt.verify(token, secret, { algorithms: ['HS256'] }); // --------------------------------------------------------------- // Python with PyJWT — same secret, same algorithm pinning. // import jwt // token = jwt.encode({"sub": "user-42"}, key, algorithm="HS256") // payload = jwt.decode(token, key, algorithms=["HS256"]) # whitelist! // --------------------------------------------------------------- // Equivalent-strength CLI generation (32 bytes for HS256): // openssl rand -base64 32 // node -e "console.log(require('crypto').randomBytes(32).toString('base64url'))" // python -c "import secrets; print(secrets.token_urlsafe(32))" ``` #### FAQ **Q: Is my generated JWT secret sent to your server?** A: No. The secret is generated entirely in your browser with crypto.getRandomValues, the platform's cryptographically secure random number generator. The bytes are produced on your device, encoded locally, and shown only to you. Nothing is uploaded, nothing is logged, nothing is written to disk, and nothing is sent to any third party — open DevTools → Network and you will see zero requests fire when you click Regenerate or Copy. That means a key you generate here is yours alone the instant it appears; no server ever observes it. This is the whole point of generating a signing secret client-side rather than on a website that could, in principle, keep a copy of every key it hands out. **Q: How do I generate a secure JWT secret?** A: Three steps. First, pick the signing algorithm your application uses — HS256, HS384, or HS512 — and the generator immediately sizes the key to the RFC 7518 §3.2 minimum for that variant (32, 48, or 64 bytes), so you never hand-pick a number or risk an undersized key. Second, leave the encoding on base64url (JWT's native, URL-safe format) or switch to base64 or hex if your config loader expects one of those. Third, click Copy — or Copy for .env to get a ready-to-paste JWT_SECRET=… line — and store the value in a secrets manager or environment variable, never in source control. Because every byte comes from crypto.getRandomValues, a 32-byte key already carries 256 bits of entropy, which is far beyond the reach of the offline brute-force attacks that break human-chosen secrets. Prefer the command line? The tool also prints equivalent openssl, Node, and Python one-liners you can paste into a terminal. **Q: How long should an HS256 JWT secret be?** A: At least 32 bytes (256 bits). RFC 7518 §3.2 — the JSON Web Algorithms spec — states that for HMAC signatures "a key of the same size as the hash output (for instance, 256 bits for HS256) or larger MUST be used." HS256 signs with HMAC-SHA-256 (256-bit hash), so the minimum key is 32 bytes; HS384 uses HMAC-SHA-384 and needs at least 48 bytes (384 bits); HS512 uses HMAC-SHA-512 and needs at least 64 bytes (512 bits). This generator picks the correct minimum automatically when you choose the algorithm, and you can request a longer key. A shorter key is not just weaker — it violates the spec and some libraries will refuse to sign with it. **Q: What is the difference between base64url, base64, and hex, and which should I pick?** A: All three encode the same random bytes; they differ only in the character alphabet, not in entropy. base64url is JWT's native encoding — it uses a URL-safe alphabet (- and _ instead of + and /) and omits padding, so it never needs escaping in a token, a URL, or a header. That is why it is the default here. Standard base64 uses +, /, and = padding; choose it when a config loader or library specifically expects classic base64. hex (base16) writes each byte as two characters 0–f, producing a longer but unambiguous string that is handy when a system rejects non-alphanumeric characters. For a JWT_SECRET environment variable, base64url is the safest default; any of the three works as long as you store the exact string and feed it back to your signing library unchanged. **Q: Can a weak JWT secret be cracked?** A: Yes — and this is the single biggest risk with HMAC-signed JWTs. Because HS256/384/512 use one shared secret, anyone who holds a token can run an offline brute-force or dictionary attack against the signature with no rate limit and no server contact. Tools like hashcat (mode 16500 targets JWT) and jwt_tool are purpose-built for this; on commodity GPUs a low-entropy secret can typically fall in seconds to hours, and a dictionary word or a leaked password can fall almost immediately. A full-entropy 32-byte random key from this generator is far beyond the reach of brute force. Once an attacker recovers the secret they can forge any token — including one that claims admin privileges — so secret strength is not optional. Generate the signing key with a CSPRNG, never a human-chosen string. For a deeper treatment of weak-secret, alg-confusion, and token-replay attacks, see our guide to JWT security best practices. **Q: Can I use a password as my JWT secret?** A: You can, but you should not. A human-memorable password — even a long passphrase — carries far less entropy than 32 random bytes, which makes it a realistic target for the offline brute-force and dictionary attacks described above. JWT secrets are machine-to-machine credentials; nobody needs to memorize them, so there is no reason to trade entropy for memorability. Generate a random secret here, store it in a secrets manager or an environment variable, and let your application read it. If you need memorable credentials for a different purpose, that is a job for a Password Generator or, for storing user passwords, a one-way hash from the bcrypt Generator — not for a token-signing key. **Q: How do I rotate a JWT secret without breaking live tokens?** A: Rotate with overlap rather than a hard cutover. Add a key identifier (the kid header) to the tokens you sign so a verifier knows which secret to check against, and publish your active keys — typically through a JWKS endpoint your services read. To rotate: generate a new secret here, start signing new tokens with the new kid while still accepting the previous key for verification, and only retire the old key after every token signed with it has expired. That overlap window means no valid session is invalidated mid-flight. In a suspected compromise, skip the graceful overlap: rotate immediately, drop the old key from the accepted set, and force re-authentication so leaked tokens stop verifying at once. **Q: How is an HMAC (HS*) key different from an RSA or ECDSA (RS*/ES*) key?** A: They solve the same problem with opposite key models. HS256/384/512 are HMAC algorithms: they use one symmetric secret — the kind this tool generates — that both signs and verifies, so every party that can verify a token can also forge one. That is simple and fast and ideal when a single service both issues and checks its own tokens. RS* (RSA) and ES* (ECDSA) are asymmetric: they use a key pair, where a private key signs and a separate public key only verifies. You hand the public key to anyone who needs to validate tokens without ever exposing the signing key — the right choice when an identity provider issues tokens that many independent services verify. This generator produces HMAC symmetric secrets only — for the asymmetric side, our RSA key pair generator creates RSA, ECDSA and Ed25519 pairs in the browser. To assemble and sign an actual token with the key you generate, use the JWT Encoder; to inspect and verify one, use the JWT Decoder. --- ### Length Unit Converter — Metric, Imperial & More URL: https://go-tools.org/tools/length-converter Convert between 16 length units instantly — metric, imperial, nautical & astronomical. 1 inch = 2.54 cm. Free, private, runs in your browser. #### What Is a Length Unit Converter? A length unit converter is a tool that translates distance and length measurements between different units of measure — bridging the metric system (meter, kilometer, centimeter) and the imperial system (inch, foot, yard, mile), as well as specialized units used in science, navigation, and astronomy. The metric system, used by most of the world, is decimal-based: each unit differs from the next by a power of 10. The core unit is the meter, officially defined since 2019 by the International Bureau of Weights and Measures (BIPM) in the International System of Units (SI) as the distance light travels in a vacuum in exactly 1/299,792,458 of a second — anchoring the meter to a universal physical constant rather than any physical artifact. Prefixes like kilo- (1,000), centi- (0.01), milli- (0.001), micro- (0.000001), and nano- (0.000000001) scale the meter up or down. The imperial system, still common in the United States, uses inches, feet (12 inches), yards (3 feet), and miles (5,280 feet). Beyond everyday units, this converter supports nautical miles (used in aviation and maritime navigation), and astronomical units like the light year (9.461 trillion km), the astronomical unit (Earth-Sun distance, ~149.6 million km), and the parsec (~3.26 light years). It also includes the fathom (6 feet, used for water depth) and the furlong (220 yards, used in horse racing). All conversions use the internationally defined exact factors established by the 1959 international yard and pound agreement, as published by BIPM and NIST, ensuring precise results traceable to the SI definition of the meter. Processing runs entirely in your browser — no data is transmitted to any server, so your measurements stay completely private. Need to convert other measurement types? Try our weight converter for mass units, volume converter for liquid measurements, or temperature converter for Celsius, Fahrenheit, and Kelvin. ``` // Key conversion factors (exact): // 1 inch = 2.54 cm // 1 foot = 0.3048 m // 1 yard = 0.9144 m // 1 mile = 1.609344 km // JavaScript conversion examples: const inchesToCm = (inches) => inches * 2.54; const feetToMeters = (feet) => feet * 0.3048; const milesToKm = (miles) => miles * 1.609344; const kmToMiles = (km) => km / 1.609344; console.log(inchesToCm(12)); // 30.48 console.log(feetToMeters(6)); // 1.8288 console.log(milesToKm(26.2)); // 42.164928 ``` #### FAQ **Q: How many centimeters are in an inch?** A: There are exactly 2.54 centimeters in one inch. This is not an approximation — it is the exact conversion factor defined by the international yard and pound agreement of 1959. To convert inches to centimeters, multiply by 2.54. To convert centimeters to inches, divide by 2.54. For example, 12 inches = 30.48 cm, and 10 cm = 3.937 inches. **Q: How many feet are in a meter?** A: One meter equals approximately 3.28084 feet, or more precisely, 1 meter = 3.2808398950131 feet. Conversely, 1 foot = 0.3048 meters exactly. This means a 6-foot person is about 1.8288 meters tall. To convert meters to feet, multiply by 3.28084. To convert feet to meters, multiply by 0.3048. **Q: What is the formula to convert inches to cm?** A: To convert inches to centimeters, multiply the inch value by 2.54. The formula is: cm = inches × 2.54. This is an exact conversion factor defined by international agreement in 1959. For example, 6 inches = 6 × 2.54 = 15.24 cm. To convert back from cm to inches, divide by 2.54: inches = cm ÷ 2.54. **Q: What is a nautical mile and how does it differ from a regular mile?** A: A nautical mile equals exactly 1,852 meters (about 1.151 regular miles or 6,076 feet). It was originally defined as one minute of arc along a meridian of the Earth, making it naturally suited for navigation. A regular (statute) mile equals 1,609.344 meters or 5,280 feet. Nautical miles are used in aviation and maritime navigation because they relate directly to degrees of latitude: 60 nautical miles = 1 degree of latitude. **Q: Which countries still use the imperial system?** A: Only three countries primarily use the imperial system for everyday measurements: the United States, Myanmar (Burma), and Liberia. The UK uses a mix — road distances are in miles, but most other measurements are metric. Canada officially uses metric but commonly uses feet and inches for personal height and real estate. Most scientific, medical, and international trade contexts use the metric system worldwide. **Q: How tall is 5'7 in cm?** A: 5 feet 7 inches equals 170.18 centimeters. To calculate: convert feet to inches (5 × 12 = 60), add remaining inches (60 + 7 = 67 total inches), multiply by 2.54 (67 × 2.54 = 170.18 cm). Common heights: 5'0" = 152.4 cm, 5'5" = 165.1 cm, 5'7" = 170.18 cm, 5'10" = 177.8 cm, 6'0" = 182.88 cm, 6'2" = 187.96 cm. **Q: What is a light year and how far is it?** A: A light year is the distance that light travels in one year in a vacuum — approximately 9.461 trillion kilometers (9.461 × 10¹² km) or about 5.879 trillion miles. Despite the name, a light year is a unit of distance, not time. For perspective, light from the Sun takes about 8 minutes to reach Earth (1 AU = 149.6 million km), while the nearest star system, Alpha Centauri, is about 4.37 light years away. Light years are used to express distances between stars and galaxies because using kilometers would require unwieldy numbers. **Q: How accurate is this length converter?** A: This converter uses the internationally defined exact conversion factors: 1 inch = 25.4 mm exactly, 1 yard = 0.9144 m exactly, and 1 mile = 1,609.344 m exactly. All calculations use IEEE 754 double-precision floating-point arithmetic, providing at least 15 significant digits of precision. For everyday conversions, the results are more accurate than any physical measurement. The only limitation is the inherent rounding of floating-point numbers at extreme precision, which affects digits beyond the 15th significant figure. **Q: How do metric prefixes work for length units?** A: Metric length units are based on the meter, with prefixes indicating powers of 10. Common prefixes from large to small: kilo (km) = 1,000 m, no prefix (m) = 1 m, centi (cm) = 0.01 m, milli (mm) = 0.001 m, micro (um) = 0.000001 m, and nano (nm) = 0.000000001 m. Each step between adjacent common prefixes is a factor of 1,000 (except centi, which is 1/100 of a meter). This decimal-based system makes metric conversions straightforward: moving the decimal point is all that is required. **Q: Is my data safe when using this length converter?** A: Yes, completely. All conversions are performed locally in your browser using JavaScript. No data is sent to any server — there are no network requests, no cookies, and no analytics on your input. The conversion logic runs entirely on your device, meaning your values never leave your browser. You can verify this by disconnecting from the internet and using the tool — it works fully offline once the page has loaded. **Q: How many feet are in a mile?** A: There are exactly 5,280 feet in one mile. This relationship is defined by the statute mile, the standard mile used in the United States and United Kingdom. Other useful conversions: 1 mile = 1,760 yards = 63,360 inches = 1,609.344 meters = 1.609344 kilometers. The word 'mile' derives from the Latin 'mille passus' meaning one thousand paces. **Q: What is the SI unit of length?** A: The meter (m) is the SI (International System of Units) base unit of length. Since 2019, it is defined as the distance light travels in a vacuum in exactly 1/299,792,458 of a second. All other metric length units are derived from the meter using decimal prefixes: 1 kilometer = 1,000 meters, 1 centimeter = 0.01 meters, 1 millimeter = 0.001 meters, 1 micrometer = 0.000001 meters, 1 nanometer = 0.000000001 meters. **Q: I need to convert my height from feet to centimeters for a medical form — how do I do it?** A: First, convert your total height to inches: multiply the feet by 12 and add any remaining inches. For example, 5 feet 9 inches = (5 x 12) + 9 = 69 inches. Then multiply by 2.54 to get centimeters: 69 x 2.54 = 175.26 cm. You can enter 69 in this tool with inches as the source unit and centimeters as the target to get the exact result. Common heights for reference: 5'4" = 162.6 cm, 5'7" = 170.2 cm, 5'10" = 177.8 cm, 6'1" = 185.4 cm. Medical forms worldwide use centimeters, so this conversion comes up frequently for travelers and expats. **Q: I need to convert kilometers to miles for a US road trip — what is the quickest way?** A: Multiply kilometers by 0.621 for a quick estimate, or use this tool for exact results. For mental math on the road, a handy trick is the Fibonacci approximation: consecutive Fibonacci numbers approximate the km-to-miles ratio. So 8 km is about 5 miles, 13 km is about 8 miles, 21 km is about 13 miles. For speed limits, 100 km/h is about 62 mph, 120 km/h is about 75 mph, and 130 km/h is about 81 mph. Enter any distance in this tool to get the precise conversion instantly. **Q: I need to figure out if my furniture will fit through a doorway — how do I convert between inches and centimeters?** A: Multiply inches by 2.54 to get centimeters, or divide centimeters by 2.54 to get inches. A standard US interior doorway is 80 inches (203 cm) tall and 30-36 inches (76-91 cm) wide. If your furniture is measured in centimeters (common for IKEA and European brands), divide by 2.54 to compare with your doorway in inches. For example, a 200 cm tall bookshelf is 78.7 inches — it will fit through a standard door with about 1.3 inches to spare. Always measure the diagonal of the item if you need to tilt it to fit through. --- ### Lorem Ipsum Generator — Free Placeholder Text Tool URL: https://go-tools.org/tools/lorem-ipsum Generate Lorem Ipsum placeholder text instantly — by paragraph, sentence, word, byte, or list. Copy or download as plain text, HTML, Markdown, or JSON. 100% free, private, in-browser. No sign-up. #### What Is Lorem Ipsum? Lorem Ipsum is the placeholder text the design and publishing world reaches for whenever a layout needs words before the real copy exists. It is deliberately meaningless — a scramble of Latin-looking fragments — so that anyone reviewing the work judges the visual design, the typography, and the spacing instead of getting pulled into reading and editing the message. A block of Lorem Ipsum wraps, breaks, and sets type much like genuine prose because it has a natural-feeling distribution of word and sentence lengths, which is exactly why it beats lazy fillers like "asdf asdf" or repeated "text text text" that distort how a real paragraph would flow. The text is not invented gibberish. It traces back to a passage from Cicero's "De Finibus Bonorum et Malorum" ("On the Ends of Good and Evil"), a work of moral philosophy written in 45 BC. Somewhere along the way the Latin was garbled — words clipped, reordered, and combined — until it stopped being readable Latin and became the neutral, language-agnostic filler we know. The famous opening, "Lorem ipsum dolor sit amet, consectetur adipiscing elit," is itself a corruption: "Lorem" isn't even a real Latin word; it's the tail of "dolorem" (pain) with its first syllable lost. Lorem Ipsum entered modern practice through print. In the 1960s the Letraset company printed Lorem Ipsum passages on its dry-transfer lettering sheets, giving graphic designers a ready supply of filler to rub onto layouts. It made the leap to the screen in the 1980s when Aldus included it as sample text in PageMaker, the application that launched desktop publishing. From there it became the default in page-layout software, website templates, and design tools, and today every major design app — from InDesign to Figma — can drop Lorem Ipsum into a frame with a single command. This generator gives you that same filler on demand, but built for how people actually work in 2026: not just paragraphs, but exact word counts, sentence counts, list items, and precise byte budgets, and not just plain text, but HTML, Markdown, and JSON output for pasting straight into markup, documents, fixtures, and mocks. It runs entirely in your browser, so it is instant, private, and available offline. Pair it with the word counter when you need to match a real content slot's length, or the case converter and JSON formatter when you're shaping the filler into a specific format for a template or test. For the full story of where Lorem Ipsum comes from and when not to use it, read our complete guide to Lorem Ipsum. ``` // Generating Lorem Ipsum (simplified) const WORDS = ['lorem','ipsum','dolor','sit','amet','consectetur','adipiscing','elit','sed','do','eiusmod','tempor']; const PREFIX = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit'; const pick = (rng) => WORDS[Math.floor(rng() * WORDS.length)]; function sentence(rng, lead) { const n = 6 + Math.floor(rng() * 9); // 6-14 words const words = Array.from({ length: n }, () => pick(rng)); const body = words.join(' '); return lead ? `${lead}, ${body}.` : `${body[0].toUpperCase()}${body.slice(1)}.`; } function paragraph(rng, startWithLorem) { const n = 3 + Math.floor(rng() * 5); // 3-7 sentences return Array.from({ length: n }, (_, i) => sentence(rng, i === 0 && startWithLorem ? PREFIX : undefined) ).join(' '); } // Three random paragraphs, beginning with the canonical line const text = Array.from({ length: 3 }, (_, i) => paragraph(Math.random, i === 0) ).join('\n\n'); ``` #### FAQ **Q: What is this Lorem Ipsum generator and what does it do?** A: It produces Lorem Ipsum — the standard dummy text used as a placeholder in design and development — in whatever quantity and format you need. You choose a unit (paragraphs, sentences, words, list items, or an exact byte count) and an amount, and the tool generates matching filler text instantly. You can output plain text, HTML (with

or

  • wrapping), Markdown, or a JSON array, then copy it to your clipboard or download it as a file. The page opens with three paragraphs already generated so you can grab text immediately. Everything runs in your browser with JavaScript — nothing is uploaded, logged, or stored, and no account is required. **Q: Is the Lorem Ipsum generator free, and do I need to sign up?** A: It is completely free with no sign-up, no account, no email capture, and no usage limit. There is no premium tier that gates formats or sizes, and the generated text carries no watermark or attribution requirement — it is placeholder text, free for any use. The tool is funded the same way the rest of the site is and asks nothing of you in return for generating text. You can use it as many times as you like, generate as much text as the size ceilings allow, and download the result without ever creating a profile. **Q: Does my text get sent to a server, or is it private?** A: Generation happens 100% client-side in your browser. There is no server round-trip: the JavaScript that builds the text runs locally, which is why output appears instantly even at the largest sizes and why the tool keeps working if your connection drops. Nothing you generate is transmitted, logged, stored, or analyzed. This matters less for placeholder text than for a tool that processes your own input, but it means the generator is fast, reliable, and verifiable — you can open your browser's Network tab and confirm that clicking Regenerate triggers zero network requests. **Q: What is Lorem Ipsum and where does it come from?** A: Lorem Ipsum is scrambled, meaningless Latin-like text used to fill space in a layout so designers and clients judge the visual design without being distracted by readable content. It is not random gibberish: it derives from a passage of Cicero's "De Finibus Bonorum et Malorum" ("On the Ends of Good and Evil"), written in 45 BC, with words altered, added, and removed so it no longer reads as real Latin. The text entered design culture in the 1960s when the Letraset company printed Lorem Ipsum passages on its dry-transfer lettering sheets, and it spread worldwide in the 1980s when Aldus bundled it into the PageMaker desktop-publishing software. The canonical opening — "Lorem ipsum dolor sit amet, consectetur adipiscing elit" — is the fragment most people recognize. **Q: Why use Lorem Ipsum instead of real text or just 'asdf asdf'?** A: Placeholder text exists so the eye evaluates layout, typography, and spacing rather than reading the words. Real copy pulls reviewers into editing the message instead of the design, and obvious filler like "asdf asdf" or repeated "text text text" produces unnatural word lengths and line breaks that misrepresent how real content will flow. Lorem Ipsum has a roughly normal distribution of word and sentence lengths, so a paragraph of it wraps, hyphenates, and sets type much like genuine prose would. That makes it a faithful stand-in for judging line length, leading, column width, and the rhythm of a text block before the real words are ready. **Q: How do I generate Lorem Ipsum directly as HTML?** A: Set the Format control to HTML. Paragraph output is then wrapped in

    tags (one per paragraph), and list output is wrapped in a
      with each item in its own
    • . The result is ready to paste straight into a template, a component, a Storybook story, or a CMS rich-text editor without the find-and-replace step you would otherwise do on plain text. If you want the text without markup, leave Format on Plain text. Markdown format is also available for blank-line-separated paragraphs and hyphen-prefixed lists, and JSON format returns an array of strings for fixtures and mocks. **Q: Can I generate an exact number of words, characters, or paragraphs?** A: Yes. The Generate control selects the unit and the Amount control sets the exact quantity. Choose Words to get a precise word count, Paragraphs or Sentences for those units, List items for short fragments, or Bytes to fill an exact byte budget — useful for testing a database column width or a character-limited field. Because the Lorem vocabulary is pure ASCII, the byte count equals the character count, so 255 bytes is also 255 characters. Each unit has a generous ceiling (for example 1,000 words or 10,000 bytes) to keep one click from generating an unmanageable wall of text; the live counter under the output confirms the word, character, and paragraph totals of what you generated. **Q: What is the difference between Lorem Ipsum, dummy text, and placeholder text?** A: In everyday use the three terms are interchangeable — they all mean filler content that stands in for real copy while a design is built. "Lorem Ipsum" names the specific pseudo-Latin text that became the industry standard; "placeholder text" and "dummy text" are the generic categories that Lorem Ipsum belongs to. Placeholder text can also include other styles, such as themed variants (Bacon Ipsum, Hipster Ipsum) or simple repeated strings, but Lorem Ipsum remains the default because its neutral, language-agnostic look doesn't tempt reviewers to read it. When a designer asks for "some dummy text," Lorem Ipsum is almost always what they mean. **Q: Will using Lorem Ipsum hurt my SEO or get my page penalized?** A: Lorem Ipsum is fine during development but should never ship to a live, indexable page. It carries no penalty by itself, but if Google crawls a published page full of placeholder text, that page has no real content to rank and may be treated as thin or unfinished — wasting crawl budget and confusing users who land on it. The risk is accidental publication: a staging template, a forgotten footer, or a CMS default that goes live with Lorem still in it. Use the generator freely while building, then replace every block with real copy before launch, and search-and-replace "Lorem ipsum" across your project as a final pre-deploy check. **Q: Does the generated text repeat, and is it the same every time?** A: Each generation draws words at random from the classic Lorem vocabulary, so two blocks at the same settings will differ — that is what the Regenerate button is for when you want several distinct samples of the same length. With the "Start with Lorem ipsum…" toggle on, every block begins with the same canonical first line and then diverges. The randomness is purely in your browser and is not cryptographic; it exists only to make the filler look natural, with varied sentence lengths and an occasional comma, rather than mechanically repetitive. **Q: Can I use the generated Lorem Ipsum in commercial projects?** A: Yes, without restriction. Lorem Ipsum is not copyrighted — it is mangled Latin in the public domain — and the text this tool produces comes with no license, attribution, or usage terms. Paste it into client work, commercial products, paid templates, or anything else. The only caveat is the universal one: it is placeholder text, so replace it with real content before the project ships to real users. Nobody owns Lorem Ipsum, and nothing about generating it here creates an obligation to anyone. --- ### Markdown to HTML Converter URL: https://go-tools.org/tools/markdown-to-html Convert Markdown to HTML in your browser — full GitHub Flavored Markdown, live preview, syntax highlighting. Export an HTML fragment, full document, or email-safe inline-styled HTML. 100% private, no upload. #### What is Markdown to HTML Conversion? Markdown to HTML conversion turns a plain-text document written in Markdown — with `#` for headings, `**bold**`, `- ` for lists, and `[text](url)` for links — into the HTML that browsers, content management systems, and email clients actually display. Markdown is designed to be readable as-is and easy to write, but a browser does not understand `# Heading`; it understands <h1>Heading</h1>. Conversion bridges that gap. Under the hood, a Markdown processor first parses your source into an abstract syntax tree (AST) — a structured representation where a heading, a paragraph, a list, and a code block are distinct nodes with their content and attributes. It then serialises that tree to HTML, emitting the correct tags and nesting. Working through an AST, rather than swapping text with regular expressions, is what lets the converter handle nested lists, tables, and embedded HTML correctly and predictably. The two recognised grammars are CommonMark, the precise standard, and GitHub Flavored Markdown (GFM), which extends it with tables, task lists, strikethrough, and autolinks. The reason you convert at all is that almost every publishing destination wants HTML, not Markdown. A static-site generator, a CMS rich-text field, an email template, and a browser tab all render HTML. So the typical workflow is to write in comfortable Markdown — a README, documentation, a blog draft, notes — and convert to HTML at the point of publishing. This tool does that conversion locally and shows a live preview, so you see the rendered result and can copy the exact HTML in the shape you need: a fragment, a full page, or email-ready inline-styled markup. The reverse operation — HTML back to Markdown — is equally useful when you are migrating existing web content into a Markdown-based system. For that, switch to the HTML → Markdown tab or open the dedicated HTML to Markdown converter. ``` Markdown in: # Release Notes We shipped **tables** and `code`: | Feature | Status | | ------- | ------ | | GFM | Done | - [x] Parse to an AST - [ ] Profit HTML out:

      Release Notes

      We shipped tables and code:

      FeatureStatus
      GFMDone
      • Parse to an AST
      • Profit
      ``` #### FAQ **Q: Does it support GitHub Flavored Markdown (GFM)?** A: Yes. The converter renders the full GitHub Flavored Markdown superset on top of CommonMark: pipe tables, task lists (`- [x]` / `- [ ]`), strikethrough with `~~text~~`, autolinked URLs, and fenced code blocks with language info strings. That means a README, an issue body, or a wiki page written for GitHub renders here the same way GitHub renders it, so your README preview matches reality before you push. Plain CommonMark documents work too — GFM only adds features, it never removes them. **Q: How do I get email-safe inline-styled HTML?** A: Choose the Email inline output tab. Most email clients — Outlook in particular — strip or ignore <style> blocks in the document head, so any CSS you put there is discarded and your formatting collapses. The Email inline format solves this by moving the styles directly onto each element as a style attribute (for example <h1 style="font-size:2em;margin:0 0 16px">), which clients honour. Paste the result straight into your email template or ESP. Keep images small and prefer data URIs or absolute https URLs, since many clients block remote images by default. **Q: What's the difference between an HTML fragment and a full document?** A: An HTML fragment is just the rendered body markup — the <h1>, <p>, <ul>, <table> and so on — with no surrounding page. Use it when you are pasting into something that already has its own <html>, <head>, and <body>, such as a CMS rich-text field, a static-site template, or a React component. A full document wraps that same markup in a complete page with a <head>, a charset declaration, and a <title>, so it stands alone — open it in a browser or save it as a .html file. Picking the wrong one is a common mistake: a fragment dropped into a browser tab renders, but without a doctype or charset it can misbehave. **Q: Is the rendered HTML XSS-safe to preview?** A: The live preview is rendered inside a sandboxed <iframe> with scripts disabled, so even if your Markdown contains raw <script> tags or an onerror handler, nothing executes while you preview. This matters because Markdown permits embedded HTML by design, and converting untrusted Markdown can otherwise inject active content. The sandbox protects you, the person doing the conversion. Note that the HTML string the tool outputs is the faithful rendering of your input — if that input came from an untrusted source and you intend to publish the result, sanitise it on your server (for example with DOMPurify) before serving it to other users. **Q: Can I add my own CSS to the preview?** A: Yes. Open the Custom CSS panel and type any rules you like — for instance h1 { color: #0969da; } or table { border-collapse: collapse; }. The styles are injected into the sandboxed preview iframe so you see your Markdown rendered with your own look immediately, which is handy for matching a site's typography or checking how a README will appear with GitHub-style CSS. The custom CSS affects only the live preview; the HTML you copy from the output tabs is unstyled fragment or document markup unless you choose the Email inline format. **Q: Are my files or text uploaded to a server?** A: No. The conversion runs entirely in your browser with JavaScript — your Markdown is parsed and serialised to HTML locally and never transmitted, stored, or logged. You can confirm this by opening your browser's Network tab: converting text triggers zero network requests. That makes the tool safe for unpublished documentation, internal READMEs, release notes under embargo, and any content you are not ready to share. There is no upload step and no file-size limit beyond what your browser can comfortably hold in memory. **Q: Does it work offline?** A: Once the page has loaded, yes — the Markdown parser, the syntax highlighter, and the HTML serialiser all run in the browser with no server round-trip, so you can convert with your network disconnected. This is a direct consequence of the privacy-first design: because nothing is sent anywhere, there is nothing the tool needs the network for after the initial load. It is convenient on a plane, behind a restrictive firewall, or any time you simply do not want a document leaving your machine. **Q: How do I convert a Markdown (.md) file to an HTML file?** A: Paste or open your Markdown in the input pane, choose the Full document output so the result is a standalone page, then click Download to save it as a .html file you can open in any browser. If you only need the body markup to drop into an existing template, choose HTML fragment instead and copy it. There is no separate upload — paste the contents of your .md file (or drag it in where supported) and the converted HTML is ready instantly, entirely in your browser. **Q: Why isn't my code block highlighted?** A: Syntax highlighting only kicks in when you tell the converter which language a fenced code block is in. Write the language as an info string immediately after the opening triple backticks — ```js, ```python, ```sql — with no space. A bare ``` fence produces a <pre><code> block with no language class and therefore no colour. A misspelled or unsupported language name (```javscript) is treated as plain text too. Also remember that highlighting adds <span> classes like hljs-keyword; you need a matching highlight stylesheet on the destination page for the colours to actually appear. **Q: Can I convert HTML back to Markdown?** A: Yes. Switch to the HTML → Markdown tab, or open the dedicated HTML to Markdown converter, paste your HTML, and get clean Markdown back — with options for ATX vs Setext headings and inline vs reference links. The two directions are complementary: use Markdown → HTML to publish or preview, and HTML → Markdown to bring existing web content into a Markdown-based workflow such as a static site or a docs repo. To tidy the HTML first, our HTML Formatter pretty-prints it. --- ### MD5 Hash Generator & File Checksum Tool URL: https://go-tools.org/tools/md5-hash-generator Generate MD5, SHA-256, SHA-1 & SHA-512 hashes online for free. Hash text or files in your browser, verify checksums and copy results. No signup needed. #### What Is an MD5 Hash Generator? MD5 (Message-Digest Algorithm 5) is a 128-bit cryptographic hash function designed by Ronald Rivest in 1991 (RFC 1321), producing a fixed 32-character hexadecimal fingerprint from any input. Once widely used for digital signatures and certificate validation, MD5 is now formally deprecated for security-sensitive uses — but remains common for non-security checksums, cache keys, and data deduplication. "MD5 must not be used for digital signatures... NIST is formally deprecating use of MD5." — NIST SP 800-131A This tool supports MD5 alongside SHA-1 (40 hex chars), SHA-256 (64 hex chars), SHA-384 (96 hex chars), and SHA-512 (128 hex chars). NIST deprecated MD5 for security use in 2011 (NIST SP 800-131A); for any security-sensitive application, use SHA-256 or SHA-512 instead. Hash functions are one-way: you can compute a hash from input, but you cannot reverse it to recover the original data. This makes them useful for verifying file integrity, generating checksums, and creating unique identifiers. Important: MD5 and SHA-1 are cryptographically broken and should NOT be used for security purposes like password hashing or digital signatures. For password storage, use bcrypt, scrypt, or Argon2 instead. All hashing runs entirely in your browser using the Web Crypto API (for SHA family) and a pure JavaScript implementation (for MD5). No data leaves your device — verify this by checking your browser's Network tab. For a detailed comparison of MD5, SHA-1, SHA-256, and SHA-512 — including when each algorithm is appropriate and common mistakes to avoid — read our MD5 vs SHA-256 hash algorithm guide. For broader security guidance including password storage and authentication, see our web security best practices guide. ``` // Hash text using Web Crypto API (SHA-256) async function sha256(text) { const data = new TextEncoder().encode(text); const hash = await crypto.subtle.digest('SHA-256', data); return Array.from(new Uint8Array(hash)) .map(b => b.toString(16).padStart(2, '0')) .join(''); } await sha256('Hello, World!'); // → 'dffd6021bb2bd5b0af676290809ec3a53191dd81c7f70a4b28688a362182986f' ``` #### FAQ **Q: What is an MD5 hash?** A: MD5 (Message-Digest Algorithm 5) is a cryptographic hash function that takes any input — text, file, or binary data — and produces a fixed 128-bit (32 hex character) fingerprint. The same input always produces the same hash, but even a tiny change in the input creates a completely different output. MD5 was designed by Ronald Rivest in 1991 and is defined in RFC 1321. **Q: Is MD5 still secure?** A: No. MD5 is cryptographically broken and should not be used for security purposes. Collision attacks against MD5 can be performed in seconds on modern hardware. MD5 is still acceptable for non-security uses like checksums, cache keys, and data deduplication, but for anything security-related, use SHA-256 or stronger. For password storage, use bcrypt or Argon2 instead of any hash function. **Q: What is the difference between MD5 and SHA-256?** A: MD5 produces a 128-bit (32 hex character) hash and is fast but insecure. SHA-256 produces a 256-bit (64 hex character) hash and remains cryptographically secure. SHA-256 is part of the SHA-2 family designed by NSA and standardized by NIST. For new projects, always prefer SHA-256 over MD5. **Q: How do I verify a file checksum?** A: To verify a file checksum: 1) Download the file and note the checksum provided by the publisher. 2) Open this tool and switch to the File tab. 3) Drag and drop your downloaded file or click to browse. 4) Select the same algorithm used by the publisher (usually SHA-256 or MD5). 5) Click Generate Hash and compare the result with the publisher's checksum. If they match, the file is intact. You can also use the Compare tab to paste both hashes for an automatic match check. For verifying Base64-encoded checksums, decode them first. **Q: MD5 vs SHA-1 vs SHA-256 — which should I use?** A: For most purposes, use SHA-256. MD5 (128-bit) is cryptographically broken — use it only for legacy compatibility or non-security checksums. SHA-1 (160-bit) is also compromised and deprecated by major browsers and CAs. SHA-256 (256-bit) remains secure and is the current industry standard for integrity verification, digital signatures, and certificate validation. SHA-512 offers even larger output but is rarely needed outside specialized applications. **Q: Can I reverse an MD5 hash to get the original text?** A: No. Hash functions are one-way by design — you cannot mathematically reverse a hash to recover the input. However, for short or common strings, attackers use precomputed 'rainbow tables' to look up known hash-to-text mappings. This is why you should never use plain MD5 to store passwords. **Q: Is my data safe when using this tool?** A: Yes. All hashing is performed entirely in your browser using JavaScript. No data is ever sent to any server. You can verify this by opening your browser's Developer Tools (F12 → Network tab) while using the tool — you'll see zero outgoing requests. Your text and files never leave your device. **Q: Why do I get different hashes for the same text?** A: If you're getting different hashes, check for invisible differences: trailing whitespace, different line endings (\n vs \r\n), or encoding differences. Hash functions are extremely sensitive — even a single extra space will produce a completely different hash. Also make sure you're using the same algorithm for both comparisons. **Q: Can I hash large files?** A: Yes. This tool can hash files of any size because all processing happens in your browser using the Web Crypto API. However, very large files (several GB) may take longer to process and use significant memory. For most files under 1 GB, hashing completes in seconds. **Q: What is an MD5 checksum and how is it different from a hash?** A: An MD5 checksum and an MD5 hash are the same thing — both refer to the 128-bit (32 hex character) output of the MD5 algorithm. The term 'checksum' is typically used when the hash is applied to verify file integrity (e.g., comparing a downloaded file against a publisher's provided value), while 'hash' is the more general term for the algorithm's output. Use the File tab above to compute an MD5 checksum of any file. **Q: Is MD5 the same as encryption?** A: No. MD5 is a hash function, not encryption. Encryption is reversible — you can decrypt data back to its original form with the correct key. Hashing is one-way — you cannot recover the original input from a hash. MD5 converts input into a fixed-length 32-character fingerprint. There is no key, and no way to 'decrypt' an MD5 hash. For actual encryption, use AES or RSA. For password storage, use bcrypt or Argon2 — never plain MD5. **Q: How do I generate an MD5 hash in JavaScript or Python?** A: In JavaScript (browser), use the Web Crypto API: const hash = await crypto.subtle.digest('SHA-256', new TextEncoder().encode('text')); Note that Web Crypto does not support MD5 natively — use a library like 'crypto-js' or a pure JS implementation. In Python: import hashlib; hashlib.md5('text'.encode()).hexdigest(). In Node.js: require('crypto').createHash('md5').update('text').digest('hex'). Or simply use this tool for quick one-off hash generation without writing code. **Q: I need to verify a file download hasn't been corrupted — should I use MD5 or SHA-256 for the checksum?** A: Use SHA-256 whenever the publisher provides it. SHA-256 (256-bit) is the current standard for file integrity verification and is used by major software distributors including Ubuntu, Debian, and most security-conscious projects. MD5 is still widely published alongside downloads for historical reasons, and it remains adequate for detecting accidental corruption (a file being garbled during transfer). However, MD5 checksums cannot protect against a malicious actor who deliberately tampers with both the file and its checksum, since MD5 collisions can be crafted in seconds. SHA-256 closes this gap. If the publisher only provides MD5, use it — any checksum is better than none for catching download corruption. Use the File tab in this tool to compute either hash directly in your browser. **Q: My legacy system stores passwords as MD5 hashes — how do I migrate to bcrypt without forcing all users to reset?** A: Use a double-hashing migration strategy. The idea: hash each user's existing MD5 hash with bcrypt. When a user logs in, you first MD5 their submitted password (getting the old hash format), then verify that MD5 hash against the stored bcrypt hash. On successful login, immediately re-hash the plaintext password with bcrypt alone and update the database record. Over time, as users log in, records naturally migrate to pure bcrypt. Users who never log in remain on the transitional double-hash scheme, which is still far safer than plain MD5 since bcrypt adds salt and work factor. Set a deadline (6-12 months) after which inactive accounts on old hashes require a password reset. This approach migrates active users transparently with zero disruption, while remaining secure because the bcrypt layer protects the MD5 hashes from rainbow table attacks. **Q: I'm building a content-addressable storage system — is MD5 still safe for non-security hashing like deduplication?** A: MD5 is acceptable for content-addressable storage (CAS) and deduplication in most practical scenarios, with caveats. For detecting accidental duplicates — two files that happen to have identical content — MD5's 128-bit output provides a vanishingly small false-positive rate (1 in 2^128). MD5's weakness is crafted collisions: an attacker can deliberately create two different files that share the same MD5 hash. If your CAS system is purely internal and adversaries cannot inject content, MD5 is fine and fast. If users can submit content (cloud storage, CDN caching, package registries), use SHA-256 to prevent hash collision attacks. Git switched from SHA-1 to SHA-256 for exactly this reason. For maximum performance with security, SHA-256 is only ~20% slower than MD5 on modern hardware with hardware acceleration, making the security upgrade almost free in practice. --- ### Open Graph & Meta Tag Generator URL: https://go-tools.org/tools/meta-tag-generator Generate Open Graph, Twitter Card & SEO meta tags with a live Google, Facebook & X preview. 100% free, in-browser, no signup — copy & paste the code. #### What are meta tags & Open Graph? Meta tags are snippets of HTML in your page's <head> that describe the page to browsers, search engines and social platforms. The classic SEO tags are the <title>, the meta description and the canonical link. Open Graph tags (og:title, og:type, og:image, og:url and friends) are a protocol, originally from Facebook, that controls the title, description and image shown when your link is shared — and almost every platform reads them, including LinkedIn, Slack, Discord, WhatsApp, Pinterest and Telegram. Twitter/X uses its own twitter:card tags but falls back to your Open Graph tags when they are missing, so a good set of OG tags plus a single twitter:card value covers every network. This generator writes all of these tags, adds optional JSON-LD structured data, and previews the result live — so you never have to hand-write them or guess how a share will look. #### FAQ **Q: What is a meta tag generator?** A: A meta tag generator builds the HTML tags that go inside your page's <head> — the <title>, meta description, canonical link, Open Graph tags for Facebook and LinkedIn, and Twitter/X Card tags. This tool generates all of them at once, shows a live preview of how your link will look on Google, Facebook, X, LinkedIn, Slack and Discord, and lets you copy the code with one click. Everything runs in your browser. **Q: How do I add Open Graph tags to my website?** A: Fill in your title, description, canonical URL and an og:image URL, choose the Open Graph type, then copy the generated tags and paste them inside the <head> of your HTML. Because Facebook, LinkedIn, WhatsApp and Telegram crawlers do not run JavaScript, the tags must be present in the server-rendered or static HTML, not injected by client-side scripts. **Q: What size should an Open Graph image be?** A: The recommended og:image size is 1200×630 pixels, a 1.91:1 aspect ratio, which also satisfies X's summary_large_image card. Facebook ignores images smaller than 200×200 and shows a small square for anything under 600×315. Always include og:image:width and og:image:height so the first share renders instantly, and serve the image over HTTPS at a public URL that is not blocked by robots.txt. **Q: Is the twitter:card tag required?** A: Yes — twitter:card is the one Twitter/X tag you must include, otherwise X will not render a card even when your Open Graph tags are present. Set it to summary_large_image for a big image or summary for a small square thumbnail. For everything else (title, description, image) X falls back to your og:* tags, so this tool leaves them out by default to keep the markup clean. **Q: Do meta tags help SEO?** A: The <title> and canonical tag matter for SEO; the meta description is not a direct ranking factor but influences click-through by shaping the search snippet. Open Graph and Twitter Card tags do not affect Google rankings, but they control how your links look when shared, which drives social traffic. Google may rewrite your title in results, so write a clear, unique one and keep the important words first. **Q: Does this tool upload my content or images?** A: No. Everything is generated locally in your browser with JavaScript. Your title, description and image URL never leave the page — there is no server, no upload and no account. The og:image is loaded only in your own browser to preview it and read its dimensions. **Q: Can I import meta tags I already have?** A: Yes. Open the Import panel, paste your existing <meta> tags, and the tool parses them locally with the browser's own HTML parser to fill in the form — no network request, nothing uploaded. It is the privacy-first alternative to tools that fetch a live URL from their server. **Q: Why does my updated preview still show the old image on Facebook or LinkedIn?** A: Platforms cache Open Graph data: Facebook for about 30 days, LinkedIn for about 7 days. After you deploy, re-scrape the page with the Facebook Sharing Debugger or LinkedIn Post Inspector to refresh the cached title, description and image. The debugger links are listed under the generated code. --- ### Nginx Location Tester — Why That Block Wins URL: https://go-tools.org/tools/nginx-location-tester See which nginx location block wins — and why every other block lost. Free location match tester for =, ^~, ~ and ~*, running entirely in your browser. #### What Is an nginx location Block? A location block tells nginx what to do with a request whose URI matches a pattern. A server block usually holds several, and the interesting part is not what each one does but which one nginx picks — because the selection rules are not the ones most people assume. There are five forms. location = /path matches only when the whole URI is equal. location /path matches any URI starting with those characters. location ^~ /path is the same prefix comparison with one extra effect. location ~ regex and location ~* regex apply a PCRE pattern, case-sensitively and case-insensitively. location @name never takes part in URI matching at all and exists only as a target for try_files and error_page. Selection runs in stages. First the URI is normalised: percent-decoded, . and .. resolved, repeated slashes collapsed, query string split off. Then an = location equal to the URI ends the search immediately. Then every matching prefix is compared and the longest is remembered — configuration order plays no part here at all. If that remembered prefix carries ^~, nginx stops and uses it. Otherwise the regex locations are tried in the order they appear in the file, and the first match wins, however specific a later one might be. If none matches, the remembered prefix is used. Two of those rules pull in opposite directions, and that is where the confusion lives: prefixes are chosen by length regardless of order, regexes by order regardless of length. A config that reads correctly from top to bottom can still route a request somewhere you did not intend, and no amount of re-reading reveals it. This page replays the whole sequence against your own config and shows where each block dropped out. ``` # From the nginx documentation. Which block serves each request? server { location = / { } # A location / { } # B location /documents/ { } # C location ^~ /images/ { } # D location ~* \.(gif|jpg|jpeg)$ { } # E } # / -> A exact match, search ends here # /index.html -> B no regex matched, longest prefix used # /documents/document.html -> C longer prefix than B # /images/1.gif -> D ^~ won the prefix stage, regex skipped # /documents/1.jpg -> E regex beats the longer prefix C # The last two lines are the whole lesson: identical-looking prefixes # behave differently because only one of them carries ^~. ``` #### FAQ **Q: How do I debug which location nginx selected?** A: Paste the config and the request URI into this tester to see the winning block and why each other block was eliminated. On a running server, add error_log /var/log/nginx/debug.log debug; and look for the "using configuration" line, which names the selected location. The two approaches answer different questions: the log tells you what a live server did, and this page tells you what a config you have not deployed yet would do. **Q: Why is my nginx location regex not working?** A: Usually one of three reasons, and the decision table names which. An earlier regex already matched, so yours never ran — regexes are tried in file order and the first match wins. Or the longest matching prefix carries ^~, which skips the regex phase completely. Or the regex is fine but the URI is not what you think: matching runs against the normalised path, after percent-decoding and .. resolution, with the query string removed. **Q: Why is my exact match not triggering?** A: An = location requires the whole URI to be equal, not to start with the pattern. location = /a/ does not match /a, and location = /a does not match /a/b. Trailing slashes are ordinary characters here, so the two forms are different strings. When an exact location does match, the search stops immediately and nothing else is even compared. **Q: Does nginx match the query string in a location block?** A: No. The query string is split off during normalisation and location selection runs against the path alone. That is why location = /a matches a request for /a?x=/b. If you need to branch on a parameter you have to read $arg_name or $args inside the block. One subtlety worth knowing: %3F decodes to a literal question mark that stays in the path, so /a%3Fx=1 has an empty query string and a path containing ?. **Q: Can I use JavaScript regex syntax in an nginx location?** A: No — nginx uses PCRE, and the differences matter. This page runs in your browser, where only ECMAScript regular expressions exist, so PCRE-only constructs are detected and flagged rather than silently mis-evaluated: atomic groups, possessive quantifiers, inline modifiers such as (?i), POSIX classes such as [[:alpha:]], and escapes like \A and \K that JavaScript quietly reads as ordinary letters. When a location is flagged, the remaining candidates are still evaluated in nginx's order, but that block's verdict needs checking on a real server. If you are writing the pattern itself, the regex tester covers ECMAScript syntax in depth. **Q: Are nginx location prefix matches case-sensitive?** A: On Linux, yes — location /Static/ does not match /static/x. On case-insensitive filesystems such as macOS and Cygwin, nginx compares prefixes case-insensitively and additionally forces every regex location to behave like ~*. This page models the Linux behaviour, which is what production servers almost always run. If you develop on a Mac and deploy to Linux, that difference can hide a broken rule until it ships. **Q: Does try_files change which location block was selected?** A: No. Location selection finishes first; try_files runs afterwards, inside whichever block already won. If a request never reaches the block containing your try_files, the directive is irrelevant — the usual cause is a ~ \.php$ regex taking the request before the prefix block with the fallback ever gets to act. An internal redirect issued later does restart matching, so a rewritten URI is resolved against the location list again from the top. **Q: Why does nginx return 301 when I request a directory without a slash?** A: Two different mechanisms produce that. If a location whose name ends in / carries proxy_pass or another *_pass directive, a request for the same path without the slash is answered with a 301 during location selection — before any regex is evaluated. Separately, the static file module issues a 301 when the path resolves to a real directory on disk. The first is visible here; the second depends on your filesystem. Adding location = /path suppresses the first. **Q: Do nested location blocks change the outcome?** A: Yes, and in a way that is easy to miss. nginx descends into the winning prefix location and searches its children, so a nested regex is tried before the parent level's regexes. A ^~ on the outer block does not shield it from its own nested regexes, and nesting can make a globally longer prefix unreachable when a sibling at the outer level wins first. Nested blocks are modelled here with the same indentation you wrote them in. **Q: Is my nginx configuration uploaded anywhere?** A: No. Parsing and matching happen locally in your browser with plain string operations — there is no server call and nothing is retained. This matters more here than for most tools, because a real server block contains upstream hostnames, internal ports, certificate paths and auth rules. You do not have to take our word for it: open your browser's developer tools and watch the Network panel stay silent while you type, or disconnect entirely and keep testing. The absence of any external request is also enforced by an automated contract test on every build, so it cannot quietly regress. --- ### PX to REM Converter — Convert Pixels to Rem URL: https://go-tools.org/tools/px-to-rem Convert px to rem instantly. 16px = 1rem at the default base, plus any custom root font-size. Free, live two-way conversion that runs 100% in your browser. #### What Is a PX to REM Converter? A px to rem converter translates pixel measurements into rem units for CSS. The rem ("root em") unit is relative to the font-size of the root element: 1rem always equals the root font-size, which browsers set to 16px by default. The conversion is a simple division — rem = px ÷ root-font-size — but doing it by hand for every font size, margin, and breakpoint in a stylesheet is tedious and error-prone, which is what this tool removes. The reason to convert at all is accessibility and scalability. When a value is written in px, it is locked to a fixed size and ignores the user's browser font-size preference. When it is written in rem, it scales proportionally if the user increases their default font size — a critical accommodation for people with low vision and a smoother experience for everyone who zooms. Expressing a design system in rem also means a single change to the root font-size rescales the entire interface consistently. This converter keeps both the pixel and rem fields linked in real time, so you can move in either direction, and — unlike tools that hard-code a 16px base — it lets you set any root font-size. That matters because a stylesheet using the 62.5% technique (a 10px root) converts differently from a default 16px setup. A live preview shows the resulting text size, and a reference table lists the most common px values at a 16px base for quick lookups. Need the reverse direction? Use the rem to px converter. For tidying up the stylesheet itself, try the CSS formatter, and for color work see the color converter. Everything runs in your browser — your values never leave your device. ``` /* The core formula */ /* rem = px ÷ root-font-size (16px by default) */ :root { font-size: 16px; /* 1rem = 16px */ } .title { font-size: 1.5rem; } /* 24px */ .body { font-size: 1rem; } /* 16px */ .caption{ font-size: 0.75rem; } /* 12px */ .card { padding: 1.5rem; } /* 24px */ /* JavaScript equivalent */ const pxToRem = (px, base = 16) => px / base; console.log(pxToRem(24)); // 1.5 console.log(pxToRem(12)); // 0.75 ``` #### FAQ **Q: What is 16px in rem?** A: 16px equals exactly 1rem when the root font-size is the browser default of 16px. The rem unit is relative to the font-size of the root element, so rem = px ÷ root-font-size. Because most browsers default to 16px, 16px is the natural anchor: 16px = 1rem, 8px = 0.5rem, 32px = 2rem. If you change the root font-size, the ratio changes accordingly. **Q: How do you convert px to rem?** A: To convert px to rem, divide the pixel value by the root font-size (16px by default). The formula is rem = px ÷ base. For example, 24px ÷ 16 = 1.5rem, and 12px ÷ 16 = 0.75rem. This tool does the division for you in real time and lets you change the base if your project uses a different root font-size. **Q: What is 1rem in pixels?** A: 1rem equals the root font-size in pixels — 16px by default. So 1rem = 16px, 1.5rem = 24px, and 2rem = 32px on a standard setup. If you set html { font-size: 62.5% } (10px), then 1rem = 10px instead. To go from rem back to pixels, multiply: px = rem × base. **Q: Why use rem instead of px?** A: Rem units respect the user's browser font-size preference, which is essential for accessibility. When someone increases their default font size — for low vision or simply comfort — everything sized in rem scales proportionally, while px values stay fixed and can break layouts or ignore the preference. Rem also keeps a design system consistent: change one root font-size and the whole interface scales together. Pixels are still useful for things that should not scale, such as 1px borders. **Q: What is the difference between px, rem, and em?** A: px is an absolute unit — one CSS pixel, fixed regardless of context. rem is relative to the root () font-size, so it is consistent everywhere on the page. em is relative to the font-size of the current element's parent, so it compounds when nested. Use px for fixed details like hairline borders, rem for most sizing so it scales with the user's preference, and em when you want a value to scale relative to its local context (for example padding that grows with a button's own font size). **Q: What is the 62.5% font-size trick?** A: Setting html { font-size: 62.5% } makes the root font-size 10px (because 62.5% of the 16px default is 10px). With a 10px base, rem math becomes trivial: 1rem = 10px, 1.6rem = 16px, 2.4rem = 24px — just divide the pixel value by 10. Many developers like the simpler arithmetic, though you then typically set body { font-size: 1.6rem } to restore readable 16px body text. Set this tool's root font-size to 10 to convert against the 62.5% base. **Q: Can I change the root font-size in this converter?** A: Yes. The Root font-size field defaults to 16px (the browser default) but you can type any value — 10, 18, 20, or whatever your project uses. Every conversion and the reference table update against the base you set. Click 'Reset to 16' to return to the default. Most tools hard-code 16px; the custom base is what lets this converter match your real stylesheet. **Q: Is 16px always equal to 1rem?** A: Only when the root font-size is 16px, which is the default in virtually all browsers. The relationship 16px = 1rem holds as long as you do not override html { font-size }. If a stylesheet sets the root font-size to something else — say 10px or 18px — then 16px no longer equals 1rem. Always convert against the actual root font-size of the page. **Q: Should I convert font sizes, padding, and margins to rem?** A: Font sizes are the strongest candidates for rem because they directly affect readability and accessibility. Padding, margin, gap, and border-radius are also commonly expressed in rem so spacing scales together with text for a cohesive layout. Media-query breakpoints in rem (or em) zoom more gracefully. Keep px for things that must not scale, like 1px borders and some box-shadow offsets. **Q: Does converting px to rem change how my site looks?** A: No — at the default 16px root font-size, a value in rem renders at exactly the same pixel size as the original px value. 24px and 1.5rem look identical on a standard setup. The difference is behavioral: the rem version will scale if the user changes their browser font size, whereas the px version will not. Visually nothing changes until the user adjusts their preferences. **Q: How accurate is this px to rem converter?** A: Conversions use IEEE 754 double-precision arithmetic and the exact formula rem = px ÷ base, with results rounded to at most five decimal places and trailing zeros trimmed for readability. For CSS that precision is far finer than the browser needs. The math is deterministic and runs the same every time, so you can rely on it for production stylesheets. **Q: Is my data safe when using this converter?** A: Completely. All conversions run locally in your browser using JavaScript. No values are sent to any server — there are no network requests, no cookies on your input, and no analytics tied to what you type. You can verify this by disconnecting from the internet: the tool keeps working fully offline once the page has loaded. --- ### Q-Format Fixed-Point Converter URL: https://go-tools.org/tools/q-format-converter Convert Q7, Q15, Q31, Q1.15, and Q16.16 values between decimal, raw integer, hex, and binary. Inspect exact stored values, quantization error, rounding, and overflow locally. #### What is Q-format fixed point? Q-format stores a real value as an integer plus an implied binary scale. For a word with F fractional bits, stored integer N represents N / 2^F; the point is not physically stored in the word. A signed word normally uses W-bit two's complement, while an unsigned word uses the full code range. This makes multiplication, filtering, and register storage predictable on hardware without floating-point support, but it also makes range, rounding, saturation, and notation part of the data contract. Q labels are not completely standardized, so this converter treats signedness, total width W, and fractional bits F as the canonical definition and displays them beside every result. #### FAQ **Q: What does Q15 mean?** A: In the common DSP convention used by this tool, Q15 is a signed 16-bit two's-complement word with 15 fractional bits: one sign/integer position and 15 fraction positions. Its scale is 2^15, resolution is 1/32768, and range is -1 through 32767/32768. Some documents use Qm.n differently, so verify the total width and signedness instead of relying on the label alone. **Q: Are Q15 and Q1.15 the same?** A: They often refer to the same signed 16-bit layout, but Q notation is not universal. Some authors count the sign bit inside the integer count and others do not. This converter resolves the ambiguity by always showing signedness, total bits W, fractional bits F, and scale 2^F; its Q15 and Q1.15 preset is signed W=16, F=15. **Q: How is a negative hexadecimal Q-format word decoded?** A: First parse the hexadecimal text as an unsigned W-bit raw code. If the format is signed and the top bit is set, subtract 2^W to obtain the two's-complement stored integer. Then divide that integer by 2^F. For signed Q15, 0xC000 becomes 49152 - 65536 = -16384, then -16384 / 32768 = -0.5. **Q: Which rounding and overflow modes are supported?** A: Decimal encoding supports round to nearest with ties to even, truncate toward zero, and floor toward negative infinity. If the rounded integer is outside the W-bit range, Error rejects it, Saturate clamps to the nearest endpoint, and Wrap applies modulo 2^W. Raw hex and binary decoding does not round because the stored word is already exact. **Q: When should I use Q-format instead of IEEE 754?** A: Use Q-format when a protocol, DSP algorithm, register, or embedded system defines a fixed binary scale for an integer word. Use IEEE 754 floating-point when the word contains a sign, dynamic exponent, and significand. For plain integer radix changes without a scale or signed width, use the number base converter. **Q: Does this converter upload my values or hex dump?** A: No. Parsing, BigInt arithmetic, rounding, overflow handling, and batch decoding run locally in your browser. The batch input is bounded to 16 KiB and 256 words for responsive use, and entered numeric values are not included in analytics events. --- ### Online QR Code Decoder URL: https://go-tools.org/tools/qr-code-decoder Decode a QR code from an image or screenshot by choosing, dropping or pasting it. Review the encoded URL and hostname before opening a link; the image stays in your browser and is never sent to a server. #### What is an online QR code decoder? An online QR code decoder reads the square QR symbol inside an existing image or same-device screenshot and reveals the encoded payload without requiring a second phone. This page accepts a chosen, dropped or pasted image, decodes it locally, and organizes recognized URL, WiFi, vCard, email, SMS and geo fields while retaining the original text. It reads one QR result rather than generic barcodes or a live camera feed. After reviewing the result, you can copy it to the QR code generator to create a replacement code. #### FAQ **Q: Are QR code images uploaded?** A: No. The selected image is decoded in your browser and is not sent to an upload endpoint. This local processing includes potentially sensitive screenshots and WiFi payloads. **Q: Which image formats and sizes work?** A: The input accepts PNG, JPEG, WebP and GIF files up to 10 MiB. For GIF files, only the first frame is examined, so move the code to the first frame or save a still image when needed. **Q: Can it scan with my camera or read barcodes?** A: No. This tool reads one QR code from an image you choose, drop or paste. It does not access a camera and it is not a generic barcode reader. **Q: Why does a clear QR still fail to decode?** A: The code may be too small, blurred, cropped through its quiet zone, strongly tilted, low contrast or outside the first GIF frame. Try a sharper crop that keeps a margin around the entire square. No decoder can guarantee every damaged image will scan. **Q: Do link warnings mean a site is malicious?** A: No. HTTPS, HTTP, credential-like URLs, punycode and IP-address notices are explainable cues derived from the text itself. The decoder performs no reputation lookup, so a warning is not a verdict and no warning is a safety guarantee. **Q: Can I turn the decoded content into a new QR code?** A: Yes. Copy the decoded payload and continue to the QR code generator. Review the copied content first, especially passwords, contact data and destinations. --- ### QR Code Generator — URL, WiFi, vCard, Email, SMS, Geo URL: https://go-tools.org/tools/qr-code-generator Free QR code generator. Make static QR codes for URL, WiFi, vCard, email & SMS. SVG & PNG download. No expiration, no signup, 100% in your browser. #### What is a QR Code? A QR Code (Quick Response Code) is a 2D matrix barcode invented by Denso Wave in 1994 and codified by ISO/IEC 18004:2015. It encodes data into a square grid of black and white modules, with three large finder patterns at the corners that let scanners locate and orient the code. Versions range from 1 (21×21 modules) to 40 (177×177); the encoder picks the smallest version that fits your content at the chosen error correction level. QR codes carry many data types via standard URI schemes. URLs are encoded directly. WiFi credentials use the de facto WIFI: protocol that iOS Camera (since iOS 11) and Android camera apps recognize natively. Contact cards use vCard 3.0 (RFC 2426) — broader scanner compatibility than vCard 4.0 (RFC 6350). Email links use mailto: (RFC 6068), text messages use sms: (RFC 5724), and map pins use geo: (RFC 5870). Scanning a well-formed QR triggers the right action — open URL, join WiFi, save contact, draft email — without the user typing anything. Reed-Solomon error correction is what keeps QR codes scannable when they're scratched, folded, or partly obscured. Four levels — L, M, Q, H — recover roughly 7%, 15%, 25%, and 30% of damaged data. Higher levels add redundancy modules, so the QR grows physically larger for the same content. Choose H for printed materials that will be handled, M for screens, L only when you're squeezing in long URLs. The biggest practical distinction is static vs dynamic. A static QR encodes your real content directly into the pixels — it works forever, with no service to maintain. A dynamic QR encodes a short link to a third-party redirect service; if that service expires, raises its price, or shuts down, every printed QR you've shipped goes dead. Read Static vs Dynamic QR Codes — why yours stops working for the full story. This tool generates static QRs only. Many online QR generators upload your WiFi password, vCard contact details, or private URL to their server before encoding. This tool runs the qrcode npm library entirely in your browser — zero uploads, zero logs, zero tracking. It's the same privacy posture as our other client-side encoders Base64 Encoder/Decoder and URL Decoder/Encoder: your inputs never leave the page. ``` // Build a WiFi payload and generate an SVG QR import QRCode from 'qrcode'; // 1. WIFI: protocol (de facto, recognized by iOS+Android) const payload = 'WIFI:T:WPA;S:My\\;Network;P:p@ss\\;word;H:false;;'; // 2. Generate SVG (vector, scales without pixelation) const svg = await QRCode.toString(payload, { type: 'svg', errorCorrectionLevel: 'M', margin: 4, color: { dark: '#000000', light: '#ffffff' }, }); // 3. Drop into the DOM (DOMParser-safe, not innerHTML) const doc = new DOMParser().parseFromString(svg, 'image/svg+xml'); preview.replaceChildren(doc.documentElement); ``` #### FAQ **Q: Why does my QR code stop working after a while?** A: Because it's a dynamic QR — the QR encodes a short tracking URL that redirects to your real content. When the redirect service expires, raises its price, or shuts down, the QR is dead. This tool generates static QR codes that encode your data directly into the pixels. They never expire. Read the full guide. **Q: Will this QR code expire?** A: No. We generate static QR codes. Your URL, text, WiFi, or vCard data is encoded directly into the QR pixels — there is no redirect, no service to maintain, no subscription. As long as the printed or saved QR is intact, it will scan forever. **Q: How do I create a WiFi QR code?** A: Switch to the WiFi tab, enter your SSID (network name) and password, then pick the security type — WPA / WPA2 / WPA3 for almost all modern networks, WEP for legacy gear, or No password for open networks. Tick Hidden network if your SSID is not broadcast. Download the QR — guests scan it with their phone camera and join automatically. **Q: How do I make a vCard QR code for my business card?** A: Switch to the vCard tab and fill name, phone, email, organization, and website. Output is vCard 3.0 (RFC 2426) — the format both iOS and Android recognize most reliably (vCard 4.0 / RFC 6350 has worse scanner support). Print the QR on paper cards; scanning offers to save the contact in one tap. **Q: Is this QR code generator free?** A: Yes — no signup, no payment, no usage limits, no watermark on the output. The site is supported by minimal display ads on unrelated pages. The tool itself runs entirely in your browser with no upload and no tracking. **Q: Can I download QR codes as SVG?** A: Yes — SVG is the default download format. SVG is vector, so it scales to any size without pixelation, and you can paste it directly into Figma, Illustrator, Sketch, or send it to a printer at billboard size. Need a raster file instead? Pick PNG at 256, 512, or 1024 px from the Download menu. **Q: What is the maximum length of data a QR code can hold?** A: Up to ~2,953 bytes for byte mode at error correction level L (Version 40 QR), ~2,331 at level M, ~1,663 at Q, ~1,273 at H. Numeric-only data fits more (up to 7,089 digits at L). If your content is rejected, lower the error correction level or shorten the URL. **Q: What error correction level should I use?** A: M (medium, ~15% recovery) is the default and works for most cases. Use H (~30%) if the QR will be printed on packaging, fabric, or anywhere damage is likely. Use L (~7%) only if you are hitting the byte limit and the QR will live on a screen rather than in print. **Q: Can I add a logo to the QR code?** A: Not yet. Logo embedding works best with a higher error correction level (Q or H) and careful sizing — otherwise the QR becomes unreliable across phones. We are evaluating it for v2 with safe defaults. For now we recommend keeping the QR clean: the highest-converting QRs are the simplest. **Q: Are my inputs uploaded to a server?** A: No. All QR generation happens in your browser using the qrcode npm library, loaded once and run locally. Your URL, WiFi password, vCard data, and any other input never leave your device. Refreshing the page clears all inputs. We do not store, log, or analyze what you generate. **Q: Why is my custom-colored QR not scanning?** A: QR scanners need high contrast between foreground (dark) and background (light). If you reverse them — light foreground on a dark background — most scanners fail. Stick to dark on light with a contrast ratio of at least 4.5:1. This tool warns you when contrast is too low. **Q: What QR code version is generated?** A: The qrcode library auto-selects the smallest QR version (1 to 40) that fits your content at the chosen error correction level. Version 1 is 21×21 modules; Version 40 is 177×177. The output follows ISO/IEC 18004:2015, the international QR Code standard. **Q: Can I use this QR code for commercial purposes?** A: Yes — QR Code is an open standard (ISO/IEC 18004) and not restricted by patents (Denso Wave released the patent rights in 1994). The QR codes you generate here are yours to use commercially without attribution or license fees. **Q: Does this work offline?** A: After the first load, the qrcode library is cached by your browser, so subsequent generations work without network access. The page itself is statically served — no API calls, no backend dependencies. Open it once on a flight and generate QRs at 30,000 ft. **Q: How do I read a QR code that is already in an image?** A: Open the QR code decoder for images, then upload the image, drag and drop it, or paste it from your clipboard. The image is processed locally in your browser and is not sent to a server. --- ### Random Password Generator — Customizable, Strong & Secure URL: https://go-tools.org/tools/random-password-generator Generate strong random passwords instantly — free, 100% in your browser. Customize length & characters, batch up to 50 with entropy analysis. #### What Is a Random Password Generator? A random password generator creates cryptographically secure passwords using unpredictable random data, making each generated password resistant to brute-force and dictionary attacks. Unlike passwords invented by humans, generated passwords carry no patterns, no dictionary words, and no personal information — the three properties most exploited by attackers. Over 80% of data breaches involve weak or stolen passwords (Verizon DBIR), making strong password generation a first-line defense. "Memorized secrets SHALL be at least 8 characters in length... Verifiers SHOULD NOT impose other composition rules." — NIST SP 800-63B This tool uses your browser's built-in Web Crypto API (crypto.getRandomValues()) to produce true cryptographic randomness. Every character is independently selected from your chosen character pool, ensuring uniform distribution and maximum entropy. Need a unique identifier instead? Try our UUID Generator. Why does this matter? A 16-character password using all character types (uppercase, lowercase, digits, symbols) has over 100 bits of entropy. At a rate of one trillion guesses per second, it would take billions of years to brute-force. A human-chosen password of the same length typically has far less entropy because people unconsciously follow patterns. Learn how password entropy works. All password generation happens entirely in your browser. No passwords are transmitted over the network, stored on any server, or logged anywhere. You can verify this by checking your browser's Network tab — there are zero outgoing requests when you generate a password. If you need to encode generated passwords for safe transport, our Base64 Encoder can help. For a comprehensive look at password hashing, authentication, and other security fundamentals, read our web security best practices guide. ``` // Generate a random password in JavaScript (unbiased via rejection sampling) // `v % charset.length` is biased whenever 2^32 isn't a multiple of charset.length. // We discard out-of-range draws so every character is uniformly distributed. function unbiasedIndex(modulus) { const limit = Math.floor(0x100000000 / modulus) * modulus; const buf = new Uint32Array(1); let v; do { crypto.getRandomValues(buf); v = buf[0]; } while (v >= limit); return v % modulus; } function generatePassword(length, charset) { let out = ''; for (let i = 0; i < length; i++) out += charset[unbiasedIndex(charset.length)]; return out; } // Example: 16-char password with all types const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*'; generatePassword(16, chars); // → 'kX#9mP$2vL!nQ7wR' (random each time) ``` #### FAQ **Q: Is it safe to use an online password generator?** A: Yes, this online password generator is safe. Every password is created entirely in your browser using JavaScript and the Web Crypto API — no data is ever sent to a server. You can verify this yourself by opening your browser's Developer Tools (F12 → Network tab) while generating passwords: you'll see zero outgoing requests. Because nothing leaves your device, there is no risk of interception or server-side leaks. This is the same local-generation approach used by reputable password managers like Bitwarden and 1Password. All our tools, including the UUID Generator and Base64 Encoder, run 100% in the browser. **Q: What makes a strong password?** A: A strong password is long, random, and unique. Length matters more than complexity — a 16-character password is exponentially harder to crack than an 8-character one, regardless of character mix. That said, using a combination of uppercase letters, lowercase letters, digits, and symbols maximizes the number of possible combinations. Avoid dictionary words, names, dates, and keyboard patterns like 'qwerty'. Most importantly, never reuse a password across multiple accounts. Use a random password generator to eliminate human bias entirely. **Q: How long should my password be?** A: In 2026, the recommended minimum password length is 12 characters, but 16 characters is the sweet spot for most accounts. For high-security accounts like banking, primary email, or cloud administration, use 20 to 32 characters. A 16-character password with all character types has about 105 bits of entropy, which would take billions of years to brute-force at one trillion guesses per second. Each additional character multiplies the number of possible combinations, so longer is always better. **Q: Is an 8-character password secure enough?** A: No, an 8-character password is no longer considered secure. Even with a full mix of uppercase, lowercase, digits, and symbols, an 8-character password has only about 52 bits of entropy and can be cracked in hours to days by modern hardware. By contrast, a 12-character password takes centuries and a 16-character password takes billions of years. Security experts and NIST recommend a minimum of 12 characters. We recommend 16 characters for everyday accounts and 20 or more for sensitive ones. **Q: Random password vs. passphrase — which is more secure?** A: Both can be highly secure, but they excel in different areas. A random password like 'kX#9mP$2vL!nQ7wR' packs maximum entropy into fewer characters, making it ideal when length is limited. A passphrase like 'correct-horse-battery-staple' is easier to remember and type but needs to be longer (4 to 6 random words) to achieve equivalent strength. For accounts stored in a password manager, random passwords are the best choice since you don't need to memorize them. For your password manager's master password, a long passphrase is often more practical. **Q: Should I include symbols in my password?** A: Yes, including symbols is recommended whenever the service allows it. Adding symbols like !@#$%^&* expands the character pool from 62 (letters and digits) to over 90 characters, which significantly increases entropy. A 16-character password with symbols has about 105 bits of entropy compared to about 95 bits without them. If a website restricts certain special characters, simply increase your password length by 2 to 4 characters to compensate for the smaller character pool. **Q: How do I remember randomly generated passwords?** A: The short answer: don't try to memorize them. Instead, use a password manager like Bitwarden, 1Password, or KeePass to store all your passwords securely. You only need to remember one strong master password or passphrase to unlock your vault. Your password manager auto-fills credentials on websites and apps, so you never need to type random passwords manually. This approach lets you use a unique, complex password for every account without any memory burden. **Q: Why shouldn't I use the same password for every account?** A: Reusing passwords is one of the biggest security risks online. When a company suffers a data breach — and breaches happen constantly — attackers use stolen credentials to try logging into other services. This is called credential stuffing, and it works because most people reuse passwords. If your one password is exposed, every account sharing that password is compromised. Using a unique random password for each account means a single breach stays contained to that one service. **Q: How often should I change my passwords?** A: According to current NIST guidelines (SP 800-63B), you do not need to change passwords on a fixed schedule if they are strong and unique. Forced periodic changes often lead to weaker passwords because people make minimal, predictable modifications. Instead, change a password immediately if you suspect it has been compromised, if a service reports a data breach, or if you've been sharing it. The best strategy is to use a strong random password with a password manager and only rotate when there's a specific reason. **Q: What are 'ambiguous characters' and why exclude them?** A: Ambiguous characters are visually similar pairs that are easy to confuse: 0 (zero) vs O (letter O), l (lowercase L) vs 1 (digit one), and I (uppercase i) vs l (lowercase L). Excluding them makes passwords easier to read, type, and communicate verbally without errors. This is especially useful for temporary passwords shared with others or passwords you need to enter on mobile devices. The slight reduction in the character pool is negligible for security — a 16-character password without ambiguous characters still has about 97 bits of entropy. **Q: How long should my passwords be in 2026 — is 12 characters still enough?** A: In 2026, 12 characters is the absolute minimum, but 16 characters is the recommended default for most accounts. NIST SP 800-63B (updated 2024) sets 8 characters as the floor but security practitioners widely recommend 16+. The reason: GPU-accelerated cracking rigs can now exhaust all 12-character passwords using only lowercase and uppercase letters in under a day. With all character types (94 characters), a 12-character password has about 79 bits of entropy — adequate but not comfortable. A 16-character password with all types reaches ~105 bits and would take billions of years to crack at one trillion guesses per second. For high-value accounts (banking, email, cloud admin), use 20-32 characters. Always pair length with uniqueness — a unique 12-character password beats a reused 32-character one every time. **Q: I need to generate API keys for my SaaS product — should I use a password generator or something else?** A: For API keys, a password generator works but isn't the ideal tool. API keys need specific properties: high entropy (256+ bits), URL-safe characters (no + or /), a fixed recognizable format, and often a prefix for quick identification (like sk_live_ or ghp_). The best approach is to generate 32 bytes of cryptographically random data and encode them as Base64URL or hex, giving you 256 bits of entropy in a compact string. In Node.js: crypto.randomBytes(32).toString('base64url'). In Python: secrets.token_urlsafe(32). If you use this password generator, set the length to 43+ characters with alphanumeric-only characters (no symbols that require URL encoding). Always prefix API keys with your product name so users can identify them in .env files and secret managers. **Q: My company requires passwords with uppercase, lowercase, numbers and symbols — but I heard NIST says that's unnecessary. Who's right?** A: Both positions have merit, but they're addressing different problems. NIST SP 800-63B (2024) discourages mandatory composition rules like "must contain uppercase, lowercase, number, symbol" because they lead to predictable patterns (Password1! is technically compliant but weak) and make passwords harder to remember, causing people to reuse them. NIST instead recommends focusing on length and checking passwords against known-breached lists. However, when you're using a random password generator stored in a password manager, composition requirements are irrelevant — the generator picks all character types optimally. The NIST guidance targets human-chosen passwords, not machine-generated ones. For generated passwords in a manager, using all character types is still strictly better because it increases the entropy per character. So: your company's policy is suboptimal for human passwords but fine for generated ones. --- ### Free Regex Tester — Debug & Match Patterns Online URL: https://go-tools.org/tools/regex-tester Test regex patterns instantly against any text. Live match highlighting, capture groups, replace preview, split, and pattern explainer. JavaScript-flavor regex, 100% private, no signup. #### What is a regex tester? A regex tester (regular expression tester) is a tool that lets you write a regex pattern, paste a piece of test text, and see exactly what the pattern matches — with capture groups, replacement preview, and a flag breakdown — without recompiling code or running a script. For developers, it shortens the loop from minutes to milliseconds: tweak the pattern, watch the highlights move, ship the regex with confidence. A regular expression is a compact language for describing text patterns. `\d+` matches one or more digits. `[A-Za-z_]\w*` matches a typical identifier. `(?\d{4})-(?\d{2})-(?\d{2})` matches an ISO date and names each part. Regular expressions are the backbone of search-and-replace in every code editor, of validation in every form, of log parsing in every observability stack, and of `grep`, `sed`, and `awk` — the Unix tools half the internet runs on. They're also notoriously hard to write correctly: an off-by-one quantifier or a missing escape can match the wrong substring, miss a match entirely, or — worst case — trigger catastrophic backtracking that takes a CPU core hostage. A good regex tester catches each of those failure modes before they reach production. This tester runs the native ECMA-262 RegExp engine that ships in every modern browser — the same engine you call from JavaScript, TypeScript, Node.js, Deno, or Bun. That means: capture groups (numbered and named with `(?...)`), lookahead and lookbehind assertions (`(?=...)`, `(?!...)`, `(?<=...)`, `(? template alphabet — exactly what JavaScript's String.replace accepts. The Split tab applies String.split with the regex and shows each part. The Explain tab tokenises the pattern and annotates each piece in plain English, useful for code review, teaching, and porting between dialects. For privacy: every operation is local. Your pattern and your test text never leave the page — they aren't logged, aren't sent to an analytics service, aren't stored on disk. Only your UI preferences (active tab + which flags you usually have on) persist to localStorage. That makes this tool safe for redacted log samples, proprietary patterns, internal config, and patterns that include hints about your data schema. Compared to server-backed testers like regex101, the privacy and latency story is strictly better; the trade-off is single-flavor support (JavaScript only). If you're new to regex, the Common patterns dropdown ships with battle-tested starters: email address, URL, IPv4, UUID, hex color, ISO date, US phone number, and a trim-trailing-whitespace pattern. Load one, observe the matches against the supplied sample text, then mutate the pattern one character at a time to feel how the engine responds. Pair this with the Text Diff tool when you want to compare before/after of a regex-driven cleanup, with JSON Formatter when your input or expected output is JSON, or with URL encoder when the strings you match are URL-encoded. ``` // The pattern you build in this tester drops straight into JavaScript. // Example: extract every ISO date from a string with named groups. const pattern = /(?\d{4})-(?\d{2})-(?\d{2})/g; const text = 'shipped 2026-05-21, scheduled 2026-06-30'; for (const m of text.matchAll(pattern)) { console.log(m.groups.year, m.groups.month, m.groups.day); // → 2026 05 21 // → 2026 06 30 } // Same regex, used in a replace with $ templates: text.replace(pattern, '$/$/$'); // → 'shipped 21/05/2026, scheduled 30/06/2026' // With the /d flag, every match carries [start, end] indices // per capture group — the Matches panel uses this to paint offsets. const p2 = /(?\d{4})-(?\d{2})/gd; const m = [...text.matchAll(p2)][0]; m.indices.groups.year; // [8, 12] ``` #### FAQ **Q: Is my regex or test text sent to your server?** A: No. Every match, replace, split, and explain operation runs in JavaScript inside your browser using the native RegExp engine. Your pattern and text are not uploaded, not logged, not stored on disk, not sent to any third party. Only your UI preferences (active tab + which flags you usually have on) are saved to localStorage so the page remembers them next visit — never the pattern or the test text. You can verify by opening DevTools → Network: typing in either box fires zero requests. This makes the tool safe for proprietary patterns, redacted log samples, internal config, and anything else you would not paste into regex101. **Q: What regex flavor does this tester use — PCRE, Python, Java, JavaScript?** A: ECMA-262 (JavaScript), the dialect implemented by V8, JavaScriptCore, and SpiderMonkey — the same engine you get from `new RegExp(pattern, flags)` in any browser, Node.js, Deno, or Bun. That means the supported features are: capture groups (numbered + named with `(?...)`), lookaheads `(?=...)` and `(?!...)`, lookbehinds `(?<=...)` and `(?...)`, possessive quantifiers `a++`, conditionals `(?(1)yes|no)`, and inline modifiers `(?i)` will throw a syntax error. Python's `re.VERBOSE` is not supported here. For Python/Java/Go regex, port the pattern back to its native engine — most simple patterns transfer unchanged, and the explainer here is flavor-neutral. **Q: What do each of the flags g, i, m, s, u, y, d do?** A: g (global) — find every match, not just the first; required for iterating with .matchAll and for global replace. i (case-insensitive) — A and a match the same character. m (multiline) — ^ and $ anchor at every line break, not just the start/end of the whole input. s (dotAll) — . matches newlines too; without /s a dot stops at \n. u (unicode) — enables \u{HHHH} escapes, Unicode property escapes (\p{Letter}), and treats the pattern as a sequence of Unicode code points instead of UTF-16 code units. y (sticky) — anchors each match at lastIndex, useful for tokenisers. d (hasIndices, ES2022) — populates `.indices` and `.indices.groups` with [start, end] pairs for every capture; this tester uses /d under the hood to draw the group boundaries. Toggle them as chips above the test text; the canonical /pattern/flags literal is shown in the readout. **Q: How do I write capture groups and how do I refer back to them?** A: Wrap a sub-pattern in parentheses: `(\d{4})-(\d{2})-(\d{2})` gives you three positional groups, accessible as $1, $2, $3 in replacements or as m.groups[0..2] in the Matches panel. Use `(?...)` for named groups: `(?\d{4})-(?\d{2})` lets you write $/$ in the replacement template. Use `(?:...)` for a non-capturing group when you only need grouping for a quantifier (`(?:foo|bar)+`) — it does not create a back-reference, which keeps your numbered $1..$N stable. Inside the same pattern, refer to an earlier capture with `\1`, `\2`, etc. — handy for finding doubled words like `\b(\w+)\s+\1\b`. **Q: How do lookahead and lookbehind work, and what are they good for?** A: Lookarounds are zero-width assertions — they check that something matches (or doesn't) without consuming characters. `(?=foo)` (positive lookahead) succeeds if `foo` follows the current position; `(?!foo)` (negative lookahead) succeeds if `foo` does NOT follow. `(?<=foo)` and `(?2KB samples copy the regex via Copy /pattern/flags and paste the text separately. For collaborative regex review without sharing the actual text, share just the pattern and flags — the recipient pastes their own corpus and gets the same matches. **Q: Does the tester support Unicode, emoji, and non-Latin scripts?** A: Yes. Enable the /u flag to opt into full Unicode handling: \w matches Latin word characters (the default semantic), but with /u you can match wider categories via Unicode property escapes — `\p{Letter}` matches every letter in every script, `\p{Script=Han}` matches Chinese ideographs, `\p{Emoji}` matches emoji, `\p{Number}` matches every digit/numeral. Without /u, surrogate-pair emoji like 👨‍💻 are seen as two UTF-16 code units and patterns like `^.$` will fail to match them; with /u the dot treats each grapheme code point as one character. For RTL scripts (Arabic, Hebrew), patterns work without special handling — direction is a render-time concern, not a regex-engine concern. CJK content matches the same way as Latin. **Q: What's the difference between .match, .matchAll, .replace, and .split with a regex?** A: String.prototype.match returns the first match (or an array of all matches when /g is set) but loses capture groups when /g is on. String.prototype.matchAll requires /g and returns an iterator of match arrays WITH capture groups and indices — what this tester uses internally. String.prototype.replace accepts either a string template ($1, $&, etc) or a callback called per match with (match, ...groups, offset, string, namedGroups). String.prototype.split splits on every match — useful with /g but the global flag is ignored for split semantics. This tool exposes match via the Match tab, replace via Replace, and split via Split, so you can preview every flavour without leaving the page; the literal /pattern/flags is one click away when you're ready to paste into code. **Q: Why doesn't my Python or Java regex work here?** A: Because this tester runs ECMA-262 (JavaScript) — most patterns port cleanly, a handful don't. Common porting gotchas: (1) Python's inline flags `(?i)` and `(?x)` are not valid in JS — use the flag chips above instead. (2) Python's `\A` and `\Z` are `^` and `$` in JS (with /m for line anchors). (3) Java/Python conditionals `(?(name)yes|no)` are not supported in JS — rewrite with an alternation. (4) Possessive quantifiers `a++` and atomic groups `(?>...)` are JS-unavailable — simulate with `(?=(a+))\1`. (5) Python `(?P...)` is `(?...)` in JS. (6) `\h` for horizontal whitespace and `\v` for vertical are not in JS — use `[ \t]` and `[\n\r]`. For a portable port, the explainer breaks down what each token does so you can swap the unsupported syntax for an equivalent. **Q: Is there a maximum text size or match count?** A: Practical limit: about 200,000 characters of test text and 500 highlighted matches displayed at once. Beyond 500 matches the Matches panel shows a Showing first 500 banner; the count badge still reports the true total. The 250ms wall-clock budget bounds runaway patterns regardless of size. For multi-megabyte log files, run the regex with command-line `grep -oE` or `rg` (ripgrep) — they stream and won't hit a UI rendering cap. For one-off scans of huge text, paste a representative slice into this tester to validate the pattern, then run the validated pattern against the full file in your shell. --- ### REM to PX Converter — Convert Rem to Pixels URL: https://go-tools.org/tools/rem-to-px Convert rem to px instantly. 1rem = 16px at the default base, plus any custom root font-size. Free, live two-way conversion that runs 100% in your browser. #### What Is a REM to PX Converter? A rem to px converter translates rem units into pixel measurements. The rem ("root em") unit is relative to the font-size of the root element: 1rem always equals the root font-size, which browsers set to 16px by default. The conversion is a simple multiplication — px = rem × root-font-size — but checking it for every value while reading or debugging a stylesheet is repetitive, which is what this tool removes. Developers usually author CSS in rem for accessibility and scalability: values written in rem scale with the user's browser font-size preference, while px values stay fixed. But there are many moments when you need the pixel figure — matching a design mockup, handing off sizes to a designer who works in pixels, setting a px-only property, or debugging why an element renders at a particular size. That is when converting rem back to px is exactly what you want. This converter keeps the rem and pixel fields linked in real time, so you can move in either direction, and — unlike tools that hard-code a 16px base — it lets you set any root font-size. That matters because a stylesheet using the 62.5% technique (a 10px root) resolves rem to different pixel values than a default 16px setup. A live preview shows the resulting text size, and a reference table lists common rem values at a 16px base for quick lookups. Need the other direction? Use the px to rem converter. For cleaning up the stylesheet itself, try the CSS formatter, and for color work see the color converter. Everything runs in your browser — your values never leave your device. ``` /* The core formula */ /* px = rem × root-font-size (16px by default) */ :root { font-size: 16px; /* 1rem = 16px */ } /* 1.5rem -> 24px */ /* 1rem -> 16px */ /* 0.75rem -> 12px */ /* 2rem -> 32px */ /* JavaScript equivalent */ const remToPx = (rem, base = 16) => rem * base; console.log(remToPx(1.5)); // 24 console.log(remToPx(0.75)); // 12 ``` #### FAQ **Q: What is 1rem in pixels?** A: 1rem equals the root font-size in pixels — 16px by default. The rem unit is relative to the font-size of the root element, so px = rem × root-font-size. On a standard setup 1rem = 16px, 1.5rem = 24px, and 2rem = 32px. If a stylesheet changes the root font-size, the pixel value of 1rem changes with it. **Q: How do you convert rem to px?** A: To convert rem to px, multiply the rem value by the root font-size (16px by default). The formula is px = rem × base. For example, 1.5rem × 16 = 24px, and 0.75rem × 16 = 12px. This tool does the multiplication for you in real time and lets you change the base if your project uses a different root font-size. **Q: What is 16px in rem?** A: 16px equals 1rem at the default 16px root font-size, because rem = px ÷ base. This is the reverse of the rem-to-px direction: 1rem = 16px, so 16px = 1rem. Change the base in the tool, or use the px to rem converter, if your project does not use a 16px root. **Q: Why do designs use rem instead of px?** A: Rem units respect the user's browser font-size preference, which is important for accessibility. When someone increases their default font size, everything sized in rem scales proportionally, while px values stay fixed. Rem also keeps a design system consistent — change one root font-size and the whole interface rescales. Converting rem back to px is still useful for pixel-precise review, design hand-off, and debugging. **Q: What is the difference between rem, em, and px?** A: px is an absolute unit — a fixed CSS pixel. rem is relative to the root () font-size, so it is consistent across the page. em is relative to the font-size of the current element's parent, so it compounds when nested. Use rem for most sizing so it scales with the user's preference, px for fixed details like 1px borders, and em when you want a value to scale relative to its local context. **Q: What is the 62.5% font-size trick?** A: Setting html { font-size: 62.5% } makes the root font-size 10px (62.5% of the 16px default). With a 10px base, 1rem = 10px, so rem-to-px is just "multiply by 10": 1.6rem = 16px, 2.4rem = 24px. Many developers like the simpler arithmetic, then set body { font-size: 1.6rem } for readable 16px body text. Set this tool's root font-size to 10 to convert against the 62.5% base. **Q: Can I change the root font-size in this converter?** A: Yes. The Root font-size field defaults to 16px but accepts any value — 10, 18, 20, or whatever your project uses. Every conversion and the reference table update against the base you set. Click 'Reset to 16' to return to the default. Most converters hard-code 16px; the custom base is what lets this tool match your real stylesheet. **Q: Is 1rem always 16px?** A: Only when the root font-size is 16px, which is the default in virtually all browsers. The relationship 1rem = 16px holds as long as html { font-size } is not overridden. If a stylesheet sets the root font-size to 10px or 18px, then 1rem equals that value instead. Always convert against the page's actual root font-size. **Q: When should I convert rem back to px?** A: Converting rem to px is useful when you need pixel-precise values: matching a design mockup, communicating sizes to a designer who works in pixels, setting a px-only property, or debugging why an element renders at a certain size. The rem authoring stays in your CSS for scalability; the px figure is for verification and hand-off. **Q: Does it matter which root font-size I use for the conversion?** A: Yes — the root font-size is the multiplier, so it directly changes the result. 2rem is 32px at a 16px base but 20px at a 10px base. If your stylesheet uses the 62.5% technique or any custom root font-size, set the tool's base to match, or the pixel values will be wrong. **Q: How accurate is this rem to px converter?** A: Conversions use IEEE 754 double-precision arithmetic and the exact formula px = rem × base, with results rounded to at most five decimal places and trailing zeros trimmed. That is far finer than the browser needs, and the math is deterministic, so you can rely on it for production work. **Q: Is my data safe when using this converter?** A: Completely. All conversions run locally in your browser using JavaScript. No values are sent to any server — there are no network requests, no cookies on your input, and no analytics on what you type. You can verify it by disconnecting from the internet: the tool keeps working fully offline once the page has loaded. --- ### Remove Duplicate Lines from Text URL: https://go-tools.org/tools/remove-duplicate-lines Remove duplicate lines from text online — free, instant, 100% in your browser. Ignore case or whitespace, keep first or last occurrence, and see how many duplicates were removed. Also sort and clean up. No signup. #### What Does Removing Duplicate Lines Mean? Removing duplicate lines — deduplication, or "dedupe" — means scanning a block of newline-separated text and keeping only one copy of each distinct line. It is one of the most common cleanup tasks anyone who works with data runs into: collapsing a mailing list that accumulated repeats, reducing a log to its unique messages, finding the distinct values in a spreadsheet column, or merging several lists into one clean set. The interesting question is what counts as a duplicate. Byte-for-byte comparison is the strict answer, but real data is messier. Two lines can look identical yet differ by a trailing space, or vary only in capitalization — `Error` versus `error`. This tool lets you decide: by default it ignores case, and you can enable whitespace-insensitive comparison so lines are matched on their trimmed, lowercased form. That way you deduplicate on the meaning of a line rather than its exact encoding, which is almost always what you actually want. The other decision is which copy to keep. Keeping the first occurrence preserves the original order and is the default; keeping the last is useful when later entries supersede earlier ones. Either way, the result is deterministic and the live stats badge tells you exactly how many duplicates were removed, so you never have to trust the tool blindly. Deduplication pairs naturally with sorting and other cleanup. This tool folds them into one panel: remove duplicates, then optionally sort, trim whitespace, drop blank lines, reverse, or add line numbers. When sorting is the main event, use the companion Sort Text Lines tool, which shares the same engine. To count the lines and words in your text, use the Word Counter; to see what changed between two versions of a list, use Text Diff; and to normalize casing before deduplicating, use the Case Converter. Everything runs entirely in your browser with no uploads, so deduplicating a confidential list is exactly as private as editing it locally. Paste, and the unique result is ready to copy in a second. ``` Input: alice@example.com bob@example.com alice@example.com ALICE@example.com Exact dedupe (case-sensitive): alice@example.com bob@example.com ALICE@example.com Case-insensitive dedupe (default): alice@example.com bob@example.com ← ALICE collapsed into alice ``` #### FAQ **Q: What does this tool do?** A: It removes duplicate lines from a block of text, keeping each unique line once. As you paste or type, the deduplicated result appears instantly with a live count of lines in, lines out, and how many duplicates were removed. You can control what counts as a duplicate (ignore case, ignore whitespace), choose to keep the first or last occurrence, and optionally sort, trim, remove empty lines, or add line numbers in the same pass. Everything runs 100% client-side in your browser — nothing is uploaded, logged, or stored — so it is safe for emails, logs, and any confidential list. You can confirm this in your browser's Network tab: deduplicating triggers zero network requests. **Q: Does it keep the first or last occurrence of a duplicate?** A: By default it keeps the first occurrence and removes later repeats, which preserves the original order of your list. Switch Keep occurrence to Last to keep the final appearance of each line instead — the surviving line then sits at its last position. Keep-first is the right choice most of the time (it is what `sort -u` and most dedupe tools do); keep-last is handy when later entries supersede earlier ones, such as configuration overrides, environment variables, or a log where the newest status wins. The stats badge reports the same duplicate count either way. **Q: Is my text uploaded anywhere?** A: No. Deduplication and every other operation run entirely in your browser using JavaScript. Your text is never transmitted, never stored on any server, never logged, and never analyzed. This makes the tool safe for email lists, access logs, internal data, and any content you would not paste into a networked service. No cookies capture your input and no third-party analytics read what you type. Open your browser's developer tools and watch the Network tab while you paste a huge list — you will see zero requests, because the work happens locally. **Q: How do I remove duplicates but ignore case?** A: Leave Case-sensitive off — that is the default. With it off, `Error`, `error`, and `ERROR` are treated as the same line and collapsed to a single entry (the first one encountered, in its original casing). Turn Case-sensitive on only when the casing is meaningful and `ID` should be kept separate from `id`. Case handling applies to the comparison, not the output: the surviving line keeps whatever casing it originally had; only the decision about which lines are duplicates changes. **Q: Why aren't my visually identical lines being deduplicated?** A: Almost always the culprit is invisible whitespace — a trailing space, a tab, or a non-breaking space — that makes two lines differ byte-for-byte even though they look the same. Turn on Ignore leading/trailing spaces so lines are compared after trimming, and the duplicates will collapse. This is extremely common in data pasted from spreadsheets, PDFs, and terminal output. If lines still won't merge, they may differ by an internal character (a double space vs single space, or a smart quote vs a straight quote); trim only handles the edges. **Q: Does removing duplicates change the order of my lines?** A: No — with keep-first (the default), the surviving lines stay in their original order; only the repeated appearances are removed. So deduplicating `a, b, a, c` gives `a, b, c` in that order. If you also want the result sorted, enable a sort order and the tool will deduplicate first, then sort. With keep-last, order is still based on original positions, but each surviving line moves to where its last occurrence was. In every mode the output is deterministic, so the same input and settings always produce the same result. **Q: Can I remove duplicates and sort at the same time?** A: Yes. Enable a sort order (Ascending or Descending) while deduplication is on, and you get a clean, unique, ordered list in a single step. The pipeline removes duplicates first, then sorts, so nothing is lost. If sorting is your primary goal and dedupe is secondary, the companion Sort Text Lines tool shares the same engine and opens with sorting enabled. Both tools expose the full option set; they differ only in which task they lead with. **Q: What happens to blank lines?** A: By default, blank lines are treated like any other line: if the same blank line appears twice, the duplicate is removed, but a single blank line is preserved. If you want to drop all blank and whitespace-only lines entirely, enable Remove empty lines — the stats badge counts them separately from duplicates, so you can see `6 lines → 3 lines · 1 duplicates removed · 2 empty removed`. A single trailing newline at the end of your input is treated as a line terminator, not an extra blank line, so it won't skew the counts. **Q: Is there a limit on list size?** A: There is no hard limit built into the tool; it is bounded only by your browser's memory and how much text you can paste into a textarea. Deduplication uses a hash set, so it is fast even for large lists — tens of thousands of lines are instantaneous and hundreds of thousands complete with a brief pause. Because everything runs locally, large lists are often quicker here than on server-based tools that must upload your data first. For multi-million-line files, a command-line tool like `sort -u` or `awk '!seen[$0]++'` will be more comfortable, but for everyday lists this handles them easily. **Q: How does this compare to the command line (sort -u, awk, uniq)?** A: `sort -u` removes duplicates but also sorts, and `uniq` only collapses adjacent duplicates (so it requires pre-sorted input). `awk '!seen[$0]++'` removes duplicates while preserving original order — the same as this tool's keep-first mode — but requires a terminal. This tool gives you order-preserving dedupe, keep-first or keep-last, case- and whitespace-insensitive comparison, optional sorting, and a live count, all without leaving the browser or installing anything. It is the fastest option when the list is already on your clipboard rather than in a file. **Q: How is this faster than removing duplicate lines in Excel or Notepad++?** A: In Excel you'd use Data → Remove Duplicates on a column, and Notepad++ needs a TextFX or sort-then-uniq plugin — both require the data to already be inside that app. This tool works the moment text is on your clipboard: paste it and duplicate lines are removed instantly, with case- and whitespace-insensitive matching, keep-first or keep-last, and a live duplicate count those tools don't show. It also preserves the original order, unlike Excel's row-based removal or a sort-first approach, and nothing is uploaded. When you just need to dedupe a chunk of text quickly, it beats switching to a spreadsheet or installing a plugin. **Q: Can I use this to find unique values in a column?** A: Yes. Copy a single column out of a spreadsheet or CSV, paste it here, and the deduplicated output is the set of unique values. Enable Ignore leading/trailing spaces to catch values that differ only by stray whitespace, and turn on a sort order if you want them alphabetized. The stats badge tells you how many duplicates existed, which doubles as a quick way to gauge the cardinality of the column. Paste the unique result back into your sheet or query when you're done. --- ### RGB to Hex Converter URL: https://go-tools.org/tools/rgb-to-hex Convert RGB to hex in your browser — integers, percentages, and rgba alpha all supported. Free, instant, no signup, your colors never leave the page. #### What Is an RGB to Hex Converter? An RGB to hex converter is a small utility that turns three 0-255 integer channel values (`rgb(255 87 51)`) into the 6-character hex code that encodes the same color (`#FF5733`). RGB and hex are the two formats every web stylesheet, design tool, and image-pixel pipeline has been built around since the late 1990s, and the conversion between them is the single most common operation in color tooling — paired with its inverse direction, this exact transform runs millions of times per day across every Figma plugin, CSS preprocessor, design-token build, and color-picker UI on the web. RGB is the channel-addressed format that hardware APIs, canvas drawing calls, image-buffer manipulation, OpenGL color attributes, and most graphics SDKs report natively — three separate 0-255 integers that map directly to the red, green, and blue subpixels of an LCD or the phosphors of a CRT. Hex is the terse copy-paste format that Figma, Sketch, Photoshop, and every brand-guidelines PDF expect for output — a 6-character base-16 string that fits in a CSS custom property comfortably and reads at a glance once your eyes learn the patterns. Converting between them is mechanical: convert each integer to a 2-digit base-16 pair and concatenate with a leading `#`. This tool runs that conversion live as you type, with no "Convert" button to click, and surfaces every other common color format (HSL, OKLCH, OKLAB, HSV, HWB, CMYK, plus the 148 CSS named colors) alongside the HEX output for free. **The hex format itself deserves a closer look.** Standard CSS hex comes in four legal shapes. The canonical 6-digit form `#RRGGBB` packs three 8-bit channels into 6 base-16 digits — 16,777,216 colors total (256³). The 3-digit shorthand `#RGB` is a compressed form where each digit is duplicated to form the 6-digit equivalent: `#F73` expands to `#FF7733`, *not* `#000F73` (this is one of the most-mistaken rules in CSS color syntax). The 8-digit alpha form `#RRGGBBAA` appends a 2-digit alpha pair on a 0-`FF` scale, with `00` fully transparent and `FF` fully opaque. The 4-digit alpha shorthand `#RGBA` mirrors 3-digit shorthand by duplicating each digit, including the alpha digit. Hex is case-insensitive — `#ff5733` and `#FF5733` parse identically, though most brand guidelines pick a case convention and stick with it. The base-16 choice is convenient because one hex digit = nibble = 4 bits, two digits = byte = 0-255, so a single 2-digit pair maps cleanly to one 8-bit channel with no padding waste. The conversion math goes both directions cleanly. **RGB to HEX**: for each channel, call `value.toString(16).padStart(2, '0')` to get the 2-digit hex pair (the `padStart` matters — without it, channel value 5 would serialize as `'5'` instead of `'05'`, producing invalid hex), then concatenate. For alpha-bearing RGB (`rgb(R G B / A)` or `rgba(R, G, B, A)`), multiply the 0-1 alpha float by 255, round to the nearest integer, hex-encode as a 4th pair, and emit the 8-digit form. **HEX to RGB** is the inverse: parse the 6-digit hex `#RRGGBB` as three 2-digit base-16 numbers via `parseInt(hex.slice(1, 3), 16)`, etc. Both directions are bit-exact: 16² = 256, exactly matching the 0-255 byte range each channel occupies, so an RGB → HEX → RGB round-trip produces the original integers verbatim with no float drift. **Why HEX over RGB in CSS?** Three reasons. Hex is shorter — `#FF5733` is 7 characters versus `rgb(255, 87, 51)` at 16 characters, a meaningful difference when packed into a CSS custom property or a Tailwind config object. Hex has no whitespace bugs — CSS minifiers, JSON serializers, and command-line tools all handle a 7-character string cleanly without worrying about parenthesis matching or comma escaping. And hex is the format the entire design-tool ecosystem speaks natively — Figma's color panel, Sketch's swatches, Photoshop's color picker, every brand-guidelines PDF, every Dribbble shot's color callout — they all export hex by default. The copy-paste path from designer to developer is hex-shaped, which is why the RGB-to-HEX conversion is so frequent: developers receive RGB from a non-design tool (a canvas call, a screenshot eyedropper, a hardware sensor) and need to turn it into the hex form that the rest of their stack expects. This tool's RGB → HEX workflow is one direction of a 5-spoke family that all share the same underlying unified color converter. The dedicated unified color converter is the hub — it shows all 9 formats simultaneously editable and is the right tool when your workflow needs more than just RGB and hex. The single-direction spokes target specific Google search intents: the reverse hex to RGB converter for the inverse direction (taking a hex from Figma and pulling out the 0-255 integers), the hex to HSL converter for the legacy designer-cognitive space, the hex to OKLCH converter for modern perceptually-uniform design systems (Tailwind v4 and shadcn both default to OKLCH now), and the hex to CMYK converter for print-prep approximations. All five spokes and the hub share the same parsing engine and the same conversion math, so the results are guaranteed identical across the family. Every conversion runs locally in your browser — your RGB values are never uploaded, never logged, and zero network requests fire as you type. Verify in DevTools. ``` // Serialize {r, g, b, alpha} → canonical hex string // Emits 6-digit #RRGGBB when alpha === 1, 8-digit #RRGGBBAA otherwise. function formatHex({ r, g, b, alpha = 1 }) { const pair = (v) => Math.round(v).toString(16).padStart(2, '0').toUpperCase(); const rgb = pair(r) + pair(g) + pair(b); if (alpha >= 1) return `#${rgb}`; const a = pair(alpha * 255); return `#${rgb}${a}`; } console.log(formatHex({ r: 255, g: 87, b: 51 })); // '#FF5733' console.log(formatHex({ r: 59, g: 130, b: 246 })); // '#3B82F6' console.log(formatHex({ r: 255, g: 87, b: 51, alpha: 0.5 })); // '#FF573380' console.log(formatHex({ r: 5, g: 0, b: 0 })); // '#050000' — padStart matters ``` #### FAQ **Q: How do you convert RGB to hex?** A: Convert each 0-255 channel integer to a 2-digit base-16 string, then concatenate with a leading `#`. In JavaScript: `[255, 87, 51].map(v => v.toString(16).padStart(2, '0')).join('')` returns `'ff5733'`. The `padStart(2, '0')` matters — without it, single-digit values like `5` serialize as `'5'` instead of `'05'`, producing an invalid hex. This tool runs the conversion live as you type — paste any `rgb()` value (with or without spaces, with comma or modern space syntax, with or without alpha) and the HEX field updates instantly with the matching `#RRGGBB` or 8-digit `#RRGGBBAA` value. **Q: What is RGB in hex?** A: RGB in hex is the same color encoded as a 6-character base-16 string. Both forms describe a color as three channels (red, green, blue) on the 0-255 scale, anchored to the sRGB color space. `rgb(255 87 51)` and `#FF5733` are interchangeable anywhere a `` is accepted in CSS — they round-trip losslessly. Hex packs the same information into a terser form that fits cleanly in CSS variables and copies cleanly between Figma, Sketch, Photoshop, and code; `rgb()` keeps the channels addressable as separate integers for canvas calls and hardware APIs. **Q: How do I get the hex code from RGB?** A: Take each channel value (0-255), call `toString(16)` to get its base-16 representation, left-pad with zero to 2 digits, and concatenate with a leading `#`. `rgb(255 87 51)` becomes: `255 → 'ff'`, `87 → '57'`, `51 → '33'`, result `#ff5733`. Capitalize if your style guide requires it (`#FF5733`); both forms are valid CSS. For alpha-bearing RGB like `rgb(255 87 51 / 0.5)`, multiply the alpha by 255, round, and append the resulting 2-digit hex pair: `0.5 × 255 = 128 = 0x80`, result `#ff573380`. This tool does both transforms automatically. **Q: What is the formula for RGB to hex?** A: For each channel: `value.toString(16).padStart(2, '0')`. The `toString(16)` converts the integer to its base-16 representation; the `padStart(2, '0')` ensures the result is exactly 2 characters (necessary for values under 16, which would otherwise serialize as 1 character). Concatenate the three results, prepend `#`, and you have the canonical hex. Mathematically: for channel `n` in `[0, 255]`, the hex digits are `Math.floor(n / 16)` and `n % 16` mapped through `'0123456789abcdef'`. There is no rounding loss — 16² = 256, exactly matching the 0-255 byte range each channel occupies. **Q: Does rgb(0,0,0) equal #000000?** A: Yes — exactly. `rgb(0, 0, 0)` and `rgb(0 0 0)` (modern space-separated CSS Color 4 syntax) both serialize to `#000000`, pure black with all three channels at zero. Every channel pair encodes as `00`, concatenated to a 6-character hex `000000`. The same equivalence holds at the other extreme: `rgb(255, 255, 255)` ↔ `#FFFFFF` (pure white). Any RGB triple has exactly one canonical 6-digit hex representation, and any 6-digit hex has exactly one RGB triple — the mapping is bijective across the full 16,777,216-color sRGB space. **Q: Can RGB have an alpha channel?** A: Yes — use the `rgba()` legacy form `rgba(255, 87, 51, 0.5)` or the modern CSS Color 4 slash syntax `rgb(255 87 51 / 0.5)`. Both encode an alpha float from 0 (fully transparent) to 1 (fully opaque). When converting to hex, alpha becomes a 4th 2-digit pair appended after RGB: multiply by 255, round, hex-encode. `0.5 × 255 = 128 = 0x80`, so `rgb(255 87 51 / 0.5)` becomes `#FF573380`. 8-digit hex with alpha shipped in all evergreen browsers in 2018; before that, the `rgba()` form was the only way to express alpha in CSS. **Q: How do hex and RGB differ?** A: They encode the same color in different notation. Hex packs three 0-255 channels into a 6-character base-16 string (`#FF5733`); `rgb()` spells the channels out in decimal (`rgb(255 87 51)`). Hex is shorter and design-tool-native — Figma, Sketch, Photoshop, and every brand guidelines PDF export hex by default, and most front-end developers can recognize `#3b82f6` as Tailwind blue-500 on sight. RGB is explicit channel-addressing, easier to compute against in JavaScript, and the only form that accepts percentage channels and natively-syntaxed alpha. Both are equally valid CSS and round-trip losslessly. **Q: How accurate is RGB to hex?** A: Bit-exact. RGB → hex is integer-to-string math with zero float involvement: `toString(16).padStart(2, '0')` produces the canonical 2-digit hex pair for every value in 0-255, and the reverse (`parseInt(pair, 16)`) recovers the original integer exactly. A round-trip RGB → HEX → RGB → HEX produces the original tuple verbatim, indefinitely. 16² = 256, exactly matching the byte range, so there's no rounding loss in either direction. Percentage RGB inputs round to the nearest integer first (`50% × 255 / 100 = 127.5 → 128`), which is the standard CSS Color 4 normalization rule. --- ### Free Online RSA Key Pair Generator — ECDSA and Ed25519 URL: https://go-tools.org/tools/rsa-key-generator Generate an RSA key pair online — the private key never leaves your browser. RSA 2048/4096, ECDSA and Ed25519, in PKCS#8 or PKCS#1 PEM, with JWK and fingerprint. #### What is an RSA key generator? An RSA key generator produces a mathematically linked pair: a private key you keep and a public key you hand out. Anything signed with the private half can be verified with the public half, and only the public half is safe to publish. That asymmetry is the whole point — it is what lets a verifier check your signatures without gaining the ability to forge them, which a shared secret can never offer. This generator runs inside your browser through the Web Crypto API, so the private key is created in the tab and the page makes no network request while generating. Beyond RSA it also produces ECDSA and Ed25519 pairs, which serve the same purpose with much shorter keys: an Ed25519 private key is 48 bytes in PKCS#8 where a 2048-bit RSA key runs past 1.2 KB. The part that trips people up is not the mathematics but the packaging. The same key can be written as PKCS#8, PKCS#1, SPKI or JWK, and a library that rejects one will often accept another with no clearer message than a parse error. The table further down maps each container to the ecosystems that expect it. ``` // Verify a downloaded key pair matches, using OpenSSL: openssl pkey -in rsa-2048-private.pem -pubout | diff - rsa-2048-public.pem // No output means the public key really belongs to that private key. ``` #### FAQ **Q: Is it safe to generate a private key on a website?** A: The key is generated by the Web Crypto API in your own browser, and this page makes no network request while generating — you can watch the Network panel, or disconnect and see that generation still works. Be clear about what that shows and what it does not: it demonstrates the page needs no server, but it is not proof against a compromised script, since the JavaScript is re-fetched from our server on every visit. The risks that remain are the browser's rather than the network's — a malicious extension can read the page and your clipboard, and anyone with access to the machine can read the downloaded file. For a key protecting production systems, generate it on the host that will use it; the OpenSSL commands below do exactly that. This page is the right tool for development, testing, learning, and anywhere a browser-generated key is acceptable. **Q: What is the difference between PKCS#8 and PKCS#1?** A: They are two containers for the same RSA key. PKCS#1, marked BEGIN RSA PRIVATE KEY, holds the RSA numbers directly and exists only for RSA. PKCS#8, marked BEGIN PRIVATE KEY, wraps those same numbers in an algorithm identifier, which lets one format carry RSA, ECDSA and Ed25519 alike. Most modern libraries expect PKCS#8 — the JDK, notably, reads only PKCS#8 without an extra library — while a number of payment gateways and older OpenSSL-era tooling still require PKCS#1. Switching the toggle on this page re-encodes the key already on screen, so no information is added or lost and the fingerprint stays the same. **Q: How do I convert a PKCS#1 key to PKCS#8, or back?** A: Switch the PEM structure toggle on this page and the same key is re-emitted in the other container. For a file you already have, OpenSSL converts locally: openssl pkcs8 -topk8 -nocrypt -in pkcs1.pem -out pkcs8.pem turns a BEGIN RSA PRIVATE KEY file into BEGIN PRIVATE KEY, and openssl rsa -traditional -in pkcs8.pem -out pkcs1.pem goes the other way. Neither direction adds or removes key material; only the wrapper changes, which is why both files describe the identical key and produce the identical fingerprint. **Q: How do I get the public key from an RSA private key?** A: The public key is derivable from the private key, never the reverse. This page shows both halves at once, so there is nothing to derive. For a private key you already have on disk, openssl pkey -in private.pem -pubout -out public.pem writes the matching BEGIN PUBLIC KEY file. That is also how you check whether a pair belongs together — regenerate the public half and compare it against the file you were given. **Q: Should I choose RSA or Ed25519?** A: Choose Ed25519 when nothing forces your hand. It reaches security comparable to RSA 3072 with a 32-byte key, signs faster, and has no parameters to misconfigure. Choose RSA when a counterparty, a certificate authority or an old library requires it, which is still common in enterprise and payment systems. ECDSA sits between the two and is widely supported in TLS. **Q: Is 2048-bit RSA still strong enough?** A: Yes, for most purposes today. NIST rates 2048-bit RSA at 112-bit security strength and carries it through 2030 in SP 800-57; 3072-bit reaches 128-bit strength and is what NIST points to beyond that. So: 2048 for anything you will rotate within a few years, 3072 or 4096 for a key you expect still in service in the 2030s or where a certificate authority requires it. The cost of the larger key is slower signing and larger signatures, not weaker security. **Q: Can I use these keys to sign JWTs?** A: Yes. RSA keys work with RS256, RS384 and RS512 as well as the PSS variants; ECDSA P-256 pairs with ES256; and Ed25519 is the EdDSA algorithm. Sign with the private key and publish the public key so verifiers can check signatures without being able to create them. One caveat if you use the JWK panel rather than the PEM: Web Crypto stamps an RSA JWK with alg RS256, and strict libraries refuse to load it for PS256 or RS512 — delete the alg member, or use the PEM. Our JWT encoder accepts these keys, and the decoder shows what the resulting token contains. **Q: Can this generate an SSH key?** A: Not directly. OpenSSH's own private key file is a different container, and this page does not write it. An RSA key from here is still usable: ssh-keygen -y -f rsa-2048-private.pem > id_rsa.pub derives the authorized_keys line from the file you downloaded. Ed25519 keys from here are not, because OpenSSH rejects the PKCS#8 form. For SSH access the better answer remains ssh-keygen -t ed25519 on the machine that needs the key, which avoids moving a private key at all. For JWT signing, CI release signing and webhook verification, the PKCS#8 key this page produces is exactly what those tools want. **Q: Can I protect the private key with a passphrase?** A: Not here. Encrypted PKCS#8 requires a key derivation step that the Web Crypto API does not expose, so implementing it would mean hand-rolling cryptography in JavaScript. Add the passphrase locally instead: openssl pkcs8 -topk8 -in private.pem -out encrypted.pem reads the file you downloaded and prompts for one. **Q: What is the fingerprint for?** A: It is a SHA-256 digest of the public key structure, short enough to compare by voice or in a chat message. When you send someone a public key, comparing fingerprints over a separate channel confirms that what arrived is what you sent. It identifies the public key only and reveals nothing about the private half. Note that it is not the number ssh-keygen -l prints: OpenSSH hashes its own wire format rather than the SPKI structure, so the two never match for the same key. --- ### SHA-1 Hash Generator (160-bit Legacy) URL: https://go-tools.org/tools/sha-1-generator Generate SHA-1 hashes in your browser — 40-character hex output, no upload. Legacy tool for Git fingerprints, old certificate checks, and migration audits. Data never leaves your device. #### What Is SHA-1? SHA-1 (Secure Hash Algorithm 1) is a 160-bit cryptographic hash function published by NIST in 1995 as FIPS 180-1. It was designed by the U.S. National Security Agency to replace SHA-0 (a flawed earlier version quickly withdrawn in 1993) and was the dominant hash algorithm for digital signatures, TLS certificates, and code signing through the 2000s. History of breaks: In 2005, Xiaoyun Wang's team published a theoretical attack reducing SHA-1 collision resistance from the expected 2^80 to 2^63 operations — a theoretical break, but not yet practical. In February 2017, Google and CWI Amsterdam released the SHAttered attack, producing two distinct PDF documents with identical SHA-1 hashes using approximately 110 GPU-years of computation. This was the definitive practical break. NIST had already deprecated SHA-1 for signatures in 2011 (NIST SP 800-131A); browser vendors and Certificate Authorities followed by removing SHA-1 certificate support in 2016–2017. Current status: SHA-1 is deprecated for all security-sensitive uses — digital signatures, certificate fingerprints, password storage, and code signing. It persists in Git's object-ID format (commit hashes), where it is used for content addressing rather than security, and in legacy software checksums where administrators have not yet migrated. The Git project added SHA-256 object format support in version 2.29 (October 2020). All new projects should use SHA-256 or stronger. This tool computes SHA-1 entirely in your browser using crypto.subtle.digest('SHA-1', ...) from the Web Crypto API. The 40-character hex output is identical to what sha1sum, openssl dgst -sha1, or git hash-object produce. No bytes are sent to any server. SHA-1 vs the SHA-2 family: SHA-1 produces 40 hex chars (160 bits). SHA-256 produces 64 hex chars (256 bits) and has no known weaknesses. MD5 produces 32 hex chars (128 bits) and was broken earlier (2004). For any new hashing work, SHA-256 is the standard choice. ``` // Hash text using Web Crypto API (SHA-1 — legacy use only) async function sha1(text) { const data = new TextEncoder().encode(text); const hash = await crypto.subtle.digest('SHA-1', data); return Array.from(new Uint8Array(hash)) .map(b => b.toString(16).padStart(2, '0')) .join(''); } await sha1('Hello, World!'); // → '0a0a9f2a6772942557ab5355d76af442f8f65e01' // ⚠️ SHA-1 is broken — use SHA-256 for new work. ``` #### FAQ **Q: Is SHA-1 still safe to use?** A: No. SHA-1 was theoretically weakened in 2005, and in February 2017 Google and CWI Amsterdam demonstrated the first practical collision via the SHAttered attack — two different PDF files with identical SHA-1 hashes. NIST deprecated SHA-1 for digital signatures in 2011 (NIST SP 800-131A) and all major browsers and Certificate Authorities stopped accepting SHA-1 certificates by 2017. SHA-1 is broken for any security-sensitive use: signatures, certificates, password storage. For all new work, switch to SHA-256. **Q: Why does Git still use SHA-1?** A: Git uses SHA-1 for object IDs (commit hashes, tree hashes, blob hashes) because it was designed in 2005 when SHA-1 was still widely trusted. Git's use is not a cryptographic signature — it is a content-addressing scheme used to detect accidental corruption, not deliberate tampering. The Git project has been migrating since Git 2.29 (2020), which added --object-format=sha256 support. GitHub and large forges are gradually rolling out SHA-256 mode. Existing repositories can be converted, but the migration is complex due to the billions of existing commit IDs. For now, SHA-1 commit IDs remain how most Git history is stored, making this tool useful for cross-checking commit object hashes. **Q: Should I migrate from SHA-1 to SHA-256?** A: Yes, for any security-sensitive system. Concrete migration checklist: (1) TLS certificates — if you still have SHA-1-signed certs, replace them immediately; CAs will not issue new ones anyway. (2) API signatures and HMACs — replace with HMAC-SHA-256. (3) Password hashes stored as SHA-1 — migrate to bcrypt or Argon2. (4) Document or package checksums — republish with SHA-256 alongside or replacing SHA-1. (5) Git repositories — opt into SHA-256 mode for new repos if your toolchain supports it. Legacy checksums on archived downloads can remain as-is since they only need to detect accidental corruption. **Q: What was the SHAttered attack?** A: SHAttered (shattered.io, February 2017) was a practical SHA-1 collision produced by Google Security and CWI Amsterdam. The attack cost approximately 110 GPU-years of computation (~$110,000 USD at 2017 cloud prices) and produced two distinct PDF files that produce the same SHA-1 hash: 38762cf7f55934b34d179ae6a4c80cadccbb7f0a. This shattered the assumption that SHA-1 collisions were only theoretical. The attack works by exploiting a differential path in SHA-1's compression function. By 2020, the cost of a chosen-prefix SHA-1 collision had dropped to ~$45,000. Contrast with SHA-256, for which no collision of any kind has ever been found. **Q: Can SHA-1 collisions happen accidentally?** A: Accidentally stumbling on a SHA-1 collision without deliberate effort is still astronomically unlikely — there are 2^160 possible SHA-1 values, so random collision probability is roughly 1 in 10^24 for any two given inputs. The danger is adversarial: a determined attacker can now craft a collision for around $45,000. Accidental corruption of Git history is not a realistic threat from SHA-1 weakness. The real risk is in digitally signed documents, certificates, and code-signing workflows where an attacker could substitute a malicious document with the same hash as a trusted one. **Q: Is SHA-1 OK for non-security uses like checksums?** A: For detecting accidental data corruption — a garbled download, a flipped bit on disk — SHA-1 is technically still adequate, since accidental collisions are still essentially impossible. However, there is little reason to use SHA-1 even for non-security checksums today, because SHA-256 is only marginally slower (hardware-accelerated in all modern CPUs), universally supported, and future-proof. The only legitimate reason to use SHA-1 now is interoperability with a legacy system that only accepts 40-character hex fingerprints. **Q: How long is a SHA-1 hash?** A: A SHA-1 hash is always exactly 160 bits, represented as 40 hexadecimal characters (2 hex chars per byte × 20 bytes). The output length is fixed regardless of input size — hashing a single character or a 10 GB file both produce exactly 40 hex chars. Compare: MD5 produces 32 hex chars (128 bits), SHA-256 produces 64 hex chars (256 bits), and SHA-512 produces 128 hex chars (512 bits). The shorter output relative to SHA-256 is one reason SHA-1's collision resistance is weaker — fewer possible values means collisions are statistically easier to find. **Q: Is my input sent to any server?** A: No. SHA-1 is computed entirely in your browser using the Web Crypto API (crypto.subtle.digest('SHA-1', data)). Open DevTools → Network tab while hashing — you will see zero outgoing requests. Files you drop in are read via the FileReader API and hashed locally; the bytes never leave your machine. This makes the tool safe for hashing confidential documents, legacy certificates, or proprietary source code fingerprints. The same privacy guarantee applies to the SHA-256 generator. **Q: Why does my SHA-1 output differ from sha1sum on the command line?** A: Almost always a trailing newline. The shell command echo 'hello' | sha1sum includes a newline (\n) after 'hello', so it hashes 'hello\n' not 'hello'. Use echo -n 'hello' | sha1sum or printf '%s' 'hello' | sha1sum to strip it. Other common causes: Windows line endings (\r\n vs \n), UTF-8 BOM at the start of the file, or encoding differences (UTF-8 vs Latin-1). This tool encodes input as UTF-8 without BOM before hashing. --- ### SHA-256 Hash Generator & Checksum Tool URL: https://go-tools.org/tools/sha-256-generator Generate SHA-256 hashes online for free. Hash text or files in your browser, verify checksums, and copy 64-character hex output. No signup; data never leaves the page. #### What Is SHA-256? SHA-256 (Secure Hash Algorithm, 256-bit) is the most widely deployed cryptographic hash function in the SHA-2 family, designed by the U.S. National Security Agency and published by NIST in 2001 as part of FIPS 180-2. It takes any input — text, file, or byte stream — and produces a fixed 256-bit (64 hexadecimal character) fingerprint that uniquely identifies the input with cryptographic-grade certainty. SHA-256 has resisted all collision attacks since publication. The NIST FIPS 180-4 specification remains current; it is approved for use by the U.S. federal government, PCI DSS, FIPS 140-3, and the IETF's Internet standards. It underpins TLS certificates (the fingerprint that browsers show in cert dialogs), Git's modern object-ID format (since Git 2.29 in SHA-256 mode), Bitcoin's transaction IDs and proof-of-work, JWT signature verification (the JWS HS256, RS256, ES256 family), and the integrity column of every major package manager (npm, pip, cargo, apt). This tool computes SHA-256 entirely in your browser using crypto.subtle.digest('SHA-256', ...) from the Web Crypto API — the same primitive that browsers use internally for TLS handshakes. No bytes are uploaded; no server is involved. The hash you see is exactly what sha256sum, OpenSSL's dgst -sha256, or Python's hashlib.sha256() would produce. When to use SHA-256: file integrity verification, content-addressed storage, digital signature workflows, certificate fingerprinting, cache-busting via content hashing, deduplication. When not to use SHA-256: password storage (use bcrypt, scrypt, or Argon2 — SHA-256 is far too fast for password defense), HMAC without the proper construction (use a dedicated HMAC library), or as a general-purpose random ID (use UUID instead). For comparison: SHA-256 produces 64 hex chars vs. MD5's 32 (broken since 2004), SHA-1's 40 (broken since 2017), SHA-384's 96, and SHA-512's 128. The 256-bit output gives 128 bits of collision resistance — far beyond any foreseeable computational attack. ``` // Hash text using Web Crypto API (SHA-256) async function sha256(text) { const data = new TextEncoder().encode(text); const hash = await crypto.subtle.digest('SHA-256', data); return Array.from(new Uint8Array(hash)) .map(b => b.toString(16).padStart(2, '0')) .join(''); } await sha256('Hello, World!'); // → 'dffd6021bb2bd5b0af676290809ec3a53191dd81c7f70a4b28688a362182986f' ``` #### FAQ **Q: What is SHA-256 and how is it different from MD5 or SHA-1?** A: SHA-256 is a 256-bit cryptographic hash function in the SHA-2 family, designed by the NSA and standardized by NIST in FIPS 180-4. It produces a 64-character hexadecimal output. Unlike MD5 (128-bit, broken since 2004) and SHA-1 (160-bit, broken since 2017), SHA-256 remains cryptographically secure: no practical collision has ever been found. It is the current industry default for digital signatures, certificate fingerprints, blockchain transaction IDs, and integrity verification. **Q: How long is a SHA-256 hash?** A: Always 64 hexadecimal characters (256 bits = 32 bytes, encoded as 2 hex chars per byte). The output length is fixed regardless of input size — a 1-byte input and a 10-GB input both produce 64 hex chars. This fixed length is what makes it useful as a fingerprint. **Q: Is SHA-256 safe for password storage?** A: No. SHA-256 is too fast — a modern GPU can compute billions of SHA-256 hashes per second, which is exactly what an attacker wants for brute-forcing passwords. Use a deliberately slow password hash: bcrypt, scrypt, or Argon2id, each with proper salt and a high cost parameter. SHA-256 is for integrity (verifying data has not been tampered with), not for storing secrets. Use the password generator for the input side; use a dedicated password-hash library on the server. **Q: Can SHA-256 be reversed to find the original input?** A: No. SHA-256 is a one-way function: given a hash, there is no efficient algorithm to recover the input. The only general attack is brute force — trying every possible input and hashing each one. For arbitrary inputs this is computationally infeasible. The exception: short, predictable inputs (common passwords, simple words) can be looked up in rainbow tables, which is why salting passwords matters. **Q: What is the difference between SHA-256 and SHA-2?** A: SHA-2 is the family name; SHA-256 is one specific member. The SHA-2 family also includes SHA-512 (512-bit), SHA-384 (truncated SHA-512), SHA-224 (truncated SHA-256), SHA-512/224, and SHA-512/256. All share the same Merkle-Damgård construction with different word sizes and truncation rules. SHA-256 is the most widely deployed member — it is what TLS, JWT, Git, and Bitcoin all default to. **Q: Is my data sent to a server when I use this tool?** A: No. SHA-256 is computed entirely in your browser using the Web Crypto API (crypto.subtle.digest). Open DevTools → Network tab while hashing — you will see zero outgoing requests. The file you drop in File mode is read with the FileReader API and hashed locally; the bytes never leave your machine. This makes the tool safe for hashing confidential documents, proprietary code, or sensitive checksums. **Q: How do I verify a SHA-256 checksum from a download?** A: 1) Download the file. 2) Open this tool and click the File tab. 3) Drag the file into the dropzone. 4) Wait for the hash to compute (large files take a few seconds). 5) Open the publisher's published SHA256SUMS file. 6) Paste both hashes into the Compare tab — green means match, red means the file is corrupted or tampered. Most Linux distributions, language runtimes (Python, Node.js), and software vendors publish SHA-256 checksums alongside their downloads precisely for this purpose. **Q: Why does my SHA-256 output differ from a command-line tool?** A: Almost always whitespace or encoding. The shell command `echo "hello" | sha256sum` includes a trailing newline (\n), so the hash is for "hello\n" not "hello". Use `echo -n "hello"` to strip it. Other gotchas: Windows line endings (\r\n vs \n), UTF-8 BOM, or the difference between hashing UTF-8 bytes vs UTF-16 bytes. SHA-256 is extremely sensitive — a single byte changes the entire output. **Q: Can SHA-256 hash an empty file?** A: Yes. The SHA-256 of zero bytes is a well-known constant: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855. This is sometimes used as a sentinel value or as a quick verification that the hashing pipeline is wired correctly. **Q: Should I use SHA-256 or SHA-512?** A: Use SHA-256 for most cases — it is faster on 32-bit hardware, ubiquitously supported, and provides 128 bits of security against collisions. Use SHA-512 when you are on 64-bit hardware where it is actually faster, or when you specifically need 256 bits of collision resistance for cryptographic protocols that demand it. For everyday use (file checksums, Git, TLS), SHA-256 is the standard. --- ### SHA-3 Hash Generator (Keccak SHA3-256) URL: https://go-tools.org/tools/sha-3-generator Generate SHA-3 hashes online free. NIST FIPS 202 sponge construction — the post-SHA-2 standard. SHA3-256 output in 64 hex chars. Browser-only via lazy-loaded js-sha3; zero uploads. #### What Is SHA-3? SHA-3 (Secure Hash Algorithm 3) is the third generation of NIST's Secure Hash Standard, standardized in FIPS 202 in August 2015. Unlike SHA-1 and SHA-2, which are based on the Merkle-Damgård construction, SHA-3 uses a radically different design called the sponge construction — a choice NIST made deliberately to ensure that a cryptanalytic break of SHA-2 would not automatically compromise SHA-3. The NIST SHA-3 competition (2007–2012): NIST solicited public submissions worldwide in 2007. After three evaluation rounds, 64 candidates were narrowed to five finalists: BLAKE, Grøstl, JH, Keccak, and Skein. In October 2012, Keccak — designed by Guido Bertoni, Joan Daemen, Michaël Peeters, and Gilles Van Assche from STMicroelectronics and NXP Semiconductors — was selected as the winner. All five finalists were considered secure; Keccak's unique sponge-based design gave it the structural diversity NIST prioritized. The sponge construction: SHA-3 absorbs input into a 1600-bit state (the Keccak-f[1600] permutation) in the absorb phase, then squeezes output bits from the state in the squeeze phase. For SHA3-256, the rate/capacity split is 1088/512 bits. Because only 256 of the 1600 internal state bits appear in the output, an attacker cannot reconstruct the full state from the hash — making length-extension attacks structurally impossible. This contrasts with SHA-256, where the full internal state is exposed in the output, requiring HMAC to prevent length-extension. SHA-3 vs. Keccak — the padding difference: NIST modified the original Keccak submission by changing the domain separation padding from 0x01 to 0x06. This means NIST SHA3-256 and original Keccak-256 produce different 64-hex-character outputs for every input. This is not a theoretical concern — it is why Ethereum's keccak256 (frozen before FIPS 202 was finalized) differs from this tool's SHA3-256 output for the same string. Never use this tool to replicate Ethereum address derivation. FIPS 202 defines four SHA-3 variants: SHA3-224 (56 hex chars), SHA3-256 (64 hex chars), SHA3-384 (96 hex chars), SHA3-512 (128 hex chars). This tool implements SHA3-256, the most common variant and the one most directly comparable to SHA-256. Library note: SHA-3 is not yet in the browser's Web Crypto API spec (crypto.subtle supports SHA-1, SHA-256, SHA-384, SHA-512 only). This tool lazy-loads the js-sha3 JavaScript library (~10 KB gzipped) on first use. After that single download, all computation runs locally in your browser — no input data is ever transmitted. When to use SHA-3: new protocols requiring structural diversity from SHA-2; keyed MACs without the HMAC wrapper (KMAC128/256 per NIST SP 800-185); post-quantum hedging as a SHA-2 backup; compliance with systems that mandate FIPS 202. When to stick with SHA-256: universal library support, hardware acceleration (SHA-NI extensions), existing protocol compatibility, and most everyday integrity use cases where SHA-256 is already the established standard. ``` // SHA-3 (NIST FIPS 202 SHA3-256) using js-sha3 library import { sha3_256 } from 'js-sha3'; const hash = sha3_256('Hello, World!'); // → '882f4b6991a775295186a4e3cc5ece9fc0b618c8c3e7a7beafdd0f56f13ae43b' // Note: this differs from Ethereum's keccak256 for the same input: // keccak256('Hello, World!') = 'acaf3289d7b601cbd114fb36c4d29c85bbfd5e133f14cb355c3fd8d99367964f' // The difference is the padding byte: 0x06 (SHA-3) vs 0x01 (original Keccak) ``` #### FAQ **Q: Is SHA-3 the same as Keccak?** A: Close, but not identical. SHA-3 is based on the Keccak algorithm, which won the 2007-2012 NIST SHA-3 competition, but NIST changed the padding rule during standardization. Original Keccak appends a 0x01 suffix byte before padding; NIST SHA-3 (FIPS 202, 2015) appends 0x06. This single-byte difference means SHA3-256 and Keccak-256 produce different 64-character outputs for every input. The algorithms are structurally identical otherwise — same Keccak-f[1600] permutation, same sponge construction. When someone says "Keccak" in the Ethereum/blockchain context, they almost always mean the original pre-FIPS padding variant, not NIST SHA-3. **Q: Why does Ethereum use keccak256, not SHA-3?** A: Ethereum was designed in 2013–2014 and launched in 2015 — before NIST finalized SHA-3 in August 2015. When Ethereum's protocol was frozen, the Keccak team's original proposal was the reference implementation. NIST then changed the padding during standardization, but Ethereum could not retroactively adopt the FIPS 202 variant without breaking consensus (every node must compute identical hashes). The result is a permanent fork: Ethereum's keccak256 uses the original Keccak padding (0x01), while this tool's SHA3-256 uses the NIST FIPS 202 padding (0x06). Same structural algorithm, different outputs. Never use this tool to replicate Ethereum address derivation or EVM keccak256 — you will get wrong results. **Q: What is the sponge construction?** A: The sponge construction is SHA-3's alternative to SHA-2's Merkle-Damgård chaining. Instead of compressing input block-by-block into a running hash state (Merkle-Damgård), the sponge has two phases: absorb (input is XORed into a portion of the state and mixed with the Keccak-f[1600] permutation repeatedly) and squeeze (output bits are read from the state). The critical security benefit: the internal state is larger than the output — SHA3-256 uses a 1600-bit state but only outputs 256 bits, leaving 1344 bits of hidden state. An attacker who sees only the 256-bit output cannot reconstruct the full state to extend the message, making length-extension attacks structurally impossible without a wrapper. This differs from SHA-256, which requires HMAC to prevent length-extension. **Q: Should I use SHA-3 instead of SHA-2?** A: Not by default — both SHA-256 and SHA3-256 are currently secure and NIST-approved. SHA-3 is designed as a structural backup for SHA-2: because they use completely different designs (Merkle-Damgård vs. sponge), a break in one algorithm does not compromise the other. Use SHA-3 when: (1) your protocol explicitly requires it, (2) you need length-extension immunity without the HMAC wrapper, (3) you are building a new system that wants future-proof algorithm agility, or (4) your organization mandates a SHA-3 backup hash alongside SHA-256. For everyday file checksums and certificate fingerprints, SHA-256 remains the universal standard with better library and hardware support. **Q: Is SHA-3 faster than SHA-256?** A: In software, usually slower. SHA3-256 typically achieves 150–400 MB/s in browser JavaScript, compared to 400–700 MB/s for SHA-256 via the Web Crypto API (which uses the browser's native, C-level SHA-2 implementation). In dedicated hardware and ASICs (e.g., custom security chips), SHA-3's Keccak-f[1600] permutation is often faster because it parallelizes better and uses only simple bitwise operations (XOR, AND, rotate) — no modular addition. SHA-3 also outperforms SHA-2 on 32-bit embedded hardware in many cases. For browser-based tools this performance gap is imperceptible; for bulk file hashing it matters. **Q: How long is a SHA-3 hash from this tool?** A: This tool defaults to SHA3-256, which always produces exactly 64 hexadecimal characters (256 bits = 32 bytes, 2 hex chars per byte). NIST FIPS 202 also defines SHA3-224 (56 hex chars), SHA3-384 (96 hex chars), and SHA3-512 (128 hex chars). All share the same Keccak-f[1600] permutation; only the capacity/rate split and output length differ. For comparison: SHA-256 also produces 64 hex chars, but they are different 64-character strings for the same input. **Q: Is SHA-3 quantum-resistant?** A: Partially. Grover's algorithm on a quantum computer can square-root the search space of any hash function, effectively halving the security level. SHA3-256 has 256 bits of pre-image resistance, which is reduced to 128 bits under a quantum attack — still secure under any near-term threat model. The important advantage over SHA-2: SHA-3's sponge construction is unrelated to Merkle-Damgård, so it is unaffected by any cryptanalytic technique specific to SHA-2's compression function. NIST's post-quantum guidance recommends SHA-3 as the preferred hash for new protocols that want design diversity alongside SHA-2. **Q: Is my data sent to a server when I use this tool?** A: No. After the initial library load (the js-sha3 script is fetched once from a CDN, ~10 KB), all hashing runs entirely in your browser in JavaScript. Open DevTools → Network tab while hashing text — you will see zero outgoing requests carrying your input data. Unlike the SHA-2 tools on this site (which use the browser-built-in Web Crypto API), SHA-3 is not yet in the Web Crypto spec, so a JavaScript library is required. The library download is the only network request; your text never leaves the page. See also: the SHA-256 generator and MD5 generator use no external library at all. **Q: What was the NIST SHA-3 competition?** A: NIST ran an open competition from 2007 to 2012 to select a third Secure Hash Algorithm standard — not to replace SHA-2 (which remains secure), but to diversify the hash algorithm landscape in case SHA-2 was ever broken. 64 initial submissions were narrowed to 5 finalists: BLAKE, Grøstl, JH, Keccak, and Skein. Keccak, designed by Guido Bertoni, Joan Daemen, Michaël Peeters, and Gilles Van Assche (from STMicroelectronics and NXP Semiconductors), was selected as the winner in October 2012 and standardized as SHA-3 in NIST FIPS 202 in August 2015. Its selection was notable for choosing a fundamentally different design from SHA-2 rather than an evolutionary variant. **Q: Is SHA-3 vulnerable to length-extension attacks?** A: No — by design. Length-extension attacks exploit the fact that Merkle-Damgård constructions (SHA-256, SHA-512, MD5) expose the internal state in the hash output, allowing an attacker to append data to a hashed message without knowing the secret prefix. SHA-3's sponge construction avoids this entirely: the 1600-bit internal state is larger than the 256-bit output, so the output does not reveal enough state to extend the message. This means you can use plain SHA3-256 as a keyed MAC primitive (KMAC) safely, whereas using raw SHA-256 as a keyed MAC (HASH(key || message)) is insecure without the HMAC wrapper. --- ### SHA-384 Hash Generator (TLS Suite B Hash) URL: https://go-tools.org/tools/sha-384-generator Generate SHA-384 hashes online — 96-character hex output, length-extension immune, NSA Suite B compliant. Paired with AES-256-GCM in TLS. All hashing runs in your browser via Web Crypto API. #### What Is SHA-384? SHA-384 is a 384-bit cryptographic hash function in the SHA-2 family, published by NIST in 2001 as part of FIPS 180-2. It is architecturally a truncated variant of SHA-512: both algorithms use identical 64-bit word arithmetic, 80 compression rounds, and 1024-bit input blocks — the only differences are the initialization vector (IV) and the fact that SHA-384 discards the last 128 bits of SHA-512's 512-bit output, producing 384 bits (96 hexadecimal characters). Why the truncation matters cryptographically: SHA-256 is vulnerable to length-extension attacks — given SHA-256(message), an attacker can compute SHA-256(message || padding || extension) without knowing the original message, by resuming the hash computation from the leaked internal state. SHA-384 eliminates this attack surface: the truncation discards 128 bits of internal state, so the published 384-bit hash does not carry enough information to resume the SHA-512 computation. This makes raw SHA-384 (without HMAC wrapping) safe for constructions where the hash output may be exposed to adversaries. NSA Suite B and TLS role: SHA-384 was mandated by NSA Suite B (CNSSP-15, 2005) for TOP SECRET classification. It is the hash algorithm in the ciphersuite ECDHE-ECDSA-AES256-GCM-SHA384, which is the standard TLS 1.2 ciphersuite for Suite B compliant systems and remains widely deployed in U.S. government, financial, and defense networks. The NSA's CNSA Suite (2015) retained SHA-384 alongside SHA-256, and SHA-384 appears in TLS 1.3's signature algorithms list (ecdsa_secp384r1_sha384). Performance: On 64-bit hardware SHA-384 and SHA-512 run at identical speed — both use 64-bit word operations exclusively. They are typically faster than SHA-256 (which uses 32-bit word operations and requires more passes for the same input) on modern x86-64 and ARM64 processors. This tool computes SHA-384 entirely in your browser via crypto.subtle.digest('SHA-384', ...) from the Web Crypto API. The output is bit-for-bit identical to what sha384sum, openssl dgst -sha384, or Python's hashlib.sha384() produce. When to use SHA-384: TLS ciphersuites mandating Suite B compliance, HMAC-SHA-384 for TLS 1.2 PRF, HKDF-SHA-384 key derivation, classified document fingerprinting, and any context where length-extension immunity is required without HMAC wrapping. When not to use SHA-384: general-purpose checksums and everyday integrity use — SHA-256 is the standard choice for those, with simpler library support and universal tool compatibility. ``` // Hash text using Web Crypto API (SHA-384) async function sha384(text) { const data = new TextEncoder().encode(text); const hash = await crypto.subtle.digest('SHA-384', data); return Array.from(new Uint8Array(hash)) .map(b => b.toString(16).padStart(2, '0')) .join(''); } await sha384('Hello, World!'); // → '5485cc9b3365b4305dfb4e8337e0a598a574f8242bf17289e0dd6c20a3cd44a089de16ab4ab308f63e44b1170eb5f515' ``` #### FAQ **Q: Why use SHA-384 over SHA-256?** A: Two reasons: length-extension immunity and Suite B compliance. SHA-384 is immune to length-extension attacks because truncating SHA-512's 512-bit state to 384 bits discards 128 bits of internal state — an attacker who knows SHA-384(message) cannot compute SHA-384(message || extension) without knowing the full message. SHA-256 is vulnerable to length-extension attacks, which is why keyed use of SHA-256 requires HMAC construction. Additionally, SHA-384 was required by NSA Suite B at the TOP SECRET level and remains prevalent in TLS ciphersuites (ECDHE-ECDSA-AES256-GCM-SHA384) and government systems transitioning to the CNSA Suite. **Q: Is SHA-384 as secure as SHA-512?** A: Yes, in terms of collision resistance. SHA-384 provides 192 bits of collision resistance (half of 384 bits) versus SHA-512's 256 bits — both are far beyond any foreseeable attack. SHA-384 provides the same second-preimage resistance as SHA-512 in practice. The only meaningful difference is output length: 96 hex chars vs 128 hex chars. If you need maximum collision resistance for extremely long-lived archives (beyond 50 years), SHA-512 provides a larger margin — but for any current system, SHA-384 is fully adequate. **Q: Is SHA-384 the same speed as SHA-512?** A: Yes — they are literally the same algorithm. SHA-384 is SHA-512 with a different initialization vector (IV) and the output truncated to the first 384 bits. Because both use 64-bit word arithmetic throughout, they run at identical speed on 64-bit hardware. Counterintuitively, SHA-384 and SHA-512 are both typically faster than SHA-256 on 64-bit machines — SHA-256 uses 32-bit word arithmetic and processes 512-bit blocks, while SHA-512 processes 1024-bit blocks in fewer passes. Typical throughput: 500–900 MB/s in a browser, comparable to native tools. **Q: When does HMAC-SHA-384 matter over HMAC-SHA-256?** A: In TLS 1.2 handshakes negotiated with SHA-384 ciphersuites, the PRF is HMAC-SHA-384 — this is a hard protocol requirement, not a choice. Outside of TLS, prefer HMAC-SHA-384 when: (1) you are targeting Suite B / CNSA compliance, (2) the system handles data classified above SECRET, or (3) you want an extra margin against future advances against 128-bit security. For general-purpose MACs where neither applies, HMAC-SHA-256 is standard and well-tested across libraries. **Q: Should I use SHA-384 for general-purpose hashing?** A: Not unless you have a specific reason. SHA-256 is the industry default for file integrity, checksums, Git objects, JWT signatures, and certificate fingerprints — it is universally supported and provides 128 bits of collision resistance, more than enough for any practical use. SHA-384 makes sense when you need (1) length-extension immunity without HMAC wrapping, (2) Suite B / CNSA compliance, or (3) interoperability with TLS ciphersuites that mandate SHA-384. For everything else, SHA-256 is simpler and equally secure. **Q: What is NSA Suite B and is it still used?** A: NSA Suite B was a set of cryptographic algorithms approved for protecting U.S. classified information, published by the NSA in 2005 (CNSSP-15). Suite B required SHA-256 for SECRET and SHA-384 for TOP SECRET. In 2015 the NSA announced a transition away from Suite B toward the Commercial National Security Algorithm Suite (CNSA), driven by post-quantum cryptography concerns — Suite B's elliptic curve algorithms (P-256, P-384) could eventually be broken by a sufficiently large quantum computer. However, SHA-384 was retained in CNSA alongside SHA-256. Many government and defense systems built for Suite B compliance still use SHA-384, and TLS ciphersuites originally required by Suite B (like ECDHE-ECDSA-AES256-GCM-SHA384) remain widely deployed in government networks. **Q: How long is a SHA-384 hash?** A: Always exactly 96 hexadecimal characters — 384 bits divided into 48 bytes, each byte encoded as two hex characters. The output length is fixed regardless of input size; a 1-byte message and a 10 GB file both produce 96 hex chars. Compare: SHA-256 produces 64 hex chars, SHA-512 produces 128 hex chars, MD5 produces 32 hex chars. The 96-character output is the immediate signal that a hash was produced by SHA-384. **Q: Is my data sent to a server when I use this tool?** A: No. SHA-384 is computed entirely in your browser using the Web Crypto API (crypto.subtle.digest('SHA-384', data)). Open DevTools → Network tab while hashing — you will see zero outgoing requests. Files you drop in are read via the FileReader API and hashed locally; the bytes never leave your machine. This makes the tool safe for hashing classified document fingerprints, TLS private key material, or any other sensitive input. The same privacy guarantee applies to the SHA-256 generator and SHA-512 generator. --- ### SHA-512 Hash Generator (512-bit SHA-2) URL: https://go-tools.org/tools/sha-512-generator Generate SHA-512 hashes online — 128 hex chars output, faster than SHA-256 on 64-bit CPUs. Ideal for long-term archives, LUKS key derivation, and HMAC-SHA-512. Browser-only, zero uploads. #### What Is SHA-512? SHA-512 (Secure Hash Algorithm, 512-bit) is the full-width member of the SHA-2 family, published by NIST in 2001 in FIPS 180-2. It takes any input — text, file, or byte stream — and produces a fixed 512-bit (128 hexadecimal character) fingerprint. SHA-512 shares the same Merkle-Damgård construction as its siblings but operates on 1024-bit input blocks with 80 compression rounds and 64-bit word arithmetic, versus SHA-256's 512-bit blocks, 64 rounds, and 32-bit words. The 64-bit performance advantage: On modern x86-64 and ARM64 hardware, SHA-512's 64-bit word operations map directly to CPU register widths. SHA-256's 32-bit operations, by contrast, require additional passes to process the same data. The practical result: SHA-512 is typically faster than SHA-256 on any 64-bit CPU — usually 600–1,000 MB/s vs. 400–700 MB/s in a browser. This counterintuitive performance advantage makes SHA-512 the preferred choice in performance-sensitive 64-bit applications that also need the stronger collision resistance. Collision resistance: SHA-512 provides 256 bits of collision resistance — twice the 128-bit resistance of SHA-256. This larger margin is why institutional archives, long-lived digital signatures, and military-grade systems prefer SHA-512: data that must remain tamper-evident for 30–50 years benefits from the extra headroom against future cryptanalytic advances and quantum computing (Grover's algorithm halves the effective security level, leaving 256-bit pre-image resistance intact). Key use cases: LUKS disk encryption key derivation (PBKDF2-SHA-512 is the LUKS2 default), Apple HFS+ filesystem integrity checksums, HMAC-SHA-512 in high-assurance APIs and hardware security modules, HKDF-SHA-512 key expansion, and long-term archive manifests for government and institutional records. SHA-512/256 — the truncated NIST variant: FIPS 180-4 (2015) standardized SHA-512/256 as a separate algorithm: it uses SHA-512's 64-bit arithmetic and 1024-bit blocks but a different initialization vector, producing a 256-bit output. SHA-512/256 is length-extension resistant (unlike plain SHA-256) and faster than SHA-256 on 64-bit hardware. It is a distinct algorithm from straight SHA-512; this tool computes full SHA-512 (128 hex chars). This tool computes SHA-512 entirely in your browser via crypto.subtle.digest('SHA-512', ...). The output is bit-for-bit identical to sha512sum, openssl dgst -sha512, and Python's hashlib.sha512(). Related tools: SHA-256 Generator (64 hex chars, 128-bit collision resistance, fastest on 32-bit), SHA-384 Generator (96 hex chars, Suite B TLS, length-extension immune), SHA-3 Generator (Keccak sponge construction — different design from SHA-2 entirely). ``` // Hash text using Web Crypto API (SHA-512) async function sha512(text) { const data = new TextEncoder().encode(text); const hash = await crypto.subtle.digest('SHA-512', data); return Array.from(new Uint8Array(hash)) .map(b => b.toString(16).padStart(2, '0')) .join(''); } await sha512('Hello, World!'); // → '374d794a95cdcfd8b35993185fef9ba368f160d8daf432d08ba9f1ed1e5abe6cc69291e0fa2fe0006a52570ef18c19def4e617c33ce52ef0a6e5fbe318cb0387' ``` #### FAQ **Q: Why use SHA-512 over SHA-256?** A: Two main reasons: greater collision resistance and better performance on 64-bit hardware. SHA-512 provides 256 bits of collision resistance vs. SHA-256's 128 bits — meaningful when data must remain tamper-evident for decades or when a protocol demands maximum cryptographic margin. On 64-bit CPUs (virtually all modern hardware), SHA-512 is also typically faster than SHA-256 because its 64-bit word arithmetic matches the CPU's native register width; SHA-256 uses 32-bit words and processes smaller 512-bit blocks, requiring more passes for the same input. If you are on 64-bit hardware and need the extra margin, SHA-512 is the natural upgrade. **Q: Is SHA-512 faster than SHA-256?** A: Yes — on 64-bit hardware. SHA-512 uses 64-bit word arithmetic and processes 1024-bit (128-byte) blocks; SHA-256 uses 32-bit words and 512-bit (64-byte) blocks. On x86-64 and ARM64 processors, native 64-bit operations run at the same cost as 32-bit operations, so SHA-512 hashes approximately twice the data per clock cycle compared to SHA-256. Typical throughput: SHA-512 at 600–1,000 MB/s vs. SHA-256 at 400–700 MB/s in browsers using the Web Crypto API. On 32-bit hardware the relationship reverses — 64-bit arithmetic requires emulation, making SHA-512 slower. See also: SHA-384 runs at identical speed to SHA-512 on 64-bit hardware. **Q: How long is a SHA-512 hash?** A: Always exactly 128 hexadecimal characters — 512 bits divided into 64 bytes, each byte encoded as two hex characters. The output is fixed-length regardless of input size: a single character and a 10 GB file both produce 128 hex chars. Compare: SHA-256 produces 64 chars, SHA-384 produces 96 chars, MD5 produces 32 chars, SHA-1 produces 40 chars. The 128-character length is the immediate visual signal that a hash was produced by SHA-512. **Q: Is SHA-512 truncation (SHA-512/256) safe?** A: Yes. NIST standardized SHA-512/256 in FIPS 180-4 as a first-class hash variant — not a workaround, but a deliberate design. SHA-512/256 uses a different initialization vector than straight SHA-512 (to prevent related-key weaknesses) and truncates the output to 256 bits. The truncation also eliminates length-extension vulnerabilities present in plain SHA-256, since the discarded 256 bits of state cannot be recovered from the published output. SHA-512/256 is therefore strictly safer than SHA-256 against length-extension attacks while offering the same 128-bit collision resistance — and running faster on 64-bit hardware. Note: SHA-512/256 is a distinct algorithm from straight SHA-512; this tool computes full SHA-512 (128 hex chars), not the truncated variant. **Q: Should I use SHA-512 for password storage?** A: No. SHA-512, like all SHA-2 variants, is designed to be fast — and fast is exactly wrong for password storage. A modern GPU can compute hundreds of millions of SHA-512 hashes per second, making brute-force attacks against a leaked database practical. For passwords, use a deliberately slow algorithm: bcrypt (2^cost iterations), scrypt (memory-hard), or Argon2id (memory-hard, time-hard, winner of the Password Hashing Competition). Many of these use HMAC-SHA-512 internally as a building block, but the slow iteration is what provides the security. Use SHA-512 for data integrity and message authentication; use bcrypt/scrypt/Argon2id for passwords. **Q: Does SHA-512 leak timing on short inputs?** A: No more than any other hash function. SHA-512 always processes a minimum of one 1024-bit block regardless of input size (due to Merkle-Damgård padding), so the computation time for very short inputs is essentially constant. The variation in timing comes from the number of full 1024-bit blocks the input fills — larger inputs take longer in direct proportion to size, not in a way that leaks content. For verifying two hashes in code, the timing concern is in the comparison step, not the hashing step: always use constant-time comparison (Node.js crypto.timingSafeEqual(), Python hmac.compare_digest()). **Q: Is SHA-512 quantum-resistant?** A: Partially. Grover's algorithm on a quantum computer can search an unsorted database of N items in √N steps, which effectively halves the security level of any hash function. SHA-512's 256-bit collision resistance would be reduced to 128 bits — still secure under any credible near-term threat model. For comparison, SHA-256's 128-bit collision resistance would be reduced to 64 bits, which is more concerning. NIST's post-quantum guidance (NIST IR 8105) recommends SHA-512 (or SHA-3-512) for applications requiring long-term security against quantum-capable adversaries. For maximum future-proofing, consider also exploring SHA-3, which uses a different construction (Keccak sponge) resistant to attacks that target Merkle-Damgård designs. **Q: Is my data sent to a server when I use this tool?** A: No. SHA-512 is computed entirely in your browser using the Web Crypto API (crypto.subtle.digest('SHA-512', data)). Open DevTools → Network tab while hashing — you will see zero outgoing requests. Files you drop in are read via the FileReader API and hashed locally; the bytes never leave your machine. This makes the tool safe for hashing sensitive documents, private keys, or confidential data. The same privacy guarantee applies to the SHA-256 generator and SHA-384 generator. --- ### URL Slug Generator — Slugify Any Text URL: https://go-tools.org/tools/slug-generator Slugify any title into a clean, SEO-friendly URL slug instantly. Transliterate accents and Cyrillic, or keep Unicode letters. 100% private, in your browser. #### What Is a URL Slug? A URL slug is the part of a web address that identifies a specific page in a human-readable way. In `https://go-tools.org/blog/how-to-write-url-slugs`, the slug is `how-to-write-url-slugs` — the segment after the last slash that names the content. The word comes from newspaper publishing, where a "slug" was the short working name editors gave a story; the web borrowed the term for the short name that identifies a page. A well-formed slug follows a few conventions that have become near-universal. It's lowercase, because search engines treat URLs as case-sensitive and a consistent lowercase form prevents the same page from being reachable at multiple URLs. It uses hyphens to separate words, because Google reads a hyphen as a word boundary (so `url-slug-generator` is three keywords) but reads an underscore as a word joiner. It strips punctuation and symbols, because characters like `?`, `&`, `#`, and spaces have reserved meanings in URLs or must be percent-encoded, which makes the address ugly and harder to share. And it's concise — long enough to describe the page and carry the target keyword, short enough to read at a glance. Generating a slug by hand is mechanical but tedious: lowercase the title, replace spaces with hyphens, remove punctuation, fold accented characters, collapse any doubled hyphens, and trim the ends. This tool does all of that in one step, on every keystroke. The interesting decisions are around non-ASCII text. There are two valid philosophies. The first, transliteration (this tool's ASCII mode), converts é to e, ü to u, ß to ss, and Привет to privet, producing a portable pure-ASCII slug that works everywhere. It relies on Unicode NFD normalization to split an accented letter into a base letter plus a combining mark, then discards the mark — a zero-dependency technique built into every JavaScript engine — plus small hand-maintained tables for characters that have no decomposition (ß, æ, ø) and for the Cyrillic and Greek alphabets. The second philosophy, Unicode preservation (this tool's Unicode mode), keeps letters from every script and only lowercases and hyphenates, producing an internationalized slug like 你好-世界. This is exactly the rule GitHub applies when it turns a Markdown heading into an anchor link, and modern browsers and search engines support it fully through the IRI standard. The slug is one small piece of URL design, but it does real work: it tells human visitors what a page is about before they click, it gives search engines keyword signals, and it makes links readable when shared in chat, email, or social posts. A descriptive slug like /tools/url-slug-generator beats an opaque one like /tools/page?id=4823 on every one of those dimensions. This tool runs entirely in your browser — the slug updates with no network request, and your text is never uploaded or logged. For related text work, the case converter switches text between camelCase, snake_case, kebab-case and other identifier styles, the URL encoder/decoder handles percent-encoding of full URLs and query strings, and the word counter measures length and reading time. Together they cover most of the text-shaping a developer or content author does before publishing. ``` // The core of a zero-dependency slugify (ASCII mode) function slugify(input) { return input .normalize('NFD') // café → cafe + combining accent .replace(/[\u0300-\u036f]/g, '') // drop the combining marks .replace(/ß/g, 'ss') // chars with no NFD decomposition .replace(/&/g, ' and ') // keep the meaning of '&' .toLowerCase() .replace(/[^a-z0-9]+/g, '-') // every other run of junk → one hyphen .replace(/^-+|-+$/g, ''); // trim leading / trailing hyphens } slugify('Crème Brûlée Recipe'); // 'creme-brulee-recipe' slugify('Salt & Pepper'); // 'salt-and-pepper' slugify('10 Tips: A Guide!'); // '10-tips-a-guide' ``` #### FAQ **Q: What is a URL slug?** A: A URL slug is the human-readable identifier at the end of a web address that names a specific page — in `https://go-tools.org/blog/url-slug-best-practices`, the slug is `url-slug-best-practices`. Slugs are lowercase, use hyphens instead of spaces, strip punctuation, and ideally contain the page's target keywords. A good slug is short, descriptive, and stable (it shouldn't change after publishing, because changing it breaks every existing link). This tool converts any title or phrase into that form automatically, so you don't have to lowercase, hyphenate, and strip characters by hand. **Q: Should I use hyphens or underscores in a URL slug?** A: Use hyphens. Google has stated for years that it treats hyphens as word separators in URLs but treats underscores as word joiners — so `url-slug-generator` is read as three words ("url", "slug", "generator") while `url_slug_generator` can be read as one token. Hyphens are the universal convention across WordPress, Ghost, Hugo, and virtually every modern CMS. This tool defaults to hyphens for that reason, but offers an underscore option for the cases where a downstream system requires it (some file naming schemes and legacy databases). **Q: What is the difference between ASCII mode and Unicode mode?** A: ASCII mode transliterates every non-ASCII character to its closest Latin equivalent and drops anything it can't convert, producing a pure a–z, 0–9 slug: café → cafe, Привет → privet, 你好 → (dropped). It's the safest, most portable option and what most CMS platforms expect. Unicode mode keeps letters from any script (Chinese, Arabic, Cyrillic, Greek) and only lowercases and hyphenates, producing an internationalized slug like 你好-世界 — the same rule GitHub uses for heading anchors. Use ASCII mode by default; use Unicode mode when your URLs are meant for readers of a non-Latin script and you want the original characters preserved. **Q: Are Unicode (non-ASCII) URL slugs safe and good for SEO?** A: Yes, with caveats. Modern browsers display Unicode characters in URLs (the IRI standard, RFC 3987) and transparently percent-encode them on the wire, and Google indexes and ranks them correctly — a slug like /статьи/привет is fully supported. The trade-offs: when copied as plain text the URL may appear percent-encoded (%D0%BF%D1%80...), which looks ugly, and some older systems or analytics tools handle the encoding imperfectly. The rule of thumb: if your audience reads the script natively, Unicode slugs improve readability and click-through; if your audience is international or you want maximum compatibility, ASCII transliteration is the safer choice. **Q: How are emojis and special symbols handled?** A: Emojis and pictographic symbols are removed in both modes, because they are not letters or digits and have no place in a clean URL. So "🚀 Launch Day" becomes `launch-day` in either mode. Among punctuation, the ampersand (&) is a special case: it's expanded to the word "and" by default so you don't silently lose meaning ("Salt & Pepper" → `salt-and-pepper`). Everything else — colons, slashes, quotes, percent signs, parentheses — is treated as a separator and collapsed into a single hyphen, so runs of punctuation never produce doubled separators. **Q: What's a good maximum length for a slug?** A: There's no hard limit, but shorter is better. Most SEO practitioners aim for slugs under about 60 characters or roughly 3–6 meaningful words — long enough to be descriptive and contain the target keyword, short enough to read at a glance and not get truncated in search results or when shared. This tool's Max length option truncates at a word boundary (it won't cut a word in half) so you can cap a long title cleanly. Set it to 0 to keep the full slug. Remember that stop words (a, the, of, for) can usually be dropped without hurting clarity, which is the easiest way to shorten a slug. **Q: How does the tool handle Chinese, Japanese, Korean, or Arabic text?** A: It depends on the mode. In ASCII mode, CJK ideographs and Arabic script have no built-in transliteration here, so they're dropped and you may get an empty slug — ASCII mode is designed for Latin, Cyrillic, and Greek source text. In Unicode mode, the characters are preserved: 你好 世界 becomes 你好-世界 and مرحبا بالعالم keeps its Arabic letters, lowercased and hyphenated. For CJK and Arabic audiences, Unicode mode is the right choice. (Full pinyin or romaji transliteration of Chinese and Japanese is a deliberately out-of-scope feature, because it requires large dictionaries and produces ambiguous results.) **Q: Should a URL slug include the date or a number?** A: Generally avoid putting dates in the slug itself. A slug like /2024-best-laptops ages badly — when you update the article in 2026 you either keep a misleading 2024 in the URL or change the slug and break inbound links. Keep dates out of the slug and let your CMS store the publish date separately. Numbers that are part of the meaning (list counts like "10 tips", version numbers, model numbers) are fine and often valuable for click-through — this tool preserves digits, so "10 Tips for X" keeps the "10". The principle is: include numbers that are part of the topic, exclude dates that will go stale. **Q: Is my text uploaded anywhere?** A: No. Every slug is generated 100% in your browser with JavaScript. Your text is never transmitted, never stored on any server, never logged, and never analyzed. You can verify in your browser's Network tab — typing in the editor or clicking Copy triggers zero network requests. This makes the tool safe for unannounced product names, draft article titles, internal document names, and any other confidential material. The shareable link feature encodes your input into the URL only in your own browser; nothing is sent anywhere until you choose to share that link. --- ### Sort Text Lines Alphabetically & Naturally URL: https://go-tools.org/tools/sort-text-lines Sort text lines alphabetically, numerically (natural order), or by length — ascending or descending. Free online line sorter, 100% in your browser. Also dedupe, reverse, and clean up lists. No signup, nothing uploaded. #### What Does Sorting Text Lines Mean? Sorting text lines means taking a block of text split on newlines and reordering those lines according to a rule — most commonly alphabetical (dictionary) order, but also numeric order, length, or reverse. It is one of the most frequent small tasks in a developer's or writer's day: alphabetizing a list of names, ordering import statements, tidying a column of tags copied from a spreadsheet, or arranging config keys so a diff stays clean. The subtlety that trips people up is that computers sort strings by character code by default, not by human intuition. A plain sort places `item10` before `item2` because the character `1` has a lower code point than `2`. It also pushes every capitalized word ahead of lowercase ones, so `Zebra` lands before `apple`. Real-world lists rarely want either behavior. This tool defaults to case-insensitive, locale-aware sorting and offers a Natural method that compares embedded numbers as numbers — the two settings that make sorting match what a human expects. Sorting is closely related to cleaning a list. You often want to sort and deduplicate together, or drop blank lines and stray whitespace in the same pass. This tool folds those operations into one panel: sort, remove duplicates, trim, remove empty lines, reverse, and even shuffle. For deduplication as the primary task — with control over keeping the first or last occurrence — reach for the companion Remove Duplicate Lines tool, which shares the same engine. To count words and lines, use the Word Counter; to compare two versions of a list, use Text Diff; and to change the casing of your lines, use the Case Converter. Everything happens entirely in your browser with no uploads, so sorting a confidential list is exactly as private as opening a text editor. Paste, choose an order, and copy the result — the whole round trip takes a couple of seconds. ``` Input (unsorted): file10.txt file2.txt file1.txt Alphabetical sort (naive): file1.txt file10.txt ← wrong: 10 before 2 file2.txt Natural sort (numeric-aware): file1.txt file2.txt file10.txt ← correct ``` #### FAQ **Q: What does this tool do?** A: It sorts lines of text right in your browser — alphabetically, in natural (numeric-aware) order, or by length, ascending or descending. As you paste or type, the sorted result appears instantly with a live count of lines in and out. You can also deduplicate, reverse, trim, remove empty lines, add line numbers, or shuffle in the same pass. Nothing is uploaded: all sorting runs 100% client-side in JavaScript, so it is safe for confidential lists, internal wordlists, and any data you would not want to send to a server. You can confirm this in your browser's Network tab — sorting triggers zero network requests. **Q: What's the difference between alphabetical and natural sort?** A: Alphabetical (lexical) sort compares strings one character at a time, so `file10` comes before `file2` because the character `1` (U+0031) sorts before `2` (U+0032). Natural sort recognizes runs of digits and compares them as numbers, so `file2` correctly precedes `file10`. Use alphabetical for plain words and codes; use natural for anything with embedded numbers — filenames, version tags (`v1.2` vs `v1.10`), invoice numbers, and numbered list items. This tool implements natural sort with `Intl.Collator({ numeric: true })`, the standards-based Unicode collation built into every modern browser. **Q: How do I alphabetize a list?** A: Paste your list, keep the default Ascending order with the Alphabetical method, and the lines are put in alphabetical order (A→Z) instantly — that's all it takes to alphabetize a list online. For names, tags, or keywords, leave Case-sensitive off so capitalized and lowercase entries sort together instead of splitting into two runs. Switch to Descending for Z→A, or enable Remove duplicate lines to alphabetize and dedupe in one step. Everything runs in your browser, so you can put even a confidential list in alphabetical order without uploading it. **Q: Is my text uploaded anywhere?** A: No. Every operation — sorting, deduplication, trimming, reversing — runs entirely in your browser using JavaScript. Your text is never transmitted, never stored on any server, never logged, and never analyzed. This makes the tool safe for confidential lists, internal data, security wordlists, and any content you would not paste into a networked service. There are no cookies used for your input and no third-party analytics that capture what you type. Open your browser's developer tools and watch the Network tab: sorting a million-line list produces zero requests. **Q: How do I sort a list of numbers correctly?** A: Choose the Natural method. Plain alphabetical sort treats numbers as text, so `100` would sort before `2` (because `1` < `2` as characters). Natural sort compares the numeric value, giving `2`, `100` in the right order. For lines that mix text and numbers like `chapter 2` and `chapter 12`, natural sort also gets it right by comparing the `2` and `12` numerically. If your lines are purely numeric and you want strict numeric ordering, natural sort handles integers and the numeric portions of mixed strings; for decimal-heavy financial data, double-check edge cases like leading zeros. **Q: Does sorting respect uppercase and lowercase?** A: By default, no — Case-sensitive is off, so `Apple` and `apple` are treated as equivalent for ordering and sort as neighbors. This is the natural choice for human-readable lists, where you rarely want all capitalized words to jump ahead of lowercase ones. Turn Case-sensitive on to get a strict ordering where uppercase letters sort before lowercase (following Unicode code-point order within the collator's variant sensitivity). Case sensitivity also affects deduplication when that option is enabled: with it off, `Foo` and `foo` count as the same line. **Q: Can I sort and remove duplicates at the same time?** A: Yes. Enable Remove duplicate lines alongside any sort order. The tool deduplicates first, then sorts, so you get a clean, unique, ordered list in a single step — and the stats badge reports how many duplicates were removed. This is the fastest way to turn a messy pasted column (say, a list of emails or tags with repeats) into a tidy sorted set. If you need the dedicated deduplication workflow with keep-first/keep-last control, use the companion Remove Duplicate Lines tool, which shares the same engine. **Q: What happens to blank lines when I sort?** A: By default, blank lines are kept and sort to the top (an empty string sorts before any non-empty line). If you don't want them, enable Remove empty lines to drop blank and whitespace-only lines before sorting — the stats badge counts how many were removed. A single trailing newline at the end of your input (common when pasting from files or editors) is treated as a line terminator, not an extra empty line, so it won't inflate your counts or add a spurious blank row to the output. **Q: How are accented and non-English characters sorted?** A: Sorting is locale-aware through the browser's `Intl.Collator`, which follows the Unicode Collation Algorithm. Accented letters are ordered the way a reader of your language expects — for example, in most Latin locales `é` sorts next to `e` rather than at the end of the alphabet. The tool uses the page's language for collation, so the German, French, Spanish, and other localized versions apply the appropriate rules. This is far more correct than a naive code-point sort, which would scatter accented and non-ASCII characters in surprising places. **Q: Is there a limit on how many lines I can sort?** A: There is no hard limit imposed by the tool — it is bounded only by your browser's memory and how much text you can comfortably paste into a textarea. Modern browsers sort tens of thousands of lines instantly and hundreds of thousands with a brief pause. Because everything runs locally with no network round-trips, large lists are often faster here than on server-based tools that upload your data. For truly massive files (millions of lines), a command-line `sort` utility will be more comfortable, but for everyday lists, wordlists, and exports this tool handles them with ease. **Q: How is this different from sorting in Excel or a code editor?** A: Spreadsheets sort cells within a grid and often reorder entire rows; this tool sorts a flat list of lines, which is exactly what you need for wordlists, config values, import statements, or any newline-separated data. Compared with a code editor's sort-lines command, this tool adds natural (numeric-aware) sorting, locale-aware collation, one-click deduplication, length sorting, and a live count — without installing an extension. And unlike online spreadsheet tools, nothing leaves your browser. It is the quickest way to sort a chunk of text when you just have it on the clipboard. **Q: What does the Shuffle option do?** A: Shuffle randomizes the order of your lines using a Fisher–Yates shuffle driven by the browser's cryptographic random number generator (`crypto.getRandomValues`), so the result is genuinely unpredictable and different each time. It is mutually exclusive with sorting — you either order the lines or randomize them. Common uses include randomizing quiz or flashcard order, choosing a fair draw sequence, sampling a subset, or de-biasing a list before manual review. Because it uses a cryptographic RNG rather than `Math.random`, it is suitable when you need defensible randomness. --- ### SQL Formatter & Beautifier URL: https://go-tools.org/tools/sql-formatter Format, beautify and minify SQL instantly in your browser. Supports PostgreSQL, MySQL, SQL Server, BigQuery, Snowflake, Oracle & SQLite. Free, private — your SQL never leaves your device. #### What is SQL Formatting? SQL formatting (also called beautifying or pretty-printing) rewrites a query with consistent indentation, line breaks and keyword casing so its structure is easy to read. The query runs identically before and after — only the whitespace changes. Formatting makes long queries reviewable in pull requests, easier to debug, and consistent across a team. Minifying does the reverse: it strips comments and collapses the query to a single compact line for embedding in code or logs. #### FAQ **Q: How do I format SQL online?** A: Paste your SQL into the input box, choose your database dialect, and click Format. The tool reindents the query with consistent line breaks and keyword casing, then lets you copy it. Everything runs locally in your browser — nothing is uploaded. **Q: How do I format PostgreSQL queries?** A: Select PostgreSQL from the dialect dropdown before clicking Format. This makes the formatter respect PostgreSQL-specific syntax such as dollar-quoted strings, casts (::), and functions, producing correct, idiomatic output. **Q: How do I format SQL Server (T-SQL)?** A: Choose "SQL Server (T-SQL)" as the dialect. The formatter then understands T-SQL constructs like bracketed [identifiers], TOP, and variables, so they are indented and cased correctly. **Q: How do I format Snowflake or BigQuery SQL?** A: Both are in the dialect dropdown. Selecting Snowflake or BigQuery applies their respective parsers so warehouse-specific functions and syntax format cleanly instead of being mangled by a generic SQL parser. **Q: Is my SQL safe with this tool?** A: Yes. All formatting and minifying happen locally in your browser using JavaScript — your queries are never sent to any server, logged, or stored. This makes it safe to use with production schemas and proprietary queries, unlike server-side formatters that receive a copy of everything you paste. **Q: What is the difference between formatting and minifying SQL?** A: Formatting (beautifying) adds indentation and line breaks to make a query readable. Minifying does the opposite: it removes comments and collapses the query to a single compact line, useful for embedding SQL in code or reducing log noise. Both produce queries that run identically to the original. **Q: Does this tool change what my query does?** A: No. Formatting and minifying only change whitespace, line breaks, comments and keyword casing — never the logic. The formatted query returns exactly the same results as the original. **Q: What indentation should I use for SQL?** A: Two spaces is the most common default and keeps diffs compact; four spaces improves readability for deeply nested queries; tabs let each developer view their preferred width. Pick one and apply it consistently across your team — this tool supports all three. --- ### IPv4 Subnet Calculator URL: https://go-tools.org/tools/subnet-calculator Free IPv4 subnet calculator: enter 192.168.1.130/26 for the network address, broadcast, usable host range, netmask and wildcard, plus paste-ready Cisco and Linux config. #### What Is a Subnet Mask? A subnet mask splits a 32-bit IPv4 address into two parts: a network portion that every host on the segment shares, and a host portion that identifies the individual machine. The mask is itself 32 bits — a run of 1s marking the network bits followed by 0s marking the host bits — which is why 255.255.255.0 and /24 describe exactly the same thing. CIDR notation just counts the 1 bits instead of spelling them out in decimal. Everything else follows from that split. Zero out the host bits and you have the network address; set them all to 1 and you have the directed broadcast address; the addresses between the two are what you can assign to hosts. That is where the familiar 2^n − 2 formula comes from, and also why a /26 yields 62 usable hosts rather than 64. When a machine decides whether a destination is local or needs a router, it applies its own mask to both addresses and compares the results — which is precisely the membership test this calculator performs. Two prefixes break the pattern on purpose. A /31 has only two addresses, leaving no room for a separate network and broadcast; RFC 3021 therefore makes both usable on point-to-point links, and every current router platform honours that. A /32 is a single host route, used for loopback interfaces, static routes, anycast addresses and single-address firewall rules. Applying 2^n − 2 blindly to either one yields 0 usable hosts — an answer no router platform agrees with. The /31 exception carries its own condition: it works only on genuinely point-to-point interfaces, so a shared LAN segment still needs /30 or shorter. The address you type also carries meaning beyond its mask. 10.0.0.0/8, 172.16.0.0/12 and 192.168.0.0/16 are private (RFC 1918) and never routed on the public internet; 100.64.0.0/10 is carrier-grade NAT space; 169.254.0.0/16 appears when DHCP fails; 224.0.0.0/4 is multicast. Historical classes A through E still surface in documentation and certification exams, but classful routing has been obsolete since CIDR arrived in 1993 — the mask, not the first octet, decides where a network ends. To inspect the same address in binary or hexadecimal by hand, the number base converter does the arithmetic. ``` // Subnetting is integer arithmetic on a 32-bit value. const toInt = (ip) => ip.split('.').reduce((acc, o) => acc * 256 + Number(o), 0); const toIp = (n) => [24, 16, 8, 0].map((s) => (n >>> s) & 255).join('.'); function subnet(ip, prefix) { const mask = prefix === 0 ? 0 : (0xFFFFFFFF << (32 - prefix)) >>> 0; const network = (toInt(ip) & mask) >>> 0; const broadcast = (network | (~mask >>> 0)) >>> 0; // RFC 3021: a /31 has two usable addresses; a /32 has one. const usable = prefix >= 31 ? 2 ** (32 - prefix) : 2 ** (32 - prefix) - 2; return { network: toIp(network), broadcast: toIp(broadcast), usable }; } subnet('192.168.1.130', 26); // { network: '192.168.1.128', broadcast: '192.168.1.191', usable: 62 } ``` #### FAQ **Q: How do I calculate a subnet mask from a CIDR prefix?** A: The prefix is simply the count of leading 1 bits in the 32-bit mask. A /26 means 26 ones followed by 6 zeros: 11111111.11111111.11111111.11000000, which reads back as 255.255.255.192. The trailing zeros are the host bits, so the block holds 2^6 = 64 addresses. Working the other way, count the 1 bits in the mask: 255.255.252.0 is 11111111.11111111.11111100.00000000 = 22 ones, so it is a /22. The binary view in this calculator shows the boundary directly, with network bits and host bits in different colours. **Q: What prefix length is 255.255.255.0?** A: It is a /24. 255 in binary is 11111111, so three octets of 255 give 24 leading 1 bits, and the last 8 bits are host bits — 255.255.255.0 = /24, with 256 addresses and 254 usable hosts. By the same reasoning 255.255.255.192 is a /26 (64 addresses, 62 usable), 255.255.252.0 is a /22 (1,024 addresses, 1,022 usable), and 255.255.255.128 is a /25 (128 addresses, 126 usable). Paste a dotted-decimal mask straight into the input to read its prefix back, or use the reference table below the calculator, which lists the mask, wildcard and host count for every prefix from /8 to /32. **Q: How do I convert an IP address to a decimal integer or hexadecimal?** A: Treat the four octets as one 32-bit integer: 192.168.1.128 = 192×256³ + 168×256² + 1×256 + 128 = 3232235904, which is 0xC0A80180 in hexadecimal. This form turns up when a database stores addresses in an integer column (MySQL's `INET_ATON` / `INET_NTOA`), when filtering logs by range, and in any bitwise membership test. The binary panel of this calculator prints the network address as a decimal integer, as hexadecimal, and as a reverse-DNS in-addr.arpa name. To convert between bases by hand, use the number base converter. **Q: How do I apply the result to a Cisco or Huawei device?** A: The ready-to-paste configuration panel generates six forms for the current block. For 192.168.1.130/26 those are: Cisco IOS interface `ip address 192.168.1.129 255.255.255.192`; Cisco ACL with the wildcard `access-list 10 permit 192.168.1.128 0.0.0.63`; OSPF `network 192.168.1.128 0.0.0.63 area 0`; Cisco ASA, which takes the subnet mask instead, `access-list OUT permit ip 192.168.1.128 255.255.255.192 any`; Huawei/H3C `ip address 192.168.1.129 26`; and Linux iproute2 `ip addr add 192.168.1.129/26 dev eth0`. **The trap is that IOS ACLs and OSPF take the wildcard while the ASA takes the subnet mask** — swapping them raises no error but matches a completely different range. **Q: Why is the usable host count two less than the total?** A: Every ordinary IPv4 subnet reserves its first address as the network identifier and its last as the directed broadcast address, so a /24 with 256 addresses offers 254 to hosts. Two prefixes are exceptions: a /31 has no room for those reserved addresses, and RFC 3021 makes both of its addresses usable on point-to-point links; a /32 is a single host route with one usable address. This calculator applies those exceptions — the generic 2^n − 2 formula would report 0 usable addresses for a /31, which no modern router agrees with. **Q: What is a wildcard mask and how is it different from a subnet mask?** A: A wildcard mask is the bitwise inverse of the subnet mask: where the subnet mask has 1s, the wildcard has 0s. For a /26, the mask is 255.255.255.192 and the wildcard is 0.0.0.63. Cisco access control lists and OSPF network statements take wildcards rather than masks, so `access-list 10 permit 192.168.1.128 0.0.0.63` matches the same block as 192.168.1.128/26. Huawei and H3C documentation calls it a wildcard mask too, while inverse mask is common shorthand among engineers; both names refer to the same value here. Note that ACL wildcards may legally have non-contiguous bits (matching odd or even addresses, for instance) while subnet masks may not. **Q: How many hosts fit in a /24, /25 or /26?** A: A /24 (255.255.255.0) has 256 addresses and 254 usable hosts. A /25 (255.255.255.128) has 128 addresses and 126 usable. A /26 (255.255.255.192) has 64 addresses and 62 usable. Each additional prefix bit halves both numbers. A useful shortcut when subnetting the fourth octet: the block size is 256 minus the last mask octet, so a mask ending in 192 steps every 64 addresses and a mask ending in 240 steps every 16. The full reference table under the calculator covers /8 through /32. **Q: Which IP ranges are private and which are public?** A: RFC 1918 reserves three private ranges: 10.0.0.0/8 (16,777,214 usable), 172.16.0.0/12 (1,048,574 usable) and 192.168.0.0/16 (65,534 usable). Note that the 172 range covers 172.16.0.0 to 172.31.255.255 only — 172.15.x.x and 172.32.x.x are public, a boundary that is easy to get wrong. Beyond those, 100.64.0.0/10 is carrier-grade NAT space (RFC 6598), 169.254.0.0/16 is link-local autoconfiguration (RFC 3927), and 192.0.2.0/24, 198.51.100.0/24 and 203.0.113.0/24 are reserved for documentation (RFC 5737). This calculator labels whichever block your address falls into. **Q: Can I use a /31 or /32 on a real network?** A: Yes. A /31 is the standard way to number router-to-router links: RFC 3021 removes the network and broadcast addresses for point-to-point interfaces so both addresses go to the two endpoints, halving the waste compared with the traditional /30. It is supported on Cisco IOS, Junos, Arista EOS and Linux, but it is only valid on genuinely point-to-point interfaces — do not put a /31 on a multi-access LAN segment. A /32 is a host route: loopback interfaces, single-address ACL entries, static routes and anycast addresses all use it. **Q: Are the addresses I type sent to a server?** A: No. Every calculation runs locally in your browser with plain integer arithmetic — no network request, no third-party library, no logging. You can open your browser's developer tools and watch the network panel stay silent while you type, or disconnect from the internet entirely and keep calculating. The Copy link button encodes the current block into the URL fragment, which browsers never transmit to a server. --- ### Temperature Converter — Celsius, Fahrenheit, Kelvin, Rankine URL: https://go-tools.org/tools/temperature-converter Convert Celsius, Fahrenheit, Kelvin and Rankine instantly. Free online browser-based tool with conversion formulas, step-by-step guides and reference charts. #### What Is a Temperature Converter? A temperature converter is a tool that translates temperature measurements between different scales — most commonly Celsius (°C), Fahrenheit (°F), Kelvin (K), and Rankine (°R). Unlike length or mass conversions that apply a simple multiplication factor, temperature conversions require offset formulas because the scales have different zero points and, in some cases, different degree sizes. The four major temperature scales are: **Celsius (°C)** — used worldwide for everyday temperature measurement. Water freezes at 0 °C and boils at 100 °C at standard pressure. Developed by Anders Celsius in 1742. **Fahrenheit (°F)** — used primarily in the United States for weather, cooking, and medical contexts. Water freezes at 32 °F and boils at 212 °F. Developed by Daniel Gabriel Fahrenheit in 1724. **Kelvin (K)** — the SI unit of temperature, named after Lord Kelvin (William Thomson) and used in science and engineering. As defined by NIST and BIPM, it starts at absolute zero (0 K = −273.15 °C), the lowest theoretically possible temperature, where all atomic motion ceases. A change of 1 K equals a change of 1 °C — only the zero point differs. Because Kelvin begins at absolute zero, thermodynamic equations can multiply and divide temperatures directly without offset adjustments, making it indispensable in physics, chemistry, and engineering. **Rankine (°R)** — an absolute temperature scale based on Fahrenheit degrees. Used in some US engineering applications. 0 °R = absolute zero = −459.67 °F. All conversions in this tool use the exact mathematical formulas, running entirely in your browser with no server calls — your temperature values never leave your device. For a deeper dive, see our temperature conversion formula guide — 5 programming languages, weather API pitfalls, and reference tables. Need to convert other measurement types? Try our length converter for distance units, weight converter for mass units, or volume converter for liquid measurements. ``` // Temperature conversion formulas: // Celsius to Fahrenheit: °F = °C × 9/5 + 32 // Fahrenheit to Celsius: °C = (°F − 32) × 5/9 // Celsius to Kelvin: K = °C + 273.15 // Kelvin to Celsius: °C = K − 273.15 const celsiusToFahrenheit = (c) => c * 9/5 + 32; const fahrenheitToCelsius = (f) => (f - 32) * 5/9; const celsiusToKelvin = (c) => c + 273.15; const kelvinToCelsius = (k) => k - 273.15; console.log(celsiusToFahrenheit(100)); // 212 console.log(fahrenheitToCelsius(98.6)); // 37 console.log(celsiusToKelvin(0)); // 273.15 ``` #### FAQ **Q: What is the formula to convert Celsius to Fahrenheit?** A: The formula is °F = °C × 9/5 + 32. To convert Celsius to Fahrenheit, multiply the Celsius value by 9/5 (or 1.8), then add 32. For example, 25 °C = 25 × 1.8 + 32 = 77 °F. To convert back, use °C = (°F − 32) × 5/9. Common reference points: 0 °C = 32 °F (freezing), 100 °C = 212 °F (boiling), 37 °C = 98.6 °F (body temperature). **Q: What is the difference between Celsius and Kelvin?** A: Celsius and Kelvin use the same degree size — a 1-degree change in Celsius equals a 1-degree change in Kelvin. The only difference is the zero point: 0 K (absolute zero) equals −273.15 °C. To convert, use K = °C + 273.15 or °C = K − 273.15. Kelvin is the SI unit of temperature and is used in scientific contexts because it starts at absolute zero, making it ideal for thermodynamic calculations. **Q: What is absolute zero?** A: Absolute zero is the lowest possible temperature, defined as 0 K (Kelvin), which equals −273.15 °C or −459.67 °F. At absolute zero, atoms reach their lowest energy state and thermal motion effectively ceases. While absolute zero cannot be physically achieved, scientists have cooled matter to within billionths of a degree above it. It serves as the foundation for the Kelvin scale and is essential in thermodynamics, cryogenics, and quantum physics. **Q: What is the Rankine scale and when is it used?** A: The Rankine scale (°R) is an absolute temperature scale based on the Fahrenheit degree. Just as Kelvin uses Celsius-sized degrees starting from absolute zero, Rankine uses Fahrenheit-sized degrees starting from absolute zero (0 °R = −459.67 °F). It is primarily used in certain engineering fields in the United States, particularly in thermodynamic calculations involving the Fahrenheit system. To convert: °R = °F + 459.67 or °R = K × 9/5. **Q: At what temperature are Celsius and Fahrenheit equal?** A: Celsius and Fahrenheit are equal at −40 degrees. You can prove this algebraically: set °C = °F in the formula °F = °C × 9/5 + 32, which gives °C = °C × 9/5 + 32, solving to °C = −40. This is the only point where the two scales intersect, and it serves as a useful memory aid for understanding the relationship between the scales. **Q: Why does the United States use Fahrenheit instead of Celsius?** A: The United States inherited the Fahrenheit scale from the British system before the metric system gained global adoption. While most countries switched to Celsius during metrication in the 1960s–1970s, the US never completed the transition. Fahrenheit remains in everyday use for weather, cooking, and medical contexts. However, US scientists, engineers, and the military typically use Celsius or Kelvin. The Fahrenheit scale offers slightly finer granularity for ambient temperatures (180 degrees between freezing and boiling vs. 100 in Celsius). **Q: How accurate is this temperature converter?** A: This converter uses the exact mathematical formulas for temperature conversion with IEEE 754 double-precision floating-point arithmetic, providing at least 15 significant digits of precision. The conversion formulas are exact — there are no approximations in the mathematical relationships between Celsius, Fahrenheit, Kelvin, and Rankine. The only limitation is inherent floating-point rounding at extreme precision levels. **Q: Is my data safe when using this temperature converter?** A: Yes, completely. All conversions are performed locally in your browser using JavaScript. No data is sent to any server — there are no network requests, no cookies, and no analytics on your input. The conversion logic runs entirely on your device, meaning your values never leave your browser. You can verify this by disconnecting from the internet and using the tool — it works fully offline once the page has loaded. **Q: What is the easiest way to convert Celsius to Fahrenheit in your head?** A: The simplest mental trick for Celsius to Fahrenheit: double the Celsius value and add 30. For example, 20 °C → 20 × 2 + 30 = 70 °F (actual: 68 °F). For Fahrenheit to Celsius: subtract 30 and divide by 2. These shortcuts are accurate within ±2–3 degrees for everyday temperatures (0–40 °C), which is close enough for weather and travel. **Q: What temperature is 350 °F in Celsius?** A: 350 °F equals approximately 176.67 °C. In practice, this is rounded to 175 °C or 180 °C depending on the recipe. This is the most common oven temperature in American baking, used for cookies, cakes, casseroles, and roasted vegetables. European recipes typically list this as 180 °C. **Q: Why do scientists use Kelvin instead of Celsius?** A: Scientists prefer Kelvin because it is an absolute scale starting at absolute zero (0 K), where all thermal motion ceases. This makes thermodynamic equations simpler — you can directly multiply, divide, and compare temperatures without negative numbers or offset adjustments. The Kelvin is also the SI base unit of temperature, ensuring consistency across international scientific communication. **Q: Can temperatures go below absolute zero?** A: In classical thermodynamics, absolute zero (0 K) is the lower limit. However, in quantum physics, researchers have created systems with 'negative absolute temperatures' — but this does not mean colder than absolute zero. Negative temperatures describe inverted energy distributions where most particles occupy high-energy states. These systems are actually hotter than any positive temperature, as energy flows from them to any positive-temperature system. **Q: I need to convert my oven temperature from Fahrenheit to Celsius for a European recipe — how do I do it?** A: Use the formula °C = (°F - 32) × 5/9, or simply enter the Fahrenheit value in this tool and select Fahrenheit-to-Celsius. Common oven conversions: 325 °F = 163 °C, 350 °F = 177 °C (use 175 or 180 °C), 375 °F = 191 °C (use 190 °C), 400 °F = 204 °C (use 200 °C), 425 °F = 218 °C (use 220 °C), 450 °F = 232 °C (use 230 °C). European ovens typically have markings in 10 °C increments, so round to the nearest 5 or 10 °C. Most recipes are forgiving of ±5 °C differences. **Q: I need to check if my child has a fever — is 99.5 °F considered a fever in Celsius?** A: 99.5 °F equals 37.5 °C. This is slightly above the commonly cited normal body temperature of 37 °C (98.6 °F), but whether it constitutes a fever depends on context. Most medical guidelines define a fever as an oral temperature of 100.4 °F (38 °C) or higher. A reading of 37.5 °C is considered a low-grade or borderline temperature. For children, always consult your pediatrician's guidelines. You can use this tool to quickly convert any temperature reading between Fahrenheit and Celsius for medical reference. --- ### Text Diff & Compare URL: https://go-tools.org/tools/text-diff Compare two texts instantly in your browser. Side-by-side view with inline word-level highlights, unified-diff export, ignore case / whitespace / blank lines. 100% private — your text never leaves your device. #### What is text diff? Text diff is a structured comparison of two text documents that finds the smallest set of insertions and deletions transforming one into the other. The output makes the change visible: green for added lines, red for removed, side-by-side or in unified-patch form (the `---/+++/@@` format used by `git`, GitHub, and the Unix `patch` command). Under the hood every modern diff is a Longest Common Subsequence (LCS) algorithm. Eugene Myers' 1986 O((N+M)D) paper is the canonical efficient implementation; classical dynamic programming (used here, with common prefix/suffix trimming) is simpler and works perfectly for typical web inputs. After lines line up, neighbouring remove + add pairs are run through a second token-level LCS so the renderer can highlight only the words that actually changed inside a line — what reviewers call intra-line or word-level diff. Why not just compare strings character-by-character? Because edits are rarely flat: inserting one line in the middle of a 200-line file shifts every line below it. A naïve `===` would call all 199 lines different. LCS tells you the truth: one line added, 199 unchanged. This tool runs the entire comparison in your browser. No upload, no temporary file, no log. Safe for proprietary code, redlined contracts, private logs, anything you'd be uncomfortable pasting into a third-party server. Need to diff JSON instead? Use the structural JSON Diff so key order and whitespace stop being noise. Comparing two configs in YAML or CSV? Convert with YAML to JSON or JSON to CSV first, then diff with the right tool for the format. ``` // Two strings that look 'mostly the same' but a naïve check disagrees const a = 'hello world'; const b = 'hello, world!'; // Character equality a === b; // false — but only 3 characters actually changed. // LCS-style diff (this tool, at line + word granularity) // → 1 line modified, inline highlight: 'hello[, ]world[!]' // → unified patch: // --- original // +++ modified // @@ -1 +1 @@ // -hello world // +hello, world! ``` #### FAQ **Q: Is the text I paste sent to your server?** A: No. Every comparison runs in JavaScript inside your browser. Your text is not uploaded, logged, stored on disk, or sent to any third party. Only your UI preferences (view mode and ignore-option toggles) are saved to localStorage so the page remembers them next visit — never the text. You can verify this by opening DevTools → Network: zero requests fire when you click Diff. **Q: What's the difference between text diff and JSON diff?** A: Text diff compares line by line — perfect for prose, code, logs, contracts, config files. JSON Diff understands JSON's data model: key order is irrelevant, types are strict, arrays can be matched by key. If you paste JSON into a text diff, key reorders and whitespace differences will be flagged as changes; JSON Diff ignores them. Use text diff for unstructured content, JSON Diff for API responses and config payloads. **Q: How do I ignore whitespace, case, or blank lines?** A: Click the 'Ignore options' panel above the diff. 'Ignore case' makes A and a equal. 'Ignore all whitespace' collapses every space, tab, and newline before comparison. 'Ignore trailing spaces / tabs' only strips end-of-line whitespace — the standard `git diff -b` behavior. 'Ignore blank lines' drops empty/whitespace-only lines before diffing. Each option is independent and persists across visits. **Q: What's a unified diff (and when do I copy it)?** A: A unified diff is the `---/+++/@@` text format used by `patch`, `git apply`, GitHub PRs, and most code-review tools. Click Copy unified diff to grab a patch with three lines of context around every change — paste it into a bug report, a code-review comment, or a `patch -p1` command and it applies cleanly. Side-by-side is for humans, unified diff is for machines (and code reviewers who think like machines). **Q: Why does the diff show whole lines changed when I only edited one word?** A: It doesn't — look closer. The full line is highlighted because something on it changed, but inside the highlight only the changed tokens carry the bright background (green for added, red strikethrough for removed). This is intra-line word diff: the line context stays readable while your eye lands on the exact edit. If two consecutive lines were both modified, both show inline highlighting. **Q: How are CRLF vs LF line endings handled?** A: Both are recognized. The diff splits on \r\n, \n, and bare \r, so Windows CRLF, Unix LF, and old-Mac CR text all line up correctly. If you want to flag line-ending changes specifically, leave 'Ignore trailing spaces / tabs' off — \r will surface as a trailing character. To erase line-ending noise entirely, turn 'Ignore all whitespace' on. **Q: How large can the two inputs be?** A: The diff runs on the main thread, so practical limits are about 5,000 lines or 1 MB per side; above that we clip and show a warning. Live diff disables above 200 KB combined and switches to a manual Diff button. For multi-megabyte files, use command-line `diff -u` or `git diff --no-index` — they stream and handle gigabytes. **Q: What about diffing code? Does it know my language?** A: The diff is language-agnostic: it sees lines and tokens, not syntax. That's a feature for review snippets, config edits, and copy-pasted patches. If you want semantic code diff (renamed function across files, AST-level), use git, GitHub PR view, or a dedicated structural diff tool. For 90% of code-review situations — eyeballing a function, comparing two snippets — line + word diff is what you want. **Q: Why does a single edit sometimes appear as a removed line plus an added line?** A: When too much of a line changed for word-level highlighting to be useful, the diff reports it as separate remove + add lines so the structure stays clean. The same heuristic gives readable output for prose rewrites and for code blocks that were rewritten rather than edited. Switch to Unified view to see the classic `-`/`+` pair format used in patches. **Q: How is the % match similarity calculated?** A: It's the number of unchanged lines (after applying ignore options) divided by the larger of the two line counts, clamped to 100%. Two identical inputs are 100%. Adding one new line to a 100-line file gives 99%. Replacing every line is 0%. Useful for quickly judging 'is this a small edit or a wholesale rewrite' before reading the diff. **Q: Can I share a diff with a colleague?** A: Yes, two ways. (1) Click Copy unified diff and paste the patch into chat, Slack, or a PR comment — anyone with a terminal can `patch < clip` it. (2) Take a screenshot of the side-by-side panel for visual review. We deliberately do not provide a 'share by URL' button: that would require uploading your text, which we don't do. **Q: Does the diff handle right-to-left languages like Arabic or Hebrew?** A: Yes for the text content — lines and tokens are Unicode-aware. The interface uses logical CSS directions, so on RTL locales the gutter and line columns flip naturally. Inside a diff cell the text direction follows the content, so Arabic and Hebrew strings render correctly while the +/- markers stay aligned to the gutter. --- ### TOML to JSON Converter URL: https://go-tools.org/tools/toml-to-json Paste TOML, get JSON instantly in your browser. Cargo.toml, pyproject.toml & config.toml ready. Dates and nested tables handled right. 100% private, no upload. #### What is TOML and Why Convert to JSON? TOML (Tom's Obvious, Minimal Language) is a configuration file format created by Tom Preston-Werner to be unambiguous, easy for humans to read, and trivially mappable to a hash table. It has become the default configuration format for the Rust ecosystem (Cargo.toml), modern Python packaging (pyproject.toml, standardized in PEP 518 and PEP 621), and tools like Hugo, Netlify, Poetry, and Foundry. JSON, by contrast, is the universal machine-interchange format — every language parses it, every API speaks it. Converting TOML to JSON is therefore a common task: you author configuration in readable TOML, then convert to JSON to feed a program, inspect it with JSON tooling, or diff it in a pipeline. This tool has several differentiators that matter for correctness: **1. Date-time fidelity.** TOML has first-class date and time types — offset date-time, local date-time, local date, and local time — that JSON lacks entirely. Naive converters flatten all of them into ISO timestamps, so a local date like 1979-05-27 becomes 1979-05-27T00:00:00Z, silently inventing a time and a timezone. This tool preserves the exact TOML kind: local dates stay calendar dates, local times stay times, and only offset date-times carry a timezone. That keeps your data honest and round-trips lossless. **2. Full TOML 1.0.0 support.** Nested tables, dotted keys, inline tables, and arrays of tables ([[section]]) are all parsed correctly according to the TOML 1.0.0 specification using the zero-dependency smol-toml library. Real Cargo.toml and pyproject.toml files with mixed inline and standard tables convert exactly as the spec requires. **3. Honest large-integer handling.** TOML integers are 64-bit, but browser JavaScript can only hold integers up to 2^53 - 1 exactly. Rather than silently corrupting a large value or crashing, this tool converts to the nearest JSON number and warns you, so you can choose to store the value as a string instead. This honesty about a fundamental browser limitation is something most converters gloss over. **4. 100% browser-based privacy.** Your TOML — which may contain registry tokens, internal package sources, or deployment secrets — never leaves your browser. No upload, no server, no logging. You can confirm this in your browser's Network tab. Need the reverse? Use the JSON to TOML Converter. Converting between other config formats? Try the JSON to YAML Converter and YAML to JSON Converter, or validate and pretty-print your JSON output first with the JSON Formatter. TOML, JSON, and YAML each have their place: TOML for human-edited application config, JSON for machine interchange, and YAML for deeply nested infrastructure manifests. This converter lets you move between the first two without a single line of code. ``` // Convert TOML to JSON in Node.js using the smol-toml library import { parse } from 'smol-toml'; const toml = ` [package] name = "my-app" version = "1.0.0" [dependencies] serde = { version = "1.0", features = ["derive"] } `; const data = parse(toml); const json = JSON.stringify(data, null, 2); console.log(json); // { // "package": { "name": "my-app", "version": "1.0.0" }, // "dependencies": { // "serde": { "version": "1.0", "features": ["derive"] } // } // } ``` #### FAQ **Q: How do I convert TOML to JSON online?** A: Paste your TOML into the input field above. The tool parses it and produces JSON instantly in your browser — no button click needed. You can switch the JSON indentation between 2 and 4 spaces in the Options panel. Once the JSON appears in the output area, click Copy to grab it to your clipboard or Download to save it as a .json file. Everything runs locally, so your TOML never leaves your device. **Q: What is TOML and why convert it to JSON?** A: TOML (Tom's Obvious, Minimal Language) is a configuration file format designed to be easy for humans to read and write, with clear semantics that map unambiguously to a hash table. It powers Rust's Cargo.toml, Python's pyproject.toml, Hugo, Netlify, and many other tools. You convert TOML to JSON when you need to feed configuration into a program or API that expects JSON, inspect a config file programmatically, diff two configs with JSON tooling, or drive a UI. TOML is written by humans; JSON is consumed by machines — this converter bridges the two. **Q: How are TOML dates and times represented in JSON?** A: JSON has no native date type, so TOML date-times are converted to strings. This tool preserves the exact TOML date kind: an offset date-time like 1979-05-27T07:32:00Z becomes "1979-05-27T07:32:00.000Z", a local date like 1979-05-27 stays "1979-05-27" (it is not padded into a full timestamp), and a local time like 07:32:00 becomes "07:32:00.000". Many converters incorrectly flatten local dates into midnight UTC timestamps — this one keeps the original meaning so round-trips are lossless. **Q: Does this tool handle Cargo.toml and pyproject.toml?** A: Yes. Cargo.toml and pyproject.toml are the two most common real-world TOML files, and both are fully supported, including nested tables ([dependencies], [tool.ruff]), inline tables (serde = { version = "1.0", features = ["derive"] }), and arrays of tables ([[bin]]). Load the Cargo.toml or pyproject.toml example above to see the exact JSON structure produced. This is handy for build dashboards, dependency audits, or any script that reads project metadata as JSON. **Q: How are TOML tables and arrays of tables converted?** A: A TOML table header like [owner] becomes a nested JSON object under the key "owner". A dotted table like [tool.ruff] becomes nested objects: { "tool": { "ruff": { ... } } }. An array of tables written with double brackets — [[servers]] repeated — becomes a JSON array of objects: { "servers": [ { ... }, { ... } ] }. Inline tables ({ a = 1, b = 2 }) become plain JSON objects. The conversion follows the TOML 1.0.0 specification exactly. **Q: What happens to very large integers when converting TOML to JSON?** A: TOML supports 64-bit signed integers, but JavaScript (and therefore JSON in the browser) can only represent integers exactly up to 2^53 - 1 (9007199254740991). If your TOML contains an integer larger than that — for example a Snowflake ID or a nanosecond timestamp — it cannot be represented losslessly as a JSON number, and the value is rounded, with a warning shown. This is a fundamental limitation of browser JavaScript that affects every browser-based converter. For exact preservation, keep such values as quoted strings in your TOML. **Q: Is my TOML data sent to any server?** A: No. All parsing and conversion happen entirely in your browser using JavaScript. Your TOML is never uploaded, never stored, and never logged. This makes the tool safe for Cargo.toml files with private registry tokens, pyproject.toml with internal package indexes, deployment configs with secrets, and any other sensitive configuration. You can verify this by opening your browser's Network tab — pasting TOML triggers zero network requests. **Q: Can I convert JSON back to TOML?** A: Yes. Use the companion JSON to TOML Converter for the reverse direction, or click the Swap direction button at the top of this tool to flip the input and output in place. Note that JSON to TOML has a few constraints TOML to JSON does not — the top level must be an object, and null values have no TOML equivalent — which the JSON to TOML tool explains and handles for you. **Q: How do I convert TOML to JSON on the command line?** A: A popular option is the Rust-based CLI 'toml' or the Go tool 'yj' (yj -tj reads TOML and writes JSON). With Python 3.11+ you can run: python3 -c "import tomllib, json, sys; print(json.dumps(tomllib.load(sys.stdin.buffer)))" < config.toml. With Node.js: npx smol-toml (or a small script using the smol-toml library). For a quick one-off in the browser without installing anything, this tool is the fastest path. **Q: How do I convert TOML to JSON in Python, Rust, or Node.js?** A: In Python 3.11+: import tomllib, json; data = tomllib.load(open('config.toml','rb')); json.dump(data, open('config.json','w')). In Rust: use the toml and serde_json crates — let value: toml::Value = toml::from_str(&text)?; let json = serde_json::to_string_pretty(&value)?. In Node.js: import { parse } from 'smol-toml'; const data = parse(text); const json = JSON.stringify(data, null, 2) — this is the same library and approach used by this tool. **Q: Does the converter preserve key order, and what about comments?** A: Key order within a table is preserved: keys appear in the JSON output in the same order they appear in your TOML. Comments, however, are dropped — JSON has no comment syntax, so TOML # comments cannot survive the conversion. If you need to keep documentation, add it as a JSON-friendly field or keep the original TOML alongside the generated JSON. **Q: Is there a file size limit for TOML input?** A: There is no hard limit, but inputs over 200KB switch from live conversion to manual mode: a Convert button appears and conversion runs only when you click it, keeping the browser responsive. Typical config files — even large Cargo.toml workspaces or multi-environment deployment configs — convert in well under 50 milliseconds. --- ### TOTP / 2FA Code Generator URL: https://go-tools.org/tools/totp-generator Generate a TOTP/2FA code from a Base32 secret instantly — 100% in your browser, your secret never leaves your device. QR setup + code verify. Free, no signup. #### What Is a TOTP / 2FA Code Generator? A TOTP generator turns a shared secret into the rotating one-time code that powers two-factor authentication. TOTP — Time-based One-Time Password, defined in RFC 6238 — takes a Base32 secret and the current time, splits time into fixed steps (30 seconds by default), and runs an HMAC over the step counter to derive a short numeric code. Because both your authenticator app and the server hold the same secret and read the same clock, they compute the identical code without ever exchanging it over the network. That is the whole point of 2FA: even if your password leaks, an attacker still needs the code that only your secret can produce right now. "The TOTP algorithm is a time-based variant of the HOTP algorithm... TOTP = HOTP(K, T), where T is an integer representing the number of time steps between the initial counter time T0 and the current Unix time." — RFC 6238, Section 4 This tool does three jobs on one page. It generates a live code from any Base32 secret with a countdown and next-code preview; it sets up a brand-new secret, building the otpauth:// URI and QR code you scan into an authenticator app; and it verifies a code against a secret with a ±1 time-step tolerance, matching how real servers accept a code that just rotated. All of it runs through the browser's native Web Crypto API with zero dependencies and zero network calls. Developers reach for a TOTP generator constantly: to reproduce the exact code a user's app shows while debugging a 2FA login, to mint a secret and QR for a new account, to confirm that a verification window on the server matches what users experience, or to build deterministic fixtures for end-to-end tests of a two-factor flow. Because the secret is a long-lived key — anyone who has it can generate every future code — it must be protected like a password. Pair this tool with our random password generator for the strong passwords and recovery codes that sit alongside 2FA, and with the QR code generator when you need a standalone enrollment image. For signing the JSON Web Tokens that often ride on top of an authenticated session, see the JWT encoder. ``` // Generate a TOTP code in the browser with the Web Crypto API // (SHA-1, 6 digits, 30s period — RFC 6238 defaults) async function generateTotp(base32Secret, time = Date.now()) { // Decode the Base32 secret to raw bytes (A-Z, 2-7) const alpha = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'; let bits = ''; for (const c of base32Secret.replace(/=+$/, '').toUpperCase()) bits += alpha.indexOf(c).toString(2).padStart(5, '0'); const bytes = new Uint8Array( bits.match(/.{8}/g).map((b) => parseInt(b, 2))); // Counter = number of 30s steps since the Unix epoch (8-byte big-endian) const counter = Math.floor(time / 1000 / 30); const msg = new Uint8Array(8); let c = counter; for (let i = 7; i >= 0; i--) { msg[i] = c & 0xff; c = Math.floor(c / 256); } const key = await crypto.subtle.importKey( 'raw', bytes, { name: 'HMAC', hash: 'SHA-1' }, false, ['sign']); const hmac = new Uint8Array(await crypto.subtle.sign('HMAC', key, msg)); // Dynamic truncation (RFC 4226) -> 6-digit code const off = hmac[hmac.length - 1] & 0x0f; const bin = ((hmac[off] & 0x7f) << 24) | (hmac[off + 1] << 16) | (hmac[off + 2] << 8) | hmac[off + 3]; return (bin % 1_000_000).toString().padStart(6, '0'); } const code = await generateTotp('JBSWY3DPEHPK3PXP'); // -> a 6-digit code that rotates every 30 seconds ``` #### FAQ **Q: Is an online TOTP / 2FA generator safe to use?** A: With this one, yes — and the reason is that nothing ever leaves your browser. The Base32 secret you type, the otpauth:// URI, and the generated code are all computed locally with the native Web Crypto API. There are no network requests, no logging, no storage, and no analytics tied to your input — you can verify this by disconnecting from the internet and watching the tool keep working. That is the opposite of a sketchy generator that POSTs your secret to a server, where the operator could mint your codes forever. A TOTP secret is a long-lived shared key, so the safest habit is still to prefer disposable or test secrets when you just need to experiment. **Q: What is TOTP and what is a Base32 secret?** A: TOTP (Time-based One-Time Password, defined in RFC 6238) is the algorithm behind the rotating 6-digit codes in authenticator apps. It combines a shared secret with the current time, divided into fixed steps (usually 30 seconds), through an HMAC to produce a short code that both your device and the server can compute independently. The secret is the shared key, and it is almost always written in Base32 — uppercase letters A–Z and digits 2–7 — because that alphabet is case-insensitive and easy to type or encode in a QR code. The string JBSWY3DPEHPK3PXP is the well-known RFC test secret. **Q: Why is the generated code different from my phone's authenticator app?** A: Four things have to match for two TOTP codes to agree. First, the clock: TOTP depends on the current time, so if your computer or phone clock is off by more than a step, the codes diverge — sync your system clock and try again. Second, the algorithm: this tool defaults to SHA-1 (what most apps use), but if your secret was issued for SHA-256 or SHA-512 you must select it here too. Third, the digits and period: 6 vs 8 digits, or a 30s vs 60s window, produce entirely different codes. Fourth, the secret itself — a single mistyped Base32 character changes every code. Line up all four and the codes will match. **Q: What's the difference between TOTP and HOTP?** A: Both come from the same HMAC-based one-time-password family, but they differ in what drives the code. HOTP (RFC 4226) is counter-based: each code is tied to an incrementing counter, so a code stays valid until it is used and the counter advances. TOTP (RFC 6238) is time-based: it replaces the counter with the current time divided into fixed steps, so codes rotate automatically every 30 seconds. TOTP is really just HOTP with the counter set to the number of time steps since the Unix epoch. This tool generates TOTP, which is what Google Authenticator, Authy, and 1Password use by default. **Q: Can I use 8-digit codes or SHA-256 / SHA-512?** A: Yes. Open the advanced options to switch the algorithm to SHA-256 or SHA-512, set digits to 8, or change the period to 60 seconds. These knobs exist because some enterprise and banking systems require longer codes or stronger hashes. That said, the overwhelming majority of services — and every mainstream consumer authenticator app — use the defaults of SHA-1, 6 digits, and a 30-second period, so leave them as-is unless your provider's setup instructions say otherwise. Whatever you choose, the otpauth:// URI the tool generates records those parameters so your app enrolls the secret correctly. **Q: How do I add this secret to Google Authenticator, Authy, or 1Password?** A: Switch to the Set up tab to generate (or paste) a secret, then either scan the QR code or copy the otpauth:// URI. In Google Authenticator or Authy, tap the add button and choose Scan a QR code to point your camera at the on-screen QR, or choose Enter a setup key and paste the Base32 secret with the matching account name and algorithm. In 1Password, edit a login item, add a One-Time Password field, and paste the otpauth:// URI directly. Need a standalone QR image for documentation? Use our QR code generator, and for the random secrets and recovery codes around it, the random password generator. --- ### traceparent Decoder — W3C Trace Context URL: https://go-tools.org/tools/traceparent-decoder Stop counting hex digits. Free online traceparent decoder — runs in your browser, nothing uploaded. Trace ID, span ID, all 8 trace-flags bits, tracestate check, Datadog/X-Ray/B3 conversion. #### What Is the traceparent Header? traceparent is the HTTP header that carries a distributed trace from one service to the next. Before it was standardised, every tracing vendor propagated context in its own header, so a request that crossed systems lost its identity at the boundary. The W3C Trace Context specification fixed that with a single, deliberately small format: version-trace-id-parent-id-trace-flags, four hexadecimal fields joined by dashes, 55 characters in total for the current version. Each field does one job. The version is always 00 today, and ff is forbidden outright. The trace-id is 16 bytes identifying the whole request end to end — it stays constant across every hop. The parent-id is 8 bytes identifying the immediate caller's span, so unlike the trace-id it changes at every hop. The trace-flags byte is where most confusion lives: it looks like a boolean because 01 is by far the most common value, but it is eight bits. Bit 0 is sampled. Bit 1, added in Trace Context Level 2, is random-trace-id, asserting that the right-most seven bytes of the trace ID are uniformly random so that downstream systems may sample or shard on them. The remaining six bits are reserved, which is precisely why the field must be read with a bitwise AND rather than compared for equality. A companion header, tracestate, carries vendor-specific key-value pairs alongside it, capped at 32 members. That ceiling explains a puzzling symptom: vendor data that is present at the edge and gone several hops later, because intermediaries began dropping entries once the list grew past the limit. The header became genuinely universal once OpenTelemetry adopted it, and this page decodes all of it — fields, bits, tracestate members and the equivalent identifiers for other propagation formats — without sending anything anywhere. ``` # The header as it travels on the wire traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01 tracestate: rojo=00f067aa0ba902b7,congo=t61rcWkgMzE # Read the four fields apart # version 00 # trace-id 4bf92f3577b34da6a3ce929d0e0e4736 (16 bytes, whole request) # parent-id 00f067aa0ba902b7 (8 bytes, calling span) # trace-flags 01 (bit 0 set = sampled) # Send one yourself $ curl -H 'traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01' \ https://example.com/api ``` #### FAQ **Q: What is the traceparent header?** A: It is the single HTTP header that carries a distributed trace across service boundaries, standardised by the W3C so that tools from different vendors can follow the same request. It holds four dash-separated fields — version, trace-id, parent-id and trace-flags — and for the current version it is always exactly 55 characters. Every major tracing system now speaks it, which is what makes a request traceable from an edge proxy through half a dozen services without every hop agreeing on a vendor. The full grammar is defined in the W3C Trace Context recommendation. **Q: What does traceparent trace-flags 00 mean?** A: It means the caller explicitly decided this trace should not be recorded. The sampled bit is bit 0 of the trace-flags byte; when it is clear, an upstream sampler evaluated the request and chose against recording it. This is the single most misread value in the header, because it looks like a failure and is in fact a decision. If spans are missing, the useful question is not what is broken in your service but which upstream service is sending you a parent span with sampling turned off — a parent-based sampler will then propagate that choice to everything downstream. **Q: What is the difference between trace-flags 01, 02 and 03?** A: trace-flags is an eight-bit field, so these values are combinations rather than an enumeration. 01 sets bit 0 only: the trace is sampled. 02 sets bit 1 only: the Level 2 random-trace-id flag, which asserts that at least the right-most seven bytes of the trace ID were generated with uniform randomness — downstream systems can then sample or shard on those bytes safely. 03 sets both. Because it is a bit field, you must test it with a bitwise AND; comparing the whole byte against 01 will misreport any trace that also carries a reserved bit, and reserved bits are exactly what future versions will start using. **Q: Why is my trace ID all zeros?** A: Because tracing was never initialised, not because the trace exists but has no data. The specification lists an all-zero trace-id as an invalid value and instructs receivers to ignore the entire header, so nothing downstream will link to it. In practice it comes from an SDK that failed to start, a manually constructed header, or middleware inserting a placeholder when no real context was available. The all-zero parent-id, 0000000000000000, is invalid for the same reason. **Q: How do I convert a W3C trace ID to a Datadog trace ID?** A: Take the lower 64 bits — the right-hand 16 hex digits — and render them as a decimal string; that is what goes in x-datadog-trace-id. The higher 64 bits stay hexadecimal and travel separately in the _dd.p.tid tag. The parent-id converts to decimal whole. Getting this backwards, or converting all 128 bits to one decimal number, produces an identifier that matches nothing in the UI, which is why it recurs across tracer issue trackers. This page performs the split for you; if you want to explore the underlying hexadecimal arithmetic on arbitrary values, the number base converter handles general base conversion, while this page stays specific to trace identifiers. **Q: Does a traceparent contain a timestamp?** A: No — and this trips people up because some other trace identifiers do. A W3C trace-id is 16 opaque bytes with no embedded time. An AWS X-Ray trace ID, by contrast, is 1-{8 hex}-{24 hex} where the first eight hex digits are the creation time in epoch seconds, so converting between the two formats moves the bytes around but cannot invent a timestamp that was never there. If you need identifiers that do sort by time, that is what UUIDv7 and ULID are for. **Q: Is the header I paste here uploaded anywhere?** A: No. Every field is parsed locally in your browser with plain string and BigInt arithmetic — there is no server call, no logging of what you type, and nothing retained. This matters more here than for most tools, because a traceparent taken from a production request identifies real traffic in your observability backend. You do not have to take our word for it: open your browser's developer tools, watch the Network panel stay silent while you type, or disconnect from the network entirely and keep decoding. The absence of any external request is also enforced by an automated contract test that runs on every build, so it cannot quietly regress. **Q: Does this decoder work offline?** A: Yes. The page is static and the decoder is a few kilobytes of JavaScript with no dependencies, so once it has loaded you can go offline — or turn on airplane mode before you paste anything — and it keeps working exactly the same. If you are handling headers from a sensitive environment, that is the sequence worth using: load the page, disconnect, then paste. The Copy link button encodes state in the URL fragment after the # character, and fragments are never transmitted to a server either. --- ### Free ULID Generator — Generate, Decode & Convert ULIDs URL: https://go-tools.org/tools/ulid-generator Generate, decode, and convert ULIDs online — free and 100% in your browser. Extract the embedded timestamp from any ULID, convert ULID to UUID and back, batch-generate, with a monotonic mode. Nothing is ever sent to a server. #### What is a ULID and why use one? A ULID — Universally Unique Lexicographically Sortable Identifier — is a 128-bit identifier created to fix a practical weakness of the random UUIDv4 while keeping its best property: you can generate one anywhere, with no central coordinator, and be confident it is unique. The difference is that a ULID is sortable by time. It is rendered as 26 characters of Crockford's Base32, split into two parts: the first 10 characters are a 48-bit timestamp counting milliseconds since the Unix epoch, and the last 16 characters are 80 bits of cryptographically secure randomness. Put the time first, encode it in an order-preserving alphabet, and the identifier sorts chronologically as a plain string. That single design choice has outsized consequences for databases. A random UUIDv4 primary key lands in an unpredictable spot in a B-tree index on every insert, which fragments the index, thrashes the cache, and slowly degrades write performance as a table grows. A ULID, because it is time-prefixed, lands at or near the end of the index every time — inserts stay sequential, the index stays compact, and range scans over a time window become cheap. You get the coordination-free generation of a UUID and the insert locality of an auto-incrementing integer, without exposing a guessable sequential counter. The encoding details are deliberate. Crockford's Base32 excludes the letters I, L, O, and U, both to avoid visual confusion with the digits 1 and 0 and to make the string case-insensitive on input. The result is 26 characters with no hyphens that are safe to drop into a URL, a filename, or a request header without escaping — noticeably shorter than a UUID's 36-character hyphenated form. The 48-bit timestamp does not run out for a long time: it can represent dates through the year 10889 before the millisecond counter overflows. ULIDs are not the right tool for everything. The embedded timestamp reveals when a record was created, which is a feature for debugging and ordering but a small information leak if you would rather not expose that. And if your stack is committed to the UUID type, you may prefer UUIDv7, which applies the same time-prefixed idea inside the standard UUID format. But when you want short, URL-safe, sortable identifiers that you can mint on any node and read a timestamp back out of, a ULID is an excellent default — and because it is just 128 bits, you can always convert it to and from a UUID with this tool's Convert tab. ``` // Browser / Node with the `ulid` package import { ulid, decodeTime } from 'ulid'; const id = ulid(); // e.g. 01KVT0F720ZK9N4T2QX7VR8WMC const ts = decodeTime(id); // 1782210600000 -> 2026-06-23T10:30:00.000Z // Monotonic factory: strictly increasing within the same millisecond import { monotonicFactory } from 'ulid'; const next = monotonicFactory(); next(1782210600000); // 01KVT0F720ZK9N4T2QX7VR8WMC next(1782210600000); // 01KVT0F720ZK9N4T2QX7VR8WMD next(1782210600000); // 01KVT0F720ZK9N4T2QX7VR8WME ``` #### FAQ **Q: What is a ULID?** A: A ULID (Universally Unique Lexicographically Sortable Identifier) is a 128-bit identifier designed as a more sortable, more compact alternative to a UUID. It is written as 26 characters of Crockford's Base32: the first 10 characters hold a 48-bit timestamp in milliseconds since the Unix epoch, and the remaining 16 characters hold 80 bits of randomness. Because the timestamp is the most significant part and Base32 preserves order, ULIDs created later always sort after earlier ones when compared as plain strings — so a column of ULIDs is naturally time-ordered. The Crockford alphabet deliberately excludes the letters I, L, O, and U to avoid confusion with digits and to keep the string case-insensitive and URL-safe. ULIDs were introduced to solve a practical problem with random UUIDv4: random identifiers scatter across a database index, hurting insert performance, whereas a time-prefixed ULID lands near the end of the index every time. **Q: ULID vs UUID — which should I use?** A: Use a ULID when you want identifiers that are both unique and naturally sortable by creation time; use a classic UUIDv4 when you specifically need an opaque, fully random identifier with no embedded timestamp. The key differences: a ULID is 26 characters of Base32 versus a UUID's 36 characters with hyphens, so ULIDs are shorter and URL-safe without escaping. A ULID encodes its creation time, which a UUIDv4 does not — useful for ordering and debugging, but worth noting if you would rather not expose when a record was made. Both are 128 bits and both avoid coordination, so collision risk is negligible for either. If your stack standardizes on UUIDs but you still want time-ordering, UUIDv7 (from the UUID Generator) offers a similar time-prefixed design in UUID format — or you can generate ULIDs here and convert them to UUID with the Convert tab. **Q: Are ULIDs sortable?** A: Yes — that is the defining feature. Because the 48-bit millisecond timestamp occupies the first 10 characters and Crockford's Base32 preserves lexicographic order, sorting ULIDs as ordinary strings sorts them by creation time. This holds in any system that compares strings byte by byte: a database ORDER BY, a sorted set, a file listing, or a simple array sort. The practical payoff is database performance: time-ordered keys append to the end of a B-tree index instead of scattering randomly like UUIDv4, which keeps inserts fast and the index compact. Within a single millisecond the ordering of plain ULIDs is random, so if you need strict ordering even for IDs minted in the same millisecond, use the monotonic mode, which increments the randomness so each value is guaranteed greater than the last. **Q: How do I decode a ULID's timestamp?** A: Paste the ULID into the Decode tab and the tool extracts the embedded creation time instantly, entirely in your browser. It reads the first 10 characters, converts them from Crockford's Base32 back to a 48-bit integer of milliseconds since the Unix epoch, and shows that moment in UTC and your local time along with the raw Unix-millisecond value. For example, the canonical ULID 01ARYZ6S41TSV4RRFFQ69G5FAV decodes to 1469918176385 ms, or 2016-07-30T22:36:16.385Z. The remaining 16 characters are the 80-bit randomness and carry no meaning to decode. Reading the timestamp this way is handy for debugging, auditing when a record was created, or sanity-checking that an identifier really is a ULID — no database query required. **Q: What is a monotonic ULID?** A: A monotonic ULID guarantees strict ordering even for identifiers generated within the same millisecond. Plain ULIDs created in one millisecond share the same 10-character time prefix, but their 80-bit random tails are independent, so their relative order is not defined. Monotonic generation solves this: the first ULID in a given millisecond gets fresh randomness, and each subsequent ULID in that same millisecond is produced by incrementing the previous randomness by one. The result is a sequence where every value is strictly greater than the one before it, so a batch inserted in a tight loop stays perfectly sorted. This matters for high-throughput systems — event logs, message queues, bulk imports — where many rows can be created faster than the millisecond clock ticks and you still need a stable, increasing key. **Q: Is this ULID generator secure and private?** A: Yes on both counts. The randomness in every ULID comes from crypto.getRandomValues, the browser's cryptographically secure random number generator — never Math.random — so the 80 random bits are unpredictable and the chance of two ULIDs colliding within the same millisecond is vanishingly small. Just as important, everything runs locally: the ULIDs are generated, decoded, and converted entirely on your device. Nothing is uploaded, logged, or stored, and you can confirm it by opening DevTools and watching the Network tab stay silent while you click Generate. That privacy property is the whole reason to create identifiers in the browser rather than on a server that could, in principle, keep a copy of every value it hands out. **Q: What is the difference between ULID and UUIDv7?** A: Both ULID and UUIDv7 are time-ordered 128-bit identifiers that put a millisecond timestamp first, so both sort by creation time and both index efficiently — the core idea is the same. The difference is format and encoding. A ULID is presented as 26 characters of Crockford's Base32 with no hyphens, which is shorter and URL-safe; UUIDv7 is presented in the standard 36-character hyphenated hexadecimal UUID layout and carries version and variant bits in fixed positions, so it is a fully valid RFC 9562 UUID that any UUID library accepts. Choose UUIDv7 when you must remain in the UUID ecosystem (a UUID database column, a UUID-typed API); choose ULID when you want the shortest sortable string. Since both are 128 bits, you can generate a ULID here and convert it to UUID form with the Convert tab, or generate a UUIDv7 with the UUID Generator. For a deeper side-by-side of ULID, UUIDv4, UUIDv7, and Snowflake IDs, see our guide to sortable unique identifiers. --- ### Unicode Converter URL: https://go-tools.org/tools/unicode-converter Paste any Unicode form and read it back as text. \uXXXX escapes, ES6 braces, U+ code points, entities and byte runs are recognised automatically, then every other representation is listed at once. Runs in your browser. #### What is a Unicode escape? A Unicode escape writes a character using only ASCII, so it survives systems that cannot carry the character itself. The same character has a different escape in almost every language: JavaScript and JSON use four hex digits per UTF-16 unit, ES6 and Rust take a full code point in braces, Python and Go add a wide eight-digit form, CSS uses a bare backslash, and HTML has its own numeric and named references. They all describe the same code point, which is why one string can arrive at you in half a dozen shapes. ``` 中文 U+4E2D U+6587 code point \u4E2D\u6587 JavaScript / Java / JSON \u{4E2D}\u{6587} ES6 / Rust E4 B8 AD E6 96 87 UTF-8 %E4%B8%AD%E6%96%87 URL 中文 HTML ``` #### FAQ **Q: What does \u4e2d\u6587 mean and how do I read it?** A: Each \uXXXX is one UTF-16 code unit written in hexadecimal. \u4e2d is U+4E2D and \u6587 is U+6587, which together spell a Chinese word. Paste the whole string above and the readable text comes back; surrounding JSON or log text is preserved untouched. **Q: The backslashes were stripped and I only have u4e2d left. Can that still be decoded?** A: Yes. Terminals and log pipelines often swallow backslashes. Bare u4e2du6587 is recognised and decoded, while ordinary words that merely contain a u are left alone. **Q: Why is one emoji written as two \u escapes?** A: Characters above U+FFFF do not fit in a single UTF-16 code unit, so JavaScript, Java and JSON write them as a surrogate pair. A grinning face is one code point, two UTF-16 units and four UTF-8 bytes. The character table shows all three counts. **Q: How do I go the other way and turn text into escapes?** A: Type or paste plain text and read the table below the result. Every representation is produced at once, so you can copy the exact form your language expects instead of converting twice. **Q: Two strings look identical but my code says they are different. Why?** A: They are probably normalised differently. An accented letter can be one code point or a base letter plus a combining mark; both render the same but are not equal. Use the NFC button to fold them together. Note that normalisation is not a no-op for CJK either — compatibility ideographs change under plain NFC. **Q: Something invisible in my string is breaking my code. How do I find it?** A: Turn on Reveal invisible characters. Byte order marks, zero-width spaces, non-breaking spaces and variation selectors get a visible stand-in, and the character table names each one and gives its bytes. **Q: Is my data uploaded anywhere?** A: No. Conversion happens in your browser with no network request. Nothing is uploaded, logged or stored, so pasting production logs is safe. --- ### Unix Timestamp & Epoch Converter — Multi-Precision URL: https://go-tools.org/tools/unix-timestamp-converter Convert Unix timestamps to dates instantly. Auto-detects seconds, milliseconds & microseconds. Live clock, bidirectional. Free & private. #### What Is a Unix Timestamp (Epoch Time)? A Unix timestamp (also called Epoch time or POSIX time) is the number of seconds elapsed since January 1, 1970 00:00:00 UTC, serving as the universal time reference for virtually all computing systems. From Linux kernels and SQL databases to JavaScript engines and mobile operating systems, virtually every modern platform stores and exchanges time as a Unix timestamp. As IEEE Std 1003.1 (POSIX) formally defines it: "The Unix epoch (January 1, 1970 00:00:00 UTC) serves as the zero-point for POSIX time" — a convention so universally adopted that it is now the de facto standard for machine-readable timestamps worldwide. Unix timestamps are used by the overwhelming majority of server-side systems, databases, and network protocols, including HTTP headers, JWT tokens, and virtually every REST API. The Unix epoch itself — January 1, 1970 — was not chosen arbitrarily. Unix was developed at Bell Labs in the late 1960s, and 1970 was a convenient, round starting point that was recent enough to represent all relevant dates with manageable integer sizes. Any moment in time can be expressed as the signed 64-bit integer count of seconds from that anchor point. Dates before the epoch are represented as negative numbers: December 31, 1969 at midnight UTC is -86400 (one day, or 86,400 seconds, before the epoch). Modern systems often need finer time resolution than whole seconds. To accommodate this, timestamps are commonly expressed in milliseconds (thousandths of a second, as returned by JavaScript's `Date.now()` or Java's `System.currentTimeMillis()`) or microseconds (millionths of a second, used in databases like PostgreSQL and in high-frequency trading systems). You can identify the precision by the number of digits: 10 digits indicates seconds, 13 digits indicates milliseconds, and 16 digits indicates microseconds. This converter auto-detects your input's precision automatically. Unix timestamps are the backbone of distributed computing because they are timezone-independent, monotonically increasing (under normal conditions), and trivially sortable as integers. Storing times as timestamps and converting to human-readable formats only at display time is a best practice that eliminates entire categories of timezone bugs. The tradeoff is readability — a raw timestamp like 1741965432 is opaque without a converter, which is exactly what this tool provides. All conversions happen entirely in your browser using the JavaScript Date API — no timestamps, dates, or any other data are ever sent to a server. This tool converts any Unix timestamp — including the current epoch time shown in the live clock above — to a human-readable date instantly, with complete privacy. Timestamps are closely related to other developer tools. UUID v1 and v7 embed timestamps directly within their identifiers, and API responses containing timestamps are often best inspected using a JSON formatter for readability. For an in-depth guide covering precision, timezone handling, and DST pitfalls with code examples in JavaScript, Python, and Go, read our Unix timestamp guide. ``` // Get the current Unix timestamp in JavaScript const timestampSeconds = Math.floor(Date.now() / 1000); console.log(timestampSeconds); // → 1741965432 // Milliseconds (native JavaScript) const timestampMs = Date.now(); console.log(timestampMs); // → 1741965432000 // Convert timestamp back to a Date object const date = new Date(timestampSeconds * 1000); console.log(date.toISOString()); // → '2025-03-14T15:37:12.000Z' // Python equivalent // import time // timestamp = int(time.time()) # → 1741965432 ``` #### FAQ **Q: Why does Unix time start from January 1, 1970?** A: The Unix epoch date of January 1, 1970 was chosen by the developers of Unix at Bell Labs in the late 1960s as a convenient round starting point that was both recent and computationally practical. At the time, timestamps were stored in 32-bit integers, so the epoch needed to be close enough to the present that common dates would fit in a reasonably sized number. 1970 was simply a clean round year that was after the system's development began. There is no deep technical significance to January 1, 1970 specifically — it was an engineering pragmatism. Other systems have chosen different epochs: the Macintosh classic toolbox used January 1, 1904; Windows NT uses January 1, 1601; GPS time starts from January 6, 1980. Each reflects the era and design constraints of the system that chose it. What made the Unix epoch stick is that Unix became the dominant operating system in computing, and every major programming language, database, and operating system eventually adopted Unix time as the universal standard for representing machine-readable timestamps. Today, the Unix epoch is effectively a universal constant in computing, recognized by every major platform from Linux kernels to JavaScript engines to SQL databases. The choice has one well-known consequence: dates before January 1, 1970 are represented as negative numbers, which some older systems cannot handle. For historical dates and astronomical calculations, alternative timestamp formats are sometimes preferred. For the vast majority of software development, however, the Unix epoch covers all relevant dates comfortably. **Q: What is the Year 2038 problem?** A: The Year 2038 problem (also called Y2K38 or the Epochalypse) is a computing issue that will affect systems storing Unix timestamps as signed 32-bit integers. A signed 32-bit integer can hold values from -2,147,483,648 to 2,147,483,647. When interpreted as a Unix timestamp, the maximum value of 2,147,483,647 corresponds to January 19, 2038 at 03:14:07 UTC. One second later, the counter will overflow and wrap around to the most negative representable value, which corresponds to December 13, 1901 — causing these systems to interpret future dates as being in the far past. The consequences can range from trivial to catastrophic depending on how timestamps are used. Systems might reject valid future dates during input validation, incorrectly sort time-sensitive records, miscalculate expiration dates for certificates and tokens, or crash entirely when encountering the overflow value. The fix is straightforward: migrate to 64-bit signed integers for timestamp storage. A 64-bit timestamp can represent dates approximately 292 billion years before and after the epoch — far beyond any practical concern. Most modern operating systems, programming languages, and databases already use 64-bit timestamps internally. The risk lies in legacy code, embedded systems, 32-bit operating systems still in production, file system metadata (such as FAT32's timestamp fields), and database columns defined as INT rather than BIGINT. Developers should audit their systems now. The migration from 32-bit to 64-bit timestamps must happen before 2038, and in practice, any systems with long-running records (mortgages, infrastructure assets, legal documents) may encounter the problem much sooner as future dates are entered into affected fields. **Q: What is the difference between seconds, milliseconds, and microseconds timestamps?** A: Unix timestamps come in three common precisions, distinguished by the number of digits in the value: **Seconds (10 digits)**: The original and most common Unix timestamp format. `1741965432` represents a specific second in time. Used by: Unix/Linux system calls (`time()`), most Unix utilities, JWT tokens (`iat`, `exp` claims), HTTP headers (`Last-Modified`), and many REST APIs. The current timestamp is approximately 10 digits long. **Milliseconds (13 digits)**: One thousandth of a second precision. `1741965432000` is the same moment as above, multiplied by 1,000. Used by: JavaScript's `Date.now()`, Java's `System.currentTimeMillis()`, Node.js, most modern JavaScript/TypeScript APIs, Redis, and many database clients. When you see a 13-digit timestamp in a JSON API response, it is almost certainly milliseconds. **Microseconds (16 digits)**: One millionth of a second precision. `1741965432000000` is the same moment multiplied by 1,000,000. Used by: PostgreSQL's `TIMESTAMP` and `TIMESTAMPTZ` types, Python's `time.time_ns()` (though that returns nanoseconds), high-frequency trading systems, and network packet analysis tools. The most common mistake is mixing precisions — for example, passing a millisecond timestamp to a function that expects seconds. This produces dates roughly 11,574 years in the future. Always check the documentation of the API or system you are working with to confirm the expected precision, and use this converter's auto-detection as a sanity check. **Q: Does Unix time account for leap seconds?** A: No — Unix time does not account for leap seconds, and this is one of its known limitations for precision timekeeping applications. Leap seconds are occasionally inserted (or theoretically removed, though none have been removed yet) by the International Earth Rotation and Reference Systems Service (IERS) to keep UTC synchronized with the Earth's slightly irregular rotation. As of 2026, 27 leap seconds have been inserted since they were first introduced in 1972. Unix time assumes a perfectly regular calendar with exactly 86,400 seconds per day (24 × 60 × 60). When a leap second is inserted, the real world has a second that Unix time ignores. Different operating systems handle this differently: Linux traditionally "smears" the leap second by running the clock slightly slow for a period around the insertion point (Google's approach, also called "leap smearing"); some systems duplicate the second at 23:59:60 UTC; others simply skip the adjustment and let the clock drift. For the overwhelming majority of software applications — web services, APIs, databases, business logic — the ~27 seconds of accumulated leap second discrepancy over 50+ years is completely irrelevant. The difference is imperceptible to any human-facing application. Where leap seconds matter: GPS synchronization, astronomical observation, packet network timing protocols (PTP/IEEE 1588), and any system that must correlate Unix timestamps with TAI (International Atomic Time) precisely. If your application falls into these categories, you should use a timekeeping library that explicitly supports leap second awareness, or work with TAI timestamps directly. **Q: Can Unix timestamps be negative?** A: Yes, Unix timestamps can be negative, and negative timestamps are a legitimate and well-defined way to represent dates before the Unix epoch (January 1, 1970, 00:00:00 UTC). Each second before the epoch corresponds to a decrement of 1 from zero. For example, -1 represents December 31, 1969 at 23:59:59 UTC; -86400 represents December 31, 1969 at 00:00:00 UTC (exactly one day before the epoch); and -2208988800 represents January 1, 1900 at 00:00:00 UTC. Most modern programming languages and operating systems support negative timestamps. Python's `datetime.fromtimestamp(-86400)` correctly returns December 31, 1969. JavaScript's `new Date(-86400 * 1000)` correctly renders the same date. PostgreSQL stores timestamps as 8-byte integers and correctly handles dates thousands of years before the epoch. However, there are important caveats. Some older systems, libraries, or database drivers may not support negative timestamps correctly. 32-bit systems using unsigned integers for timestamps cannot represent negative values at all. Some databases defined as UNSIGNED BIGINT or DATETIME types may reject negative values or interpret them as far-future dates. For historical dates (anything before 1970), it is often safer to store the date as an ISO 8601 string or use a database-native date type rather than relying on negative Unix timestamps for portability. This converter handles negative timestamps correctly and will display the corresponding pre-1970 date. **Q: How do I get the current Unix timestamp in JavaScript, Python, or other languages?** A: Getting the current Unix timestamp is straightforward in every major programming language: **JavaScript / TypeScript:** ```javascript // Seconds (most APIs expect this) const seconds = Math.floor(Date.now() / 1000); // Milliseconds (JavaScript native) const milliseconds = Date.now(); ``` **Python:** ```python import time seconds = int(time.time()) # 1741965432 import datetime milliseconds = int(datetime.datetime.now(datetime.UTC).timestamp() * 1000) ``` **Go:** ```go import "time" seconds := time.Now().Unix() // int64 milliseconds := time.Now().UnixMilli() // int64 microseconds := time.Now().UnixMicro() // int64 ``` **Java:** ```java long seconds = System.currentTimeMillis() / 1000L; long milliseconds = System.currentTimeMillis(); // Or with java.time (Java 8+): long seconds2 = Instant.now().getEpochSecond(); ``` **PHP:** ```php $seconds = time(); // integer $milliseconds = round(microtime(true) * 1000); ``` **Ruby:** ```ruby seconds = Time.now.to_i milliseconds = (Time.now.to_f * 1000).to_i ``` **Bash / Shell:** ```bash date +%s # seconds date +%s%3N # milliseconds (GNU date) ``` The most important thing to remember is that JavaScript works natively in milliseconds, while virtually every other language defaults to seconds. Always be explicit about which precision you are using, and document it in your API contracts to prevent integration bugs. **Q: How do I convert epoch time to a human-readable date?** A: There are three fast ways to convert epoch time (Unix timestamps) to a human-readable date: **1. Use this online converter (fastest)** Paste your epoch timestamp into the input field above. The tool auto-detects whether it is in seconds, milliseconds, or microseconds and instantly displays the result in UTC, your local timezone, ISO 8601, and relative time formats. Click Copy to grab any format. **2. Use code** In JavaScript: `new Date(1741965432 * 1000).toISOString()` returns `'2025-03-14T15:37:12.000Z'`. In Python: `from datetime import datetime, UTC; datetime.fromtimestamp(1741965432, UTC)` returns the same result. Note that JavaScript expects milliseconds while Python expects seconds — the most common source of conversion bugs. **3. Use the command line** On Linux or macOS with GNU date: `date -d @1741965432` (Linux) or `date -r 1741965432` (macOS). On Windows PowerShell: `[DateTimeOffset]::FromUnixTimeSeconds(1741965432).DateTime`. All three methods produce the same result. The online converter above is the fastest option when you just need a quick answer without opening a terminal or writing code. **Q: What is the current Unix timestamp right now?** A: The current Unix timestamp is displayed in the live clock at the top of this page, updating every second. The Unix timestamp is simply the number of seconds since January 1, 1970 00:00:00 UTC, and it increments by exactly 1 each second. To get the current timestamp programmatically: - **JavaScript**: `Math.floor(Date.now() / 1000)` (seconds) or `Date.now()` (milliseconds) - **Python**: `import time; int(time.time())` - **Bash**: `date +%s` As of 2026, the current Unix timestamp is in the 1.77 billion range (10 digits). It will reach 2 billion around May 2033, and the maximum value for 32-bit systems (2,147,483,647) will be reached on January 19, 2038 at 03:14:07 UTC — the so-called Year 2038 problem. Bookmark this page to always have the current epoch time one click away. **Q: I need to debug a timestamp in my API response — how do I convert it?** A: Copy the timestamp value from your API response (it will typically be a 10-digit or 13-digit number in a JSON field like "created_at" or "timestamp"). Paste it directly into the input field above — the tool auto-detects whether it is in seconds or milliseconds and instantly shows the UTC date, your local time, and ISO 8601 format. If the timestamp is nested inside a JWT token, decode the JWT payload (which is Base64URL-encoded) first to extract the iat, exp, or nbf fields, then paste those values here. For batch debugging, use the code snippet: new Date(timestamp * 1000).toISOString() in your browser console to quickly check multiple timestamps without leaving your dev tools. **Q: How do I get the current Unix timestamp in Python/JavaScript/Go?** A: In JavaScript, use Math.floor(Date.now() / 1000) for seconds or Date.now() for milliseconds. In Python, use import time; int(time.time()) for seconds, or int(time.time() * 1000) for milliseconds. In Go, use time.Now().Unix() for seconds, time.Now().UnixMilli() for milliseconds, or time.Now().UnixMicro() for microseconds. Remember that JavaScript natively works in milliseconds while Python and Go default to seconds — this is the single most common source of timestamp bugs when integrating systems written in different languages. Always document which precision your API expects in your OpenAPI/Swagger specification. **Q: What happens to Unix timestamps during daylight saving time changes?** A: Unix timestamps are completely unaffected by daylight saving time (DST) changes because they are based on UTC, which does not observe DST. When clocks "spring forward" or "fall back" in a local timezone, the Unix timestamp continues to increment by exactly 1 per second without any gap or repetition. This is one of the key advantages of storing times as Unix timestamps rather than local datetime strings. However, if you convert a Unix timestamp to a local time during a DST transition, the same local time can correspond to two different Unix timestamps (during the "fall back" hour when clocks repeat). Always store and compare timestamps in UTC, and only convert to local time for display purposes. **Q: I have a timestamp in milliseconds — how do I convert it to seconds?** A: Divide the millisecond timestamp by 1000 and discard the decimal portion. In JavaScript: Math.floor(ms / 1000). In Python: ms // 1000 (integer division). For example, 1741965432000 (milliseconds) becomes 1741965432 (seconds). You can identify millisecond timestamps by their 13-digit length versus the 10-digit length of second timestamps. This tool auto-detects the precision, so you can paste either format directly. The reverse conversion (seconds to milliseconds) is simply multiplication by 1000: 1741965432 * 1000 = 1741965432000. Be careful not to accidentally pass a millisecond value to a function expecting seconds — the result would be a date roughly 11,574 years in the future. --- ### URL Encoder & Decoder with Built-in URL Parser URL: https://go-tools.org/tools/url-decoder-encoder Decode or encode URLs in real time with built-in URL parser. Dual mode: encodeURI & encodeURIComponent. 100% private, no data sent to any server. #### What is URL Encoding (Percent Encoding)? URL encoding, formally known as percent encoding, is a mechanism defined in RFC 3986 for representing characters in a Uniform Resource Identifier (URI) that are not allowed or have special meaning. It converts each unsafe byte into a percent sign (%) followed by two hexadecimal digits — for example, a space becomes %20, an ampersand becomes %26, and the Chinese character 中 becomes %E4%B8%AD (its three UTF-8 bytes, each percent-encoded). URLs can only contain a limited set of characters from the ASCII character set. Letters, digits, and a handful of symbols (- _ . ~) are considered 'unreserved' and can appear as-is. All other characters — including spaces, punctuation, and the entire range of Unicode — must be percent-encoded to be safely transmitted in a URL. Reserved characters like ?, &, =, and # serve as structural delimiters in the URL syntax, so they must also be encoded when used as literal data rather than delimiters. Percent encoding is essential throughout the web: browsers encode form submissions, APIs require encoded query parameters, OAuth flows depend on correctly encoded redirect URIs, and internationalized domain names rely on encoding for non-ASCII characters. Getting encoding wrong leads to broken links, security vulnerabilities (like open redirect attacks), and data corruption. This tool provides both encodeURI and encodeURIComponent modes, a built-in URL structure parser, real-time conversion, and double-encoding detection — all running privately in your browser. URL encoding is often used alongside other web development tools. You might need to Base64-encode a URL for embedding in a JWT token or API payload, or format JSON data that contains URL strings to inspect their structure. For a deeper, byte-level walkthrough of how percent encoding works, read our complete URL encoding guide for developers. Encoding a URL for inclusion inside a QR code? Use the QR Code Generator — long URLs may exceed the QR byte limit, so this tool helps shorten them first. ``` // Encode a query parameter value const param = encodeURIComponent('hello world & goodbye'); console.log(param); // → 'hello%20world%20%26%20goodbye' // Encode a full URL (preserves structure) const url = encodeURI('https://example.com/path name?q=hello world'); console.log(url); // → 'https://example.com/path%20name?q=hello%20world' // Decode a percent-encoded string const decoded = decodeURIComponent('hello%20world%20%26%20goodbye'); console.log(decoded); // → 'hello world & goodbye' // Build a URL with encoded parameters const base = 'https://api.example.com/search'; const query = `?q=${encodeURIComponent('你好')}&lang=zh`; console.log(base + query); // → 'https://api.example.com/search?q=%E4%BD%A0%E5%A5%BD&lang=zh' ``` #### FAQ **Q: What is URL encoding and why is it necessary?** A: URL encoding (percent encoding) converts unsafe characters into %XX hex sequences so they can be safely included in URLs. Characters like spaces, ampersands, and non-ASCII text must be encoded because they would otherwise break URL structure or be misinterpreted by browsers and servers. Defined in RFC 3986, this mechanism works because URLs can only contain a limited set of US-ASCII characters — letters (A-Z, a-z), digits (0-9), and a few symbols like hyphens, underscores, periods, and tildes. Any character outside this safe set is encoded as a percent sign (%) followed by two hexadecimal digits representing the character's byte value. For example, a space becomes %20, a forward slash becomes %2F, and an ampersand becomes %26. Characters like &, =, ?, and # have special structural meaning in URLs — they delimit query parameters, fragments, and other components. Without encoding, a literal & in a parameter value would be misinterpreted as a parameter separator, breaking the URL structure entirely. **Q: What is the difference between encodeURI and encodeURIComponent?** A: encodeURI() encodes a full URL while preserving structural characters like :, /, ?, and #. encodeURIComponent() encodes everything except letters, digits, and - _ . ~, making it the correct choice for encoding individual query parameter values. Use encodeURIComponent() for parameter keys and values, and encodeURI() only for complete URLs. For example, encodeURI('https://example.com/path name') produces 'https://example.com/path%20name', preserving the :// and /. encodeURIComponent() is far more aggressive — it encodes :, /, ?, #, &, and =. If you used encodeURI() on a parameter value that contained an ampersand, the & would pass through unencoded and be misinterpreted as a parameter separator. encodeURIComponent() would correctly encode it as %26. Mixing these up is one of the most common causes of URL-related bugs in web applications. **Q: Is URL encoding the same as HTML encoding?** A: No. URL encoding converts characters to %XX hex sequences for URLs (RFC 3986), while HTML encoding converts characters to entities like & and < for HTML documents. They serve completely different purposes and should never be interchanged. URL encoding is for data transport within URLs — a space becomes %20. HTML encoding is for safely displaying content in HTML — an ampersand becomes &. A common mistake is to apply HTML encoding to URL parameters or vice versa. For example, encoding a space as   in a URL would not work — it must be %20 or +. Similarly, using %3C in HTML text instead of < would not achieve the desired escaping. **Q: Why does my URL break when I use it in a curl command?** A: The shell interprets special URL characters before curl sees them: & runs commands in background, ? triggers globbing, and # starts a comment. Fix this by wrapping the URL in single quotes: curl 'https://example.com/api?key=value&page=2#section'. Single quotes prevent the shell from interpreting any special characters. The ampersand (&) is the most common culprit — it tells the shell to run the preceding command in the background, splitting your URL at the first & and discarding everything after it. Alternatively, you can escape individual characters with backslashes, but quoting the entire URL is simpler and less error-prone. If your URL also contains single quotes, use double quotes instead and escape any embedded dollar signs or backticks. **Q: Why do Chinese characters become strings like %E4%B8%AD in URLs?** A: Chinese characters are first converted to UTF-8 bytes, then each byte is percent-encoded as %XX. The character 中 (U+4E2D) becomes three UTF-8 bytes (E4, B8, AD), producing %E4%B8%AD — that's why one Chinese character expands to 9 characters in a URL. This three-step process — character to Unicode code point, code point to UTF-8 bytes, bytes to percent-encoded hex — applies to all non-ASCII characters. Emoji often require 4 UTF-8 bytes and thus expand to 12 characters when percent-encoded. When the URL is decoded, the reverse happens: the hex values are converted back to bytes, the bytes are interpreted as UTF-8, and the original characters are restored. **Q: Should I encode the OAuth redirect_uri parameter?** A: Yes, always encode it with encodeURIComponent(). The redirect_uri is a full URL embedded as a query parameter value, so its special characters (?, &, =) must be encoded to prevent them from being misinterpreted as part of the outer URL's structure. For example, redirect_uri=https://myapp.com/callback?code=abc&state=xyz without encoding would cause the authorization server to see redirect_uri as only https://myapp.com/callback?code=abc, while state=xyz would be parsed as a separate parameter of the outer URL. The correctly encoded version is redirect_uri=https%3A%2F%2Fmyapp.com%2Fcallback%3Fcode%3Dabc%26state%3Dxyz. **Q: What is the difference between Node.js querystring and URLSearchParams?** A: Use URLSearchParams for new projects — it's the WHATWG standard, matches browser behavior, and works identically in Node.js and browsers. The querystring module is legacy and no longer actively developed. Key differences: URLSearchParams encodes spaces as + (form encoding standard), handles repeated keys via getAll(), and provides an iterable interface with entries(), keys(), and values() methods. The querystring module encodes spaces as %20 and has quirks with array handling (key=1&key=2 becomes { key: ['1', '2'] }). URLSearchParams is standards-compliant and recommended by the Node.js documentation itself. **Q: How do I encode a URL in Python, JavaScript, and Java?** A: JavaScript: encodeURIComponent('hello world') produces 'hello%20world'. Python: urllib.parse.quote('hello world') produces 'hello%20world'. Java: URLEncoder.encode('hello world', StandardCharsets.UTF_8) produces 'hello+world' (replace + with %20 for RFC 3986). In JavaScript, use encodeURIComponent() for parameter values and encodeURI() for full URLs. In Python 3, use urllib.parse.quote() for path segments and urllib.parse.urlencode() for query parameters — urlencode({'q': 'hello world'}) produces 'q=hello+world'. In Java, note that URLEncoder uses form encoding (spaces as +). For building complete URIs, use java.net.URI or the URIBuilder class from Apache HttpClient. **Q: Which characters are not encoded by URL encoding?** A: RFC 3986 defines 66 unreserved characters that never need encoding: A-Z, a-z, 0-9, hyphen (-), period (.), underscore (_), and tilde (~). These can appear literally in any part of a URL. Reserved characters — :, /, ?, #, [, ], @, !, $, &, ', (, ), *, +, ;, and = — are allowed in their structural roles (like ? for query strings) but must be percent-encoded when used as literal data. In JavaScript, encodeURIComponent() encodes everything except A-Z a-z 0-9 - _ . ~ and ! ' ( ) *, while encodeURI() additionally preserves reserved characters that serve as URL delimiters. **Q: What is the difference between + and %20 for encoding spaces?** A: Both represent a space, but %20 is the universally safe choice. The + convention comes from HTML form encoding (application/x-www-form-urlencoded) and only works in query strings. In URL path segments, + is a literal plus sign, not a space. When in doubt, use %20. The + encoding originates from the HTML specification — when browsers submit forms, spaces become + and the + character itself becomes %2B. The %20 encoding comes from RFC 3986 (URI syntax), where every non-unreserved character is encoded as a percent sign followed by two hex digits. %20 works in all parts of a URL: path, query, and fragment. **Q: How does URL encoding handle emoji?** A: A single emoji typically expands to 12 characters in a URL. Emoji are converted to UTF-8 bytes (usually 4 bytes), then each byte is percent-encoded as %XX. For example, 🚀 (U+1F680) becomes %F0%9F%9A%80. Most emoji use code points in the range U+1F000 to U+1FFFF or higher, which require 4 bytes in UTF-8. Some emoji with skin tone modifiers or ZWJ sequences consist of multiple code points and can expand to 30+ characters when encoded. Despite this expansion, the encoding is fully reversible — decoding %F0%9F%9A%80 correctly produces 🚀. **Q: Can URL encoding be used for encryption or security?** A: No. URL encoding is not encryption and provides zero security. It is a fully reversible, deterministic transformation — anyone can decode a percent-encoded string instantly without any key or secret. It exists solely to escape special characters for safe URL transport. Treating URL encoding as obfuscation or security is a dangerous misconception. Sensitive data like passwords, tokens, or personal information should be protected by HTTPS (TLS encryption of the entire request), not by URL encoding. Additionally, URLs often appear in server logs, browser history, and referrer headers, so sensitive data should generally be sent in request bodies rather than URLs. **Q: What is the maximum length of a URL?** A: There is no official maximum, but keep URLs under 2,000 characters for maximum compatibility. Most browsers support ~2,048 characters, Apache defaults to 8,190 bytes, and Nginx to 8,192 bytes. For large data, use POST requests instead. The HTTP specifications (RFC 7230) do not define a maximum URL length, but practical limits exist at every layer. Chrome and Firefox can handle URLs exceeding 100,000 characters, while IIS limits query strings to 16,384 bytes. CDNs and proxies may have even stricter limits. When URL encoding expands characters — especially non-ASCII text — a seemingly short URL can quickly approach these limits. **Q: What is the difference between a URL and a URI?** A: A URI (Uniform Resource Identifier) is any string that identifies a resource. A URL (Uniform Resource Locator) is a URI that also specifies how to access it via a protocol like https://. All URLs are URIs, but not all URIs are URLs — for example, a URN like urn:isbn:0451450523 identifies a book by ISBN but doesn't tell you where to find it. In everyday web development, the terms URL and URI are often used interchangeably. The encoding rules defined in RFC 3986 apply to both. JavaScript's functions are named encodeURI/decodeURI, reflecting the broader URI terminology, even though most developers work exclusively with URLs. --- ### UUID Generator & Decoder — v1, v4, v5, v7 Batch Mode URL: https://go-tools.org/tools/uuid-generator Free UUID generator — create v1, v4, v5, v7 UUIDs instantly. Decode & validate any UUID. Batch generate up to 50. No signup, 100% browser-based. #### What Is a UUID? A UUID (Universally Unique Identifier) is a 128-bit globally unique identifier standardized by RFC 9562 (IETF, May 2024), designed to generate collision-free IDs across distributed systems without central coordination. UUIDs are the most widely adopted identifier format in modern software — used in database primary keys, API request tracing, session management, and microservice architectures. UUIDs are written as 32 hexadecimal digits in the canonical 8-4-4-4-12 format, such as `550e8400-e29b-41d4-a716-446655440000`. The specification is maintained by the IETF; RFC 9562 supersedes the earlier RFC 4122 (2005) and formally introduces UUID versions 6, 7, and 8. There are five widely used UUID versions. Version 1 (v1) encodes the current timestamp and the generating machine's MAC address, making each UUID unique in both time and space. Version 3 (v3) and version 5 (v5) are deterministic — they hash a namespace and a name using MD5 or SHA-1 respectively, always producing the same UUID for the same inputs. Version 4 (v4) is the most common: it fills 122 bits with cryptographically secure random data, giving over 5.3 × 10³⁶ possible values (RFC 9562, Section 5.4). Version 7 (v7) is the newest standard: as RFC 9562 Section 5.7 states, "UUID version 7 features a time-ordered value field derived from the widely implemented and well-known Unix Epoch timestamp source" — combining a 48-bit millisecond timestamp with random data to produce UUIDs that are both unique and naturally sortable by creation time. UUIDs are essential in distributed systems, databases, APIs, and anywhere unique identifiers are needed without centralized coordination. They eliminate the risk of ID collisions across independent systems, making them ideal for microservices, event sourcing, and multi-tenant architectures. This tool generates all UUID versions entirely in your browser using the Web Crypto API — no UUIDs are transmitted to any server. Unlike server-based generators, there are no uploads, no logging, and no data retention. Safe to use for production database keys, API identifiers, and security-sensitive applications. You can also decode and validate existing UUIDs to inspect their version, variant, and embedded data. UUIDs are closely connected to other developer primitives. UUID v1 and v7 embed Unix timestamps directly, UUID v3 and v5 use MD5 and SHA-1 hashes as their foundation, and UUID strings are often transported inside JSON payloads best inspected with a JSON formatter. For a thorough introduction to UUID format, versions, and real-world use cases, read our complete UUID guide. If you are choosing between UUID v4, v7, ULID, and Snowflake IDs for a database primary key, see our ID selection comparison. ``` // Generate a UUID v4 using the Web Crypto API const uuid = crypto.randomUUID(); console.log(uuid); // → '550e8400-e29b-41d4-a716-446655440000' // Manual v4 generation with crypto.getRandomValues() function generateUUIDv4() { const bytes = new Uint8Array(16); crypto.getRandomValues(bytes); bytes[6] = (bytes[6] & 0x0f) | 0x40; // version 4 bytes[8] = (bytes[8] & 0x3f) | 0x80; // variant 10 const hex = Array.from(bytes, b => b.toString(16).padStart(2, '0')).join(''); return `${hex.slice(0,8)}-${hex.slice(8,12)}-${hex.slice(12,16)}-${hex.slice(16,20)}-${hex.slice(20)}`; } ``` #### FAQ **Q: What is a UUID?** A: A UUID (Universally Unique Identifier) is a 128-bit identifier standardized by RFC 9562. It is written as 32 hexadecimal digits displayed in five groups separated by hyphens, following the 8-4-4-4-12 format — for example, 550e8400-e29b-41d4-a716-446655440000. UUIDs are designed to be globally unique without requiring a central registration authority. The term GUID (Globally Unique Identifier) is Microsoft's name for the same concept and uses the identical format. UUIDs are used extensively in databases, distributed systems, APIs, and software development wherever unique identifiers are needed. With over 5.3 x 10^36 possible v4 UUIDs, the probability of generating a duplicate is astronomically small, making them safe for independent generation across uncoordinated systems. **Q: What are the differences between UUID versions?** A: UUID v1 encodes a 60-bit timestamp and the machine's 48-bit MAC address, guaranteeing uniqueness in time and space but potentially leaking hardware identity. UUID v3 hashes a namespace and name with MD5 to produce a deterministic UUID — the same inputs always yield the same output. UUID v4 fills 122 of its 128 bits with cryptographically secure random data, making it the most widely used version for general-purpose unique identifiers. UUID v5 is identical to v3 but uses SHA-1 instead of MD5, offering stronger hash collision resistance. UUID v7, introduced in RFC 9562 (May 2024), embeds a 48-bit Unix timestamp in milliseconds followed by random bits, producing UUIDs that are both unique and naturally sortable by creation time. Version choice depends on your requirements: v4 for simplicity, v5 for determinism, and v7 for time-sortable database keys. **Q: When should I use UUID v4 vs v7?** A: UUID v4 is the most popular version and an excellent default choice. It generates 122 bits of pure randomness, requires no configuration, and works everywhere. Use v4 when you simply need a unique identifier and don't care about ordering. UUID v7 is the better choice when UUIDs will be used as database primary keys or need to be sorted by creation time. Because v7 embeds a millisecond-precision timestamp in the most significant bits, v7 UUIDs naturally sort in chronological order. This property dramatically improves B-tree index performance — inserts always go to the end of the index rather than random positions, reducing page splits and fragmentation by up to 90%. For new projects in 2026, the general recommendation is to use v7 for database keys and v4 for everything else. Both versions are equally unique and cryptographically random in their random portions. **Q: What is the probability of UUID collision?** A: UUID v4 has 122 random bits, giving 2^122 (approximately 5.3 x 10^36) possible values. To have a 50% probability of at least one collision, you would need to generate approximately 2.71 x 10^18 UUIDs — that is 2.71 quintillion. To put this in perspective, if you generated one billion UUIDs per second, it would take about 86 years to reach a 50% collision probability. At more realistic generation rates, the probability is vanishingly small. For example, generating 10 million UUIDs produces a collision probability of roughly 1 in 10^22. In practice, hardware failures, software bugs, and human errors are all billions of times more likely to cause duplicate IDs than UUID v4 collisions. The math is based on the birthday problem formula: p(n) approximately equals n^2 / (2 * 2^122). **Q: What is the difference between UUID and GUID?** A: UUID (Universally Unique Identifier) and GUID (Globally Unique Identifier) are essentially the same thing. GUID is the term coined by Microsoft and used predominantly in Windows, .NET, COM, and SQL Server environments. UUID is the standard term defined by RFC 9562 (and its predecessor RFC 4122) and is used in most other contexts including Linux, Java, Python, PostgreSQL, and web development. Both use the identical 128-bit format displayed as 32 hexadecimal digits in the 8-4-4-4-12 pattern. The only minor difference is that Microsoft tools sometimes display GUIDs in uppercase with curly braces, like {550E8400-E29B-41D4-A716-446655440000}, while UUIDs are conventionally shown in lowercase without braces. This tool supports both formats via the output format selector — choose the Braces {GUID} format for Microsoft-style output. **Q: Is UUID v4 cryptographically secure?** A: When generated using crypto.getRandomValues() or an equivalent CSPRNG (Cryptographically Secure Pseudo-Random Number Generator), UUID v4 contains 122 bits of cryptographically secure random data. This tool uses the Web Crypto API, which draws entropy from the operating system's secure random source. However, UUIDs should not be used as security tokens, passwords, or encryption keys. While 122 bits of randomness makes prediction infeasible, UUIDs have a predictable structure — the version nibble (4) and variant bits are fixed and publicly known. For security tokens, use purpose-built APIs like crypto.getRandomValues() with a full 128 or 256 bits of entropy, or use established token formats like JWT. Use UUIDs for identification, not for security. **Q: How to validate a UUID format?** A: A valid UUID matches the regular expression pattern: ^[0-9a-f]{8}-[0-9a-f]{4}-[1-7][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$ (case-insensitive). This pattern enforces the 8-4-4-4-12 hexadecimal format, checks that the version digit (position 15) is between 1 and 7, and verifies that the variant nibble (position 20) starts with 8, 9, a, or b (indicating the RFC 4122/9562 variant). In JavaScript, you can validate with: /^[0-9a-f]{8}-[0-9a-f]{4}-[1-7][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(uuid). Most programming languages also have built-in UUID parsing — for example, Python's uuid.UUID() constructor, Java's UUID.fromString(), and Go's uuid.Parse(). Always validate UUIDs at system boundaries before storing or processing them to prevent injection attacks and data corruption. **Q: Are UUIDs good database primary keys? (Performance, safety & best version)** A: Yes, UUIDs are safe and increasingly popular as database primary keys, with UUID v7 being the recommended version. The key advantages: (1) UUIDs can be generated anywhere — clients, servers, or edge functions — without a round trip to the database, enabling offline-first and distributed architectures. (2) UUIDs prevent enumeration attacks since they are not sequential integers. (3) UUIDs simplify data merging across systems since IDs never collide. UUID v7 is the best version for primary keys because its time-ordered structure keeps B-tree indexes sequential, dramatically reducing page splits, write amplification, and index fragmentation by up to 90% compared to random v4 UUIDs. The tradeoffs: UUIDs use 16 bytes versus 4-8 bytes for integers, increasing storage and memory for indexes. In MySQL/InnoDB, where the primary key is the clustered index, random v4 UUIDs can cause significant performance degradation — v7 solves this by ensuring inserts always append to the end of the index, achieving performance comparable to auto-increment integers. PostgreSQL stores UUIDs natively in 16 bytes with the uuid type. For most modern applications, the benefits of globally unique, coordination-free ID generation far outweigh the extra storage cost. **Q: What is a namespace UUID (v3/v5)?** A: A namespace UUID is a predefined or custom UUID that serves as a scope for generating deterministic v3 and v5 UUIDs. RFC 4122 defines four standard namespace UUIDs: DNS (6ba7b810-9dad-11d1-80b4-00c04fd430c8), URL (6ba7b811-9dad-11d1-80b4-00c04fd430c8), OID (6ba7b812-9dad-11d1-80b4-00c04fd430c8), and X.500 DN (6ba7b814-9dad-11d1-80b4-00c04fd430c8). When you combine a namespace UUID with a name string, the v3 or v5 algorithm hashes them together to produce a deterministic UUID — the same namespace plus name always produces the same UUID. This is useful when you need reproducible identifiers derived from meaningful names. For example, hashing the DNS namespace with 'example.com' always yields the same v5 UUID. You can also use any valid UUID as a custom namespace for your application's own deterministic ID scheme. **Q: What is the UUID nil value?** A: The nil UUID (also called the zero UUID) is 00000000-0000-0000-0000-000000000000 — all 128 bits set to zero. It is defined in RFC 9562 Section 5.9 as a special UUID that can represent the absence of a value, similar to null or None in programming languages. The nil UUID is useful as a sentinel value, a default in configuration systems, or a placeholder in database records where a UUID field must not be empty but no real value exists yet. RFC 9562 also defines the max UUID: ffffffff-ffff-ffff-ffff-ffffffffffff (all bits set to one), which can serve as a boundary marker. Important caveats: never use the nil UUID as an actual identifier — it is not unique. Some UUID libraries and databases may reject or specially handle nil UUIDs, so ensure your system treats them consistently. Always document whether nil UUIDs are accepted in your API contracts and database schemas. **Q: What is UUID v7 and why should I use it?** A: UUID v7 is the newest UUID version defined in RFC 9562 (May 2024). It embeds a 48-bit Unix timestamp in milliseconds in the most significant bits, followed by cryptographically random data. This design produces UUIDs that are globally unique, chronologically sortable, and highly efficient as database primary keys. Unlike UUID v1, which also contains a timestamp, v7 uses a simpler Unix epoch format and does not expose your MAC address. The time-ordered structure reduces B-tree index fragmentation by up to 90% compared to random UUID v4, resulting in faster inserts, smaller indexes, and better cache hit rates. For new projects starting in 2026, UUID v7 is the recommended choice for any scenario requiring time-based ordering — especially database primary keys, event logs, and distributed message queues. **Q: How to decode a UUID?** A: Decoding a UUID means extracting the structural information encoded within its 128 bits. Every UUID contains a version field (bits 48-51) identifying how it was generated, and a variant field (bits 64-65) identifying the UUID standard it conforms to. Beyond these common fields, different versions embed different data: UUID v1 and v6 contain a 60-bit timestamp and a 48-bit node (MAC address); UUID v7 contains a 48-bit Unix timestamp in milliseconds; UUID v3 and v5 contain a truncated hash of a namespace and name. To decode a UUID, paste it into the Decode tab of this tool. It will instantly display the version, variant, timestamp (for time-based versions), and validity status. Programmatically, you can decode by parsing the hex digits and applying bitwise operations to extract each field according to the RFC 9562 specification. **Q: UUID vs ULID vs nanoid — which should I use?** A: UUID, ULID, and nanoid serve the same fundamental purpose — generating unique identifiers — but differ in format, sortability, and standardization. UUID is the most widely adopted standard (RFC 9562), supported natively by virtually all databases, languages, and frameworks. UUID v7 provides time-based sorting in the standard 128-bit, 36-character format. ULID (Universally Unique Lexicographically Sortable Identifier) predates UUID v7 and uses Crockford Base32 encoding to produce a 26-character, sortable string. Now that UUID v7 exists as an IETF standard, ULID's main advantage — sortability — is available in the universal UUID format. nanoid generates shorter identifiers (default 21 characters) using a URL-safe alphabet, making it ideal when string length matters and you don't need cross-system interoperability. For most applications, UUID v4 (general purpose) or UUID v7 (database keys) is the recommended choice due to universal tooling support, native database types, and formal standardization. **Q: I'm building a microservice and need to choose between UUID v4 and v7 for my PostgreSQL primary keys — which one should I use and why?** A: Use UUID v7 for PostgreSQL primary keys. UUID v7 embeds a millisecond-precision Unix timestamp in the most significant bits, so generated IDs are naturally chronologically sorted. This keeps your B-tree indexes sequential — inserts always append to the end rather than landing at random positions, reducing page splits and index fragmentation by up to 90% compared to random UUID v4. PostgreSQL 17+ has native uuid type support optimized for this pattern. UUID v4 is still fine for non-indexed identifiers like correlation IDs or session tokens where sort order doesn't matter. **Q: My team is debating whether to use UUIDs or auto-increment integers as database IDs — what are the real-world tradeoffs?** A: Auto-increment integers are smaller (4-8 bytes vs 16 bytes), faster to compare, and produce naturally sequential indexes. However, they require a centralized sequence (the database), making them problematic in distributed systems, offline-first apps, and data migrations. UUIDs can be generated anywhere — clients, edge functions, multiple databases — without coordination. They also prevent enumeration attacks (users can't guess /users/124 to find other records). The storage overhead is real but usually acceptable: a UUID index is roughly 2x the size of an integer index. For most modern applications, UUID v7 offers the best of both worlds — globally unique, coordination-free, and sequentially sortable like auto-increments. --- ### Volume & Liquid Measurement Converter — 15 Units URL: https://go-tools.org/tools/volume-converter Convert volume & liquid capacity between 15 units — metric, US customary, imperial & cooking. Free online converter, 100% private, runs in your browser. #### What Is a Volume Unit Converter? A volume unit converter is a tool that translates capacity and volume measurements between different units of measure — spanning metric (milliliters, liters, cubic meters), US customary (cups, pints, quarts, gallons, fluid ounces, tablespoons, teaspoons), Imperial (Imperial gallons, Imperial fluid ounces), and cubic measurements (cubic inches, cubic feet). The metric system defines volume through the liter. By exact SI definition (BIPM), 1 liter equals exactly 1 cubic decimeter (dm³) — which means 1 milliliter equals exactly 1 cubic centimeter (cm³). This elegant equivalence makes the metric system especially practical: a liter of water at 4 °C weighs almost exactly 1 kilogram, linking volume and mass directly. The US customary system uses a hierarchy: 1 gallon = 4 quarts = 8 pints = 16 cups = 128 fluid ounces. The Imperial system (used in the UK) shares some unit names with the US system but with different sizes — an Imperial gallon is about 20% larger than a US gallon (4,546 mL vs 3,785 mL). Cooking measurements add further complexity: US recipes use cups, tablespoons, and teaspoons, while most of the world measures by weight (grams) or metric volume (milliliters). This converter handles all these systems, including the often-confused distinction between US and Imperial fluid ounces (29.57 mL vs 28.41 mL). All conversions use the exact factors defined by NIST Handbook 44 and the BIPM SI Brochure, and are calculated entirely in your browser — no data is transmitted to any server, so your values stay completely private. Need to convert other measurement types? Try our length converter for distance units, weight converter for mass units, or temperature converter for Celsius, Fahrenheit, and Kelvin. ``` // Key volume conversion factors: // 1 liter = 1000 mL // 1 US gallon = 3785.411784 mL // 1 US cup = 236.588 mL // 1 US fl oz = 29.5735 mL // 1 US tbsp = 14.787 mL // 1 US tsp = 4.929 mL // JavaScript conversion examples: const litersToGallons = (l) => l / 3.785411784; const cupsToMl = (cups) => cups * 236.5882365; const tbspToTsp = (tbsp) => tbsp * 3; console.log(litersToGallons(3.785)); // ~1.0 console.log(cupsToMl(2)); // 473.176 console.log(tbspToTsp(3)); // 9 ``` #### FAQ **Q: How many milliliters are in a cup?** A: One US cup equals approximately 236.588 milliliters. This is based on the US customary system where 1 cup = 8 fluid ounces = 16 tablespoons. For quick mental math, round to 240 mL — many measuring cups sold outside the US use this rounded value. Note that the metric cup (used in Australia) is exactly 250 mL, and the Imperial cup (UK, Canada historical) is about 284 mL. **Q: What is the difference between a US gallon and an Imperial gallon?** A: A US gallon contains 3,785.41 mL (128 US fluid ounces), while an Imperial gallon contains 4,546.09 mL (160 Imperial fluid ounces). The Imperial gallon is about 20% larger. This means a US vehicle getting 30 miles per US gallon would get about 36 miles per Imperial gallon. The difference originated from different historical definitions of the gallon in the US and British systems. **Q: How many liters are in a gallon?** A: One US gallon equals exactly 3.785411784 liters. One Imperial gallon equals exactly 4.54609 liters. For a quick US gallon approximation, remember that 1 gallon is roughly 3.8 liters, or that 4 liters is slightly more than 1 gallon. Common reference: a standard 2-liter soda bottle is about half a US gallon. **Q: How do I convert between tablespoons and teaspoons?** A: One US tablespoon equals exactly 3 US teaspoons. So to convert tablespoons to teaspoons, multiply by 3. To convert teaspoons to tablespoons, divide by 3. Other useful relationships: 1 tablespoon = 14.787 mL, 1 teaspoon = 4.929 mL, 1 cup = 16 tablespoons = 48 teaspoons. These are US customary measurements; metric tablespoons and teaspoons may differ slightly. **Q: How many fluid ounces are in a cup?** A: One US cup contains exactly 8 US fluid ounces. This makes cups convenient for scaling: 1/4 cup = 2 fl oz, 1/3 cup = 2.67 fl oz, 1/2 cup = 4 fl oz, 3/4 cup = 6 fl oz. Note that fluid ounces measure volume, not weight — 8 fl oz of water weighs about 8.35 ounces by weight, because water is slightly denser than the standard used to define fluid ounces. **Q: What is a cubic meter in liters?** A: One cubic meter equals exactly 1,000 liters. This relationship is fundamental to the metric system — the liter was originally defined as one cubic decimeter (0.001 m³). This means 1 mL = 1 cm³ exactly. Common conversions: a cubic meter of water weighs 1,000 kg (1 metric ton) at standard conditions, a typical bathtub holds about 0.15-0.3 m³ (150-300 liters). **Q: How accurate is this volume converter?** A: This converter uses the exact US customary and metric conversion factors defined by NIST (National Institute of Standards and Technology). All calculations use IEEE 754 double-precision floating-point arithmetic, providing at least 15 significant digits of precision. For everyday cooking and engineering conversions, the results are far more precise than any physical measurement. The tool runs entirely in your browser with no rounding shortcuts. **Q: Is my data safe when using this volume converter?** A: Yes, completely. All conversions are performed locally in your browser using JavaScript. No data is sent to any server — there are no network requests, no cookies, and no analytics on your input. The conversion logic runs entirely on your device, meaning your values never leave your browser. You can verify this by disconnecting from the internet and using the tool — it works fully offline once the page has loaded. **Q: How do I convert cooking measurements between US and metric?** A: Key cooking conversions: 1 cup = 236.6 mL, 1 tablespoon = 14.8 mL, 1 teaspoon = 4.9 mL, 1 fluid ounce = 29.6 mL. For practical cooking, you can round: 1 cup is about 240 mL, 1 tablespoon is about 15 mL, 1 teaspoon is about 5 mL. When precision matters (baking), use exact values. When it does not (soups, sauces), rounded values work fine. **Q: How many cups are in a quart, pint, and gallon?** A: In the US customary system: 1 gallon = 4 quarts = 8 pints = 16 cups = 128 fluid ounces. Working down: 1 quart = 2 pints = 4 cups = 32 fluid ounces, and 1 pint = 2 cups = 16 fluid ounces. A helpful mnemonic: 'Gallon man' — a gallon contains 4 quarts, each quart contains 2 pints, and each pint contains 2 cups. **Q: I need to convert liters to gallons for my car's fuel tank capacity — how do I do it?** A: Divide the liter value by 3.785 to get US gallons. For example, a 60-liter fuel tank equals about 15.85 US gallons (60 ÷ 3.785 = 15.85). For Imperial gallons (used in the UK), divide by 4.546 — the same 60-liter tank would be about 13.2 Imperial gallons. This matters when comparing fuel economy: a car rated at 8 L/100 km gets about 29.4 miles per US gallon or 35.3 miles per Imperial gallon. Enter any value in this tool to get the precise conversion instantly. **Q: I need to scale a recipe from cups to milliliters — how do I convert cooking measurements?** A: Key conversions: 1 US cup = 236.6 mL, 1 tablespoon = 14.8 mL, 1 teaspoon = 4.9 mL, 1 fluid ounce = 29.6 mL. For practical cooking, round to: 1 cup ≈ 240 mL, 1 tbsp ≈ 15 mL, 1 tsp ≈ 5 mL. To scale a recipe, multiply each measurement by your scaling factor, then convert. For example, to double a recipe calling for 1.5 cups of milk: 1.5 × 2 = 3 cups = 3 × 236.6 = 710 mL. For precise baking (pastry, bread), use grams instead of volume — volume measurements for flour can vary by 20-30% depending on how it is scooped. **Q: I need to figure out how many gallons my aquarium holds based on its dimensions — can this tool help?** A: Yes. First calculate the volume in cubic inches by multiplying length × width × height (all in inches). Then use this tool to convert cubic inches to gallons. For example, a tank that is 36" × 18" × 16" has a volume of 10,368 cubic inches, which equals about 44.9 US gallons. Alternatively, measure in centimeters, calculate cubic centimeters (cm³), and know that 1 cm³ = 1 mL, then convert mL to gallons. A 90 × 45 × 40 cm tank holds 162,000 mL = 162 liters = about 42.8 US gallons. Remember that actual water volume will be slightly less due to substrate, decorations, and equipment. --- ### Weight Converter — kg, lbs, oz, g, Stone & 13 Units Total URL: https://go-tools.org/tools/weight-converter Convert weight between 13 units instantly — metric, imperial & troy. Conversion tables, formulas & references. Free, runs in your browser. #### What Is a Weight & Mass Unit Converter? A weight and mass unit converter is a tool that translates measurements between different units of mass — covering metric (micrograms, milligrams, grams, kilograms, metric tons), avoirdupois/imperial (ounces, pounds, stone, short tons, long tons), and specialty units (carats, troy ounces, grains). The metric system defines mass through the kilogram, the SI base unit. Since the 2019 BIPM redefinition of SI base units, the kilogram has been defined by fixing the numerical value of the Planck constant (h = 6.62607015 × 10⁻³⁴ J·s), replacing the International Prototype of the Kilogram (IPK) — a platinum-iridium cylinder kept in a vault at BIPM near Paris. This redefinition anchors the kilogram to a universal constant of nature. One kilogram = 1,000 grams = 1,000,000 milligrams. The avoirdupois system, used in the US and UK for everyday commerce, defines 1 pound = 16 ounces = 453.59237 grams exactly. The stone (14 pounds) is still used for body weight in the UK. Specialty units serve specific industries: the carat (0.2 grams) is the universal standard for gemstone weight; the troy ounce (31.1035 g) is the standard for precious metals trading; and the grain (64.79891 mg) is used in ballistics and pharmacy. Three different 'tons' cause frequent confusion: the metric ton (1,000 kg), the US short ton (2,000 lbs / 907.2 kg), and the British long ton (2,240 lbs / 1,016 kg). This converter handles all three. Weight measurement conventions vary worldwide. The United States uses pounds and ounces for nearly everything — groceries, body weight, shipping. The United Kingdom uses a hybrid system: stone and pounds for body weight, but kilograms and grams in shops (by law since 2000). Continental Europe, East Asia, and most of the world use kilograms and grams exclusively. China uses the 斤 (jīn, 1 jīn = 500 g) as an informal everyday unit alongside kilograms. In the precious metals and gemstone industries, troy ounces and carats are universal regardless of country. All conversions use the exact factors defined by NIST and are calculated entirely in your browser — no data is transmitted to any server, keeping your measurements completely private. Need to convert other measurement types? Try our length converter for distance units, volume converter for liquid measurements, or temperature converter for °C, °F and Kelvin. ``` // Key weight conversion factors: // 1 kg = 1000 g // 1 lb = 453.59237 g (exact, by definition) // 1 oz = 28.349523125 g // 1 stone = 6350.29318 g (14 lbs) // 1 troy oz = 31.1034768 g // 1 carat = 0.2 g (exact) // 1 grain = 0.06479891 g // JavaScript conversion examples: const kgToLbs = (kg) => kg * 2.2046226218; const ozToGrams = (oz) => oz * 28.349523125; const stoneToKg = (st) => st * 6.35029318; console.log(kgToLbs(70)); // 154.324 console.log(ozToGrams(8)); // 226.796 console.log(stoneToKg(11)); // 69.8532 ``` #### FAQ **Q: How many pounds are in a kilogram?** A: One kilogram equals approximately 2.20462 pounds. For quick mental math, multiply kilograms by 2.2 — this gives a result accurate to within 0.2%. For example, 80 kg × 2.2 = 176 lbs (exact: 176.37 lbs). The precise NIST conversion factor is 1 kg = 2.2046226218 lbs. To convert pounds back to kilograms, divide by 2.2046 or multiply by 0.4536. **Q: How many grams are in an ounce?** A: One avoirdupois ounce equals approximately 28.3495 grams. For a quick estimate, use 28.35 grams per ounce. Common reference points: 1 oz = 28.35 g, 4 oz (quarter pound) = 113.4 g, 8 oz (half pound) = 226.8 g, 16 oz (one pound) = 453.6 g. Note that a troy ounce (used for precious metals) is heavier at 31.1035 grams. **Q: How many pounds are in a stone?** A: One stone equals exactly 14 pounds (6.35029 kg). The stone is commonly used in the UK and Ireland for expressing body weight. Common stone-to-pound conversions: 8 stone = 112 lbs, 10 stone = 140 lbs, 12 stone = 168 lbs, 14 stone = 196 lbs, 16 stone = 224 lbs. To convert stone to kilograms, multiply by 6.35029. **Q: What is the difference between a metric ton, a short ton, and a long ton?** A: A metric ton (tonne) = 1,000 kg = 2,204.6 lbs. A US short ton = 2,000 lbs = 907.2 kg. A British long ton = 2,240 lbs = 1,016 kg. The metric ton is used internationally, the short ton is standard in the US, and the long ton is used in British shipping. The differences are significant: a metric ton is about 10% heavier than a short ton, and a long ton is about 1.6% heavier than a metric ton. **Q: What is the difference between mass and weight?** A: Mass is the amount of matter in an object, measured in kilograms. Weight is the force of gravity on that mass, measured in newtons. On Earth's surface, a 1 kg mass has a weight of approximately 9.81 newtons. In everyday use, 'weight' and 'mass' are used interchangeably because we almost always measure on Earth. This converter handles mass units — the values are correct anywhere on Earth's surface, though technically your 'weight' varies slightly with altitude and latitude. **Q: What is the difference between a troy ounce and a regular (avoirdupois) ounce?** A: A troy ounce equals 31.1035 grams, while a standard avoirdupois ounce equals 28.3495 grams — the troy ounce is about 9.7% heavier. Troy ounces are exclusively used for precious metals (gold, silver, platinum, palladium). When a news report says 'gold is $2,000 per ounce,' it always means troy ounces. Confusing the two would cause a 9.7% pricing error. **Q: How can I quickly convert kg to lbs in my head?** A: The easiest mental math method: multiply by 2 and add 10%. For example, 75 kg: 75 × 2 = 150, plus 10% (15) = 165 lbs (exact: 165.35 lbs). This gives accuracy within 0.2%. Another approach: multiply by 11 and divide by 5, which gives the exact factor of 2.2. For going from lbs to kg, divide by 2.2 — or halve the number and subtract 10%. **Q: How accurate is this weight converter?** A: This converter uses the exact conversion factors defined by NIST (National Institute of Standards and Technology). All calculations use IEEE 754 double-precision floating-point arithmetic, providing at least 15 significant digits of precision. For everyday conversions, scientific calculations, and commercial transactions, the results are far more precise than any physical scale. The tool runs entirely in your browser with no rounding shortcuts. **Q: Is my data safe when using this weight converter?** A: Yes, completely. All conversions are performed locally in your browser using JavaScript. No data is sent to any server — there are no network requests, no cookies, and no analytics on your input. The conversion logic runs entirely on your device, meaning your values never leave your browser. You can verify this by disconnecting from the internet and using the tool — it works fully offline once the page has loaded. **Q: How many ounces are in a pound?** A: One pound contains exactly 16 avoirdupois ounces. This is an exact definition, not an approximation. Useful fractional references: 1/4 lb = 4 oz, 1/2 lb = 8 oz, 3/4 lb = 12 oz. Note that a troy pound (rarely used) contains only 12 troy ounces. When someone says 'pound' and 'ounce' without qualification, they mean the avoirdupois system used in everyday commerce. **Q: How many milligrams are in a gram?** A: 1 g = 1,000 mg exactly. This is a standard metric decimal relationship. Common references: one aspirin tablet = 325 mg, one vitamin C tablet = 500 mg, one caffeine capsule = 200 mg. The mg→g conversion is most frequently used in pharmaceutical dosages, nutrition labels, and laboratory measurements. **Q: How many grams are in a pound?** A: 1 lb = 453.59237 g (exact NIST definition). For quick estimates, use 454 g. Common references: 1/4 lb = 113.4 g (quarter-pounder burger patty), 1/2 lb = 226.8 g, 1 lb butter = 454 g (4 sticks). This conversion comes up frequently when adapting recipes between US and metric measurements. See also our volume converter for liquid ingredient conversions. **Q: What is a grain and where is it used?** A: 1 grain = 64.79891 mg = 0.0648 g. The grain is one of the oldest weight units still in active use, surviving in two specialized fields: pharmacy (aspirin was historically dosed in grains — 5 gr = 325 mg) and ballistics (bullet weights are measured in grains, e.g. a 9mm round = 115–147 gr). Try converting grains using the Specialty unit group above. **Q: How do I convert between 斤 (jīn) and kilograms?** A: 1 斤 (jīn, also called "catty") = 500 g = 0.5 kg. This is the most common everyday weight unit in mainland China, used for groceries, produce, and casual body weight references. 1 kg = 2 斤. Note: Southeast Asian catties differ (Malaysia: 1 catty = 604.79 g). This tool uses international standard units; for 斤 conversions, simply multiply the kg result by 2. **Q: What do common everyday objects weigh?** A: Weight reference points for building intuition: a paper clip ≈ 1 g, a US quarter coin ≈ 5.67 g, a medium egg ≈ 50 g, a smartphone ≈ 172 g, a liter of water = 1,000 g (1 kg), a bowling ball ≈ 6 kg (13 lbs), an average adult ≈ 70 kg (154 lbs). These anchors help you estimate whether a conversion result is reasonable. For size comparisons, our length converter covers 16 units of distance. --- ### Free Word Counter & Character Count Tool URL: https://go-tools.org/tools/word-counter Count words, characters, sentences, paragraphs, and reading time instantly. Real-time word counter with Twitter, meta description, and Instagram limit checks. Free, private, no signup. #### What Is a Word Counter? A word counter is a tool that takes a block of text and reports the metrics writers, editors, and publishers care about: how many words it contains, how many characters (with and without spaces), how many sentences and paragraphs, and how long it would take to read aloud or silently. Word counters are older than personal computers — typewriter editors counted by hand and later by typewriter-attached digital counters — but the browser-based, real-time word counter is the form most writers use today. The core unit, the word, sounds simple but isn't. English word counters tokenize on whitespace and hyphens-as-internal-punctuation: "don't" is one word, "state-of-the-art" is one word, "twenty-five" is one word. Numbers are usually counted as words (so "42" is one word) but stop-word filters in NLP contexts may exclude them from analysis. For Chinese, Japanese, and Korean text, the convention is one word per ideograph — a 500-character Chinese essay is a 500-word Chinese essay — because CJK doesn't use word-spaces and the smallest semantic unit is the character. Microsoft Word, Google Docs, native Chinese word-processors, and every serious bilingual counter follow this rule, and so does this tool. Beyond raw counts, modern word counters compute reading time and speaking time. The standard reading rate is 230 words per minute, the median silent-reading speed measured across decades of academic research on native-English readers (Brysbaert's 2019 meta-analysis). The standard speaking rate is 130 wpm, the rate that conference speakers, voiceover artists, and TED talks converge on — slow enough to be heard clearly, fast enough to feel natural. Blog platforms, news sites, and content management systems use these rates to display "x-minute read" indicators that set reader expectations. For digital publishing, character ceilings now matter more than word counts in many contexts. Twitter/X posts are capped at 280 characters. Google's meta description displays roughly 150-160 characters before truncation on desktop, fewer on mobile. Page title tags clip at about 60 characters in search results. SMS messages bill per 160-character segment. Instagram captions cap at 2,200 characters. A word counter that doesn't show these limits leaves you eyeballing the line — this one displays a live progress bar against each limit so you know when you've crossed a ceiling that affects display, deliverability, or ranking. Under the hood, a word counter is a few hundred lines of regex and string handling. The interesting engineering is in the edge cases: mixed CJK and Latin text, Unicode code-point counting versus UTF-16 code unit counting (emoji are surrogate pairs and would otherwise count as two), apostrophes inside contractions versus surrounding quoted speech, em-dash separators versus em-dash compounds, sentence terminators inside abbreviations. This tool's counting follows the conventions of Microsoft Word and Google Docs because those are the editors most people receiving your text will use to verify the count — agreement matters more than philosophical purity. All computation runs entirely in your browser — no text leaves the page, no signup is required, no logging. This matches the privacy expectations of journalists with source notes, lawyers with client drafts, marketers with unannounced campaigns, and anyone else who treats their work in progress as confidential. To go deeper into related text tooling, the Base64 encoder handles binary-to-text encoding, the URL encoder handles URL-safe text, and the MD5 hash generator handles fingerprinting — together they cover most non-format text manipulation a developer or content worker needs. ``` // What's actually being counted (simplified) function countWords(text) { // CJK: each ideograph is one word const cjk = text.match(/[\u4E00-\u9FFF\u3040-\u30FF\uAC00-\uD7AF]/g) || []; // Latin: word = letters/digits with optional internal hyphen or apostrophe const latin = text .replace(/[\u4E00-\u9FFF\u3040-\u30FF\uAC00-\uD7AF]/g, ' ') .match(/[A-Za-z0-9]+(?:[''-][A-Za-z0-9]+)*/g) || []; return cjk.length + latin.length; } // Reading time at 230 wpm function readingMinutes(words) { return Math.round((words / 230) * 60); // seconds } // Twitter limit check — raw character ceiling 280 function underTwitterLimit(text) { return [...text].length <= 280; // Unicode code points, not UTF-16 units } ``` #### FAQ **Q: What does this word counter do?** A: It counts every metric writers, editors, marketers, students, and developers care about — words, characters with and without spaces, sentences, paragraphs, and lines — in real time as you type or paste. It also computes reading time (230 wpm), speaking time (130 wpm), and compares your text against the character limits of Twitter/X, Instagram, LinkedIn, SMS, page title tags, and meta descriptions. A secondary analysis panel shows the top ten most frequent meaningful words (English stop-words filtered out), the longest single word, the average word length, and the average sentence length — the metrics that actually help you tighten prose. Everything runs 100% in your browser using JavaScript: your text is never uploaded, never logged, never stored, and no signup is required. **Q: How accurate is the word count compared to Microsoft Word and Google Docs?** A: The Latin word count matches Microsoft Word and Google Docs in the overwhelming majority of cases. All three tools tokenize on whitespace, treat hyphenated compounds (e.g., "state-of-the-art") as a single word, and split contractions ("don't" = 1 word). For mixed English-CJK text, this counter follows the same convention as Microsoft Word's Chinese/Japanese mode: each CJK ideograph counts as one word, and Latin tokens count individually. The handful of edge cases where counters diverge — em-dash compounds, em-dashes used as commas, numbers with decimals — affect <0.1% of typical text and produce off-by-one differences at most. For billable counts, copy your text into both this tool and your editor of choice once to confirm; you'll see the numbers agree. **Q: Is my text uploaded or stored anywhere?** A: No. All counting and analysis runs 100% client-side in your browser using JavaScript. Your text is never transmitted, never stored on any server, never logged, and never analyzed by humans or AI. This makes the tool safe for drafts containing client information, unannounced product names, internal memos, legal documents, journalist source notes, and any other confidential material. You can verify this in your browser's Network tab — typing in the textarea triggers zero network requests. The tool uses no cookies for the input text and no third-party analytics that would capture what you type. **Q: How is reading time calculated?** A: Reading time uses the adult silent-reading average of 230 words per minute, which is the median rate measured across 17 academic studies of native-English silent reading (Brysbaert 2019 meta-analysis). This is the rate most blog platforms, news sites, and "x-minute read" indicators converge on. Speaking time uses 130 words per minute, the standard rate for clear conference talks, voiceover, and TED-style delivery — slow enough for audiences to absorb, fast enough to feel natural. Both rates are conservative defaults: skim reading runs 400+ wpm, audiobook narration 150-180 wpm, and rapid auctioneer-style speech 250+ wpm. For specialized contexts, treat the displayed time as a baseline and adjust mentally. **Q: Does this work for Chinese, Japanese, Korean, and Arabic?** A: Yes. The counter handles all major scripts natively. For Chinese, Japanese, and Korean (CJK), each ideograph counts as one word — the same convention used by Microsoft Word, Google Docs, and native CJK word-processors. So a 500-character Chinese essay shows as 500 words, matching what your Chinese teacher or editor expects. For Arabic and other RTL scripts, the counter measures by whitespace-separated tokens and renders results respecting your input direction. Mixed-script text (English + Chinese, Arabic + English) is counted using both rules: CJK characters and Latin tokens are tallied separately and combined. The character count is always Unicode-code-point accurate — emoji, combining marks, and surrogate-pair characters each count once, not twice. **Q: Why is the Twitter limit 280 characters and how does this tool handle URLs?** A: Twitter/X enforces a 280-character ceiling on tweet text (doubled from the original 140 in 2017). This tool counts the raw character length of your text — what you'd see if you pasted into Twitter's compose box. Twitter automatically shortens URLs to a fixed 23 characters at publish time regardless of original length, so a 100-character URL in your draft still costs 23 once published. To check what Twitter will count, treat any URL as 23 characters: if your draft has one URL, the published length is (your raw count) − (URL length) + 23. The counter displays your raw count; the math for URL substitution is a manual step. For Twitter premium long-form posts (up to 25,000 characters), the 280-cap doesn't apply. **Q: What's the right meta description length for Google?** A: Aim for 150-160 characters. Google's meta description display on desktop typically truncates between 155 and 165 characters depending on the title and URL length of the snippet, with mobile clipping a few chars earlier. Below 120 characters Google often substitutes a longer auto-generated description from the page body — wasting your hand-crafted message. The sweet spot is 150-160: enough to use Google's full display width, short enough to survive mobile truncation. This counter's meta-description progress bar turns green between 120-160, amber 161-200, and red above 200. Note that Google sometimes truncates earlier than 155 if it can fit a better excerpt; the only way to fully control your snippet is to hand-write it under 155 chars. **Q: What counts as a sentence?** A: This counter treats a sentence as a run of text ending in a sentence terminator: period (.), question mark (?), exclamation mark (!), Chinese full stop (。), Japanese full stop (。), or Arabic question mark (؟). Multiple terminators in a row ("What?!") count as one sentence boundary. Sentences without a final terminator ("Hello world" with no period) still count as one. The counter intentionally doesn't apply heuristics for "Mr." or "U.S.A." — the rare false positives are noisier than the false negatives, and most published prose uses different abbreviation conventions. For a strict linguistic parse, use a dedicated NLP library; for everyday writing, the terminator-based count matches what teachers, editors, and word-processors report. **Q: How do I count words in a Word document or PDF?** A: Open the file, copy all the text (Ctrl/Cmd-A then Ctrl/Cmd-C), and paste into the counter's textarea above. The count appears instantly. For PDFs with multi-column layouts or table-heavy content, the copy may include extra spaces or interleaved column text — review the pasted text for obvious artifacts before trusting the count. For documents you can't open in a browser (scanned PDFs, image-only files), use OCR first (Adobe Acrobat, Google Drive OCR, or any free OCR tool), then paste the extracted text. This tool intentionally doesn't accept file uploads: keeping everything in the browser means your sensitive documents never leave your device. **Q: Why are some words excluded from the top frequency list?** A: The top frequency analysis filters out English stop-words — common function words like "the", "and", "is", "of", "that" that dominate any text but carry no editorial signal. The filtered list is the universal stop-word set used by most search engines and NLP libraries. The frequency analysis also skips words shorter than 2 characters and pure numbers. For non-English text, no stop-word filter is applied — you'll see the raw frequencies, which is still useful for catching repetition in any language. To get the unfiltered top words, mentally include the obvious function words from your language; the editorial value is in what comes after them. **Q: Does the counter support markdown or HTML?** A: The counter treats markdown and HTML as plain text — markdown syntax (`**bold**`, `[link](url)`, `# heading`) and HTML tags (`

      `, ``) are counted as characters and tokenized as words. If you want to count just the rendered text (excluding markdown syntax or HTML tags), preview your markdown in a renderer (or open the HTML in a browser) and copy the rendered output before pasting. For most prose-with-light-formatting, the raw count is within 5% of the rendered count and good enough for tracking. For HTML-heavy content with lots of tags, the difference becomes significant — render first, then count. **Q: Is there a daily word count goal I should hit?** A: Nonfiction writers and bloggers typically aim for 500-1,500 words per day; novelists chasing NaNoWriMo target 1,667 words per day for a 50,000-word manuscript in November. Academic writers commonly set 250-500 words per day during dissertation phases — the slow-but-sustainable pace. Twitter threads, marketing copy, and shorter formats track total characters instead. The right target is the one you can sustain seven days a week without burning out; consistency beats peak velocity. This counter shows reading time alongside word count so you can also target by audience time: a 4-minute read is roughly 1,000 words, a 10-minute read is roughly 2,500. **Q: Why might my count differ from another online word counter?** A: Different counters handle edge cases differently: (1) Hyphenated compounds — most counters (including this one and Microsoft Word) treat "state-of-the-art" as one word; a minority split on hyphens and count four. (2) Em-dash separators — some counters split words on em-dashes, this one does not. (3) Numbers — some skip them entirely, this one counts them as words. (4) CJK text — many Western counters split CJK on whitespace only and undercount by 100x; this counter follows the Microsoft Word convention of one word per CJK character. (5) URLs — some counters strip URLs before counting, this one includes them as one token each. The Word/Google Docs convention is the de facto standard and what this tool follows; if a counter disagrees with both Word AND Google Docs, it's the outlier. --- ### XML Formatter URL: https://go-tools.org/tools/xml-formatter Beautify, minify, and validate XML in-browser — nothing uploaded. Re-indents messy XML, reports well-formedness errors with line/column. Free, private, no signup. #### What is an XML Formatter and Why Use One? XML (Extensible Markup Language) is a text-based format for structured data, used everywhere from web services and configuration files to RSS feeds, SOAP APIs, office documents (DOCX, XLSX), SVG graphics, and Android layout files. Unlike JSON, XML supports comments, namespaces, mixed content (text and elements interleaved), and document-type declarations — making it the format of choice for enterprise integration, document exchange, and anywhere structured data needs to coexist with human-readable markup. XML in the wild is often badly indented or completely minified — API responses arrive on a single line, config files accumulate inconsistent indentation, and generated XML from serializers adds no whitespace at all. An XML formatter takes that messy input and re-indents it into a clean, hierarchical structure where every level of nesting is visually clear. This is essential for code review, debugging, documentation, and understanding unfamiliar XML schemas. **What this tool does differently from a plain text editor:** **1. Well-formedness validation with precise error location.** The formatter uses the browser's DOMParser (the same engine that parses HTML and SVG) to parse the XML. If the document is not well-formed — mismatched tags, unclosed elements, unescaped characters, or multiple root elements — the parser reports the exact line and column number where it failed. This is far faster than reading raw XML looking for where a tag was accidentally left open. **2. Lossless formatting.** The formatter preserves comments, processing instructions, CDATA sections, namespace declarations, attribute order, and all text content exactly. It only adjusts the whitespace between element tags. You can safely format any XML you care about — nothing meaningful will change. **3. Minification for production use.** The minifier strips all insignificant inter-element whitespace, producing the smallest valid XML representation. This is the right preprocessing step before storing XML in a database column, inserting it as a string into JSON, or transmitting it over a byte-counted channel. **4. 100% browser-based privacy.** Healthcare XML (HL7, FHIR), financial data, SOAP payloads with credentials, and internal configuration files are all common XML payloads that contain sensitive data. This tool never uploads anything — all processing runs in your browser's JavaScript engine. See our companion tools if you need to convert rather than format: XML to JSON Converter for converting XML to JSON, and JSON to XML Converter for the reverse direction. ``` Wireless Headphones79.99 Wireless Headphones 79.99 ``` #### FAQ **Q: Is my XML data sent to a server when I use this tool?** A: No. All formatting, minification, and validation happens entirely inside your browser using JavaScript. Your XML is never transmitted over the network, never stored on any server, and never logged or analyzed by anyone. This makes the tool safe to use with XML payloads that contain API credentials, internal service data, financial records, healthcare HL7/FHIR documents, or any other sensitive content. You can confirm this by opening your browser's Network tab — you will see zero requests triggered by pasting or processing XML. **Q: What does the Validate button check?** A: The Validate button checks whether your XML is well-formed according to the XML 1.0 specification. Well-formedness means: every opening tag has a matching closing tag, tags are properly nested (no overlapping elements), the document has exactly one root element, attribute values are quoted, and reserved characters (&, <, >) are properly escaped as &, <, and >. When the XML is well-formed, a green 'Valid XML' banner appears. When it is not, an error message appears with the line number and column number where the problem was found, so you can locate and fix the issue immediately. **Q: Does validation check against an XML Schema (XSD) or DTD?** A: No. This tool checks well-formedness only — it does not validate against an XSD schema, a DTD, a RELAX NG schema, or any other grammar. Well-formedness is a precondition for schema validation, but they are different levels of correctness. An XML document can be perfectly well-formed but still violate a schema (for example, a required element is missing, or a numeric field contains a string). For full schema validation you need a tool like xmllint (command-line), Oxygen XML Editor, or your programming language's XML parser with schema support enabled. **Q: What is the difference between Format (Beautify) and Minify?** A: Format (Beautify) adds consistent indentation and newlines to make XML human-readable. Each nested element is indented by the selected number of spaces (2 or 4), so the hierarchical structure is immediately visible. This is what you want when reading, editing, or diffing XML. Minify does the opposite: it removes all whitespace that is not part of element text content, collapsing the document to the minimum number of characters. This is useful before storing XML in a database, transmitting it over a network where byte count matters, or embedding it as a string in another format. Both operations produce semantically identical XML — only the non-significant whitespace changes. **Q: Does formatting preserve XML comments and attributes?** A: Yes. Formatting is completely lossless with respect to XML content. Comments () are preserved in place with their surrounding whitespace adjusted for indentation. All attributes, their order within a tag, and their values are preserved exactly. CDATA sections, processing instructions (), and namespace declarations are also preserved. The formatter only adjusts the whitespace between element tags — it never modifies element names, attribute names, attribute values, text content, or comments. **Q: How do I format XML with 4-space indentation instead of 2?** A: Click the '4 spaces' radio button in the Indent control (next to the Format, Minify, and Validate buttons), then click Format. The output panel will re-render the XML with 4-space indentation. You can switch between 2 and 4 spaces at any time and click Format again — the indentation size is read at the time you click the button. Two-space indentation is the most common convention in web services and data exchange formats; four-space indentation is sometimes preferred in enterprise XML schemas and SOAP-heavy environments. **Q: What XML version and encoding does this tool support?** A: The formatter uses the browser's built-in DOMParser with the text/xml MIME type, which supports XML 1.0 documents in any character encoding that the browser can handle — in practice, UTF-8, UTF-16, and ISO-8859-1 cover virtually all real-world XML. The XML declaration () is preserved if present. For XML 1.1 documents (rare in practice, mainly used for Unicode control characters), the same parser applies but some XML 1.1-specific features may not be fully enforced. **Q: What causes a 'well-formedness' error and how do I fix it?** A: The most common well-formedness errors are: (1) Mismatched tags — an opening tag like with a closing tag like (note the plural). Fix: match tag names exactly, including case. (2) Unclosed tags — a tag that never has a corresponding close tag or self-closing slash. Fix: add the closing tag or change to . (3) Unescaped special characters — using & directly in text content instead of &, or < instead of <. Fix: replace bare & with & and bare < with < outside CDATA sections. (4) Multiple root elements — XML requires exactly one root element wrapping everything else. Fix: wrap all content in a single root tag. The error message from this tool includes the line and column number of the first problem found. **Q: Can I use this tool to format XHTML or SVG files?** A: Yes. XHTML and SVG are both valid XML applications, so this tool formats, minifies, and validates them correctly. For XHTML, it will catch mismatched or unclosed tags that would be silently ignored in HTML5 parsers but are errors in strict XHTML. For SVG, it is particularly useful for formatting complex path-heavy files generated by tools like Figma or Illustrator, making it easier to inspect or edit the element structure manually. **Q: How does this tool handle XML namespaces?** A: XML namespaces (xmlns declarations, namespace prefixes like soap:, xsi:, and so on) are fully preserved by the formatter. The namespace declarations remain on the element where they were originally declared and are not moved or deduplicated. Namespace-prefixed element names and attribute names are treated as opaque strings by the formatter — the prefix and local name are preserved exactly as written. The SOAP Envelope example above demonstrates a document with three namespace prefixes. **Q: Is there a file size limit for XML input?** A: There is no hard size limit enforced by the tool, but the browser's DOM-based parser will consume memory proportional to the document size. For most real-world XML files (configuration files, API responses, RSS feeds, SOAP payloads) well under 1MB, performance is instant. For very large XML files — multi-megabyte data exports or log files — consider using a command-line tool instead: xmllint --format input.xml on Linux/macOS (part of libxml2), or python3 -c "import xml.dom.minidom; print(xml.dom.minidom.parse('input.xml').toprettyxml(indent=' '))" as a cross-platform option. **Q: How do I convert XML to JSON or JSON to XML?** A: This tool focuses on formatting and validating XML structure. To convert between XML and JSON, use the companion tools: XML to JSON Converter converts XML documents to their JSON representation, and JSON to XML Converter converts JSON objects to XML. Both tools are also 100% browser-based with no data upload. --- ### XML to JSON Converter URL: https://go-tools.org/tools/xml-to-json Paste XML, get JSON instantly. Converts attributes to @_ keys, handles repeated elements as arrays. 100% in-browser, nothing uploaded, no signup. #### What is XML-to-JSON Conversion and How Does It Work? XML (Extensible Markup Language) and JSON (JavaScript Object Notation) are both structured data formats, but they have fundamentally different models: XML is a tree of elements with attributes and mixed content (text interleaved with child elements); JSON is a tree of objects, arrays, strings, numbers, booleans, and null values. Converting between them requires a set of conventions to bridge the mismatch. This tool uses the most widely adopted convention, the same one used by popular libraries like fast-xml-parser (Node.js), xmltodict (Python), and JAXB (Java): **1. Attributes → @_ prefix.** XML attributes have no direct JSON equivalent. The convention is to represent them as keys prefixed with @_. So becomes { "@_id": "42", "@_role": "admin" } inside the user object. This prefix is unambiguous: no valid XML element name starts with @, so there is no collision with child element names. **2. Element text content with attributes → #text.** When an element has both attributes and text content — 29.99 — the text must share the same JSON object as the attributes. The convention is to store it under the key #text, producing { "@_currency": "USD", "#text": "29.99" }. Elements with only text content and no attributes convert to a plain string value. **3. Repeated sibling elements → arrays.** XML allows multiple child elements with the same name; JSON objects cannot have duplicate keys. The solution is to collect same-named siblings into an array. One child becomes a single object; two or more children become an array of objects. This is the most important behavioral detail to understand: the JSON shape changes based on how many siblings exist in the XML. **4. No type coercion — all values stay strings.** XML has no native type system for text content. A value of "123" in XML is a string. Converting it to the JSON number 123 requires making an assumption about the author's intent — an assumption that is wrong for ZIP codes ("01234" → 1234), phone numbers, padded identifiers, and precision-sensitive decimal strings. This tool preserves all values as strings. Apply type coercion in your own code for the fields where you know the type. **5. Lossy for comments, processing instructions, and namespaces.** XML supports features that JSON does not: comments (), processing instructions (), and namespace semantics. These are discarded or approximated during conversion. For lossless XML work — reformatting, minifying, validating — use the XML Formatter instead. For the reverse conversion — building XML from JSON — use the JSON to XML Converter. **Why convert XML to JSON at all?** JSON is the native format of JavaScript and the default interchange format for REST APIs. If you receive XML from a legacy SOAP service, an RSS feed, a sitemap, or an enterprise system, converting it to JSON lets you work with the data using standard JavaScript object access, JSON path queries, and any JSON-aware database or API. The conversion is a one-way bridge: useful for consuming XML data in a modern stack, but not for preserving or round-tripping XML documents. ``` // Convert XML to JSON in Node.js using fast-xml-parser import { XMLParser } from 'fast-xml-parser'; const xml = ` Wireless Headphones 79.99 `; const parser = new XMLParser({ ignoreAttributes: false, // preserve attributes attributeNamePrefix: '@_', // @_ prefix for attributes textNodeName: '#text', // #text for mixed element content parseAttributeValue: false, // no type coercion on attributes parseTagValue: false, // no type coercion on element text }); const result = parser.parse(xml); console.log(JSON.stringify(result, null, 2)); // { // "catalog": { // "product": { // "@_id": "P01", // "name": "Wireless Headphones", // "price": { // "@_currency": "USD", // "#text": "79.99" // } // } // } // } ``` #### FAQ **Q: Is my XML data sent to a server when I use this tool?** A: No. All conversion happens entirely inside your browser using JavaScript. Your XML is never transmitted over the network, never stored on any server, and never logged or analyzed. This makes the tool safe to use with XML payloads containing API credentials, internal service configuration, SOAP WS-Security tokens, healthcare HL7/FHIR data, or any other sensitive content. You can verify this by opening your browser's Network tab — you will see zero requests triggered by pasting or converting XML. **Q: How do XML attributes map in the JSON output?** A: XML attributes become JSON keys prefixed with @_. For example, produces a JSON object containing "@_id": "P01" and "@_category": "electronics" alongside any child element keys. When an element has both attributes and text content — such as 29.99 — the text content is stored under the special key "#text", so the result is { "@_currency": "USD", "#text": "29.99" }. This convention is consistent and predictable: @_ always means attribute, #text always means element text content. **Q: Does the converter coerce numbers or booleans?** A: No. All XML text content and attribute values become JSON strings, regardless of how they look. 42 becomes "count": "42", not 42. true becomes "enabled": "true", not true. This is intentional and important: it preserves leading zeros (phone numbers, account codes, ZIP codes like "01234"), numeric precision for values like "0.100", and string values that happen to look boolean. If you need numbers or booleans in your downstream JSON, apply type coercion in your own code after converting — where you control exactly which fields get coerced. **Q: How are repeated (same-named sibling) elements handled?** A: A single child element becomes a JSON object. Two or more child elements with the same tag name under the same parent become a JSON array. For example, a produces { "root": { "item": "a" } } — item is an object (string). But ab produces { "root": { "item": ["a", "b"] } } — item is an array. This means the structure of your JSON output depends on the number of sibling elements in the XML, which is one reason XML-to-JSON conversion is convention-based. If your XML schema can have either one or many items, your consumer code must handle both the object and array cases. **Q: Is XML-to-JSON conversion lossless?** A: No. XML has features that have no JSON equivalent and are dropped during conversion: XML comments () are discarded, processing instructions () are discarded, namespace prefix bindings are partially preserved as @_ attributes but their semantics are not, and the relative order of mixed-content nodes (text interleaved with child elements) may not round-trip perfectly. For purely structural XML without comments or processing instructions, the conversion preserves all element names, attribute names, attribute values, and text content. For lossless XML work — formatting, validating, or inspecting XML without any data loss — use the XML Formatter instead. **Q: How do I convert JSON back to XML?** A: Use our companion JSON to XML Converter. It applies the same conventions in reverse: @_-prefixed keys become XML attributes, #text keys become element text content, and JSON arrays become repeated same-named sibling elements. This makes the two tools symmetric for round-trip use cases. **Q: What happens to XML namespaces?** A: Namespace declarations (xmlns="..." and xmlns:prefix="...") are treated as regular attributes and appear in the JSON output as @_xmlns and @_xmlns:prefix keys. The namespace prefix in element names is preserved as part of the element name key (e.g., becomes "soap:Body" in the JSON). The semantic meaning of namespaces — that two prefixes might point to the same URI — is not interpreted. If precise namespace handling matters for your use case, parse the XML in a namespace-aware parser rather than converting to JSON. **Q: Why does 0123 become "0123" and not 123?** A: Because the converter performs no type coercion. The string "0123" and the number 123 are different values: "0123" has a leading zero that is meaningful in many contexts (account codes, postal codes, national identification numbers, padded identifiers). Silently dropping that leading zero would corrupt data. The safe default is to preserve all values as strings exactly as they appear in the XML. Apply numeric parsing selectively in your own code for the specific fields where you know the value is always a plain integer. **Q: What is the difference between this tool and an XML formatter?** A: The XML Formatter reformats XML — it changes indentation and whitespace but the output is still XML. This XML-to-JSON Converter changes the format entirely: the output is a JSON document that represents the XML structure using the @_ attribute convention. Use the formatter when you want to read, edit, validate, or minify XML. Use this converter when you need to work with the XML data in a JavaScript application, feed it into a REST API, or store it in a JSON document store. **Q: Is there a file size limit?** A: There is no hard limit, but inputs larger than 200KB automatically switch from live conversion to manual mode. In manual mode, a Convert button appears and conversion runs only when you click it — this keeps the browser responsive while parsing large XML documents. For very large XML files (multi-megabyte data exports), consider command-line tools for better performance: python3 -c "import sys, xmltodict, json; print(json.dumps(xmltodict.parse(sys.stdin.read()), indent=2))" or node -e with a dedicated XML-to-JSON library. **Q: Does the converter handle CDATA sections?** A: Yes. CDATA section content () is treated as element text content and appears as a plain string value in the JSON output. The CDATA delimiters themselves are stripped — only the content inside is preserved. For example, produces "note": "if (a < b) return;" in JSON. This is the correct behavior: CDATA is just a way to embed text with special characters without escaping them; the semantic meaning is the text content. **Q: Can I convert XML with multiple root elements?** A: No. XML with multiple root elements is not well-formed, and this tool requires well-formed XML input. If your XML parser gives you multiple root elements (common when stitching together XML fragments), wrap them in a single root element before converting. For example, if you have , convert it as . The error message will indicate the position of the well-formedness problem so you can fix it quickly. --- ### YAML to JSON Converter URL: https://go-tools.org/tools/yaml-to-json Paste YAML, get JSON instantly. Live conversion in your browser. K8s manifests, OpenAPI specs, helm values supported. 100% private, no upload. #### What is JSON and Why Convert from YAML? JSON (JavaScript Object Notation) is a lightweight, text-based data interchange format standardized as RFC 8259 and ECMA-404. It supports six data types — strings, numbers, booleans, null, arrays, and objects — with a strict, minimal syntax that virtually every programming language, API, and toolchain can parse natively. While YAML is the preferred format for human-written configuration files (Kubernetes manifests, GitHub Actions, Ansible playbooks, Helm values), JSON is the universal machine-readable format for APIs, automation scripts, and programmatic data processing. Converting YAML to JSON is therefore one of the most common tasks in DevOps and backend development — you have a YAML config file but need JSON to feed into a REST API, query with jq, or process with JavaScript tooling. This tool has four important differentiators compared to typical online converters: **1. Multi-Document YAML Handling.** YAML supports multiple documents in a single stream separated by --- (the document start marker). Many real-world YAML files — including some Kubernetes manifests and Ansible playbooks — contain multiple documents. This tool uses parseAllDocuments from the eemeli/yaml library with { version: '1.2', merge: true } options and returns the first document as JSON, clearly communicating what was taken. If you need all documents, split on --- and convert each individually. **2. Anchor and Alias Expansion.** YAML anchors (&name) and aliases (*name) allow reuse of data blocks — a powerful YAML feature with no JSON equivalent. This tool fully expands all anchors and aliases (including merge keys: <<: *anchor) so the JSON output contains complete, self-contained data without any references. This is always the correct transformation because JSON has no reference syntax. The expansion is handled safely by the eemeli/yaml library, which includes protection against circular references. Learn how this compares to the reverse direction at JSON to YAML Converter. **3. Comment Loss — Educational Transparency.** YAML supports # comments, which are frequently used in Kubernetes manifests, Helm values, and Ansible playbooks to document intent. JSON has no comment syntax, so comments are permanently dropped during conversion. This is not a bug — it is a fundamental format difference. This tool makes this explicit so you know what to expect. If you need to preserve annotations, encode them as JSON fields (_comment keys or a dedicated metadata object) before converting, or keep YAML as the authoritative source. See our deep dive on YAML-JSON differences for more on format tradeoffs. **4. 100% Browser-Based Privacy.** Your YAML data — which often contains Kubernetes secrets, database credentials, Helm values with passwords, and internal service configurations — never leaves your browser. No data is sent to any server. You can verify this in your browser's Network tab. After converting to JSON, you can validate and format the result with our JSON Formatter before using it downstream. YAML's richness (comments, anchors, multi-document support, block scalars) makes it excellent for human-authored configuration files where readability and documentation matter. JSON's strictness and universality make it the better choice when a machine is the primary consumer. This converter bridges the two worlds: keep your configuration in YAML for human maintainability, convert to JSON when you need machine-readable interchange. Need to compare two JSON documents and find what changed? Try our JSON Diff. ``` // Convert YAML to JSON in Node.js using the eemeli/yaml library import { parseAllDocuments } from 'yaml'; const yamlString = `apiVersion: apps/v1 kind: Deployment`; // parseAllDocuments handles multi-document YAML (--- separator) // version: '1.2' ensures yes/no are strings, not booleans // merge: true expands anchor/alias merge keys (<<: *anchor) const docs = parseAllDocuments(yamlString, { version: '1.2', merge: true }); // Take the first document (skip additional --- blocks) const json = JSON.stringify(docs[0].toJSON(), null, 2); console.log(json); // { // "apiVersion": "apps/v1", // "kind": "Deployment" // } ``` #### FAQ **Q: How do I convert YAML to JSON online?** A: Paste your YAML into the input field above. The tool converts it to JSON instantly in your browser — no button click needed. You can adjust the output indentation (2 or 4 spaces) from the Options panel. Once the JSON appears in the output area, click Copy to grab it to your clipboard or Download to save it as a .json file. Everything runs locally — your data never leaves your device. **Q: How does this tool handle multi-document YAML (--- separator)?** A: YAML supports multiple documents in a single stream, separated by --- (the document start marker). When you paste a multi-document YAML string, this tool uses parseAllDocuments from the eemeli/yaml library and returns the first document as JSON. The additional documents beyond the first are silently ignored. If you need to process all documents, split your YAML on --- and convert each section individually. The tool shows the first document's JSON so you can verify the result. **Q: How are YAML anchors and aliases (&anchor and *alias) handled?** A: YAML anchors (&name) define a reusable block, and aliases (*name) reference it. This tool fully expands all anchors and aliases during parsing, so the output JSON contains the complete, dereferenced data. For example, if a YAML anchor defines a set of resource limits and multiple services alias it with merge keys (<<: *anchor), the JSON output shows every field explicitly inlined for each service. This is the correct behavior for JSON, which has no concept of references. The eemeli/yaml library handles anchor/alias expansion safely, including circular reference detection. **Q: Are YAML comments preserved in the JSON output?** A: No. JSON does not support comments of any kind — no #, //, or /* */ syntax. When you convert YAML to JSON, all comments are permanently lost. This is a fundamental format difference, not a limitation of this tool. If you need to preserve annotations, consider encoding them as a dedicated JSON field (such as a _comment key) before converting, or keeping the YAML source as the authoritative version with comments. This tool clearly reflects the data as JSON without any comment approximation, which is the correct and standard behavior. **Q: How do I use this tool with a Kubernetes manifest?** A: Paste your Kubernetes YAML manifest (from a .yaml file, kubectl get -o yaml output, or a Helm template) into the input field. The JSON output can then be queried with jq, sent directly to the Kubernetes REST API, used in Terraform data sources, or processed by any tooling that expects JSON. A common workflow is to convert YAML manifests to JSON to extract specific fields — for example: jq '.spec.replicas' on the JSON output to verify replica counts across deployments. The K8s Deployment example above shows a complete manifest you can load and modify. **Q: How does this tool help with Docker Compose files?** A: Docker Compose files are YAML by convention. Converting them to JSON lets you process service definitions with JavaScript tooling, jq scripts, or any system that reads JSON. Common use cases include extracting all image names to build a dependency list, generating reports from a compose file, or feeding Compose configurations into CI/CD orchestration tools that accept JSON. Paste your compose.yaml into the input and the JSON output is immediately ready for downstream processing. **Q: What is the difference between YAML 1.1 and YAML 1.2, and which does this tool use?** A: YAML 1.1 (the older spec, still used by PyYAML, Ansible, Ruby Psych, and many Kubernetes tools) treats bare strings like yes, no, on, off, y, and n as boolean true/false values. This caused the infamous Norway Problem where the ISO country code 'NO' was parsed as false. YAML 1.2 (the current spec, released in 2009) fixed this: all bare strings are strings, and only true/false are boolean. This tool uses the YAML 1.2 schema for parsing, meaning yes and no in your YAML input are preserved as the string values 'yes' and 'no' in the JSON output — not boolean true and false. This is the correct, modern behavior. If your YAML was originally authored for a YAML 1.1 parser and relied on yes/no as booleans, be aware the JSON output will treat them as strings. **Q: Why does YAML forbid tab indentation?** A: The YAML specification explicitly forbids tab characters (\t) for indentation — only spaces are allowed. This is a deliberate design decision to avoid the ambiguity caused by inconsistent tab width across editors. If your YAML uses tabs for indentation (common when copying from text editors that auto-convert spaces to tabs), the YAML parser will throw a parse error. The fix is to replace all tab indentation with spaces. Most code editors have a setting to convert tabs to spaces (for example, 'Expand Tabs' in Vim, 'Insert Spaces' in VS Code). If you paste YAML and see a parse error mentioning 'tab' or 'indentation', this is almost always the cause. **Q: Can large numbers lose precision when converting YAML to JSON?** A: Yes. This is a fundamental JavaScript limitation that affects all browser-based tools. JavaScript's IEEE 754 double-precision float can only represent integers exactly up to 2^53 - 1 (9007199254740991). YAML numbers larger than this — such as Kubernetes int64 fields like resourceVersion — will be silently rounded when the YAML parser hands them to JavaScript's number type. For example, the YAML value 9007199254740993 becomes 9007199254740992 in the JSON output. The safe workaround is to quote large numbers in your YAML source (resourceVersion: '9007199254740993') so the parser treats them as strings, which are then preserved exactly in JSON as string values. **Q: How can I convert YAML to JSON on the command line?** A: The most popular approach uses yq (Mike Farah's version) and jq. Install yq: brew install yq on macOS or download from github.com/mikefarah/yq/releases for Linux. Then run: yq -o json input.yaml to convert a YAML file to JSON, or cat input.yaml | yq -o json - to pipe from stdin. For pretty-printed output: yq -o json input.yaml | jq . — this pipes the JSON through jq for consistent formatting. For a Python one-liner: python3 -c "import sys, json, yaml; print(json.dumps(yaml.safe_load(sys.stdin), indent=2))" < input.yaml. For multi-document YAML with yq: yq -o json '.[0]' input.yaml to extract only the first document as JSON. **Q: Is my YAML data sent to any server when I use this tool?** A: No. All conversion happens entirely in your browser using JavaScript. Your YAML data is never transmitted over the network, never stored on any server, and never logged or analyzed. This makes the tool safe to use with Kubernetes secrets, database credentials, internal Helm values, API keys in config files, and any other sensitive infrastructure configuration. You can verify this by opening your browser's Network tab — you will see zero requests triggered by pasting YAML. **Q: Is there a file size limit for YAML input?** A: There is no hard file size limit, but large inputs (over 200KB) automatically switch from live conversion to manual mode. In manual mode, a Convert button appears and conversion runs only when you click it — this prevents the browser's main thread from blocking on every keystroke. For very large YAML files (multi-megabyte), consider using command-line tools like yq for better performance. The tool efficiently handles typical real-world payloads like full Kubernetes namespace exports, large OpenAPI specs, and multi-service Helm chart values files. ## Blog ### AES Decryption Failed: Key, IV, Mode and Padding Fixes URL: https://go-tools.org/blog/aes-decryption-failed-troubleshooting-guide AES decryption failed? A wrong key throws a padding error, a wrong IV corrupts only block one, and GCM tags move between languages. Debug yours free online. # AES Decryption Failed: Key, IV, Mode and Padding Fixes When AES decryption failed in your logs, the message you got is probably describing the wrong problem. Four unrelated bugs produce nearly identical symptoms, and the most common one, a wrong key, announces itself as a padding error. Ranked by how often each one turns out to be the culprit, starting from a CBC decryption that throws `BadPaddingException`: 1. The key bytes differ between the two sides. This is by a wide margin the most common cause. 2. The key derivation differs. Same passphrase, different KDF or iteration count, so different key bytes. 3. The ciphertext was damaged in transit: truncated, base64 mangled, or round-tripped through a text encoding. 4. The IV is wrong. This one is real, but it does *not* throw a padding error. It corrupts sixteen bytes and raises nothing. The ordering is structural. CBC checks padding as the last step of decryption, after the key has been applied and the chain unwound, so padding is a checksum on everything upstream and it fails loudly no matter which upstream thing broke. You can also skip straight to the bisection in section 9 and paste your ciphertext into the [AES decrypt tool](/tools/aes-decrypt). Everything below was measured on `java 1.8.0_162`, `node v25.8.2` and `openssl 3.6.2`. Defaults move between versions, so treat the version numbers as part of the result. ## 1. Start with what your error actually rules out An AES failure message says almost nothing about the cause and a lot about what the cause cannot be. That makes it good for deleting branches even though it will never hand you the answer. | What you see | What it rules out | What is still live | |---|---|---| | `BadPaddingException`, `bad decrypt`, `wrong final block length` | GCM; a pure IV mistake; a decode failure | wrong key, wrong KDF, truncated ciphertext, IV bytes eaten as ciphertext, mode mismatch, padding scheme mismatch | | GCM `Authentication failed`, `Unsupported state or unable to authenticate data` | padding; any theory involving partial output | wrong key, wrong nonce, detached or misplaced tag, wrong tag length, mismatched AAD | | No exception, output is garbage | every authenticated mode | ECB, CTR, CBC that got lucky, mode mismatch, wrong IV | ### `BadPaddingException`, `bad decrypt`, `wrong final block length` These three are the same event in three ecosystems: Java, OpenSSL and .NET. It fires at the end of CBC or ECB decryption, when the last plaintext block does not end in a valid PKCS#7 pattern. The useful part is the negative: getting this far means your base64 or hex decoded and the byte count was a nonzero multiple of 16, so the transport did not shred the data and you are not in GCM. `wrong final block length` is the exception. There the count was *not* a multiple of 16, which points at truncation rather than the key, so jump to section 8. ### GCM `Authentication failed` and friends GCM compares the tag before releasing a single byte of plaintext, as NIST SP 800-38D requires. That makes it honest in a way the padding error is not: something in the tuple (key, nonce, ciphertext, additional authenticated data, tag) does not match what the encryptor used. It cannot tell you which element, and it never will, because narrowing that down is deliberately outside what the algorithm does. Section 6 covers the element that breaks most often across languages: where the tag sits in the output. ### No error, but the output is garbage This is the dangerous outcome, because a dashboard records it as success. CTR never throws and ECB never throws. CBC throws only when the final byte pattern fails the padding check, and with a wrong key that byte is effectively random, so roughly one attempt in 256 lands on `0x01` and validates. A little under 0.4% of wrong-key CBC decryptions "succeed". Garbage has a shape, though, and the shape names the bug: sections 4 and 5 have the two fingerprints worth memorising. ## 2. The most misleading error in AES One measurement reorders most people's debugging priorities. Key `0123456789abcdef`, all-zero IV, `AES/CBC/PKCS5Padding`, plaintext `hello world`, on `java 1.8.0_162` with the JDK's built-in SunJCE provider: | Scenario | Change | Measured result | |---|---|---| | A | Key wrong by 1 byte (last character `f` → `X`) | throws `javax.crypto.BadPaddingException: Given final block not properly padded`. The padding itself was never malformed; the error is misleading | | B | Key correct, IV wrong by 1 byte | no exception, plaintext `hello world` came back as `iello world`. Only the corresponding byte of the first block was damaged | | C | Key correct, decrypt the CBC ciphertext with `AES/ECB` | silently succeeded, no exception. A mode mismatch does not have to raise anything | Scenario A misdirects entire afternoons. Scenario C ships bad data to production. ### Why a wrong key produces a padding error Nothing about the padding was wrong. The encryptor appended five `0x05` bytes to bring `hello world` up to sixteen, encrypted that block, and it is sitting in your ciphertext unharmed. The failure happens on the way out. CBC decryption runs the block cipher in reverse, XORs each result with the previous ciphertext block, and only then reads the tail of the final block to decide how many bytes to strip. With the wrong key the cipher produces sixteen bytes of noise, and noise almost never ends in a valid PKCS#7 pattern. The library reports what it saw, bad padding, which is true and useless. Read `BadPaddingException` as "the plaintext I reconstructed does not end the way padded plaintext ends". The most likely reason your reconstruction is wrong is the key, which is why a search for `aes decrypt wrong key` and a search for a bad padding exception land you in the same threads: the two symptoms are one symptom. One design note while you are in there. Never expose that distinction to a caller, because telling "padding invalid" apart from "padding valid, content wrong" is what a padding oracle attack feeds on (Vaudenay, EUROCRYPT 2002). ### What GCM does differently GCM inverts the order, verifying the tag before producing any plaintext, so there is no window in which partially correct bytes exist. A GCM failure never leaves you wondering whether the output is real, because there is no output. GCM also has no padding at all, being a counter mode underneath, so ciphertext length equals plaintext length. A padding error in a system you thought was GCM therefore proves the system is not GCM, usually a config that fell back to CBC. ## 3. Are both sides using the same key bytes? AES does not see your key string. It sees 16, 24 or 32 bytes. Two systems can hold identical key material in a config file and still disagree, because the text matching says nothing about what each side decodes it into. ### The three ways a key string gets turned into bytes Hand the literal string `0123456789abcdef` to three different libraries: ``` as hex -> 8 bytes (invalid AES key length) as base64 -> 12 bytes (invalid AES key length) as raw UTF-8 -> 16 bytes (valid AES-128) ``` Sixteen characters produce three different byte counts. The case is nasty precisely because it is valid under all three readings: every character is in both the hex and base64 alphabets, and sixteen characters is a legal length for both decoders, so nothing errors at parse time. The [JWT invalid signature troubleshooting guide](/blog/jwt-invalid-signature-troubleshooting-guide) has the full cross-library matrix of how each ecosystem interprets a secret string; the short version for AES is to write down which encoding your key material is in and make both sides decode explicitly. The HMAC form of the same bug bites webhook receivers, covered in the [webhook signature verification guide](/blog/webhook-signature-verification-failed-hmac-guide). ### AES is strict: exactly 16, 24 or 32 bytes This is where AES differs from the primitive most developers meet first. HMAC accepts any key length: RFC 2104 hashes anything longer than the block size and zero-pads anything shorter, so an [HMAC generator](/tools/hmac-generator) takes a 7-byte or 700-byte secret without complaint. AES has exactly three legal key lengths and rejects everything else before a single block is processed. The strictness helps, because a length error is the one AES failure that names its own cause instead of hiding behind padding. Our tool phrases it as `Key must be 16, 24, or 32 bytes (AES-128/192/256).` The traps that produce a wrong length: - A trailing newline from `KEY=$(cat key.txt)` or `echo "$KEY"`. Use `printf` and `echo -n` instead. A trailing space pasted out of a secrets manager UI does the same thing. - A `0x` prefix copied out of a debugger, which leaves thirty-four characters that are no longer valid hex. - Non-ASCII characters. `contraseña` is 10 characters and 11 bytes in UTF-8, so a "32-character" passphrase with one accented letter is 33 bytes. ### `SecretKeySpec` and the platform default charset Java has a version of this that only appears after deployment. `"my secret".getBytes()` with no argument uses the platform default charset, which before JDK 18 came from the `file.encoding` property and therefore from the machine's OS and locale. A laptop on UTF-8 and a container on ANSI_X3.4-1968 produce different bytes for any non-ASCII character. JEP 400 made UTF-8 the default in JDK 18, which fixes new code and nothing else. ```java // wrong: bytes depend on the machine SecretKeySpec ks = new SecretKeySpec(secret.getBytes(), "AES"); // right: bytes depend on nothing SecretKeySpec ks = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "AES"); ``` If your code works locally, fails on the server with a padding error, and the passphrase contains anything outside ASCII, check this first. ## 4. The IV: where it goes and how a wrong one looks An `aes iv mismatch` is the failure people suspect first and diagnose last, because it does not behave like the others: it is quiet, and its damage is local. ### A wrong IV corrupts exactly one block Look again at scenario B. Key correct, IV wrong by one byte: ``` hello world -> iello world ``` Nothing threw, and exactly one character changed. Write down the CBC step for the first block and it is obvious: `P1 = D(C1) XOR IV`. The IV is XORed straight into the first plaintext block and touches nothing else, so flipping one bit of the IV flips the same bit of the plaintext in the same position. Here `h` (0x68) became `i` (0x69), so the IV's first byte moved by exactly 0x01. That gives you a fingerprint. In CBC, first 16 bytes garbage and everything after them clean means the IV is wrong and the key is right. Every block garbage means the key is wrong. That one observation separates the two most common causes without changing a line of code, and the [AES decrypt tool](/tools/aes-decrypt) shows the decoded bytes so you can read it directly. As for why nothing threw: `hello world` is 11 bytes, so it is a single block, and the PKCS#7 padding lives in bytes 11 through 15 of it. The IV byte that changed was byte 0, so the padding region was untouched and validated. Corrupt an IV byte at position 11 or later and you get a padding error instead, which is another route by which the padding error lies to you. ### Three transmission conventions No standard says where the IV goes. Instead there are three habits, and they interoperate badly. Prepending it, as `iv || ciphertext`, is the most common convention and the default in our tools. Both sides must agree on how much to strip: 16 bytes for CBC and CTR, 12 for GCM. The mirror-image bug is a producer that prepends and a consumer that does not. The first 16 bytes of "ciphertext" are then the IV, every block shifts, and you get a padding error. Giving it a separate field, `{"iv": "...", "ciphertext": "..."}`, is cleaner in principle and doubles the places an encoding can disagree, since the IV now has its own base64-versus-hex question. The third habit is a fixed constant, usually all zeros, hardcoded because someone needed determinism. It interoperates perfectly, which is what makes it the dangerous one: in CBC a fixed IV leaks equality across records, and in GCM reusing a nonce under one key reveals the XOR of the two plaintexts and can expose the GHASH subkey that authenticates the tag. SP 800-38D is explicit about uniqueness. The tool's bare-ciphertext switch plus an explicit IV override tests all three conventions against the same bytes in a minute. ### GCM's IV is 12 bytes, not 16 Teams that adopt GCM by editing an existing CBC path carry the 16-byte IV across, and the result fails without a clue. SP 800-38D standardises a 96-bit IV. Other lengths are permitted but they are not simply "a longer IV": when the IV is not 96 bits, GCM derives its initial counter block by running the IV through GHASH instead of using it directly. The same 16 bytes used as a nonce therefore produce a completely different keystream and tag than the first 12 would, and you get a generic authentication failure. If the ciphertext came from elsewhere and you are guessing at the layout, count backwards: the tag is the last 16 bytes, the nonce almost always the first 12. ## 5. Mode mismatch, including the silent kind ### `Cipher.getInstance("AES")` is ECB Java lets you name a cipher without naming a mode or a padding scheme. It does not refuse and it does not warn. Under the JDK's built-in SunJCE provider it fills the blanks with ECB and PKCS5Padding. Proving it needs the right experiment: encrypt 32 identical bytes (two blocks of `A`) with key `0123456789abcdef`, then check whether the two ciphertext blocks match. On `java 1.8.0_162`: ``` getInstance("AES") ciphertext = 3bfd04cc0d7ed55358e2cbe19de213833bfd04cc0d7ed55358e2cbe19de21383377222e061a924c591cd9c27ea163ed4 block1 = 3bfd04cc0d7ed55358e2cbe19de21383 block2 = 3bfd04cc0d7ed55358e2cbe19de21383 <- identical blocks = the ECB fingerprint (plaintext structure leaks) getInstance("AES/CBC/PKCS5Padding") blocks differ = chaining is active ``` The two blocks match byte for byte. That is the ECB signature, the same property that makes the famous encrypted-penguin image still look like a penguin. The experiment only works with identical plaintext blocks: sixteen `A` bytes followed by sixteen `B` bytes produce two different ciphertext blocks under ECB as well, and you would wrongly conclude the default was CBC. Scope the result carefully: it describes the JDK's built-in SunJCE provider on the version above. The default transformation is a provider decision, so another provider such as BouncyCastle can resolve the same shorthand differently. What generalises is the weaker claim: an unqualified transformation string means whatever your provider decides, which is the reason never to write one. ### The wrong mode may not raise an error Scenario C decrypted CBC ciphertext with `AES/ECB` and returned the correct plaintext with no exception. That looks impossible until you write out the arithmetic. CBC encryption of the first block is `C1 = E(P1 XOR IV)`, and ECB decryption of that block is `D(C1) = P1 XOR IV`. The IV here was all zeros, so `P1 XOR 0 = P1` and the first block decrypts perfectly. `hello world` is one block long, so "the first block" was the whole message. The rule generalises: with a zero IV, ECB and CBC agree on the first block and disagree on every block after it. Decrypt a long CBC message as ECB and you get sixteen clean bytes followed by noise, the exact inverse of the wrong-IV fingerprint. Two opposite shapes point at two different bugs, and neither one produces an error message. Hardcoded zero IVs are common enough that this comes up outside the laboratory. ### What a minimal call gives you in each language | Ecosystem | Minimal call | Mode you actually get | |---|---|---| | Java (SunJCE) | `Cipher.getInstance("AES")` | ECB with PKCS5Padding, silently | | Node `crypto` | `createDecipheriv('aes-256-cbc', key, iv)` | whatever the algorithm string says; no default exists | | Web Crypto | `crypto.subtle.decrypt({ name: 'AES-CBC', iv }, ...)` | named explicitly; ECB is not implemented at all | | Python `cryptography` | `Cipher(algorithms.AES(key), modes.CBC(iv))` | the mode object is mandatory | | PyCryptodome | `AES.new(key, AES.MODE_ECB)` | mandatory argument, but ECB is right there in the autocomplete | | Go `crypto/aes` | `aes.NewCipher(key)` returns a raw `cipher.Block` | calling `Decrypt` on that block *is* ECB; wrap it in `cipher.NewCBCDecrypter` or `cipher.NewGCM` | | CryptoJS | `CryptoJS.AES.decrypt(ct, "passphrase")` | CBC, PKCS#7, EVP_BytesToKey with MD5 (see section 7) | Ecosystems where the mode lives in a string or an object never surprise you. The two that offer a "just AES" call, Java and Go, are where the accidental-ECB reports come from. If the code does not tell you which mode produced a given ciphertext, [switch modes against the same bytes](/tools/aes-decrypt) until one of them returns something readable. ## 6. GCM: the same bytes, different APIs Most cross-language `aes gcm auth tag` failures are not cryptographic. Both sides computed the same 16 bytes and disagree about where those bytes live. ### The measurement Key = 32 bytes `0123456789abcdef0123456789abcdef`, IV = 12 zero bytes, plaintext `hello world`, on `node v25.8.2` and `java 1.8.0_162`: ``` Node ciphertext = a616cd6d7d2328379d41e5 (11 B) <- update+final authTag = c87af9f8ad7148e873fa797292c0af3f (16 B) <- fetched separately via getAuthTag() Java doFinal() = a616cd6d7d2328379d41e5c87af9f8ad7148e873fa797292c0af3f (27 B) <- ciphertext and tag already concatenated ``` `Node ciphertext || authTag` is exactly `Java doFinal()`, all 27 bytes of it. There is no encoding difference to negotiate; Node simply hands you the two pieces separately and Java hands them over glued together. The 11 bytes of plaintext also gave 11 bytes of ciphertext, because GCM adds no padding. That is why a padding error can never come from a genuine GCM path. ### Concatenated or separated, by runtime | Runtime | Encrypt API | Where the tag ends up | |---|---|---| | Node `crypto` | `update()` + `final()`, then `getAuthTag()` | separate | | Java (SunJCE, `AES/GCM/NoPadding`) | `doFinal()` | appended | | Go `cipher.AEAD` | `Seal()` | appended | | Python `cryptography`, `AESGCM` | `encrypt()` | appended | | Python `cryptography`, `Cipher` + `modes.GCM` | `finalize()`, then `encryptor.tag` | separate | | Web Crypto | `crypto.subtle.encrypt` | appended | Node is the odd one out among the high-level APIs, which is why Node-to-anything is the most reported direction of failure. When you inherit a blob and cannot tell which convention produced it, [check whether the last 16 bytes are the tag](/tools/aes-decrypt) before you go near the key. Packing Node output for a Java, Go, Python or browser consumer: ```js const cipher = crypto.createCipheriv('aes-256-gcm', key, iv); const ct = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]); const packed = Buffer.concat([ct, cipher.getAuthTag()]); // now matches doFinal() ``` Unpacking a concatenated blob for Node: ```js const decipher = crypto.createDecipheriv('aes-256-gcm', key, iv); decipher.setAuthTag(packed.subarray(packed.length - 16)); // must come before final() const pt = Buffer.concat([ decipher.update(packed.subarray(0, packed.length - 16)), decipher.final(), ]); ``` The ordering constraint is real: call `setAuthTag()` after `final()` and Node throws `Unsupported state or unable to authenticate data` even when every byte is correct. ### Tag length is configurable, and the unit differs by API GCM permits tags of 128, 120, 112, 104 or 96 bits, with 64 and 32 reserved for constrained applications (SP 800-38D, Appendix C). Almost everyone uses 128, and the trouble is how each API asks for it. Java's `new GCMParameterSpec(128, iv)` takes its first argument in bits. Web Crypto's `{ name: 'AES-GCM', iv, tagLength: 128 }` is also in bits and defaults to 128. Node's `createCipheriv(algo, key, iv, { authTagLength: 16 })` is in bytes. `new GCMParameterSpec(16, iv)` is a legal-looking Java line that asks for a 16-bit tag; some JDKs reject it, and where it is accepted you have swapped your integrity guarantee for a one-in-65,536 coin flip. When the two sides disagree on tag length the packed lengths differ too, so the receiver slices at the wrong boundary and gets an authentication failure that has nothing to do with the key. ## 7. You have a passphrase, not a key If either side takes a human-typed string, there is a key derivation function between that string and AES, and a KDF mismatch is invisible. It never errors. It returns 32 perfectly good bytes that happen to be the wrong 32 bytes, and the failure surfaces one layer down as (you know this one) a padding error. ### PBKDF2 needs four things to line up - The salt. In the OpenSSL `Salted__` format it is 8 bytes inside the ciphertext; in our tools' passphrase format it is a 16-byte prefix; in hand-rolled schemes it is frequently a hardcoded constant. - The iteration count. `openssl enc -pbkdf2` defaults to 10,000. OWASP currently recommends 600,000 for PBKDF2-HMAC-SHA256, which is what our passphrase mode uses. Frameworks pick their own numbers. - The hash. SHA-1 versus SHA-256 versus SHA-512. Older code and some mobile SDKs still default to SHA-1. - The output length. Thirty-two bytes for AES-256, sixteen for AES-128. Some schemes derive key and IV together from one longer call, which never matches a plain 32-byte derivation. ### EVP_BytesToKey, and why CryptoJS keeps not working `cryptojs aes decrypt not working` is usually one specific mismatch. `CryptoJS.AES.encrypt(text, "passphrase")` does not use PBKDF2. It uses EVP_BytesToKey, OpenSSL's pre-1.1 derivation, with MD5 and a single iteration. EVP_BytesToKey also does something PBKDF2 does not: it derives the key *and the IV* from the passphrase and salt in one pass. That is why an OpenSSL `Salted__` file carries no separate IV field, and why reproducing CryptoJS output with PBKDF2 plus a random IV is wrong twice over. The format is recognisable on sight: the 8 ASCII bytes `Salted__` followed by an 8-byte salt, base64-encoded, always begin `U2FsdGVkX1`. If your ciphertext starts that way it is passphrase-derived and you need to know which derivation; the [AES decrypt tool](/tools/aes-decrypt) detects the prefix and switches between the three without code changes. ### Why the same password gives different keys There is no such thing as "the AES password". Every library invented its own path from string to key: | Producer | Derivation | Result for one passphrase | |---|---|---| | CryptoJS `AES.encrypt(text, pass)` | EVP_BytesToKey, MD5, 1 iteration | key A | | `openssl enc` 1.0.2 and earlier | EVP_BytesToKey, MD5, 1 iteration | key A | | `openssl enc` 1.1+ without `-pbkdf2` | EVP_BytesToKey, SHA-256, 1 iteration | key B | | `openssl enc -pbkdf2` | PBKDF2-HMAC-SHA256, 10,000 iterations | key C | | Our passphrase mode | PBKDF2-HMAC-SHA256, 600,000 iterations | key D | | Java, Python, Go | no default at all; you write the derivation | whatever you wrote | Four keys from one password before anyone has made a mistake. The 1.0.2-to-1.1 upgrade changed the default digest from MD5 to SHA-256, which is why ciphertext from old scripts stopped decrypting with the same command on a newer box. If you inherited data and nobody remembers the toolchain, try the derivations in that order. There are only three to test, so it finishes in minutes. ## 8. What the transport did to your bytes Ciphertext is uniformly random binary, which makes it hostile to anything that treats bytes as text. A large share of AES failures never involve the cipher. ### Base64 variants and missing padding Standard base64 (RFC 4648 §4) uses `+` and `/`; the URL-safe variant (§5) uses `-` and `_`. A URL-safe string handed to a standard decoder either throws or, in lenient decoders, silently discards the offending characters and returns short, misaligned bytes, which is why Java ships `Base64.getUrlDecoder()` and `Base64.getDecoder()` as separate objects. Some encoders also drop the trailing `=`, some decoders insist on it, and JWT-adjacent code paths strip it by default. Before suspecting the key, decode the ciphertext and check its length against the mode: - CBC and ECB want a nonzero multiple of 16. Anything else is truncation or a decode problem rather than a key problem. - GCM ciphertext is the length of the plaintext, plus 16 for the tag, plus 12 at the front if the nonce is prepended. - CTR accepts any length, so this check tells you nothing. The [Base64 decoder](/tools/base64-decode-encode) gives you the byte count in one paste, often the fastest measurement in the whole investigation. ### Newlines, smart quotes and the UTF-8 round trip `openssl base64` wraps output at 64 columns unless you pass `-A`, and some decoders skip embedded newlines while others reject them, so the same file decodes on one machine and fails on another. Copying through a chat client or a document editor turns straight quotes curly and hyphens into en dashes, and the difference is nearly invisible in a terminal. The unrecoverable one is a UTF-8 round trip. If raw AES output is ever held as a string without being encoded first (`new String(cipherBytes)` in Java, `bytes.decode('utf-8', errors='replace')` in Python, a `TextDecoder` anywhere), every byte sequence that is not valid UTF-8 collapses to U+FFFD, and encoding it back gives you `EF BF BD` where your data used to be. Since roughly half of random bytes are non-ASCII, most of the ciphertext is destroyed and no key recovers it; the [UTF-8 and UTF-16 encoding guide](/blog/utf-8-utf-16-unicode-encoding-guide) covers why the loss is one-way. Binary ciphertext travels as base64, as hex, or as binary. Putting it in a string is what destroys it. ### Database columns Storage applies the same damage more quietly. Ciphertext written to a `VARCHAR(255)` that is one block too long gets cut, and MySQL outside strict mode does it without an error. The tail is where the padding block and the GCM tag live, so a row written "successfully" months ago now fails, and if the cut landed on a 16-byte boundary the length check above will not catch it either. Charset conversion does the rest: a `latin1` column receiving UTF-8 bytes rewrites your data on the way in. Store ciphertext in `VARBINARY`, `BLOB` or `bytea`, or store base64 in a text column with room to spare. ## 9. A bisection workflow that finds it in five minutes Every section above narrows one variable. Running them in order against a reference implementation you control converges fast, and the browser tools work well as that reference because they run entirely in your browser, where your key and ciphertext never leave the page, and you can change one setting at a time and see the bytes. 0. **Step 0: measure the shape.** Decode the ciphertext and note the byte count, the first few bytes, and whether it begins `U2FsdGVkX1`. Check the count against section 8. If it is not a multiple of 16 and you believe you are in CBC, stop: this is a transport bug. 1. **Step 1: encrypt a known plaintext.** In the [AES encrypt tool](/tools/aes-encrypt), encrypt a short known string with the parameters you *believe* production uses, then compare the shape of the two outputs rather than their values: total length, prefix bytes, presence of a salt header. A mismatch means your assumption about the format or the KDF is wrong, and no amount of key-fiddling fixes it. 2. **Step 2: cycle the derivations.** For passphrase-derived data, run PBKDF2 with the exact iteration count, then EVP-SHA256, then EVP-MD5 in the [AES decrypt tool](/tools/aes-decrypt). Exactly one can be right. If none works, the bug is above the KDF. 3. **Step 3: remove every convention.** Switch to a raw key, turn on bare ciphertext, supply the IV explicitly. You are now stating exactly which bytes are key, IV and ciphertext, with nothing inferred. If it decrypts here but not in your code, your bug is a framing bug (an unstripped IV prefix, a tag in the wrong place) rather than a cryptographic one. 4. **Step 4: flip the mode.** Try CBC, then CTR, then GCM against the same bytes. CTR returning readable text where CBC failed means you have a mode mismatch and nothing else. 5. **Step 5: read the garbage.** First block bad and the rest clean means the IV. First block clean and the rest bad means you decrypted CBC as ECB with a zero IV. Everything bad means the key or the derivation. ## 10. FAQ ### Why does my AES code work locally but fail in production? AES code that works locally and fails in production means the environment changed something that is not in source control. Usual suspects, in order: the key arrived from an environment variable or secret manager with a trailing newline; Java's platform default charset differs between laptop and container, so `getBytes()` produced different bytes (section 3); production OpenSSL is 1.1+ while your local scripts targeted 1.0.2, changing the EVP_BytesToKey digest from MD5 to SHA-256; or a database column truncates the ciphertext in one environment only. Print the key length and ciphertext length in bytes on both sides first, because those two numbers usually settle it. ### I encrypted in Node and can't decrypt in Java. Where do I start? When Node encrypts and Java cannot decrypt, start with the GCM tag, the most common and least obvious cause. Node returns ciphertext and tag separately; Java's `doFinal()` expects them concatenated as `ciphertext || tag`, and section 6 shows the bytes are otherwise identical. On CBC instead, start with the IV convention: did Node prepend it, and does the Java side strip 16 bytes before decrypting? Third is the key itself, where `Buffer.from(k, 'hex')` and `k.getBytes(StandardCharsets.UTF_8)` produce different lengths from the same string. ### Is Java's `PKCS5Padding` the same as PKCS#7? For AES, Java's `PKCS5Padding` is PKCS#7 in effect. PKCS#5 (RFC 8018) is defined only for 8-byte blocks; PKCS#7 (RFC 5652) generalises the scheme to block sizes from 1 to 255 bytes. Java's `PKCS5Padding` applied to a 16-byte block cipher implements PKCS#7 behaviour, and the name is a historical leftover, so this is never your bug. `NoPadding` is: it requires plaintext already a multiple of 16, and on decryption it hands the padding back as data, so you see plausible text with trailing bytes like `\x05\x05\x05\x05\x05`. ### My key is 32 characters but AES says the key length is invalid. Why? A length error means the library got a byte count that is not 16, 24 or 32. With a 32-character string that is usually a trailing newline (33 bytes), a `0x` prefix that makes the string invalid hex, or a non-ASCII character occupying two or three bytes in UTF-8. The more dangerous variant is getting no error at all: 32 hex characters decode to 16 valid bytes and 32 base64 characters decode to 24 valid bytes, both legal AES lengths. The library accepts them, uses the wrong key, and hands you a padding failure instead. Whatever the string looks like, the byte count is the number to check. ### Decryption "succeeded" but the output is garbage. What went wrong? Decryption that "succeeded" and handed you garbage means you are in a mode that verifies nothing. CTR and ECB never throw, and CBC throws only when the final byte pattern fails the padding check, which a wrong key passes a little under 0.4% of the time. Read the shape: first 16 bytes corrupt and the rest clean means the IV; first 16 clean and the rest corrupt means you decrypted CBC ciphertext as ECB with a zero IV; uniformly corrupt means the key or the derivation. Readable text with a few odd trailing bytes means `NoPadding` on padded data. The durable fix is GCM, so that "succeeded" means something. ### Can I still decrypt if I lost the IV? Without the IV you can still decrypt everything in CBC except the first 16 bytes. Blocks 2 onward are recovered as `D(C_i) XOR C_{i-1}`, and every input to that is already in the ciphertext, so only the first block needs the IV. If you also know how the plaintext starts, say a JSON blob beginning `{"userId":`, you can recover the IV outright as `D(C1) XOR P1`. In CTR the IV seeds the entire keystream, so losing it loses everything. In GCM the nonce feeds both the counter and the tag, so there is no partial recovery. ### Can I recover the plaintext if the GCM tag was truncated or dropped? A truncated or dropped GCM tag still leaves the plaintext recoverable: mathematically yes, practically with effort. GCM is CTR mode underneath, so the key and nonce alone reproduce the keystream. No mainstream library will do it for you: Java, Go, Python and Web Crypto all refuse to release plaintext without a valid tag, by design. The workaround is to decrypt the same bytes as AES-CTR with the initial counter block set to the 12-byte nonce followed by `00000002`, which is where GCM's first data block starts. You get the data back and give up every integrity guarantee, so treat the result as untrusted. If you still have all 16 tag bytes and authentication fails anyway, the tag is not missing and something else on this page is your bug. Take it to the [AES decrypt tool](/tools/aes-decrypt) and start at step 0. --- ### Advanced Base64: MIME, Data URLs, Performance & Security URL: https://go-tools.org/blog/base64-complete-guide Implement Base64 in JavaScript and Python, optimize data URLs, choose standard vs URL-safe variants, and avoid common security pitfalls. # Base64 in Production: MIME, Data URLs, Performance Traps & Security Pitfalls > **New to Base64?** If you're just getting started, read our [beginner-friendly introduction to Base64 encoding](/blog/understanding-base64) first. Base64 encoding is everywhere in modern web development, from email attachments to data URLs, from API authentication to image embedding. This guide focuses on practical implementation, performance optimization, and the advanced details you need for production use. ## What is Base64? Base64 is a binary-to-text encoding scheme that converts binary data into a safe ASCII string using 64 printable characters. For a thorough introduction to Base64 fundamentals — including the character set, why it exists, and how the encoding algorithm works step by step — see our [beginner-friendly Base64 guide](/blog/understanding-base64). ## How Base64 Encoding Works ### The Algorithm Step by Step 1. **Take 3 bytes of input** (24 bits total) 2. **Split into 4 groups of 6 bits each** 3. **Map each 6-bit value to a Base64 character** 4. **Add padding if necessary** ### Example: Encoding "Man" ``` M = 01001101 (77 in decimal) a = 01100001 (97 in decimal) n = 01101110 (110 in decimal) ``` **Step 1**: Concatenate the bits ``` 010011010110000101101110 ``` **Step 2**: Split into 6-bit groups ``` 010011 | 010110 | 000101 | 101110 ``` **Step 3**: Convert to decimal and map to Base64 ``` 010011 = 19 → T 010110 = 22 → W 000101 = 5 → F 101110 = 46 → u ``` **Result**: "Man" becomes "TWFu" ### Handling Padding When the input length isn't divisible by 3, padding is needed: - **1 byte remaining**: Add 2 padding characters (`==`) - **2 bytes remaining**: Add 1 padding character (`=`) ## Base64 in MIME (Email Attachments) ### The MIME Standard MIME (Multipurpose Internet Mail Extensions) was one of the first major applications of Base64. Email was originally designed for 7-bit ASCII text, but users needed to send binary files like images and documents. ### How Email Attachments Work When you attach a file to an email: 1. The file is read as binary data 2. Base64 encoding converts it to text 3. The encoded text is embedded in the email 4. The recipient's email client decodes it back to binary ### MIME Example ``` Content-Type: image/jpeg Content-Transfer-Encoding: base64 /9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAEBAQEBAQEBAQEBAQEBAQEBAQEB... ``` ## Base64 in Data URLs ### What are Data URLs? Data URLs allow you to embed small files directly in HTML, CSS, or JavaScript using the `data:` scheme: ``` data:[mediatype][;base64], ``` ### Common Use Cases **Embedding Images in CSS** ```css .icon { background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUg...); } ``` **Inline SVG Icons** ```html Circle ``` **Small JavaScript Files** ```html ``` ## Base64 Variants ### Standard Base64 (RFC 4648) - Uses `+` and `/` as the last two characters - Uses `=` for padding - Safe for most applications ### URL-Safe Base64 (RFC 4648 Section 5) - Replaces `+` with `-` - Replaces `/` with `_` - May omit padding (`=`) - Safe for URLs and filenames ### Comparison Example ``` Standard: "??>" → Pz8+ URL-Safe: "??>" → Pz8- ``` ## Practical Code Examples ### JavaScript Implementation ```javascript // Encoding function encodeBase64(str) { return btoa(unescape(encodeURIComponent(str))); } // Decoding function decodeBase64(str) { return decodeURIComponent(escape(atob(str))); } // Usage const original = "Hello, World!"; const encoded = encodeBase64(original); const decoded = decodeBase64(encoded); console.log(`Original: ${original}`); console.log(`Encoded: ${encoded}`); console.log(`Decoded: ${decoded}`); ``` ### Python Implementation ```python import base64 # Encoding def encode_base64(data): if isinstance(data, str): data = data.encode('utf-8') return base64.b64encode(data).decode('ascii') # Decoding def decode_base64(encoded_data): return base64.b64decode(encoded_data).decode('utf-8') # Usage original = "Hello, World!" encoded = encode_base64(original) decoded = decode_base64(encoded) print(f"Original: {original}") print(f"Encoded: {encoded}") print(f"Decoded: {decoded}") ``` ## Real-World Applications ### Web API Authentication Many APIs use Base64 for basic authentication: ```javascript const username = "user"; const password = "pass"; const credentials = btoa(`${username}:${password}`); fetch('/api/data', { headers: { 'Authorization': `Basic ${credentials}` } }); ``` ### JSON Web Tokens (JWT) JWTs use Base64URL encoding for their header and payload: ``` eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0... ``` ### Image Embedding Embedding small images directly in HTML: ```html 1x1 transparent pixel ``` ## Performance Considerations ### Size Increase Base64 encoding increases data size by approximately **33%**: - 3 bytes of binary data → 4 bytes of Base64 text - Overhead ratio: 4/3 = 1.33 ### When to Use Base64 **Good for:** - Small files (< 10KB) - Reducing HTTP requests - Embedding in CSS/HTML - Text-based protocols **Avoid for:** - Large files - Frequently changing content - When binary transfer is available - Performance-critical applications ### Caching Implications - Base64 data URLs can't be cached separately - Changes to embedded data require cache invalidation - Consider external files for frequently updated content ## Best Practices ### 1. Choose the Right Variant - Use standard Base64 for general purposes - Use URL-safe Base64 for URLs and filenames - Consider omitting padding when safe ### 2. Optimize for Performance - Keep embedded data small (< 10KB) - Use external files for large or frequently changing content - Consider gzip compression for Base64 text ### 3. Security Considerations - Base64 is encoding, **not encryption** - Don't use Base64 to hide sensitive data - Validate decoded data before use ### 4. Debugging Tips - Use online tools for quick encoding/decoding - Check for proper padding - Verify character set compatibility - When debugging config files that contain Base64 values, a [JSON5/JSONC-aware formatter](/blog/json5-jsonc-formatting-guide) can help you inspect them without stripping comments ## Try It Yourself *Encode and decode Base64 instantly with our [Base64 Encoder/Decoder](/tools/base64-decode-encode) — supports UTF-8, URL-safe variants, and real-time conversion. 100% in your browser.* ## Frequently Asked Questions ### Does Base64 encoding provide any security? No — Base64 is an encoding scheme, not encryption. Anyone can decode Base64 data without a key. It is designed for safe data transport, not confidentiality. Never use Base64 to "protect" sensitive information like passwords or API keys. For security, use proper encryption algorithms like AES-256 or TLS for data in transit. ### Why does Base64 increase data size by about 33%? Base64 represents every 3 bytes of binary data as 4 ASCII characters. This 3-to-4 ratio means the output is always approximately 4/3 (133%) of the input size — a 33% increase. This overhead is the trade-off for being able to safely transmit binary data through text-only channels like email or JSON. ### What is the difference between standard Base64 and URL-safe Base64? Standard Base64 uses `+` and `/` characters, which have special meanings in URLs. URL-safe Base64 (RFC 4648) replaces them with `-` and `_`, making the output safe for use in URLs, query parameters, and filenames without additional [percent-encoding](/tools/url-decoder-encoder). Most modern APIs prefer URL-safe Base64 for tokens and identifiers. ### When should I use Base64 Data URLs instead of regular image files? Use Data URLs for small images under 2-4KB, like icons and simple logos, to eliminate an HTTP request. For larger images, regular files with proper caching are more efficient — Data URLs cannot be cached independently, increase HTML size by 33%, and must be re-downloaded with every page load. ### Can I use Base64 to encode non-ASCII text like Chinese or emoji? Yes, but you must first convert the text to bytes using a character encoding like UTF-8, then Base64-encode those bytes. When decoding, reverse the process: Base64-decode to bytes, then interpret the bytes as UTF-8 text. Most modern libraries handle this automatically, but always specify UTF-8 explicitly to avoid encoding errors. ## Conclusion Base64 encoding is a fundamental technology that bridges the gap between binary data and text-based systems. From its origins in email attachments to modern web applications, Base64 continues to be an essential tool for developers. **Key takeaways:** - Base64 converts binary data to safe ASCII text - It's essential for email attachments and data URLs - Choose the right variant for your use case - Consider performance implications for large data - Remember: it's encoding, not encryption --- ### Image to Base64 & Data URIs: When to Inline Images (2026) URL: https://go-tools.org/blog/base64-images-data-uri-inline-guide Should you convert an image to Base64? See when data URIs help, the 33% size cost, CSS/HTML inlining, caching tradeoffs, and when a normal image file wins. When you convert an **image to Base64**, you get a **data URI**: a string like `data:image/png;base64,iVBORw0KGgo…` that you can paste straight into an HTML `src` or a CSS `url()`. The browser decodes it on the spot and shows the picture with no separate download. No file to host, no extra request. So should you do it? Here is the short rule. Inline an image as Base64 when it is small (under about 2 KB), rarely changes, and you want to skip one HTTP request. Think tiny icons and logos. For everything else, keep it as a normal image file: large images, anything reused across pages, anything you want the browser to cache. The catch is that Base64 makes a file about 33% larger, and once that text is embedded in your HTML or CSS it can no longer be cached on its own. If you want the exact numbers for a specific file, the [Image to Base64 converter](/tools/image-to-base64) does the encoding in your browser and shows the precise size increase, so you can decide with real data instead of a rule of thumb. This guide covers what that data URI actually is, the math behind the size tax, a decision matrix for when inlining pays off, and the cases where a plain file wins. ## What "image to Base64" actually produces: the data URI Converting an image to Base64 does not give you a file. It gives you one long string that follows the data URI format defined in RFC 2397 (see [MDN's `data:` URL reference](https://developer.mozilla.org/en-US/docs/Web/URI/Reference/Schemes/data) for the full spec). The string has three parts: ``` data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA… └──┬─┘ └───┬───┘ └─┬──┘ └─────────┬──────────┘ data: MIME type marker the encoded image bytes ``` The MIME type tells the browser what kind of image it is decoding. The common ones for images are `image/png`, `image/jpeg`, `image/gif`, `image/webp`, `image/svg+xml`, and `image/x-icon` for favicons. The `;base64,` marker says the payload that follows is Base64 rather than plain text. Everything after the comma is the image, re-expressed as printable ASCII. That last part matters for privacy. The conversion runs entirely in your browser through the `FileReader` API's `readAsDataURL`, so nothing is uploaded to a server. You can drop a pre-launch screenshot or unreleased artwork into the tool and watch the Network tab stay empty. For the mechanics of how raw bytes become that ASCII string, [understanding Base64](/blog/understanding-base64) covers the encoding from the ground up, and the [complete Base64 guide](/blog/base64-complete-guide) extends the same data-URL idea to fonts, PDFs, and other file types. ### A real example: a 68-byte transparent PNG Here is the smallest practical case, a 1×1 transparent PNG, 68 bytes on disk, written out as a complete data URI: ``` data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg== ``` Paste that into a browser address bar and you will see (well, not see, since it is transparent) a valid image render with zero network activity. Notice the trailing `==`: that is padding, which we will get to. This is also exactly what text Base64 looks like, just applied to image bytes instead of text. If you only need to encode or decode plain text strings, the [Base64 encode/decode](/tools/base64-decode-encode) tool handles that case. ## The 33% size tax (and why it compounds) Base64 works in fixed groups: every 3 bytes of binary become 4 ASCII characters. Four-thirds is roughly 1.33, which is where the +33% figure comes from. Add a byte or two of padding plus the `data:image/png;base64,` prefix and the overhead is slightly higher for tiny files. A concrete example: a 9 KB PNG becomes about 12 KB of text. Why exactly 3-to-4? Base64 uses a 64-character alphabet: `A`–`Z`, `a`–`z`, `0`–`9`, plus `+` and `/`. Sixty-four symbols is 6 bits of information per character, while binary bytes are 8 bits each. The lowest common multiple of 6 and 8 is 24 bits, which is 3 bytes or 4 Base64 characters, so the encoder works through the image 24 bits at a time. When the image length is not a clean multiple of 3, one or two `=` characters pad the final group. That math is fixed; no encoder setting shrinks the 33%. That 33% is the visible cost. The hidden cost is that it compounds, and this is the part most "just inline it" advice skips: - **The image is re-downloaded whenever the containing file changes.** An external `logo.png` is its own resource. Inline it into `styles.css`, and now any edit to that stylesheet, even a one-line color tweak, invalidates the cache for the image too. Visitors re-download the picture they already had. - **It cannot be cached independently.** A normal image file is fetched once and reused across every page and every visit. An inlined data URI is part of the document, so it ships again on every page that embeds it and on every cache miss of that document. - **CSS is render-blocking.** The browser will not paint until it has the CSS. Stuff a large data URI into a stylesheet and you have made a render-blocking resource bigger, delaying first paint for the whole page. ### Does gzip or brotli cancel the 33% out? Partly, not fully. Base64 text is repetitive enough that gzip and brotli compress it well, clawing back a good chunk of the inflation over the wire. But two things remain true. First, the compressed Base64 is usually still a little larger than the compressed original binary, because you have handed the compressor a less efficient starting point. Second, and this is the part that bites, compression does nothing about caching or render-blocking. A smaller-over-the-wire data URI is still re-downloaded with its host file and still cannot be cached on its own. So compressing the bytes is not the same as removing the cost of inlining them. If the distinction between minifying, gzipping, and brotli is fuzzy, the [code minification guide](/blog/code-minification-guide-css-js-html) lays out how those layers stack, and why squeezing the bytes never fixes the caching problem that inlining creates. ## When to use a Base64 image (the decision matrix) The whole decision comes down to a handful of factors. Here they are side by side: | Factor | Lean toward inlining (Base64) | Lean toward a normal file | |--------|-------------------------------|---------------------------| | **Size** | Under ~2 KB (green) | Over ~10 KB (red); 2–10 KB is a judgment call (amber) | | **Reuse** | One page, a place or two | Repeated across many pages | | **Change frequency** | Almost never changes | Edited often | | **Context** | HTML email, self-contained widget or bookmarklet, JSON/API payload, a critical above-the-fold icon worth one saved request | Content images, shared cacheable assets | Those size thresholds are not arbitrary. They mirror the traffic-light badge built into the [Image to Base64 converter](/tools/image-to-base64): green under 2 KB, amber up to 10 KB, red above. The tool reads your actual file and tells you which bucket it lands in. ### A simple rule of thumb If you remember one line, make it this: **under ~2 KB and used in only one or two places, inlining usually pays off; over ~10 KB or reused across pages, a normal cached file almost always wins.** The 2–10 KB middle is where you weigh the saved request against the lost cache for your specific situation. ### Good fits in detail A few cases where Base64 genuinely earns its keep: - **HTML email.** Many email clients block externally hosted images by default for privacy, which breaks any layout that depends on a remote logo. A small inlined data URI renders immediately with no server fetch. Keep these to logos and icons; never inline a photograph into an email. - **Self-contained widgets and bookmarklets.** A bookmarklet or an embeddable widget has to work with zero external dependencies. Inlining its icons keeps everything in a single droppable file. - **JSON and API payloads.** Shipping a thumbnail inside a JSON document or a config file is sometimes the cleanest option: one round trip, one object, no second request to wire up. - **A critical above-the-fold icon.** When a tiny logo is part of your Largest Contentful Paint and you want to shave one request off the critical path, inlining can help. Emphasis on *tiny*. One pattern ties these together: in each case the asset travels *with* something else and would otherwise need its own delivery channel. An email cannot rely on your CDN, a bookmarklet has no second file to fetch, and a JSON response arrives as a single payload. So the alternative to inlining here is not a cached file but a missing image, which changes the calculus entirely. The useful question for a Base64 fit is not only whether the asset is small, but whether a separate file is even an option in the first place. ## When NOT to inline: caching, lazy loading, and Core Web Vitals The flip side is longer, because inlining quietly disables several things the browser does well. **You lose independent caching.** This stings most for returning visitors. A normal image sits in their cache after the first visit and loads instantly forever after. An inlined image has no independent cache entry; it rides along with the document every single time, so a repeat visitor pays the byte cost again and again. **You lose lazy loading.** The `loading="lazy"` attribute lets the browser defer images that are below the fold until the user scrolls near them. A data URI is parsed and "downloaded" the instant the HTML is read, so there is nothing to defer. Inline a dozen below-the-fold images and you have forced all of them into the initial load. **You enlarge render-blocking resources.** As noted earlier, a data URI inside CSS bloats a resource that blocks first paint. The bigger that stylesheet, the longer the page sits blank. **Decoding is more expensive on mobile.** A data URI is Base64-decoded every time its document loads, and on low-end phones that extra CPU work adds up. Worse, the bytes never land in the browser's disk cache, so a heavy inlined image is re-decoded on each visit instead of being cached and decoded once like a normal file. There is also a historical reason this advice has shifted. The original case for inlining, made loudly in the HTTP/1.1 era, was request reduction: each connection could fetch one resource at a time, so a page with 40 small icons paid 40 round trips. HTTP/2 changed that by multiplexing many requests over a single connection, which made extra small files cheap. The big payoff of inlining, fewer requests, mostly evaporated, while the costs stayed: lost caching, no lazy loading, bigger render-blocking files. If you read older articles enthusiastic about Base64 sprites, weigh them against the protocol your site actually runs on today. ### The Core Web Vitals angle Inlining cuts both ways on LCP ([Largest Contentful Paint](https://web.dev/articles/lcp)). For a small, above-the-fold image that *is* the LCP element, removing a request can nudge LCP earlier. But inline a large image and you do the opposite: you delay the document or stylesheet it lives in, pushing LCP later for the whole page. The size threshold decides which way it goes. For CLS (Cumulative Layout Shift), inlining changes nothing about the core rule: an image still needs explicit `width` and `height` (or an aspect-ratio box) so the browser can reserve space before it renders. A data URI without dimensions shifts layout exactly like a remote image without dimensions. A better lever than inlining is usually shrinking the source. Compressing an image before you encode it makes both the file and any resulting data URI smaller. The [browser vs Node image compression guide](/blog/image-compression-browser-vs-node) covers how to do that client-side or in a build step, and [WebP vs AVIF vs JPEG](/blog/webp-vs-avif-vs-jpeg-image-format-guide) helps you pick a format that is small to begin with. ## How to inline images in HTML, CSS, Markdown, and JSON Once you have a data URI, here is how it drops into each context. These are the four ready-to-paste snippets the [Image to Base64 converter](/tools/image-to-base64) generates for you. **HTML**: paste the URI into any `src`: ```html logo ``` **CSS**: wrap it in `url()` for a `background-image` (this is the canonical base64 image in CSS pattern): ```css .icon { background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0i…"); } ``` **Markdown**: a self-contained image link for READMEs, GitHub issues, and notebooks where you cannot host a file: ```markdown ![chart](data:image/jpeg;base64,/9j/4AAQSkZJRgABAQ…) ``` **JSON**: an embedded asset inside an API or config payload: ```json { "icon": "data:image/png;base64,iVBORw0KGgo…" } ``` All four work anywhere a URL is accepted: `img src`, CSS `background`, `mask-image`, even a favicon ``. Every modern browser supports the `data:` scheme. ### Generating these quickly Building these by hand is error-prone: one wrong MIME type or a stray line break and the image silently fails to render. Drop your file into the [Image to Base64 converter](/tools/image-to-base64) and it produces all four snippets with their own copy buttons, plus the exact size increase so you know up front whether the asset belongs inline at all. ## SVG: the special case where Base64 usually loses SVG breaks the usual logic, because SVG is text, not binary. Base64 exists to make binary data text-safe, but SVG is already XML text. Encoding it as Base64 just inflates a string that did not need encoding, and makes it unreadable in the process. So for SVG specifically, Base64 is almost always the wrong choice. Compare three ways to inline the same icon: ```css /* 1. Base64 data URI — adds the 33% tax to text that didn't need it */ .a { background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0i…"); } /* 2. URL-encoded data URI — percent-encode a handful of characters, no 33% tax */ .b { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg'…%3C/svg%3E"); } /* 3. Inline directly in the HTML — fully styleable with CSS */ ``` ```html ``` Option 2 (URL-encoding) is usually smaller than option 1, stays human-readable, and compresses better. You only percent-encode the characters that would break the URI (`<`, `>`, `#`, and quotes) and leave the rest legible. The [URL encoder/decoder](/tools/url-decoder-encoder) approach is documented in the tool itself; reach for Base64 SVG only when a build pipeline specifically demands it. ### Why an inline `` often beats a Base64 PNG icon If you are choosing between a Base64-encoded PNG icon and an inline ``, the SVG usually wins. It scales to any size without blurring and carries no 33% tax, and unlike any data URI you can style it with CSS, animate it, and recolor it with `currentColor`. A Base64 PNG is a fixed-resolution blob you cannot touch once encoded. Reserve raster Base64 for cases where you genuinely need a photograph or a raster screenshot inline. ## Decoding the other way: Base64 back to an image The reverse problem is just as common: you have a Base64 string, pulled from an API response, a log line, or a stylesheet you are debugging, and you need to see the actual picture. Two details trip people up. First, raw Base64 versus a full data URI. A complete data URI (`data:image/png;base64,…`) carries its own MIME type; a bare payload (`iVBORw0KGgo…`) does not. To render a bare payload you either prepend a correct `data:` prefix or let a tool infer the format from the leading bytes: `iVBORw0KGgo` means PNG, `/9j/` means JPEG, `R0lGOD` means GIF. Second, line wrapping. Base64 from email or older tooling is often wrapped at 76 characters per RFC 2045. Those newlines must be stripped before decoding, or the string is invalid in an HTML attribute or `url()`. In the browser you can hand a complete data URI straight to an ``: ```html decoded ``` On the server, Node reconstructs the file from the payload: ```js import { writeFileSync } from "node:fs"; const b64 = "iVBORw0KGgoAAAANSUhEUgAA…"; // raw payload, no data: prefix writeFileSync("output.png", Buffer.from(b64, "base64")); ``` For a no-code path, use the [Base64 to Image converter](/tools/base64-to-image): paste a string (with or without the prefix, line breaks and all), preview it, read its dimensions and MIME type, and download a real PNG, JPG, GIF, or SVG. It strips whitespace, tolerates a missing prefix, and detects the format from magic bytes automatically. One sanity check worth doing on a decoded image: look at its reported dimensions. If you pulled one string out of a file that held several and the result is 1×1, you probably grabbed a tracking pixel instead of the asset you wanted. And remember that decoding is purely mechanical and lossless: a Base64 PNG comes back as the exact same PNG, byte for byte, with no recompression. The only thing that changed along the way was the container, a text string on the way out and a binary file on the way back. ## FAQ ### Should I convert my images to Base64? Only when it is worth it: small (under ~2 KB), rarely-changing icons or logos where skipping one HTTP request matters, plus HTML email, self-contained widgets, and JSON payloads. Large images or anything reused across pages should almost always stay as normal files, so you keep caching and lazy loading. ### How much larger does Base64 make an image? About +33%. Base64 encodes every 3 bytes of binary as 4 ASCII characters, plus a little padding and the `data:` prefix. A 9 KB PNG becomes roughly 12 KB of text. To [convert an image to Base64](/tools/image-to-base64) and see the exact increase for your file, the tool reports the precise number in its metadata bar. ### Does Base64 make images load faster? For a very small above-the-fold icon it can, by saving one request's round trip. For larger or reused images it is usually slower: you lose independent caching, you cannot lazy-load it, and inlining it into CSS enlarges a render-blocking resource. Size is the deciding factor. ### Can I use a Base64 image in CSS? Yes: `background-image: url("data:image/png;base64,…")`. It is fine for tiny icons. Just remember the data URI becomes part of the stylesheet, so the whole file re-downloads whenever the CSS changes, and the image cannot be cached separately from it. ### Should I use SVG or Base64 for icons? Prefer an inline `` or a URL-encoded SVG data URI. SVG is text, scales cleanly, and carries no 33% tax, so it is usually smaller than a Base64 PNG and you can style it with CSS. Reach for Base64 only when you specifically need a raster icon. ### How do I convert a Base64 string back to an image? In the browser, drop a full `data:image/…;base64,…` URI into an ``. On a server, use `Buffer.from(b64, "base64")` to write the file. A raw payload needs a `data:` prefix added, and line-wrapped strings need their newlines stripped first. The [Base64 to Image tool](/tools/base64-to-image) handles all of that and lets you download the result. --- ### bcrypt 72-Byte Password Error: Why Short Passwords Fail Too URL: https://go-tools.org/blog/bcrypt-72-byte-password-error-troubleshooting-guide Even a 14-byte password throws "password cannot be longer than 72 bytes". Blame passlib's 255-byte probe, not your password. Hash bcrypt free online. # bcrypt 72-Byte Password Error: Why Short Passwords Fail Too Two different problems produce the same message, and only one of them has anything to do with your password. If your password really is over the bcrypt 72 bytes limit, bcrypt reads the first 72 bytes and discards the rest. We hashed two 82-byte passwords that shared their first 72 bytes under a fixed salt. Both produced `$2a$10$abcdefghijklmnopqrstuu.hioaszd4nGKdJlcuRzR1xqPIcN/X.S`, and `bcrypt.compareSync(p2, hash(p1))` returned `true`. The second password logs into the first password's account. If your password is obviously short and you still get this: ``` password cannot be longer than 72 bytes, truncate manually if necessary (e.g. my_password[:72]) ``` then the message is wrong about the cause. Under passlib 1.7.4 with bcrypt 5.0.0, a 14-byte password raises it. The culprit there is a fixed 255-byte self-test probe inside passlib. It runs once, when the backend initialises, before your password reaches the hashing call at all. bcrypt 5.0.0 rejects the probe, the exception escapes, and you read a complaint about a password nobody typed. The `__about__` monkey patch that dominates search results for this error does not fix it. We re-ran it in a clean process with the patch applied before `import passlib`, and the ValueError came back unchanged. ## 30-second triage: which one are you | Your password | When the error fires | Root cause | Go to | |---|---|---|---| | Longer than 72 bytes | When you call hash | Genuinely too long. bcrypt 5.0 raises, bcrypt 4.x truncates silently | Sections 2 and 3 | | Under 72 bytes, using passlib | On the first call in the process | passlib's 255-byte probe. Nothing to do with your password | Section 4 | | Contains Chinese, Japanese, or emoji | Looks short, is not | Characters are not bytes | Section 3 | | Started failing after a dependency upgrade | After deploy | The bcrypt 5.0 breaking change | Sections 4 and 5 | If you are in row 2, skip ahead. Nothing in the next two sections will help you, and the fix is different. ## What bcrypt's 72-byte limit does to your password ### Why bcrypt stops at 72 bcrypt is built on Blowfish, and it feeds your password in as the Blowfish key. Blowfish expands its key into a P-array of 18 subkeys, each 32 bits wide. That is 18 × 4 = 72 bytes of key material, and the expansion loop wraps back to the start of the key once it has filled all 18 slots. The ceiling is structural rather than a lazy implementation or a buffer someone forgot to raise. Every conforming bcrypt implementation on every platform has the same limit, which is why you see the number 72 in Python, Node, Go, Java, and PHP alike. ### Two different passwords, one hash bcrypt password truncation is a security property rather than a length inconvenience, which is what makes this worth checking on your own stack. Using bcryptjs 3.0.3 with the fixed salt `$2a$10$abcdefghijklmnopqrstuv`, we hashed two passwords of 82 bytes each: | Password | Value | Bytes | |---|---|---:| | p1 | `"A"×72 + "XXXXXXXXXX"` | 82 | | p2 | `"A"×72 + "ZZZZZZZZZZ"` | 82 | Both produced the same digest: ``` $2a$10$abcdefghijklmnopqrstuu.hioaszd4nGKdJlcuRzR1xqPIcN/X.S ``` Two different passwords, one hash. The consequence: ```js bcrypt.compareSync(p2, hash(p1)) // true ``` An attacker who knows the first 72 bytes of a long passphrase can append anything at all and authenticate. Every byte past the boundary contributes exactly zero to the strength of the stored hash, no matter how carefully your users chose them. If you want to check a hash you already have against a candidate password without wiring up a script, you can [generate and verify bcrypt hashes in the browser](/tools/bcrypt-generator) and watch the same behaviour yourself. ### Where the boundary falls We narrowed the cutoff one byte at a time, keeping a shared prefix and changing exactly one byte after it: | Identical prefix bytes | Byte N+1 differs at | Same hash? | |---:|---:|---| | 70 | 71 | `false` | | 71 | 72 | `false` | | **72** | **73** | **`true`** | | 73 | 74 | `true` | Byte 72 still counts. Byte 73 is the first one that does not. The cutoff is abrupt, with no partial mixing on either side of it, and the same comparison is cheap to run against your own library. ## Characters are not bytes bcrypt counts UTF-8 bytes, and your users type characters. For ASCII the two numbers happen to coincide, which is exactly why this bites teams the moment they ship outside an English-speaking market. | Character type | Example | Bytes per character | 72 bytes equals | |---|---|---:|---:| | ASCII Latin letters | `A` | 1 | 72 characters | | Chinese Han characters | `密` | 3 | 24 characters | | Japanese kana | `あ` | 3 | 24 characters | | Emoji | `🔒` | 4 | 18 characters | | Cyrillic | `я` | 2 | 36 characters | | German umlauts | `ü` | 2 | 36 characters | We confirmed both extremes: with a Chinese password, differences after the 24th character are ignored (`true`), and with an emoji password, differences after the 18th are ignored (`true`). A 25-character Chinese passphrase looks generous in a password field. It has already crossed the line. A user who picks 20 emoji has been over the limit for two characters and will never be told. ### Measuring byte length in your own code Length checks written against character counts will pass while the underlying value is already too long. Measure bytes: ```python # Python len(pw.encode("utf-8")) ``` ```js // Node.js Buffer.byteLength(pw, "utf8") ``` ```go // Go len([]byte(pw)) ``` In browsers without `Buffer`, `new TextEncoder().encode(pw).length` gives the same number. Put this check in front of your hashing call and return a real validation message, rather than letting the library decide for you at 3 a.m. If you are also revisiting your minimum-length policy while you are in there, [how password strength is actually measured](/blog/password-entropy-explained) covers what a length rule buys you and what it does not. ## Why short passwords fail too: passlib's 255-byte probe Your password is fourteen characters long and the library insists it is over 72 bytes. This is the case that sends most people to a search engine. ### Reproducing it Three lines, on Python 3.14.5 with bcrypt 5.0.0 and passlib 1.7.4: ```python from passlib.hash import bcrypt bcrypt.hash("short-password") # 14 bytes # ValueError: password cannot be longer than 72 bytes, truncate manually if necessary (e.g. my_password[:72]) ``` Fourteen bytes in, a complaint about 72 bytes out. The passlib bcrypt error is real, but the number in it describes something else entirely. ### The full call stack Traced through passlib 1.7.4, this is what runs: 1. The first call triggers backend initialisation: `_calc_checksum` → `_stub_requires_backend()` → `set_backend()`. 2. `_load_backend_mixin` reads `bcrypt.__about__.__version__`. The attribute does not exist, so an `AttributeError` is raised. passlib swallows it and prints `(trapped) error reading bcrypt version`. 3. Initialisation continues into `_finalize_backend_mixin` (`passlib/handlers/bcrypt.py:421`), which calls `detect_wrap_bug(IDENT_2A)`. 4. `detect_wrap_bug` (same file, `:378`) verifies a fixed 255-byte probe. 5. bcrypt 5.0.0 raises `ValueError` for anything over 72 bytes, so the probe blows up on itself. 6. The exception propagates out to your call site. You see a message about 72 bytes that was never about your input. The whole sequence happens once per process, on the first hash or verify. That is why the failure is so reliably reproducible and so completely insensitive to what you pass in. ### What the probe looks like ```python secret = (b"0123456789" * 26)[:255] ``` That constant comes from the wraparound bug in BSD's bcrypt that [Openwall disclosed in 2012](https://www.openwall.com/lists/announce/2012/01/02/1), where long keys wrapped around and collapsed into weaker hashes. passlib checks at startup whether the backend it just loaded carries that flaw, and refuses to trust a backend that does. `detect_wrap_bug` is not a passlib bug. It is defensive code doing exactly what it was written to do, using a test vector that has been valid for over a decade. What changed is that bcrypt 5.0.0 now treats a 255-byte input as an error rather than something to hash, which turns a passing self-test into an uncatchable one. The [pyca/bcrypt discussion in issue #1082](https://github.com/pyca/bcrypt/issues/1082) covers the collision between the two libraries. ### Why the `__about__` patch does not fix it Search this error and you will be told, over and over, that bcrypt removed `__about__` and that restoring it repairs passlib. Both halves of that are wrong, and the measurement shows it: | Version | `hasattr(bcrypt, "__about__")` | Prints trapped warning | passlib works | |---|---|---|---| | bcrypt 5.0.0 | `False` | Yes | No (ValueError) | | bcrypt 4.3.0 | `False` | Yes | Yes | bcrypt 4.3.0 has no `__about__` either. It prints the same `(trapped) error reading bcrypt version` line. And passlib runs on it without complaint. The missing attribute is therefore not the dividing line between working and broken. The `ValueError` behaviour change in 5.0.0 is. Which means the popular patch cannot work, and it does not: ```python import bcrypt, types bcrypt.__about__ = types.SimpleNamespace(__version__=bcrypt.__version__) # before importing passlib from passlib.hash import bcrypt as pl pl.hash("short-password") # still ValueError: password cannot be longer than 72 bytes, ... ``` We ran this in a clean process, with the patch applied before `import passlib`, precisely so nobody can attribute the failure to import ordering. It still fails. All the patch achieves is silencing a harmless warning. The 255-byte probe in step 4 is a separate stage that never consulted `__about__` in the first place, and it detonates either way. ## What bcrypt 5.0 actually changed The bcrypt 5.0 breaking change is one line of behaviour with a large blast radius: | Input | bcrypt 4.3.0 | bcrypt 5.0.0 | |---:|---|---| | 72 bytes | OK | OK | | 73 bytes | OK (silently truncated) | `ValueError` | | 100 bytes | OK (silently truncated) | `ValueError` | | 255 bytes | OK (silently truncated) | `ValueError` | The truncation in the 4.x column is not a figure of speech. Under 4.3.0, `hash(73 bytes)` and `hash(100 bytes)` built from the same prefix come out equal: `true`. So bcrypt 5.0 is the more correct library here. Quietly discarding key material is a worse outcome than refusing to proceed, and refusing to proceed is what a hashing library should do when it cannot honour the input it was given. That does not make the upgrade painless. Code that was silently losing bytes for years now throws, and if that code path sits behind passlib, it throws before your input is even involved. The damage from that table lands differently depending on how you call bcrypt. If you call it directly, the upgrade is visible: you get an exception on registration or login, at a code location you own, with a stack trace pointing at your own hashing call. Add a byte-length check in front of it and you are done in an afternoon. If you go through passlib, the upgrade is invisible until it is total. The failure is not proportional to how many of your users have long passwords, because it does not depend on user input at all. Every hash and every verify in the process fails, from the first call onward, on a codebase where nothing about password handling changed. That is why this shows up as a deployment incident rather than a bug report, and why the error text sends people looking in exactly the wrong place. ## Fixing it ### If you can change the code Drop passlib and call bcrypt directly. passlib's last release was 1.7.4 and the project has been quiet for a long time, so the layer buys you very little on a project that only needs bcrypt: ```python import bcrypt password = "correct horse battery staple".encode("utf-8") hashed = bcrypt.hashpw(password, bcrypt.gensalt(rounds=12)) bcrypt.checkpw(password, hashed) # True ``` `hashpw` and `checkpw` both take bytes, so encode at the boundary and keep the rest of your code working in `str`. There is no backend detection and no self-test probe, so nothing can fail on a password you never supplied. If you want to eyeball the resulting hash, or verify one your app produced, the [bcrypt generator](/tools/bcrypt-generator) runs entirely in your browser. Servers using bcrypt for HTTP Basic Auth have the same structural constraint in a different file format, which [the htpasswd guide](/blog/htpasswd-http-basic-auth-guide) walks through. ### If you cannot change the code today Pin below 5: ``` bcrypt<5 ``` We verified bcrypt 4.3.0 with passlib 1.7.4 and it works. It is a tourniquet rather than a repair, though. You are staying on a version whose behaviour with a bcrypt long password is to silently discard bytes, which is the exact problem 5.0 was released to stop. Put a date on the pin and plan the move. ### If your users really do type long passphrases Hash the password once with SHA-256 first, base64-encode the digest, then feed that to bcrypt: ```python import base64, hashlib, bcrypt def prehash(password: str) -> bytes: return base64.b64encode(hashlib.sha256(password.encode("utf-8")).digest()) hashed = bcrypt.hashpw(prehash(pw), bcrypt.gensalt(rounds=12)) bcrypt.checkpw(prehash(pw), hashed) ``` The output is always 44 bytes, comfortably under 72, whatever the input length. And it restores the property truncation destroyed: the two 82-byte passwords from the opening section, run through this, give `checkpw(prehash(p2), hash(prehash(p1)))` = `False`. The collision is gone. The base64 step is doing real work, so do not drop it. A raw SHA-256 digest is arbitrary binary and can contain NUL bytes, which bcrypt implementations handle inconsistently. base64 gives you a NUL-free ASCII string of fixed length. Apply the same function on registration and on login, or every existing hash stops verifying. ### What not to do The `__about__` monkey patch does not work. Section 4 has the measurement. If someone on your team is about to paste it in, that table will save them an afternoon. Truncating with `pw[:72]` yourself is worse than doing nothing. It converts a loud failure back into a silent one, and it recreates the collision from section 2 in your own code. You would be hand-rolling the behaviour bcrypt 5.0 now refuses to perform, and unlike the library, your version will never warn anyone. If you need long passwords to work, pre-hash. If you do not, validate the byte length and reject with a clear message. ## What about hashes already in your database ### Which rows are affected Only accounts whose owners registered with a password over 72 bytes. For most consumer products that is a small set, and for anything ASCII-only it usually means passphrase enthusiasts. For products with users typing Chinese, Japanese, or emoji, section 3 applies and the affected set can be much larger than a byte-blind audit suggests. You cannot identify these rows from the hashes. A bcrypt digest is fixed-width and carries no record of how long its input was. If you logged password length at registration, that log is your only inventory. Most teams have not, and reconstructing it after the fact is not possible, so plan around not knowing rather than around a list. ### You cannot recompute in bulk There is no plaintext to re-hash, which is the entire point of storing hashes. So the migration has to be lazy: upgrade each account the next time its owner successfully authenticates, while you briefly hold the plaintext in memory. ```python def login(user, password: str) -> bool: if not verify_legacy(password, user.password_hash): return False if needs_rehash(user.password_hash): user.password_hash = hash_new_scheme(password) save(user) return True ``` Verify with the old scheme first, and only then re-hash. Reversing those two steps rewrites the stored hash before you have confirmed the password was correct. Store a scheme identifier alongside each hash so `needs_rehash` is a field comparison rather than a guess, and expect a long tail of dormant accounts that never log in. Those you handle at password reset, not by force. ### When a full migration is worth it If you are already writing the lazy-rehash path, that is the cheapest moment you will ever get to change the algorithm underneath it. The 72-byte ceiling does not exist in Argon2id, and the [in-depth comparison of Argon2id and bcrypt](/blog/bcrypt-vs-argon2-vs-scrypt-password-hashing) covers when the switch pays for itself and when staying on bcrypt is the right call. The [OWASP Password Storage Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html) is the reference to check your parameters against. Do not start a migration solely because of this error. If your passwords are comfortably under 72 bytes, bcrypt remains a sound choice and section 6 already fixed your problem. ## FAQ ### Why does bcrypt say my password is longer than 72 bytes when it is short? Because the bcrypt error is about passlib's internal probe, not your password. On the first call, passlib runs `detect_wrap_bug` with a fixed 255-byte test string. bcrypt 5.0.0 raises `ValueError` for anything over 72 bytes, so the probe fails and the error surfaces at your call site. A 14-byte password triggers it. ### Does bcrypt really ignore everything after 72 bytes? Yes — bcrypt ignores every byte after 72, completely. Two 82-byte passwords sharing their first 72 bytes produce the identical hash `$2a$10$abcdefghijklmnopqrstuu.hioaszd4nGKdJlcuRzR1xqPIcN/X.S`, and each verifies against the other's hash. The boundary is exact: a difference at byte 72 changes the hash, a difference at byte 73 does not. ### Is the 72-byte limit a security problem? bcrypt's 72-byte limit is a problem for long passphrases. Anyone who knows the first 72 bytes can append arbitrary bytes and authenticate, so every byte past the limit adds nothing. For passwords under 72 bytes it changes nothing at all. Pre-hashing with SHA-256 removes the exposure if long inputs must count fully. ### How many characters is 72 bytes? 72 bytes is 72 ASCII letters, but it depends on encoding: 36 Cyrillic or umlaut characters, 24 Chinese Han characters, 24 Japanese kana, or 18 emoji. bcrypt counts UTF-8 bytes rather than characters, so measure with `len(pw.encode("utf-8"))` in Python or `Buffer.byteLength(pw, "utf8")` in Node. ### Does patching `__about__` fix the passlib error? No. Patching `__about__` does not fix the passlib bcrypt error: we applied the patch before `import passlib` in a clean process and the `ValueError` still fired. bcrypt 4.3.0 also lacks `__about__` and works fine with passlib, which proves the missing attribute is not the cause. The patch only silences the `(trapped) error reading bcrypt version` warning. ### Should I downgrade bcrypt to below 5.0? As a stopgap, yes: bcrypt 4.3.0 with passlib 1.7.4 works. But 4.x silently truncates anything past 72 bytes, which is the behaviour 5.0 was released to stop, so treat the pin as temporary and move to calling bcrypt directly. ### Can I just truncate the password to 72 bytes myself? Do not truncate a password to 72 bytes yourself. `pw[:72]` recreates the collision described above inside your own code, silently, with no library warning to catch it. Either pre-hash with SHA-256 and base64 so long inputs stay distinct, or validate the byte length up front and reject with a clear error message. ### What happens to passwords already hashed before I fixed this? Existing bcrypt hashes keep verifying, because your verify path truncates the same way the hash path did. Only accounts registered with over-72-byte passwords are weakened, and you cannot recompute them without plaintext. Re-hash lazily on the next successful login, and handle dormant accounts at password reset. --- ### bcrypt vs Argon2 vs scrypt: password hashing in 2026 URL: https://go-tools.org/blog/bcrypt-vs-argon2-vs-scrypt-password-hashing Compare bcrypt, Argon2id, and scrypt against OWASP 2026 parameters, with a decision guide and code samples for picking a password hash. # bcrypt vs Argon2 vs scrypt: Password Hashing in 2026 **Short answer:** for any new project in 2026, use **Argon2id** with `m=19456, t=2, p=1`. That matches the [OWASP Password Storage Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html) baseline, and it gives you the best GPU and side-channel resistance you can ship today. If Argon2 isn't in your stack (rare, but it happens on some embedded or older runtimes), pick **scrypt** with `N=2^17, r=8, p=1`. Use **bcrypt** with `cost=12` only when you're stuck with a legacy system that already speaks bcrypt and you can't add a new dependency. Stick to **PBKDF2-HMAC-SHA-256 with 600,000 iterations** when FIPS-140 compliance is mandatory. | Algorithm | OWASP 2026 parameters | When to pick | |-----------|----------------------|--------------| | Argon2id | `m=19456 KiB, t=2, p=1` | Default for new projects | | scrypt | `N=2^17, r=8, p=1` | Argon2 not available | | bcrypt | `cost=12` (min 10) | Legacy systems only | | PBKDF2 | HMAC-SHA-256, 600k iterations | FIPS-140 required | The rest of this article explains why these numbers, how to tune them for your hardware, and how to migrate without forcing a password reset. If you need strong test passwords for benchmarking, use the [random password generator](/tools/random-password-generator). For the broader picture, see the [web security best practices guide](/blog/security-best-practices). ## Why password hashing is different from general hashing Hash functions look the same from the outside: data goes in, a fixed-length digest comes out, and you can't reverse it. But the design goals for "hash this 4 GB ISO" and "hash this 12-character password" pull in opposite directions. One should run as fast as silicon allows. The other should run as slow as your login latency budget tolerates. Mixing them up is how breaches turn into account takeovers. ### Why MD5 and SHA-256 fall short for passwords General-purpose hashes like MD5, SHA-1, and SHA-256 were built for throughput. They process gigabytes per second on commodity CPUs and tens of gigabytes per second on GPUs. That makes them excellent for file checksums and content addressing, and disastrous for passwords. Hashcat benchmarks on a single RTX 4090 show roughly **164 GH/s for MD5** and **22 GH/s for SHA-256** in 2024. An eight-character lowercase-alphanumeric password (36^8 ≈ 2.8 × 10^12 candidates) falls to a single GPU in under a minute against MD5 and under a couple of minutes against SHA-256. A breached database storing `sha256(password)` is basically plaintext. Salt won't save you either. It blocks pre-computed rainbow tables, but it does nothing to slow down a per-account attack: the attacker just hashes each candidate concatenated with the leaked salt. For non-security checksums, MD5 and SHA-256 still pull their weight; that's what tools like the [general-purpose hash generator](/tools/md5-hash-generator) are built for. For a deeper comparison of when each algorithm is appropriate, read [MD5 vs SHA-256 hash algorithm comparison](/blog/md5-vs-sha256-hash-algorithm-comparison). But for passwords, you need a hash that runs slow on purpose. ### What a modern password hash needs to do A password hash worth shipping in 2026 has three properties: 1. **Slow on purpose, with a tunable work factor.** Login should take 100–500 ms: fast enough that users don't notice, slow enough that an offline attacker burns days per million guesses. The work factor needs to be a parameter so you can crank it up as hardware improves. 2. **Per-record salt.** A unique random salt per password defeats rainbow tables and forces the attacker to attack each account on its own. Modern algorithms generate and embed the salt in the output string for you. 3. **Memory-hard.** GPUs and ASICs are fast at compute but expensive at high-bandwidth memory. An algorithm that requires tens of MiB per hash forces an attacker to provision RAM proportional to their parallelism, killing the cost-effectiveness of GPU farms. bcrypt nails (1) and (2) but not (3). scrypt was the first algorithm to hit all three. Argon2 refined the design and won the Password Hashing Competition. The next section walks through each one. ## The three algorithms: architecture and tradeoffs ### bcrypt: Blowfish-based, time-hard bcrypt was designed in 1999 by Niels Provos and David Mazières for OpenBSD. It's built on the Blowfish cipher, with an expensive key-setup phase ("EksBlowfish") repeated 2^cost times. The single tunable parameter is the **cost factor** (also called the "log rounds"): each increment doubles the work. A `cost=10` hash does 1,024 key schedules; `cost=14` does 16,384. A bcrypt hash looks like this: ``` $2b$12$R9h/cIPz0gi.URNNX3kh2OPST9/PgBkqquzi.Ss7KIUgO2t0jWMUW │ │ │ │ │ │ │ └─ 31-char base64 hash │ │ └─ 22-char base64 salt │ └─ cost factor (12) └─ algorithm identifier ($2b$ = bcrypt v2) ``` The format is self-describing: `verify()` reads the cost and salt from the stored string, no separate columns required. The downsides are real. bcrypt's memory footprint is about **4 KiB**, small enough that a high-end GPU can run thousands of bcrypt cores in parallel. And bcrypt **silently truncates input at 72 bytes**. A 100-character passphrase has the same security as its first 72 bytes. The maximum cost is 31, but anything above ~16 starts hurting login latency on commodity hardware. ### scrypt: the memory-hard pioneer scrypt was published in 2009 by Colin Percival for the Tarsnap backup service and standardized as [RFC 7914](https://datatracker.ietf.org/doc/html/rfc7914) in 2016. It introduced the idea of **memory-hardness**: the algorithm fills a large buffer with pseudo-random data, then reads from random positions, forcing any implementation to actually allocate the memory. scrypt takes three parameters: - **N** — CPU/memory cost (must be a power of 2) - **r** — block size in bytes (multiplier on memory and mixing rounds) - **p** — parallelism (independent computations, mostly used to scale CPU time without scaling memory) Memory usage is roughly `128 × N × r` bytes. With OWASP's recommended `N=2^17, r=8`, that's `128 × 131072 × 8 = 134,217,728` bytes, or **128 MiB per hash**. scrypt also doubles as a key derivation function, not just a password hash. You'll find it in cryptocurrency wallets, full-disk encryption, and the original Litecoin proof-of-work. That dual role is convenient when you need both password storage and key derivation in one library. ### Argon2 (id/i/d): Password Hashing Competition winner The Password Hashing Competition ran from 2013 to 2015, evaluating 24 candidate algorithms against memory-hardness, side-channel resistance, and implementation simplicity. Argon2 won. It was standardized as [RFC 9106](https://datatracker.ietf.org/doc/html/rfc9106) in 2021. Argon2 has three variants. The differences come down to how the memory gets addressed during mixing: - **Argon2d** uses data-dependent memory addresses. That gives the best resistance to GPU and ASIC attacks but leaks information through cache-timing side channels. Suitable for cryptocurrency proof-of-work, not authentication. - **Argon2i** uses data-independent addresses. Side-channel safe, but slightly weaker against GPU tradeoff attacks. - **Argon2id** is a hybrid: the first half of the first pass uses Argon2i indexing (side-channel safe), and the rest uses Argon2d indexing (GPU-resistant). RFC 9106 explicitly recommends Argon2id for password hashing, and so does OWASP. Argon2 takes three parameters: - **m** — memory in KiB - **t** — time cost (number of passes over the memory buffer) - **p** — parallelism (number of lanes processed concurrently) An Argon2id hash uses the PHC string format and looks like this: ``` $argon2id$v=19$m=19456,t=2,p=1$c29tZXNhbHQ$RdescudvJCsgt3ub+b+dWRWJTmaaJObG ``` Like bcrypt, all parameters live inside the string, so `verify()` doesn't need a parameter table. ## OWASP 2026 recommended parameters The [OWASP Password Storage Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html) is the canonical reference. The numbers below match its current guidance. They're conservative, sized for a typical web server with a 100–500 ms login latency budget, and you should still benchmark on your own hardware before shipping. ### Argon2id parameters: first choice OWASP's baseline recommendation: **`m=19456 (19 MiB), t=2, p=1`**. If your server has more RAM headroom, you can shift the work between memory and time. RFC 9106 publishes equivalent profiles; OWASP recommends any of these: | memoryCost (m) | timeCost (t) | parallelism (p) | RAM per hash | |----------------|--------------|-----------------|--------------| | 47104 | 1 | 1 | 46 MiB | | 19456 | 2 | 1 | 19 MiB (baseline) | | 12288 | 3 | 1 | 12 MiB | | 9216 | 4 | 1 | 9 MiB | | 7168 | 5 | 1 | 7 MiB | **Tuning rule of thumb.** Pick `m` first based on your peak concurrent-login RAM budget. If you expect 100 simultaneous logins and have 4 GiB to spare, that's 40 MiB per hash. Then increase `t` until a single verify takes 100–500 ms on your production CPU. Leave `p=1` unless you have a specific multi-core reason to change it (most web frameworks already give each request its own thread). ### scrypt parameters: when Argon2 isn't available OWASP's recommendation: **`N=2^17 (131072), r=8, p=1`**, which uses 128 MiB per hash. If 128 MiB per concurrent login is too much for your server, OWASP allows weaker profiles: | N | r | p | RAM per hash | |-----------|---|---|--------------| | 2^17 | 8 | 1 | 128 MiB (preferred) | | 2^16 | 8 | 1 | 64 MiB | | 2^15 | 8 | 1 | 32 MiB | `N` must be a power of two. Increasing `r` raises both memory and CPU work proportionally; increasing `p` raises CPU work without raising per-instance memory. For password hashing, leave `r` and `p` at the defaults and only tune `N`. ### bcrypt: cost factor 10+ for legacy only OWASP no longer recommends bcrypt for new projects, but it's still everywhere: Devise, Spring Security, ASP.NET Identity, and countless homegrown auth systems default to it. If you're stuck with bcrypt, the rules are: - **Minimum bcrypt cost factor: 10.** Below 10, a single GPU finishes a leaked database in days. - **Recommended: 12 to 14**, depending on hardware. On a modern x86 server, `cost=12` takes around 250 ms per hash; `cost=13` takes 500 ms. - Target **100–300 ms per verify** on your production hardware. Benchmark, don't guess. - Remember the **72-byte input limit**. If users can choose passphrases, pre-hash with SHA-256 (see the FAQ). bcrypt's GPU resistance is bounded by its 4 KiB memory footprint. No bcrypt cost factor will ever match Argon2id's memory-hardness, so pick Argon2id when you can. For a practical reference, on a 2024 EPYC server, `bcrypt(cost=12)` runs in roughly 250 ms; on a high-end laptop, closer to 350 ms. If your numbers fall outside 100–500 ms by an order of magnitude, recheck whether your library is actually doing native bcrypt or falling back to a slow JavaScript polyfill (some bundlers strip native dependencies in serverless builds). ### PBKDF2: FIPS-140 compliance path PBKDF2 (RFC 8018) is the algorithm of last resort in security guidance. It's older than bcrypt, it isn't memory-hard, and it falls to GPU attacks faster than any of the three above. But it's the only password-hashing primitive that's **FIPS-140 validated**, which matters for federal government, healthcare HIPAA, and certain financial deployments. When you need PBKDF2, use: - **HMAC-SHA-256** as the PRF (don't use SHA-1; don't use plain SHA-256 without HMAC) - **600,000 iterations** minimum (OWASP 2026 baseline) - **At least a 16-byte random salt per password** If FIPS doesn't apply to you, prefer Argon2id. PBKDF2's fixed-output, fixed-memory design means every dollar of GPU silicon an attacker buys translates directly into more password guesses per second. NIST's [SP 800-63B](https://pages.nist.gov/800-63-3/sp800-63b.html) calls PBKDF2-HMAC "approved" for password hashing but stops short of recommending it over memory-hard alternatives. Read that as: NIST permits PBKDF2 because retiring it would invalidate every legacy government deployment, not because it's the best choice for a greenfield project. ## Decision framework: which algorithm should you pick? ### Comparison table | Dimension | bcrypt | scrypt | Argon2id | PBKDF2 | |-----------|--------|--------|----------|--------| | Memory-hard | No | Yes | Yes | No | | GPU resistance | Medium | High | Very high | Low | | Side-channel resistance | Medium | Medium | High (id) | Medium | | Parameter complexity | 1 (cost) | 3 (N, r, p) | 3 (m, t, p) | 1 (iterations) | | Library maturity | Excellent | Good | Good | Excellent | | Input length limit | 72 bytes | None | None | None | | Standardization | de facto | RFC 7914 | RFC 9106 | RFC 8018 | | OWASP 2026 status | Legacy only | Alternative | **First choice** | FIPS only | ### Use Argon2id by default For a new project (typical web app, modern Node/Python/Go/Rust/JVM stack, no FIPS constraint), **use Argon2id with `m=19456, t=2, p=1`**. You get the best GPU and side-channel resistance available today, an embedded-parameter format that survives library upgrades, and no 72-byte input cap. The library ecosystem is mature: `argon2` on npm, `argon2-cffi` on PyPI, `golang.org/x/crypto/argon2`, the `argon2` crate on crates.io, all maintained and benchmarked. ### When to pick scrypt or bcrypt instead **Pick scrypt when** Argon2 isn't available in your runtime (genuinely rare in 2026; even Cloudflare Workers and Deno have it now), or when you already have a scrypt-based system in production and the migration cost outweighs the security delta. scrypt is still a solid algorithm; it just lacks the side-channel polish of Argon2id. **Pick bcrypt when** you're maintaining a legacy system, you have a hard dependency-minimization requirement (no native code, no extra packages), and the 72-byte input limit is acceptable for your user base. bcrypt has run at internet scale for two decades; its failure modes are documented. **Pick PBKDF2 when** the regulator says so. That's the only reason. If your auditor accepts Argon2id (which a growing number now do for non-FIPS workloads), use Argon2id. ### Common mistakes to avoid Most password-storage breaches in the last decade trace back to a handful of recurring engineering mistakes. None of them are exotic, and all of them get caught by reviewing your auth code with the list below in front of you. - **Hashing passwords with raw SHA-256 or MD5.** This is the single biggest password-storage failure. See [MD5 vs SHA-256](/blog/md5-vs-sha256-hash-algorithm-comparison) for why these are wrong for passwords. - **Reusing a single global salt across all users.** A salt has to be unique per record. Argon2 and bcrypt generate one for you; don't override that. - **Setting hash time below 50 ms.** You traded security for a speed gain no user can perceive. Aim for 100–500 ms. - **Setting hash time above 1 second.** You created a denial-of-service vector against your own login endpoint. Cap at ~500 ms. - **Hashing passwords client-side and sending the digest to the server.** The hash is now the password. Anyone who steals the database can authenticate without ever inverting it. Always hash on the server. - **Storing the algorithm parameters in a separate column.** The PHC string format puts them in the hash for you. Use it. - **Logging passwords or hashes during error handling.** Both belong to the user, not your log aggregator. Scrub them at the request-parsing layer before they reach any logger. - **Treating `verify()` exceptions as authentication failures.** A library that throws on a malformed stored hash should surface the error, not silently fall through to "wrong password." Distinguish between "wrong password" (return 401) and "stored hash is corrupt" (return 500 and page on-call). ## Real-world implementation ### Argon2id in Node.js The `argon2` package (native bindings to the reference implementation) is the canonical choice on Node: ```js import argon2 from 'argon2'; // Hashing on signup or password change const hash = await argon2.hash(password, { type: argon2.argon2id, memoryCost: 19456, // 19 MiB timeCost: 2, parallelism: 1, }); // → '$argon2id$v=19$m=19456,t=2,p=1$$' // Verifying on login const ok = await argon2.verify(hash, candidate); if (!ok) throw new Error('Invalid credentials'); // Detect outdated parameters and re-hash on successful login if (argon2.needsRehash(hash, { type: argon2.argon2id, memoryCost: 19456, timeCost: 2, parallelism: 1 })) { const upgraded = await argon2.hash(candidate, { type: argon2.argon2id, memoryCost: 19456, timeCost: 2, parallelism: 1, }); await db.users.update({ id: user.id }, { password_hash: upgraded }); } ``` The `needsRehash` step is what makes long-term migration painless: every successful login becomes an opportunity to upgrade the stored hash to current parameters, without bothering the user. The same pattern in Python with `argon2-cffi`: ```python from argon2 import PasswordHasher from argon2.exceptions import VerifyMismatchError ph = PasswordHasher(memory_cost=19456, time_cost=2, parallelism=1) # Hash stored = ph.hash(password) # Verify try: ph.verify(stored, candidate) except VerifyMismatchError: raise ValueError('Invalid credentials') # Re-hash on parameter upgrade if ph.check_needs_rehash(stored): stored = ph.hash(candidate) ``` In Go with `golang.org/x/crypto/argon2`: ```go import ( "crypto/rand" "golang.org/x/crypto/argon2" ) func hashPassword(password string) ([]byte, []byte) { salt := make([]byte, 16) rand.Read(salt) hash := argon2.IDKey([]byte(password), salt, 2, 19456, 1, 32) return hash, salt } ``` The Go standard library doesn't ship a PHC-format encoder; if you use the `argon2.IDKey` primitive directly, you have to encode the parameters and salt alongside the hash yourself. Most Go projects use a wrapper like `github.com/alexedwards/argon2id` for that. Rust with the `argon2` crate is similarly idiomatic: ```rust use argon2::{Argon2, PasswordHasher, PasswordVerifier, password_hash::{SaltString, rand_core::OsRng}}; let salt = SaltString::generate(&mut OsRng); let argon2 = Argon2::default(); // Argon2id, m=19456, t=2, p=1 by default let hash = argon2.hash_password(password.as_bytes(), &salt)?.to_string(); // On verify let parsed = argon2::password_hash::PasswordHash::new(&hash)?; argon2.verify_password(candidate.as_bytes(), &parsed)?; ``` In all three runtimes, the produced string is interchangeable: a hash created in Node verifies cleanly in Python or Rust. That cross-runtime compatibility makes Argon2 a safer bet for polyglot architectures than algorithm-specific wrappers. ### bcrypt-to-Argon2id migration pattern You almost never get to wipe the user table and start over. The pattern that actually works is the one used in the [MD5-to-bcrypt section of our hash generator FAQ](/tools/md5-hash-generator): a soft, login-driven upgrade. Add a column to track the algorithm: ```sql ALTER TABLE users ADD COLUMN password_algo VARCHAR(16) NOT NULL DEFAULT 'bcrypt'; ``` On login, dispatch to the right verifier: ```js async function verifyAndMaybeRehash(user, candidate) { let ok; if (user.password_algo === 'argon2id') { ok = await argon2.verify(user.password_hash, candidate); } else if (user.password_algo === 'bcrypt') { ok = await bcrypt.compare(candidate, user.password_hash); if (ok) { // Successful legacy verify → re-hash with Argon2id const newHash = await argon2.hash(candidate, { type: argon2.argon2id, memoryCost: 19456, timeCost: 2, parallelism: 1, }); await db.users.update({ id: user.id }, { password_hash: newHash, password_algo: 'argon2id', }); } } return ok; } ``` Set a sunset window of **6–12 months**. Send a "your password is stored using an outdated method, please log in to upgrade" email at the 9-month mark. After 12 months, accounts still on bcrypt require a forced password reset on next login. Active users migrate transparently; inactive accounts get a one-time friction event. The same pattern works for migrating off scrypt or PBKDF2. The only state you need is the `password_algo` column. ### Pepper, length limits, and encoding pitfalls A few sharp edges that bite real deployments: **Pepper.** A pepper is an application-level secret added to every password before hashing, stored separately from the database (in a KMS, env var, or Hashicorp Vault). If your database leaks but your app secret doesn't, the leaked hashes are unattackable without the pepper. Apply it as an HMAC, not concatenation: ```js import { createHmac } from 'crypto'; const peppered = createHmac('sha256', process.env.PEPPER).update(password).digest(); const hash = await argon2.hash(peppered, { type: argon2.argon2id, /* ... */ }); ``` Rotate the pepper rarely (it requires re-hashing) but do support rotation by versioning it: `PEPPER_V2`, with a fallback to `PEPPER_V1` on verify. **bcrypt 72-byte limit.** If you must use bcrypt and want to support arbitrary-length passwords, pre-hash with SHA-256 and base64-encode (avoiding embedded NUL bytes that bcrypt also handles inconsistently): ```js import { createHash } from 'crypto'; const prepped = createHash('sha256').update(password, 'utf8').digest('base64'); const hash = await bcrypt.hash(prepped, 12); ``` The same `prepped` transformation must run on verify. Document this in your auth code with a giant comment so the next person to touch it knows what's happening. **UTF-8 normalization.** The string `"café"` can be encoded as either `c-a-f-é` (4 codepoints, NFC) or `c-a-f-e + combining acute` (5 codepoints, NFD). They look identical but produce different hashes. Always normalize to NFC before hashing: ```js const normalized = password.normalize('NFC'); ``` This bites mobile keyboards and copy-paste from PDFs more often than you'd expect. **Never pre-hash on the client.** A client-computed hash sent to the server is the new password. Anyone who reads your database can authenticate. Hash on the server, period. JWTs don't change this; see [how to decode JWT tokens](/blog/how-to-decode-jwt-token-guide) for what JWTs do and don't authenticate. **Benchmark on production hardware, not your laptop.** A 13th-gen Intel laptop running Argon2id at `m=19456, t=2, p=1` finishes in roughly 35 ms. The same parameters on a `t3.small` EC2 instance take closer to 180 ms; on a Raspberry Pi 4, over 600 ms. Pick the hardware that will actually run production, time 1,000 verifies, and tune from the median. Login latency variance from cold-start serverless containers is also worth measuring; Lambda cold starts can add 200–800 ms unrelated to hashing. ## FAQ ### What's the difference between password hashing and encryption? Hashing is one-way: you compute a fixed-length fingerprint that can't be reversed to recover the input. Encryption is two-way: with the right key, you can decrypt back to the original. Passwords must be hashed, not encrypted. A server shouldn't be able to recover any user's password, so that a database leak doesn't turn into a credential leak. ### Why can't I just use SHA-256 for passwords? SHA-256 is built for speed. A modern GPU computes 22 billion SHA-256 hashes per second, so an 8-character lowercase password from a leaked database falls in minutes. Password hashes need three properties SHA-256 lacks: slow execution on purpose, per-record salt, and memory-hardness. The tradeoff principle is the same one explained in our [hash generator's "Don't Use MD5 for Security" guidance](/tools/md5-hash-generator), and you can read more about how attackers turn weak hashes into plaintext in [password entropy explained](/blog/password-entropy-explained). ### Is bcrypt still secure in 2026? bcrypt itself hasn't been broken. The Blowfish-based key schedule remains cryptographically sound. What has changed is the threat model: GPUs and ASICs make bcrypt's lack of memory-hardness a meaningful weakness compared to Argon2id. OWASP's 2026 stance is that bcrypt is acceptable for legacy systems with cost ≥ 10, but new projects should pick Argon2id. ### Argon2i vs Argon2d vs Argon2id: which should I use? Use **Argon2id**. RFC 9106 specifies it as the recommended variant for password hashing. Argon2i is data-independent (side-channel safe but weaker against GPU tradeoff attacks). Argon2d is data-dependent (GPU-strong but vulnerable to cache-timing side channels). Argon2id is a hybrid that gets both properties for the price of one. ### How do I choose Argon2id parameters for my app? Start with the OWASP baseline: `m=19456, t=2, p=1`. Then benchmark on your production CPU and adjust: 1. Decide your per-login RAM budget (say, 50 MiB at peak concurrency). 2. Set `m` to that value or below. 3. Run `argon2.hash()` in a loop and measure wall time. 4. Raise `t` until the median sits between 100 and 500 ms. Leave `p=1` unless you've profiled and know multi-lane parallelism helps your runtime. For high-traffic auth servers, biasing toward higher `t` and lower `m` often gives better RAM headroom. ### What's bcrypt's 72-byte limit and how do I handle long passphrases? bcrypt feeds its input into the Blowfish key schedule, which truncates at 72 bytes. A 150-character passphrase has the same security as its first 72 bytes; the rest is ignored. The fix is to pre-hash with SHA-256 (32 bytes) or SHA-512 (64 bytes), base64-encode the digest to avoid NUL bytes, and feed that to bcrypt. Argon2id and scrypt have no such limit; they accept arbitrarily long input directly. ### Can I migrate bcrypt to Argon2 without forcing password resets? Yes. The pattern is: store both algorithms behind a `password_algo` column, dispatch verification to the right library, and on every successful bcrypt verify, immediately re-hash with Argon2id and update the row. Active users migrate silently within their normal login cadence. Set a 6–12 month sunset window for inactive accounts, then force a password reset for any record still on bcrypt. The same pattern works for any algorithm-to-algorithm migration. ### Is PBKDF2 still a good choice in 2026? Only when FIPS-140 compliance forces your hand: typical in federal government, regulated healthcare (HIPAA), and certain financial systems. Use HMAC-SHA-256 as the PRF with at least 600,000 iterations. PBKDF2 isn't memory-hard, so it falls to GPU attacks faster than Argon2id at equivalent latency budgets. If FIPS doesn't apply, pick Argon2id and skip the extra compliance work. --- The 2026 password hashing answer is short: default to Argon2id with OWASP's baseline parameters, fall back to scrypt if Argon2 isn't available, keep bcrypt only where legacy demands it, and reserve PBKDF2 for FIPS-bound systems. Pair the hash with a per-record salt (every modern library handles this automatically), an application-level pepper stored outside the database, and a login-driven re-hash loop that lets you raise work factors as hardware improves. Generate a representative password set with the [random password generator](/tools/random-password-generator), benchmark your verify path against your production CPU, and write the parameters into a constants file so the next engineer knows exactly what to bump in 2028. The full security context (TLS, session management, rate limiting, MFA) lives in our [web security best practices guide](/blog/security-best-practices). --- ### Bitwise Operations Explained: AND, OR, XOR, Shifts, and Masks URL: https://go-tools.org/blog/bitwise-operations-complete-guide Master bitwise operations with hands-on examples: AND, OR, XOR, shifts, two's complement, bitmasks, and feature flags, with code in JS, Python, Go, and C. # Bitwise Operations in Practice: AND, OR, XOR, Shifts, Masks You open a legacy PostgreSQL migration and see `permissions & 0b100`. A colleague ships a feature flag system that packs 32 booleans into a single integer. A Kubernetes subnet calc spits out `192.168.1.0/24` and you need to extract the network address in code. Three situations, one underlying skill: bitwise operations. Most application-layer developers never need to reach for `&` or `^` in a web app, until suddenly they do. This guide walks through the six bitwise operators, two's complement, nine patterns worth memorizing, and the language-specific traps that will bite you (especially in JavaScript). Code is in JS, Python, Go, and C, and every example is runnable. Open our [Base Converter](/tools/base-converter) in another tab. Several sections invite you to type in a number and watch the bit pattern change. ## Why bitwise operations still matter in 2026 High-level languages have not made bitwise operations obsolete. They have just hidden where the operations happen. A few places you are relying on them today, whether you realize it or not: - PostgreSQL row-level security uses a bitmap of ACL privileges (`SELECT`, `INSERT`, `UPDATE`, `DELETE`, ...) packed into an integer. - Linux capabilities replace the old root-or-nothing model with 40+ permission bits you combine with `|`. - JWT algorithm headers encode the hash algorithm in a small field where bit-level comparison is common at the library layer. - Snowflake, ULID, and UUIDv7 pack timestamp, machine ID, and sequence number into a single 64-bit or 128-bit integer using left shifts. - Redis `BITCOUNT` and `BITOP` expose bitwise primitives directly to application code for cardinality estimation and A/B bucketing. - Image processing reads 32-bit RGBA pixels and extracts channels with `&` and `>>`. Bitwise operations remain O(1) at the CPU instruction level. When you pack 32 booleans into one integer, you save 31 bytes of memory, and (more importantly) you can check "any of these 32 flags set" in a single `!= 0` test. ## Binary foundations you need first This guide assumes you already know how binary works. If you need a refresher, read our [Number Base Conversion Guide](/blog/number-base-conversion-binary-hex-octal-guide) first and come back. A quick vocabulary check before we start: - A bit is a 0 or a 1. - A nibble is 4 bits (one hex digit). - A byte is 8 bits. - A word is typically 32 or 64 bits, depending on your CPU. Integers in most languages come in fixed widths: 8, 16, 32, 64. The width matters a lot for bitwise operations because shifts can push bits off the end, and the sign bit sits at the leftmost position of signed integers. Try this now. Open the [Base Converter](/tools/base-converter), enter `170` as decimal, and look at the binary output. You should see `10101010`, an alternating pattern we will come back to several times below. ## The six bitwise operators Every mainstream language gives you the same six operators, sometimes with slight syntax differences. The symbols `&`, `|`, `^`, `~`, `<<`, `>>` work in JavaScript, Python, Go, Rust, C, C++, Java, and C# unchanged. JavaScript adds one extra: `>>>`, the unsigned right shift. ### AND (`&`): bit filter The output bit is 1 only if both input bits are 1. | A | B | A & B | |---|---|-------| | 0 | 0 | 0 | | 0 | 1 | 0 | | 1 | 0 | 0 | | 1 | 1 | 1 | Think of AND as a gate: only bits that are set in *both* operands survive. The most common use is masking, keeping some bits and zeroing others. ```javascript // Extract the low 4 bits (the rightmost nibble) const value = 0b11010110; // 214 const low4 = value & 0x0F; // 0b00000110 = 6 // Check if a number is odd const isOdd = (n) => (n & 1) === 1; isOdd(7); // true isOdd(42); // false ``` ```python # Same in Python value = 0b11010110 low4 = value & 0x0F # 6 def is_odd(n): return (n & 1) == 1 ``` ### OR (`|`): bit setter The output bit is 1 if *either* input bit is 1. | A | B | A \| B | |---|---|--------| | 0 | 0 | 0 | | 0 | 1 | 1 | | 1 | 0 | 1 | | 1 | 1 | 1 | OR combines flags. If you have `READ = 1`, `WRITE = 2`, `EXECUTE = 4`, then `READ | WRITE` is `3`, with both permissions enabled. ```javascript const READ = 0b001; const WRITE = 0b010; const EXEC = 0b100; const rw = READ | WRITE; // 0b011 = 3 ``` ```python READ, WRITE, EXEC = 0b001, 0b010, 0b100 rw = READ | WRITE # 3 ``` ### XOR (`^`): bit toggle The output bit is 1 if the input bits *differ*. | A | B | A ^ B | |---|---|-------| | 0 | 0 | 0 | | 0 | 1 | 1 | | 1 | 0 | 1 | | 1 | 1 | 0 | XOR has three algebraic properties that power some of the cleverest tricks in computer science: - `a ^ a = 0`: anything XOR'd with itself cancels. - `a ^ 0 = a`: XOR with zero is the identity. - `a ^ b ^ a = b`: XOR is its own inverse. The last property is why XOR shows up in parity checks, stream ciphers, and the notorious "find the single non-duplicated number in an array" interview question. CRC checksums are XOR and shifts all the way down; see [why the same bytes give four different CRC-16 results](/blog/crc16-variants-modbus-ccitt-xmodem-guide) for the parameters that separate the variants. To actually compute one, the [CRC calculator](/tools/crc-calculator) runs all 63 catalogued variants at once. ```javascript // Find the one unique number in an array where every other number appears twice const findUnique = (arr) => arr.reduce((a, b) => a ^ b, 0); findUnique([4, 1, 2, 1, 2]); // 4 ``` ```python from functools import reduce from operator import xor find_unique = lambda arr: reduce(xor, arr, 0) find_unique([4, 1, 2, 1, 2]) # 4 ``` ### NOT (`~`): bit inverter Unary `~` flips every bit: 0 becomes 1, 1 becomes 0. ```javascript ~0b00001111 // -16 (JavaScript coerces to 32-bit signed) ~5 // -6 ``` ```python ~5 # -6 ``` ```go // Go uses ^ as unary bitwise NOT, watch out var x int8 = 5 fmt.Println(^x) // -6 ``` The result of `~5` is `-6` in every mainstream language, and this surprises beginners. The reason is two's complement, which we cover in the next section. For now, just know that `~x` equals `-(x + 1)` in any language that uses two's complement for negatives (which is all of them). ### Left shift (`<<`): power-of-two multiplier `x << n` shifts every bit of `x` to the left by `n` positions, filling zeros on the right. Mathematically, this multiplies by 2ⁿ. ```javascript 1 << 0 // 1 (2^0) 1 << 1 // 2 (2^1) 1 << 3 // 8 (2^3) 1 << 10 // 1024 (2^10 = 1 KiB) // Building bit flags const FLAG_ADMIN = 1 << 0; const FLAG_EDITOR = 1 << 1; const FLAG_REVIEWER = 1 << 2; ``` The handy thing about `1 << n` is that it creates a number with a single bit set at position `n`. That bit becomes a flag. Watch out for overflow. In JavaScript, `1 << 31` is `-2147483648` (not `2147483648`) because JavaScript bitwise operators work on 32-bit signed integers. ### Right shift (`>>` vs `>>>`): divide or padded? Right shift moves bits to the right. The question is what fills the vacated leftmost positions. - `>>` (arithmetic right shift) preserves the sign bit. Negative numbers stay negative. - `>>>` (logical or unsigned right shift) fills with zeros. Only JavaScript has this as a dedicated operator. ```javascript -8 >> 1 // -4 (sign bit preserved) -8 >>> 1 // 2147483644 (sign bit treated as a data bit) 8 >> 1 // 4 8 >> 2 // 2 ``` In C, whether `>>` is arithmetic or logical for signed types is implementation-defined. Most compilers do arithmetic, but do not rely on this without checking. Go requires shift amounts to be unsigned integers and treats signed and unsigned types explicitly. Python has no `>>>` because it has no fixed-width integers. ## Two's complement: how computers represent negatives If bits are just 0 and 1, how do you encode `-5`? The answer the world settled on in the 1960s is two's complement, and every modern CPU uses it. The naive approach (reserve one bit for the sign) has two problems. First, you end up with both `+0` and `-0`, which is awkward. Second, addition and subtraction circuits have to check the sign bit, making the hardware more complex. Two's complement solves both. The rule is short: 1. Take the positive binary representation. 2. Flip every bit (that is the "one's complement"). 3. Add 1. Worked example, encoding `-5` in 8-bit two's complement: ``` 5 in binary: 0000 0101 flip all bits: 1111 1010 (this is -6 in two's complement!) add 1: 1111 1011 ← this is -5 ``` Verify with our base converter: input `251` (decimal) into the [Base Converter](/tools/base-converter) with base 10, and the binary output is `11111011`. In an 8-bit signed context, `11111011` is `-5`. In an 8-bit unsigned context, the same bit pattern is `251`. The bits are identical; the interpretation differs. This explains the earlier `~5 = -6` surprise. Bitwise NOT inverts bits, which gives you one's complement. Two's complement is one's complement plus 1. So: ``` ~x = -(x + 1) // identity in any two's complement language ~5 = -6 ~(-3) = 2 ``` For n-bit signed integers, the representable range is `-2ⁿ⁻¹` to `2ⁿ⁻¹ − 1`. An 8-bit signed integer covers `-128` to `127`. A 32-bit signed integer covers roughly `-2.1 billion` to `+2.1 billion`. ## Essential bit manipulation patterns These nine patterns cover maybe 95% of the bit manipulation you will ever write. Memorize them and you will recognize them everywhere in systems code. ### Set a bit: `x | (1 << n)` Turn bit `n` on, leave other bits unchanged. ```javascript let flags = 0b0100; flags = flags | (1 << 0); // 0b0101 ``` ### Clear a bit: `x & ~(1 << n)` Turn bit `n` off, leave other bits unchanged. `~(1 << n)` is a mask with every bit set *except* bit `n`. ```javascript let flags = 0b0111; flags = flags & ~(1 << 1); // 0b0101 ``` ### Toggle a bit: `x ^ (1 << n)` Flip bit `n` regardless of its current state. ```javascript let flags = 0b0100; flags = flags ^ (1 << 2); // 0b0000 flags = flags ^ (1 << 2); // 0b0100 again ``` ### Check a bit: `(x >> n) & 1` Returns 1 if bit `n` is set, 0 otherwise. Equivalent form: `(x & (1 << n)) !== 0`. ```javascript const flags = 0b0101; const isBit2Set = (flags >> 2) & 1; // 1 ``` ### Isolate lowest set bit: `x & -x` Produces a value with only the rightmost `1` bit of `x` kept. The trick works because `-x` in two's complement is `~x + 1`, which flips every bit up to and including the lowest set bit. ```javascript const x = 0b10110100; const lowest = x & -x; // 0b00000100 = 4 ``` This is the core trick inside Fenwick trees (Binary Indexed Trees) for O(log n) prefix sums. ### Count set bits (popcount) Counting the number of `1` bits in an integer. Most languages now have a native function: ```javascript // JavaScript (BigInt or manual) const popcount = (n) => { let count = 0; while (n) { count += n & 1; n >>>= 1; } return count; }; popcount(0b10110100); // 4 ``` ```python # Python 3.10+ (0b10110100).bit_count() # 4 ``` ```go // Go import "math/bits" bits.OnesCount(0b10110100) // 4 ``` ### XOR swap without a temp variable A classic party trick: swap two integers without a third variable. Never use this in production (it is slower than a temp variable and breaks if `a` and `b` alias the same memory location), but it is worth understanding. ```javascript let a = 5, b = 9; a = a ^ b; // a = 5 ^ 9 b = a ^ b; // b = (5 ^ 9) ^ 9 = 5 a = a ^ b; // a = (5 ^ 9) ^ 5 = 9 // a = 9, b = 5 ``` ### Detect power of two: `(x & (x - 1)) === 0` A power of two has exactly one bit set. Subtracting 1 flips that bit off and sets every lower bit. ANDing gives zero only for powers of two (and 0 itself, so guard with `x > 0`). ```javascript const isPow2 = (x) => x > 0 && (x & (x - 1)) === 0; isPow2(16); // true isPow2(17); // false ``` ### Fast oddness check: `x & 1` Faster than `x % 2` in some languages, identical in others after compiler optimization. Worth it in hot loops or when readability does not matter. ```javascript const isOdd = (x) => (x & 1) === 1; ``` ## Bitmask flags in real code The patterns above show up in production code every day. Here are four places you will meet them. ### Feature flags in 32 booleans Instead of a 32-field struct of booleans, pack them into one integer: ```javascript const FLAGS = { DARK_MODE: 1 << 0, NEW_NAV: 1 << 1, AI_SUGGESTIONS: 1 << 2, BETA_EDITOR: 1 << 3, // ... up to 1 << 31 }; let userFlags = 0; userFlags |= FLAGS.DARK_MODE | FLAGS.AI_SUGGESTIONS; // opt in if (userFlags & FLAGS.AI_SUGGESTIONS) { showSuggestions(); } userFlags &= ~FLAGS.DARK_MODE; // opt out ``` This stores 32 booleans in 4 bytes and lets you query any subset with a single AND. Databases love this pattern because it is one column instead of 32. ### Unix file permissions `chmod 755` is bitwise. The three octal digits map to three triples of bits: ``` 7 = 111 (owner: rwx) 5 = 101 (group: r-x) 5 = 101 (others: r-x) ``` Try it: open the [Base Converter](/tools/base-converter), set source to octal, enter `755`, and look at the binary output `111101101`. That is literally how the filesystem stores the permission field. Setting only "group write": ```javascript const perms = 0o755; const withGroupWrite = perms | 0o020; // 0o775 ``` ### IP subnet masking Given `192.168.1.10/24`, extract the network address by ANDing with the mask: ```javascript const ip = 0xC0A8010A; // 192.168.1.10 const mask = 0xFFFFFF00; // 255.255.255.0 (/24) const network = ip & mask; // 0xC0A80100 = 192.168.1.0 ``` If you would rather not do that masking by hand, the [subnet calculator](/tools/subnet-calculator) runs the same AND for you and reports the network address, broadcast address, and usable host range in one pass. ### Packed IDs: Snowflake Twitter's Snowflake packs timestamp, machine ID, and sequence into a 64-bit integer: ``` ┌─ 1 bit ─┬─── 41 bits ───┬─ 10 bits ─┬─ 12 bits ─┐ │ sign │ timestamp │ machine │ seq │ └─────────┴───────────────┴───────────┴───────────┘ ``` Encoding an ID is two shifts and two ORs: ```javascript const id = (BigInt(timestamp) << 22n) | (BigInt(machineId) << 12n) | BigInt(sequence); ``` Decoding is the reverse: right shift and mask. For a full walkthrough of when to pick Snowflake vs ULID vs UUIDv7, see our [distributed ID comparison](/blog/uuid-v4-v7-ulid-snowflake-id-comparison). ## Cross-language gotchas ### JavaScript: the 32-bit coercion trap JavaScript converts operands to 32-bit signed integers before every bitwise operation, then converts the result back to a `Number`. Any value above `2³¹ − 1 = 2147483647` overflows: ```javascript 2147483647 | 0 // 2147483647 (still fine) 2147483648 | 0 // -2147483648 (overflowed!) 4294967295 | 0 // -1 (all bits set, interpreted signed) ``` For 64-bit work, use `BigInt`. It has independent bitwise operators with no width limit: ```javascript (2n ** 40n) | 1n // 1099511627777n ``` ### Operator precedence bugs This is one of the most common real-world bitwise bugs: ```javascript // Buggy: reads as (x & (1 == 0)) because == binds tighter than & if (x & 1 == 0) { /* ... */ } // Correct: parenthesize if ((x & 1) == 0) { /* ... */ } ``` Comparison operators bind tighter than bitwise AND/OR/XOR in C, JavaScript, Python, Go, and most descendants. Parenthesize when in doubt. ### Language comparison table | Language | Width coercion | Negative `>>` | BigInt support | |----------|---------------|---------------|----------------| | JavaScript | Forces 32-bit signed; `>>>` is unsigned | arithmetic | `BigInt` has separate operators | | Python | Arbitrary precision; no fixed width | arithmetic | Native | | Go | Strict; shift amount must be unsigned | arithmetic for signed types | `math/big` | | C/C++ | Follows type; `int`, `unsigned`, etc. | implementation-defined for signed | None built in | | Rust | Strict; panics on overflow in debug | arithmetic for signed types | `u128` / external crates | ### Python's infinite-width twist Python integers have no fixed width, so two's complement logic extends "infinitely" to the left. That is why `~5` is `-6` (not `250` or `65530`): Python treats the result as a negative integer, not a fixed-width bit pattern. If you need wrap-around semantics, mask explicitly: ```python # Simulate 8-bit NOT (~5) & 0xFF # 250 ``` ## Performance reality check in 2026 The common lore is that bitwise operations are "always faster." In 2026, that is half true. Compilers already do the obvious rewrites. Modern optimizers turn `x * 2` into `x << 1` automatically. Writing `x << 1` in application code for speed is cargo-cult performance tuning. It does not help, and it hurts readability. Where bitwise code genuinely wins: - Hot loops in numeric code: popcount, leading and trailing zero counts, bitboard chess engines. - Compact data structures: Bloom filters, roaring bitmaps, Fenwick trees. - Hardware registers and memory-mapped I/O: embedded code, kernels, firmware. - Cryptography primitives: AES, ChaCha20, and SHA are all built from XOR, rotates, and shifts. - Compression and decompression: Huffman coding, run-length, packed integers. - Database engines: bitmap indexes, packed column formats like Parquet dictionary encoding. Where it does not help: replacing `x % 2` with `x & 1` in a business-logic function that runs twice per request. The speedup is unmeasurable; the readability cost is real. The one case where bit manipulation always wins is memory footprint. Packing 32 flags into an `int` saves 31 bytes compared to 32 booleans. At scale (millions of user records, billions of events) that is the difference between a cache-friendly layout and a workload that thrashes L2. ## Quick reference cheat sheet | Operation | Operator | Example | Result | Typical Use | |-----------|----------|---------|--------|-------------| | AND | `&` | `0b1100 & 0b1010` | `0b1000` | Mask/extract bits | | OR | `\|` | `0b1100 \| 0b1010` | `0b1110` | Combine flags | | XOR | `^` | `0b1100 ^ 0b1010` | `0b0110` | Toggle / detect diff | | NOT | `~` | `~0b1100` | `...11110011` | Invert for mask | | Left shift | `<<` | `1 << 3` | `8` | Multiply by 2ⁿ | | Right shift | `>>` | `16 >> 2` | `4` | Divide by 2ⁿ (signed) | | Unsigned right shift (JS) | `>>>` | `-1 >>> 0` | `4294967295` | Treat as unsigned | | Set bit `n` | `\|` | `x \| (1 << n)` | | Turn bit on | | Clear bit `n` | `&` `~` | `x & ~(1 << n)` | | Turn bit off | | Toggle bit `n` | `^` | `x ^ (1 << n)` | | Flip bit | | Check bit `n` | `&` | `(x >> n) & 1` | `0` or `1` | Test bit | | Lowest set bit | `&` `-` | `x & -x` | | Isolate bit | | Is power of 2 | `&` | `x > 0 && (x & (x-1)) == 0` | bool | Test power | ## FAQ ### What's the difference between logical (`&&`) and bitwise (`&`) AND? Logical AND works on whole boolean values and short-circuits, so `false && expr` never evaluates `expr`. Bitwise AND works on individual bits of integers and always evaluates both sides. Use `&&` for conditions, `&` for bit manipulation. ### Why does `~1` equal `-2` in most languages? Bitwise NOT on `1` flips every bit to produce the one's complement. In two's complement integer representation, flipping all bits of `x` gives `-(x + 1)`, so `~1` equals `-2`, `~0` equals `-1`, and `~(-1)` equals `0`. This identity holds in JavaScript, Python, Go, C, Rust, and every other language that stores signed integers in two's complement. ### Is `x << 1` really faster than `x * 2`? Not in practice. Every modern compiler recognizes `x * 2` and emits the same shift instruction at the machine level, so benchmarks show no measurable difference on x86 or ARM. Use `x * 2` for readability; reserve `<<` for cases where you are intentionally thinking in bits, such as building a bitmask or packing structured IDs. ### Does JavaScript support 64-bit bitwise operations? JavaScript does not support 64-bit bitwise operations with the standard `&`, `|`, `^`, `<<`, `>>` operators, because those force operands to 32-bit signed integers before the operation runs. For 64-bit or larger values, use `BigInt` literals such as `1n << 40n`, which give arbitrary-precision bitwise operations with their own matching operators. ### How do I count the number of set bits efficiently? Use your language's built-in: `bits.OnesCount` in Go, `Integer.bitCount` in Java, `.bit_count()` in Python 3.10+, `popcount` intrinsics in C/C++. These map to a single `POPCNT` CPU instruction on modern x86 and ARM. ### When should I use bitmask flags instead of a struct of booleans? Use bitmask flags when you need to store many booleans compactly (databases, network protocols, file formats) or test combinations quickly with a single AND such as `flags & REQUIRED_MASK`. Prefer a struct of booleans when fields have different types, when you need descriptive debug output, or when readability matters more than a few bytes of memory. ### What happens when I shift by more than the bit width? Undefined in C/C++. In JavaScript, the shift count is taken `mod 32`, so `1 << 32` is `1`, not `0`. In Python, there is no width, so `1 << 100` is just a larger integer. Never rely on overshift behavior; mask the shift count yourself if needed. ### Why does Python's `~5` give `-6` instead of `2`? Python integers have no fixed width, so two's complement extends conceptually to infinity. `~5` equals `-(5 + 1) = -6`, same as every other two's complement language. If you want the 8-bit "inverted" value `250`, mask: `(~5) & 0xFF`. ### Is XOR encryption secure? A one-time pad with a truly random key as long as the message is information-theoretically unbreakable. Reusing the same key across messages is catastrophically insecure, and standard XOR "encryption" with a short repeating key is trivially breakable. Real ciphers like AES and ChaCha20 use XOR internally, but as one step among many. ### How do I represent a negative number using two's complement by hand? Write the positive value in binary at the target width, flip every bit, then add 1. Example: `-5` in 8 bits = `00000101` → flip to `11111010` → add 1 → `11111011`. Verify with our [Base Converter](/tools/base-converter) by converting `251` (the unsigned interpretation of `11111011`) and confirming you get `11111011`. ## Related tools and further reading - [Base Converter](/tools/base-converter): type any number and watch the bits - [Number Base Conversion Guide](/blog/number-base-conversion-binary-hex-octal-guide): prerequisite reading on binary, octal, and hex - [UUID v4 vs v7 vs ULID vs Snowflake](/blog/uuid-v4-v7-ulid-snowflake-id-comparison): bit packing in distributed IDs - [Security Best Practices](/blog/security-best-practices): permission bitmaps and their pitfalls --- ### Character & Word Limits 2026: Twitter, SMS, SEO, Instagram Guide URL: https://go-tools.org/blog/character-limits-by-platform-guide Character and word limits 2026 across Twitter, SMS GSM-7/UCS-2, SEO meta, Instagram, and LinkedIn — counting math plus live progress bars for 6 platforms. # Character & Word Limits 2026: Twitter, SMS, SEO, Instagram Guide A **character limit** is the maximum number of Unicode code points a platform accepts in a single field: 280 for a Twitter post, 160 for a single-segment SMS in GSM-7, around 160 for a Google meta description before truncation. The number you care about depends on where you publish and whether your text contains emoji, smart quotes, or CJK characters, all of which change the math. This guide is for social-media writers, SEO specialists, marketing copywriters, SMS senders billed per segment, and developers writing validation that has to match what Twitter, Instagram, or SMS gateways actually count. Jump to the [quick reference table](#quick-reference-every-platforms-character-and-word-limit) for the 25-platform cheat sheet, or check your draft live against six major platforms in the [Word Counter](/tools/word-counter), where progress bars turn red the moment you cross a limit. ## Quick reference: every platform's character and word limit The table below covers the 30+ fields writers and developers run into most often. "Hard limit" is the platform-enforced ceiling; "Visible / above the fold" is what readers see before a truncation point; "Sweet spot" is the empirical range where content performs best. | Platform | Hard limit | Visible / above the fold | Sweet spot | Counts emoji as | |---|---|---|---|---| | Twitter / X post | 280 chars | 280 | 70-100 chars | 1 codepoint | | Twitter / X bio | 160 chars | 160 | — | 1 codepoint | | Twitter / X display name | 50 chars | 50 | — | 1 codepoint | | X Premium long-form | 25,000 chars | — | — | 1 codepoint | | Instagram caption | 2,200 chars | first 125 (then "more") | <125 for hook | 1 codepoint | | Instagram bio | 150 chars | 150 | — | 1 codepoint | | Instagram hashtags | max 30 | — | 5-10 | — | | LinkedIn post | 3,000 chars | first 210 (then "see more") | <1,300 | 1 codepoint | | LinkedIn article | 110,000 chars | — | — | 1 codepoint | | LinkedIn headline | 220 chars | 220 | — | 1 codepoint | | Facebook post | 63,206 chars | ~477 desktop / ~125 mobile | <80 for organic | 1 codepoint | | TikTok caption | 2,200 chars | first ~100 | <150 | 1 codepoint | | YouTube title | 100 chars | 70 (search) | <60 | 1 codepoint | | YouTube description | 5,000 chars | first 100-150 above fold | first 150 for hook | 1 codepoint | | YouTube comment | 10,000 chars | — | — | 1 codepoint | | Reddit title | 300 chars | — | <60 (subreddit-dependent) | 1 codepoint | | Reddit comment | 10,000 chars | — | — | 1 codepoint | | Discord message | 2,000 chars | 2,000 | — | 1 codepoint | | Discord embed description | 4,096 chars | — | — | 1 codepoint | | Slack message | 40,000 chars | — | <2,000 for readability | 1 codepoint | | Pinterest pin description | 500 chars | first 50-60 | <125 | 1 codepoint | | Mastodon toot | 500 chars (configurable) | 500 | — | 1 codepoint | | Bluesky post | 300 chars | 300 | — | 1 grapheme cluster | | Threads post | 500 chars | 500 | — | 1 codepoint | | SEO meta description (Google) | ~160 chars desktop / ~120 mobile | 150-160 | 150-160 | 1 codepoint | | SEO page title (Google) | ~60 chars desktop / ~50 mobile | 50-60 | 50-60 | 1 codepoint | | Open Graph description | ~200 chars before LinkedIn/FB clip | 150-200 | 150-200 | 1 codepoint | | Twitter Card description | 200 chars max | 200 | 150-200 | 1 codepoint | | SMS single segment (GSM-7) | 160 chars | — | — | special — see below | | SMS single segment (UCS-2 / emoji) | 70 chars | — | — | 1 codepoint | | WhatsApp message text | 65,536 chars | — | — | 1 codepoint | | Email subject line | no platform limit | ~60 desktop / ~30 mobile | <50 | 1 codepoint | | Google Ads headline | 30 chars × 15 headlines | 30 each | 30 | 1 codepoint | | Google Ads description | 90 chars × 4 desc | 90 each | 90 | 1 codepoint | | App Store title | 30 chars | 30 | 30 | 1 codepoint | | App Store subtitle | 30 chars | 30 | 30 | 1 codepoint | | App Store description | 4,000 chars | first 252 above fold | 252 hook | 1 codepoint | | Play Store short description | 80 chars | 80 | 80 | 1 codepoint | | Play Store long description | 4,000 chars | first 80 above fold | 80 hook | 1 codepoint | Content above the "sweet spot" line tends to get truncated, downranked, or cropped off the visible card. X Premium long-form and Mastodon (configurable per instance) are the rare exceptions that let you write past 500 characters without penalty. Every count above, except where SMS rules apply, is a Unicode code-point count: one emoji costs 1 character, not 2. To verify a draft against the six most common limits at once, paste it into the [Word Counter](/tools/word-counter); the progress bars catch over-limit text before you hit publish. ## How characters are actually counted (Unicode code points vs UTF-16) Three different tools can hand you three different character counts for the same string. "Character" is not a single thing: it could mean a Unicode code point, a UTF-16 code unit, or a grapheme cluster, and each platform picks one. ### What is a "character": codepoint vs code unit vs grapheme A **codepoint** is a Unicode scalar value: any integer from U+0000 to U+10FFFF that Unicode has assigned to a character or marked as reserved. A **code unit** is the smallest piece of an encoding; UTF-16 uses 16-bit code units, UTF-8 uses 8-bit code units. A **grapheme cluster** is what humans perceive as a single visible character. Sometimes that means one codepoint, sometimes a base codepoint plus combining marks, sometimes a zero-width-joiner sequence like the family emoji 👨‍👩‍👧‍👦 (seven codepoints joined into one visible glyph). For the string `"a🌍👨‍👩‍👧"` the three counts disagree: | Counting method | Result | Used by | |---|---|---| | UTF-16 code units (JS `string.length`) | 10 | Naive JavaScript code | | Unicode code points | 6 | Twitter, Instagram, SMS gateways | | Grapheme clusters | 3 | Bluesky, screen readers, text editors | ### Why `string.length` lies about emoji JavaScript stores strings as UTF-16 internally. Any codepoint above U+FFFF (every emoji, all astral-plane characters) is encoded as a surrogate pair: two 16-bit code units. The `.length` property reports those two units, not one character. ```javascript "🌍".length // 2 (UTF-16 code units) [..."🌍"].length // 1 (codepoints — what Twitter/SMS counts) "🌍".match(/./gu).length // 1 (codepoints via regex with /u flag) ``` The spread operator and the `/u` regex flag both iterate by codepoint, which matches what Twitter, Instagram, and SMS gateways measure against their limits. A validation function that uses raw `.length` will reject tweets that are actually under the cap, or, worse, let through messages your downstream system will reject. ### What about CJK and combining marks Chinese, Japanese, and Korean ideographs are each a single codepoint and count as one character on every platform. Where they get expensive is SMS: any non-GSM-7 character flips the whole message to UCS-2 encoding, dropping the segment limit from 160 to 70 (covered in the next section). Combining marks behave differently. The accented `á` written as `á` is one codepoint; the same `á` written as `a` + `́` (combining acute accent) is two codepoints but one grapheme cluster. Most platforms count by codepoint, so the second form costs one extra character. Bluesky is the visible exception: it counts grapheme clusters, so both forms cost 1. ### Counting in different languages: quick reference ```javascript // JavaScript [...str].length // codepoints Array.from(str).length // codepoints // Python 3 — len() is codepoint by default len(s) // Go — utf8 package utf8.RuneCountInString(s) // Rust — chars() iterates codepoints s.chars().count() // Java — codePointCount s.codePointCount(0, s.length()) ``` For comparison, the [Base64 encoder](/tools/base64-decode-encode) reminds you of the other direction: when text is encoded to Base64 for transmission, every 3 bytes of UTF-8 input become 4 ASCII output characters, so the encoded length depends on the byte count, not the codepoint count. Paste a single emoji and watch the Base64 output expand to 8 characters; the same emoji that costs 1 character on Twitter takes 4 bytes in UTF-8. To see codepoint counts (the number Twitter actually measures) on any draft, the [Word Counter](/tools/word-counter) is Unicode-correct by default. ## SMS character limit: GSM-7, UCS-2, and multi-part messages SMS is the only major channel where adding a single emoji can literally double your bill. The reason is encoding, and the math has been the same since 1985. ### The 160-character magic number: GSM-7 history The 1985 GSM-03.38 standard fixed an SMS payload at 140 bytes. With a 7-bit character encoding, 140 bytes hold 1,120 bits ÷ 7 = 160 characters. That's where the famous **sms character limit** of 160 comes from. The GSM-7 character set covers 128 base characters plus a 10-character extension (covering `{ } [ ] | \ ~ ^ €` and form feed). Inside that set you get the full 160-char budget per segment. Characters that fall **outside** GSM-7 and force a switch: - All emoji - Curly / smart quotes (`"` `"` `'` `'`); note these are different from the ASCII straight quotes `"` `'` - Most accented Latin letters beyond the 35 in GSM-7 (`é á ñ ü ø` etc.; GSM-7 includes only `ä ö å æ ø à è ì ò ù` and a few others) - Full-width punctuation, CJK characters, Arabic, Hebrew, Greek lowercase, Cyrillic - Backtick `` ` `` and tilde `~` (the tilde is in the GSM-7 extension table, so it costs 2 of your 160 chars) ### UCS-2 trap: one emoji drops you from 160 to 70 The moment a single non-GSM-7 character appears anywhere in the message, the entire message switches to UCS-2 encoding. UCS-2 uses 16 bits per character, so 140 bytes ÷ 2 = **70 characters per segment**. Some real examples: ``` "Hello, your code is 12345" → 26 chars, GSM-7, 1 segment "Hello, your code is 12345 ✓" → 28 chars, GSM-7 (✓ in extension), 1 segment "Hello, your code is 12345 ✅" → 28 chars, UCS-2 (emoji), 1 segment (under 70) "Hello, "your" code is 12345 ✅" → smart quotes + emoji → UCS-2 "Hi 你好" → CJK → UCS-2, 1 segment (5 chars) ``` That last "Hi 你好" example is the gotcha: it's only 5 characters but it eats UCS-2 pricing and the next 65 characters you add will fit in one segment, then segment 2 starts. ### Multi-part SMS segments (concatenation) Once you cross 160 (GSM-7) or 70 (UCS-2), the message splits into multiple segments. Each segment carries a 7-character User Data Header (UDH) used for reassembly, so the available payload per segment drops: - GSM-7 multi-part: **153 characters per segment** - UCS-2 multi-part: **67 characters per segment** The receiving phone reassembles the segments invisibly to the recipient, but **billing is per segment**, not per message. A 161-character GSM-7 message costs 2 segments. A 1,000-character GSM-7 message costs 7 segments (153 × 6 = 918, 7th segment carries the last 82). ### Cost math: when one emoji doubles your bill Take an 80-character plain-text marketing message: - Plain text: 80 chars → GSM-7 → 1 segment at price X - Add one emoji: 80 chars → UCS-2 → 80 > 70 → 2 segments at price 2X Doubling the bill from one emoji is real and it scales. A campaign of 100,000 messages at $0.0075 per segment costs $750 in GSM-7 vs. $1,500 in UCS-2, a $750 emoji. Every major SMS provider (Twilio, Bandwidth, AWS SNS, MessageBird, Vonage) bills this way. The encoding rules are GSM standard, not vendor policy. The history of byte-level encoding tradeoffs, and why ASCII / UTF-8 / UCS-2 even exist as separate standards, is covered in [Understanding Base64](/blog/understanding-base64), which is the same family of "bits into characters" problem applied to email instead of SMS. ### How to keep messages in GSM-7 - Use ASCII straight quotes `"` `'`, not smart quotes - Use ASCII hyphen `-`, not em-dash `—` or en-dash `–` - Spell out `(c)` and `(R)`, not `©` and `®` - Avoid emoji unless the campaign budget assumes UCS-2 cost - Provider consoles (Twilio's, Bandwidth's, MessageBird's) show "encoding: GSM-7" or "UCS-2" next to the preview; verify before broadcast The fastest sanity check during drafting is the [Word Counter](/tools/word-counter)'s SMS progress bar, which reports against the 160-char baseline. If your text triggers UCS-2, mentally divide your character count by 2.29 to estimate the segment count under the 70-char rule. ## SEO limits: meta description, title tag, OG, Twitter Card SEO character limits are softer than platform limits (Google won't reject your page if a meta description hits 300 characters), but the practical truncation rules matter for click-through rate. The numbers below still apply in 2026. ### Meta description: 150-160 character sweet spot Google's desktop search results truncate the meta description around 155-165 characters; mobile clips somewhere between 100 and 120. The exact truncation point varies because **Google measures display pixels, not characters**. A description full of `W` and `M` glyphs hits the truncation pixel earlier than one full of `i` and `l`. Practical writing rules: - Target 150-160 characters total - Put core message in the first 120 characters (mobile-safe) - Lead with the **meta description character limit** keyword for the page in the first 30 characters - End with a CTA in the last 30 characters, readable even when desktop cuts the middle The 2017-2018 era saw Google briefly expand meta description display to 320 characters, and a generation of SEO tutorials still cites that number. Google reverted to 160 in mid-2018. Writing past 200 characters today just hides the second half. A different failure mode: descriptions under 120 characters often get replaced entirely. Google decides your description doesn't fully serve the query and pulls a different passage from the page body, so you lose CTR control without warning. ### Title tag: 60 desktop, 50 mobile Title tags clip at roughly 60 characters on desktop and 50 on mobile. Same pixel-based truncation as descriptions, same caveat about wide glyphs. Sweet spot: 50-60 characters, with the target keyword in the first 30 so it survives any clip. Long-tail brand suffixes (`| Brand Name`) belong at the end, where truncation is least painful. ### Pixel-width vs character-count: Google's actual rule Google's SERP description container is roughly 920 pixels wide on desktop. Average character width sits around 6.5 pixels, yielding the 140-160 character empirical target. But the per-character spread is wide: `i` renders at about 3 pixels, `M` at about 11. A description of all-caps copy ("BEST WIDGETS FOR WINTER WEDDINGS") clips substantially earlier than a lowercase equivalent. Pre-publish previews using pixel-accurate SERP simulators are more reliable than character counters for SEO copy. ### OG description and Twitter Card description The Open Graph protocol's `og:description` is what Facebook, LinkedIn, Slack, and Discord render under a shared link preview. Display caps vary by platform: most clip around 200 characters, some extend to 300. The Twitter Card `twitter:description` is hard-capped at 200 characters in Twitter's parser. Sensible defaults: - 150-200 characters for both OG and Twitter Card - They can match your meta description, but OG can run slightly longer because OG length doesn't affect search ranking - Validate your structured-data choices (especially what gets pulled into OG by mistake) using the patterns in [Security Best Practices](/blog/security-best-practices), where untrusted OG metadata is a common phishing vector ### What "no character limit" actually means H1 tags, body content, and URL slugs have no platform-enforced SEO character limit, but soft limits still apply: - H1 > 70 characters breaks visual hierarchy and skim-ability - URL slugs technically unlimited; Google displays around 90 characters in the SERP, anything beyond is cosmetic - Body content has no length cap, but Google ranks helpful content over padding, so word count alone is not a ranking signal The [Word Counter](/tools/word-counter) tracks both meta description (160) and title tag (60) live as you draft, with progress bars that turn amber and red as you approach the truncation pixel. ## Social platforms: Twitter/X, Instagram, LinkedIn, Facebook, and beyond Each platform's character ceiling has a story behind it and a sweet spot below the hard limit where content actually performs. ### Twitter / X: 280, premium 25,000, URL substitution rule The standard **twitter character limit** is 280 characters, doubled from 140 in November 2017. X Premium subscribers can post long-form content up to 25,000 characters with rich formatting, but the 280-char post is still the dominant form for organic reach. The non-obvious rule is URL substitution. Twitter wraps every URL, no matter how long, in a 23-character `t.co` short link at publish time. The 23-character cost is fixed. ``` published_length = raw_length − URL_length + 23 ``` Example: a draft like `"Check this: https://example.com/very-long-path?id=12345"` is 53 raw characters. The URL is 38 characters, so it gets replaced with a 23-char `t.co` link, and the published length is 53 − 38 + 23 = 38 characters. Save 15 characters you didn't know you had. For pasting a long URL into a draft, the [URL encoder/decoder](/tools/url-decoder-encoder) is a quick way to verify what counts as a URL (Twitter recognizes URLs by RFC 3986 patterns, query strings and fragments included). Subdomains, schemes, ports, paths, queries, and fragments are all swallowed by the 23-character substitution. Other Twitter fields: display name 50 chars, bio 160 chars, handle 15 chars. Threads (Meta's Twitter equivalent) uses a 500-character limit instead. ### Instagram: 2,200 caption, 30 hashtags, 125-char hook Instagram captions allow 2,200 characters, but the feed only shows the first **125 characters** before collapsing the rest behind a "... more" tap. More than half of readers never tap. The **instagram caption limit** that matters for engagement is therefore 125, even though the hard limit is 2,200. The 30-hashtag cap is hard, and attempting a 31st hashtag fails the post. The 5-10 hashtag range tends to perform best; beyond 11 the discovery boost flattens and the post starts looking like spam to the algorithm. Other fields: bio 150 chars, display name 30 chars, DM 1,000 chars. ### LinkedIn: 3,000 post, 1,300 sweet spot, "see more" fold The **linkedin character limit** for posts is 3,000, but feed displays only the first 210 characters before the "see more" fold. Posts in the 1,200-1,500 character range win engagement on LinkedIn (multiple Buffer and Hootsuite studies converge on around 1,300 as the peak); they're long enough to demonstrate value, short enough not to wear out the scroll. LinkedIn Articles (the long-form publishing surface) allow 110,000 characters, which is effectively unlimited. Profile headlines cap at 220, about-section text at 2,600. ### Facebook: 63,206 chars, 80-char organic sweet spot Facebook's 63,206-character post limit is mostly trivia; in practice posts under 80 characters get about 30% higher organic engagement than longer ones (HubSpot consistently reports this across years). Above the fold, desktop shows about 477 characters; mobile cuts at around 125. Comment max is 8,000 characters. Reactions, shares, and click-throughs all skew toward shorter posts, so long copy belongs in the linked article, not the Facebook caption. ### Newer platforms: Bluesky, Mastodon, Threads, TikTok - **Bluesky** posts cap at 300 characters and are the unusual case: Bluesky counts grapheme clusters, so the seven-codepoint family emoji 👨‍👩‍👧‍👦 costs 1 character, not 7 - **Mastodon** defaults to 500 characters per toot, but instance admins can raise this to 5,000 or even unlimited; check the instance you're posting from - **Threads** uses Twitter-style 500-character limits with codepoint counting - **TikTok** captions allow 2,200 characters with about 100 shown above the fold ### Reddit, Discord, Slack: long-form and community defaults - **Reddit** title 300 characters (subreddit moderators often enforce <60 via AutoModerator); comments 10,000 characters - **Discord** standard message 2,000 characters; embed descriptions 4,096; Nitro raises to 4,000 on plain messages - **Slack** message 40,000 characters; above 2,000 readability drops sharply and many recipients ignore long messages ## Word count targets by content type Character limits dominate social and SEO; word counts dominate everything else: academic work, billing, content marketing, manuscripts. The table below gives a target range and a reading-time estimate (230 wpm, the Brysbaert 2019 silent-reading meta-analysis median) for each common content type. | Content type | Word target | Reading time @ 230 wpm | Notes | |---|---|---|---| | Tweet | 30-40 words | 10 sec | optimize for character, not word | | LinkedIn post (sweet spot) | 170-250 words | 1 min | above the fold | | Instagram caption (hook) | 20-25 words | <10 sec | first 125 chars | | Blog post — short | 500-700 words | 2-3 min | listicle, news, hot take | | Blog post — standard | 1,000-1,500 words | 4-7 min | tutorial, deep guide | | Blog post — long | 2,000-3,000 words | 9-13 min | comprehensive guide | | SEO pillar page | 2,500-5,000 words | 11-22 min | topical authority | | Academic essay (high school) | 500-1,500 words | 2-7 min | varies by assignment | | Academic essay (undergrad) | 1,500-3,000 words | 7-13 min | per assignment | | NaNoWriMo daily | 1,667 words/day | — | 50K words in 30 days | | Novel — short | 50,000-70,000 words | — | YA, mystery | | Novel — standard | 80,000-100,000 words | — | adult fiction | | Conference talk (12 min @ 130 wpm) | 1,500-1,600 words | speaking | rehearse to confirm | | Podcast episode (30 min @ 130 wpm) | 3,900 words | speaking | scripted portion | Reading time is the more useful target unit for content marketing; readers respond to a "5-minute read" label more reliably than to a "1,150 words" label. Word count remains the unit for billing (translation invoiced per source word), platform compliance (NaNoWriMo's 50K, an academic 2,000-word ceiling), and contract terms. The [Word Counter](/tools/word-counter) shows both in real time as you type, plus speaking time at 130 wpm for talks and podcasts. ## 6 counting mistakes that break real apps Six recurring failures seen in shipped code and shipped marketing campaigns. Each one is paired with the symptom, the root cause, and the fix. ### Mistake 1: Using `string.length` for character-limit validation **Symptom:** A user pastes a tweet with three emoji that's actually 270 codepoints. Your front-end validation says 276 and refuses to submit. Or, worse, your code accepts a 285-codepoint draft because the emoji budget cancels out, and Twitter rejects it server-side. **Root cause:** `String.prototype.length` in JavaScript returns UTF-16 code units. Every emoji is a surrogate pair, costing 2 units. Every astral-plane character (math symbols, ancient scripts) does the same. **Fix:** Iterate by codepoint with the spread operator or `Array.from`. ```javascript // ❌ wrong function isUnderTwitterLimit(text) { return text.length <= 280; } // ✅ correct function isUnderTwitterLimit(text) { return [...text].length <= 280; } ``` For deeper regex-based codepoint iteration patterns (including grapheme cluster handling), the [Regex Cheat Sheet](/blog/regex-regular-expression-cheat-sheet-guide) covers the `/u` and `/v` flags and Unicode property escapes. ### Mistake 2: Splitting CJK text on whitespace for word count **Symptom:** A 500-character Chinese article reports as 1 word. The translation quote based on it is off by 500x. **Root cause:** CJK languages don't use word-spaces. `text.split(/\s+/)` returns a single token containing the entire essay. **Fix:** Count each CJK ideograph as one word, which is the convention used by Microsoft Word, Google Docs, and every native CJK word processor. ```javascript function countWordsMixed(text) { const cjk = (text.match(/[一-鿿぀-ヿ가-힯]/g) || []).length; const latin = (text .replace(/[一-鿿぀-ヿ가-힯]/g, ' ') .match(/[A-Za-z0-9]+(?:['’-][A-Za-z0-9]+)*/g) || []).length; return cjk + latin; } ``` The Unicode ranges cover CJK Unified Ideographs (U+4E00 to U+9FFF), Hiragana and Katakana (U+3040 to U+30FF), and Hangul Syllables (U+AC00 to U+D7AF), which are the four blocks Microsoft Word's word-count counts as ideographs. ### Mistake 3: Forgetting Twitter URL 23-char substitution **Symptom:** A draft shows 320 characters in your counter, including an 80-character URL. You spend 10 minutes trimming it, only to realize Twitter would have accepted the original at 263 characters. **Root cause:** Twitter replaces every URL with a 23-character `t.co` link at publish time. Your raw counter doesn't know. **Fix:** Pre-compute published length using `raw − URL_length + 23` for each URL. For drafts containing multiple URLs, sum the corrections. URL detection in published content follows RFC 3986, the same parsing rules the [URL Encoding & Decoding](/blog/url-encoding-decoding-guide) guide walks through. ### Mistake 4: Writing meta description to 320 chars (old guideline) **Symptom:** You crafted a 280-character meta description with the CTA at the end. In Google search results, the description cuts off mid-sentence at character 158 and the CTA never appears. **Root cause:** Between December 2017 and May 2018, Google briefly expanded meta description display to 320 characters. Many SEO tutorials still cite that number. Google reverted to ~160 in mid-2018 and has held there ever since. **Fix:** Write to 150-160 characters. Put the primary keyword in the first 30 characters and the CTA in the last 30. Use a pixel-accurate SERP simulator for high-stakes pages; wide glyphs (`W`, `M`, `K`) eat the budget faster than narrow ones (`i`, `l`, `t`). ### Mistake 5: Confusing 280 characters with 280 words **Symptom:** Someone on the team writes "we need a 280-word tweet" and produces 1,500 characters of perfectly fine prose. The tweet won't post. **Root cause:** Character-versus-word confusion. The two units differ by roughly 5-6x for English prose. **Fix:** Pin the rule per platform. Twitter, SMS, and SEO meta count characters. NaNoWriMo, academic assignments, translation contracts, and most content-marketing briefs count words. When in doubt, check the platform's own counter (Twitter's compose box, Word's Review > Word Count) before locking the spec. ### Mistake 6: Pasting smart quotes that silently switch SMS to UCS-2 **Symptom:** You copy a customer-receipt template from a Google Doc into your SMS sender. The original was 145 characters and shipped as one GSM-7 segment. After paste, it's the same 145 characters but bills as 2 UCS-2 segments. Costs double across a million-message campaign. **Root cause:** Google Docs and Word auto-convert `"` and `'` to typographer's quotes `" "` and `' '`. Those quotes aren't in the GSM-7 character set, which flips the entire message to UCS-2. **Fix:** Normalize before transmit: ```javascript function toGsm7Quotes(s) { return s .replace(/[“”]/g, '"') // " " → " .replace(/[‘’]/g, "'") // ' ' → ' .replace(/[–—]/g, '-'); // – — → - } ``` Run this before billing-sensitive sends. Twilio, MessageBird, and Bandwidth all expose an encoding field on the response; log it and alert when UCS-2 appears in templates you intended as GSM-7. ## FAQ ### What is the difference between character count and word count? Character count counts every character including spaces, punctuation, and emoji, measured by Unicode codepoint on most modern platforms. Word count counts whitespace-separated tokens for Latin scripts and ideograph-by-ideograph for CJK. Twitter, SMS, and SEO meta descriptions use character count. Academic essays, NaNoWriMo manuscripts, and translation invoices use word count. ### Why does Twitter count emoji as 1 character but JavaScript counts them as 2? Twitter measures by Unicode code point, and every emoji is one codepoint, one character. JavaScript's `string.length` measures UTF-16 code units. Most emoji are above U+FFFF and are encoded as surrogate pairs in UTF-16, so they take two code units and `.length` returns 2. Use `[...text].length` or `Array.from(text).length` to get the codepoint count Twitter actually counts. ### Why is the SMS character limit 160 sometimes and 70 other times? SMS uses 7-bit GSM-7 encoding by default, giving 160 characters in a 140-byte payload. If the message contains any non-GSM-7 character (emoji, smart quotes, CJK, accented Latin beyond a small set), the whole message switches to 16-bit UCS-2 encoding and the per-segment limit drops to 70 characters. One emoji anywhere in the message triggers the switch. ### What is the ideal meta description length in 2026? Aim for 150-160 characters. Google's desktop SERP truncates around 155-165 depending on display pixel width; mobile clips between 100 and 120. Below 120 characters Google often replaces your description entirely with a passage from page body. Lead with the primary keyword in the first 30 characters and end with the CTA in the last 30, so the message survives truncation either direction. ### Does character limit include spaces and emoji? Yes, on virtually every platform. Spaces, line breaks, punctuation, and emoji each count as one Unicode codepoint. The two exceptions worth knowing: SMS where emoji trigger the encoding switch described above, and Bluesky which counts grapheme clusters so a multi-codepoint emoji like the family 👨‍👩‍👧‍👦 costs 1 character instead of 7. ### How is word count calculated for Chinese, Japanese, Korean text? Each CJK ideograph counts as one word, the convention used by Microsoft Word's Chinese-mode word count, Google Docs, native CJK editors, and every commercial translation memory system. A 500-character Chinese essay reports as 500 words. Mixed text counts CJK ideographs by character and Latin tokens by whitespace, summing the two. ### How does Twitter handle URL length in the 280-character limit? Twitter automatically wraps every URL in a 23-character `t.co` short link at publish time, regardless of original length. The published length follows the formula `published = raw − URL_length + 23` per URL. A draft of 320 characters containing one 100-character URL ships as 243 characters. Twitter recognizes URLs by RFC 3986 patterns, so query strings and fragments are absorbed into the URL token. ## Related reading - [Regex Cheat Sheet](/blog/regex-regular-expression-cheat-sheet-guide): pattern matching for character validation, Unicode property escapes - [Text Diff Online Guide](/blog/text-diff-online-compare-tool-guide): comparing two pieces of text, line by line and character by character - [URL Encoding & Decoding Guide](/blog/url-encoding-decoding-guide): character escaping rules when text travels through URLs - [Understanding Base64](/blog/understanding-base64): the other half of "bits into characters" encoding, applied to email and binary data --- ### CIDR Notation Explained: Subnet Masks from /8 to /32 URL: https://go-tools.org/blog/cidr-notation-subnet-mask-cheat-sheet-guide A /26 leaves 62 usable hosts, not 64, and on a /31 the minus-two rule breaks. Read any CIDR prefix, do the mask in your head, check it in the calculator. # CIDR Notation and Subnet Masks: How to Read /8 Through /32 The number after the slash in `192.168.1.0/24` counts bits, not addresses. It says how many of the 32 bits in an IPv4 address belong to the network; whatever is left over belongs to the hosts. That one sentence is most of what CIDR notation is. A /24 leaves 8 host bits, so the block holds 2⁸ = 256 addresses. A /26 leaves 6, so it holds 64. Every bit you hand back to the host side doubles the block; every bit you take doubles the number of blocks. The count you can actually assign to devices is normally two lower than the total, because the first address names the network and the last one is the broadcast. A /26 gives you 62 usable hosts, not 64. Two prefixes break that minus-two rule on purpose, and a wildcard mask is not a subnet mask even though the two get pasted into each other's fields all the time. If you only want the answer for one block, the [subnet calculator](/tools/subnet-calculator) prints it; this article is about getting there without one. ## What CIDR notation says A subnet mask is itself a 32-bit number, written the way an address is written. Its bits are a run of 1s followed by a run of 0s. The 1s mark network bits, the 0s mark host bits. Spell a /26 out in full and you get: ``` 11111111.11111111.11111111.11000000 255 . 255 . 255 . 192 ``` Convert each octet back to decimal and that is 255.255.255.192. CIDR notation counts the leading 1s instead of writing all thirty-two of them out. `192.168.1.0/26` and `192.168.1.0 255.255.255.192` are the same statement in two syntaxes, and which one a device wants depends entirely on the command you are typing. /8, /16 and /24 land on octet boundaries, so they look tidy in decimal: 255.0.0.0, 255.255.0.0, 255.255.255.0. Nothing about the notation requires that. /22 and /27 are just as valid. They cut through the middle of an octet and produce masks like 255.255.252.0 that look arbitrary right up until you write them in binary. Before 1993 the leading bits of an address decided its size: class A took a /8, class B a /16, class C a /24, and there was nothing in between. An organisation with 300 hosts had to claim a class B and waste more than 65,000 addresses, or take two class C blocks and carry two routes. CIDR (RFC 1519, later revised as RFC 4632) broke that coupling. The prefix travels with the address, so a block can be any power of two. Classes still turn up in certification exams and in old documentation, but classful routing has been obsolete since CIDR arrived in 1993. The mask decides where a network ends, not the first octet. ## The subnet mask cheat sheet, /8 to /32 The last column of this CIDR to subnet mask chart is deliberate: it shows what the generic `2ⁿ − 2` formula produces, which matches the real usable count everywhere except the bottom two rows. | Prefix | Subnet mask | Wildcard mask | Total addresses | Usable hosts | Naive `2ⁿ − 2` gives | |---|---|---|---|---|---| | /8 | 255.0.0.0 | 0.255.255.255 | 16777216 | 16777214 | 16777214 | | /9 | 255.128.0.0 | 0.127.255.255 | 8388608 | 8388606 | 8388606 | | /10 | 255.192.0.0 | 0.63.255.255 | 4194304 | 4194302 | 4194302 | | /11 | 255.224.0.0 | 0.31.255.255 | 2097152 | 2097150 | 2097150 | | /12 | 255.240.0.0 | 0.15.255.255 | 1048576 | 1048574 | 1048574 | | /13 | 255.248.0.0 | 0.7.255.255 | 524288 | 524286 | 524286 | | /14 | 255.252.0.0 | 0.3.255.255 | 262144 | 262142 | 262142 | | /15 | 255.254.0.0 | 0.1.255.255 | 131072 | 131070 | 131070 | | /16 | 255.255.0.0 | 0.0.255.255 | 65536 | 65534 | 65534 | | /17 | 255.255.128.0 | 0.0.127.255 | 32768 | 32766 | 32766 | | /18 | 255.255.192.0 | 0.0.63.255 | 16384 | 16382 | 16382 | | /19 | 255.255.224.0 | 0.0.31.255 | 8192 | 8190 | 8190 | | /20 | 255.255.240.0 | 0.0.15.255 | 4096 | 4094 | 4094 | | /21 | 255.255.248.0 | 0.0.7.255 | 2048 | 2046 | 2046 | | /22 | 255.255.252.0 | 0.0.3.255 | 1024 | 1022 | 1022 | | /23 | 255.255.254.0 | 0.0.1.255 | 512 | 510 | 510 | | /24 | 255.255.255.0 | 0.0.0.255 | 256 | 254 | 254 | | /25 | 255.255.255.128 | 0.0.0.127 | 128 | 126 | 126 | | /26 | 255.255.255.192 | 0.0.0.63 | 64 | 62 | 62 | | /27 | 255.255.255.224 | 0.0.0.31 | 32 | 30 | 30 | | /28 | 255.255.255.240 | 0.0.0.15 | 16 | 14 | 14 | | /29 | 255.255.255.248 | 0.0.0.7 | 8 | 6 | 6 | | /30 | 255.255.255.252 | 0.0.0.3 | 4 | 2 | 2 | | /31 | 255.255.255.254 | 0.0.0.1 | 2 | **2** | **0** | | /32 | 255.255.255.255 | 0.0.0.0 | 1 | **1** | **−1** | The table is more useful as a set of relationships than as a list of answers. Each row down the table halves the block: /24 holds 256 addresses, /25 holds 128, /26 holds 64. The wildcard column is the subnet mask with every bit flipped, which is why 255.255.255.192 and 0.0.0.63 always appear on the same line. The two bold rows are where the standard formula stops describing reality. Only the last octet values in the mask column need memorising, because they repeat: 128, 192, 224, 240, 248, 252, 254, 255. Those are the only eight non-trivial byte values a valid mask can end with. If you want to see why, the [number base converter](/tools/base-converter) prints any of them in binary. ### Reading the table backwards: from subnet mask to CIDR Given a dotted-decimal mask, count the 1 bits. Every 255 contributes 8, and the one interesting octet contributes the rest: | Last mask octet | 128 | 192 | 224 | 240 | 248 | 252 | 254 | 255 | |---|---|---|---|---|---|---|---|---| | Bits it adds | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | So 255.255.255.192 is 8 + 8 + 8 + 2 = /26. And 255.255.252.0 is 8 + 8 + 6 + 0 = /22, because 252 in binary is `11111100`. Two consequences follow. Any octet that is neither 255 nor 0 is the boundary octet, and a valid mask can only ever have one of them. The value of that octet also gives you the block size directly. ## How to calculate a subnet mask and network by hand The mechanical method has three steps and works for any prefix. The worked example below uses `192.168.1.130/26`, but the point is the procedure, not this address. ### Step 1: find the block size Block size is `256 − the boundary mask octet`. For a /26 the mask is 255.255.255.192, so the block size is `256 − 192 = 64`. Subnets of that size sit on multiples of 64 in the fourth octet: 0, 64, 128, 192. There are no other possible starting points. Block size and address count are the same number, reached from the mask side instead of the bit-count side. ### Step 2: find which block the address falls in Divide the boundary octet of the address by the block size and round down. The address is 192.168.1.**130**, the block size is 64, so `130 ÷ 64 = 2.03…`, which rounds down to `2`. Multiply back: `2 × 64 = 128`. The address sits in the block that starts at 128. Most errors happen here, always in the same direction: people assume the address they were handed is the start of its block, and it usually is not. `192.168.1.130` is a host address living in the third /26 of that /24. ### Step 3: network, broadcast, first and last host The block start is the network address. The broadcast is the block start plus the block size minus one. Everything strictly between them is assignable: ``` Network 192.168.1.128 Broadcast 192.168.1.191 Usable 192.168.1.129 - 192.168.1.190 Usable 62 (of 64 total) Netmask 255.255.255.192 Wildcard 0.0.0.63 ``` Broadcast is `128 + 64 − 1 = 191`. First host is network + 1, last host is broadcast − 1, and the usable count is 64 − 2 = 62, which is what the /26 row of the cheat sheet says. Compressed into something you can say to yourself: **block size is 256 minus the mask octet; round the address down to a multiple of it; that is your network, and the next block start minus one is your broadcast.** The method is not specific to the fourth octet. For a /22 the mask is 255.255.252.0, so the boundary octet is the third and the block size there is `256 − 252 = 4`. Blocks therefore start at 10.0.0.0, 10.0.4.0, 10.0.8.0, and the block `10.0.0.0/22` runs through broadcast 10.0.3.255 with usable addresses 10.0.0.1 to 10.0.3.254 — four consecutive /24s inside one broadcast domain. Same three steps, different octet. If the binary-to-decimal side of this is where you slow down, the [guide to number base conversion](/blog/number-base-conversion-binary-hex-octal-guide) covers the conversion itself in more depth than a subnetting article should. For checking your work in a script rather than in your head, Python's standard library already knows all of this: ```python import ipaddress net = ipaddress.ip_network("192.168.1.130/26", strict=False) print(net) # 192.168.1.128/26 print(net.network_address) # 192.168.1.128 print(net.broadcast_address) # 192.168.1.191 print(net.netmask) # 255.255.255.192 print(net.hostmask) # 0.0.0.63 print(net.num_addresses) # 64 print(len(list(net.hosts()))) # 62 ``` `strict=False` is what lets you pass a host address instead of a network address; with the default `strict=True` the same call raises `ValueError`. ## Where the minus-two rule stops being true The subtraction has a reason behind it. In an ordinary subnet the all-zeros host pattern names the network itself and the all-ones host pattern is the directed broadcast address. Neither can be configured on an interface, so a block of 2ⁿ addresses offers 2ⁿ − 2 to hosts. That is why a /24 gives 254 and a /26 gives 62. The reason is also the limit: when a block is too small to contain those two reserved addresses, subtracting them stops making sense. **A /31 has two addresses and two usable hosts.** RFC 3021 defines /31 for point-to-point links. Such a link has exactly two endpoints and no shared segment, so there is nothing for a broadcast address to do and nothing for a network address to identify. Both addresses go to the two ends. Applying `2ⁿ − 2` here returns 0, because the formula assumes a topology this link does not have. The condition attached is real: /31 is valid only on genuinely point-to-point interfaces. A multi-access LAN segment still needs /30 or shorter, and Windows will not accept a /31 on a NIC. **A /32 has one address and one usable host.** It is a single host route: loopback interfaces, static routes, anycast addresses, single-address firewall rules. There is no broadcast address, so the formula's `− 2` would return −1. Python agrees with both: ```python import ipaddress p2p = ipaddress.ip_network("203.0.113.4/31") print([str(h) for h in p2p.hosts()]) # ['203.0.113.4', '203.0.113.5'] host = ipaddress.ip_network("10.0.0.1/32") print([str(h) for h in host.hosts()]) # ['10.0.0.1'] ``` It is easier to remember this as one rule than as two exceptions: the subtraction removes two specific addresses, so check that they exist before you remove them. A /31 and a /32 have no broadcast address at all, so nothing is removed. ### Total, usable, and number of subnets are three different numbers These three get conflated constantly, and the confusion is understandable because they are all powers of two derived from the same prefix. - **Total addresses** in a /p is `2^(32 − p)`. A /26: 64. - **Usable hosts** is that minus 2, except for /31 and /32. A /26: 62. - **Number of subnets** you get by splitting a parent /p into children /q is `2^(q − p)`. Splitting a /24 into /26s borrows two bits, so `2² = 4` subnets. The three answer different questions, so a sentence like "a /26 gives you 4" is only true if the question was about splitting a /24. Splitting also costs you addresses, because every child subnet reserves its own network and broadcast pair. Four /26s carved out of a /24 hold `4 × 62 = 248` usable addresses against the parent's 254. Six addresses go to the split itself. ## Wildcard mask vs subnet mask: which command wants which A wildcard mask is the bitwise inverse of the subnet mask. Where the subnet mask has 1s, the wildcard has 0s. Take the /26 row of the cheat sheet: mask 255.255.255.192, wildcard 0.0.0.63. Flip every bit of one and you have the other, so they never appear apart. The two are used in opposite senses, and that is where people trip. A subnet mask is applied with a bitwise AND, so a 1 bit means "this bit is part of the network". A wildcard is a match filter, so a 0 bit means "this bit must match" and a 1 bit means "don't care". Same underlying operation, opposite polarity. If the bit-level mechanics are fuzzy, the [guide to bitwise operations](/blog/bitwise-operations-complete-guide) covers AND, OR and NOT in more general terms. Which one a device wants is not a matter of preference. For the block 192.168.1.128/26: ``` ! wants the subnet mask ip address 192.168.1.129 255.255.255.192 ! wants the wildcard mask access-list 10 permit 192.168.1.128 0.0.0.63 network 192.168.1.128 0.0.0.63 area 0 ``` ``` ! Cisco ASA — wants the subnet mask, unlike IOS ACLs access-list OUT permit ip 192.168.1.128 255.255.255.192 any ``` ```bash # Linux iproute2 — takes the prefix directly ip addr add 192.168.1.129/26 dev eth0 ``` Cisco IOS ACLs and OSPF network statements take the wildcard mask; the ASA takes the subnet mask instead. That is one vendor with two conventions in the same product family, and it is the most common copy-paste failure in the area. Other platforms have their own conventions, so before pasting a value into an unfamiliar field, confirm which of the two that field expects. Paste 255.255.255.192 into an IOS ACL and the router reads it as a wildcard: three octets of all-1s mean "don't care", so the first three octets stop being matched at all and the rule reaches far outside the block you had in mind. The router reports no syntax error and logs nothing; you are left with a permit statement that has the wrong scope. The reverse mistake at least has a chance of being caught, because 0.0.0.63 is not a valid subnet mask; it has no leading run of 1s. Whether a given platform rejects it or accepts it quietly is not something to discover in production. ### Why ACL wildcards may have gaps but subnet masks may not A subnet mask must be one contiguous run of 1s followed by 0s. That requirement is what makes the AND operation split an address into exactly two parts. A value like 255.0.255.0 has a hole in it, describes no coherent boundary, and devices reject it. So does Python: ```python import ipaddress ipaddress.ip_network("10.0.0.0/255.0.255.0") # ValueError: '10.0.0.0/255.0.255.0' does not appear to be an IPv4 or IPv6 network ``` Only 33 masks are valid, /0 through /32. Anything else is a typo. ACL wildcards are under no such constraint, because they are not splitting an address into a network and a host part. They are a per-bit match filter, so gaps are legal and occasionally useful. A single wildcard with a gap can match every odd-numbered address in a range, for instance. That difference is why the two values cannot be swapped: they are not the same kind of object, they just happen to look alike in dotted-decimal. ## Private, CGNAT and other reserved ranges A prefix tells you how big a block is. Which block it is tells you whether it is yours to use. | Block | Range | Reserved by | |---|---|---| | 10.0.0.0/8 | 10.0.0.0 – 10.255.255.255 | RFC 1918 private | | 172.16.0.0/12 | 172.16.0.0 – 172.31.255.255 | RFC 1918 private | | 192.168.0.0/16 | 192.168.0.0 – 192.168.255.255 | RFC 1918 private | | 100.64.0.0/10 | 100.64.0.0 – 100.127.255.255 | RFC 6598 carrier-grade NAT | | 169.254.0.0/16 | 169.254.0.0 – 169.254.255.255 | RFC 3927 link-local | | 255.255.255.255/32 | single address | limited broadcast | The RFC 1918 ranges are never routed on the public internet, which is what makes them safe to allocate from. The other three show up for different reasons. `100.64.0.0/10` is carrier-grade NAT space. If your ISP hands you an address in it, you are behind their NAT and no inbound connection will reach you without a tunnel. It is not private space in the RFC 1918 sense and it is not yours to use in an internal plan, because your provider may already be using it on the other side of your router. `169.254.0.0/16` is link-local. A host assigns itself one of these when DHCP fails, so seeing a 169.254 address on an interface is a diagnosis rather than a configuration: nothing answered the DHCP request. Traffic to it never crosses a router. For examples in documentation and runbooks, RFC 5737 reserves 192.0.2.0/24, 198.51.100.0/24 and 203.0.113.0/24 precisely so that a copy-pasted example cannot point at a real host. ### 172.16.0.0/12 is sixteen /16s, not one People get this reserved range wrong because of the prefix. A /12 borrows four bits from the second octet, so the block spans 172.16.0.0 through 172.31.255.255: sixteen consecutive /16s, not just `172.16.x.x`. The consequences run both ways. An address like 172.20.5.1 **is** private, sitting comfortably inside the range, even though it does not start with 172.16. And 172.15.x.x and 172.32.x.x are **public** addresses belonging to somebody else, so a firewall rule or a "trust the internal range" check written against 172.0.0.0/8 quietly trusts a large slice of the internet. If you need to confirm a boundary like this, the cheat sheet gives you the arithmetic: a /12 has `2^(32−12)` addresses, the second octet moves in steps of `256 − 240 = 16`, and 16 + 16 = 32, so the block ends immediately before 172.32.0.0. ## VLSM: splitting one block into unequal subnets Equal-sized subnets are easy and usually wrong. A branch office given a single 192.168.1.0/24 might need four segments with nothing in common: a hundred workstations, fifty phones, a dozen servers, and a handful of management interfaces. Split the /24 into four equal /26s and the workstation segment overflows at 62 hosts, while the management segment sits on 62 addresses to serve ten devices. Variable Length Subnet Masking means giving each segment the prefix it actually needs. The table below carves the same /24 into one /25, one /26 and two /28s. **Allocate from largest to smallest.** Each block must start on a multiple of its own size, so the biggest block gets first pick: | Segment | Hosts needed | Prefix | Network | Usable range | Broadcast | Subnet mask | |---|---|---|---|---|---|---| | Workstations | 100 | /25 | 192.168.1.0 | 192.168.1.1 – 192.168.1.126 | 192.168.1.127 | 255.255.255.128 | | Voice | 50 | /26 | 192.168.1.128 | 192.168.1.129 – 192.168.1.190 | 192.168.1.191 | 255.255.255.192 | | Servers | 12 | /28 | 192.168.1.192 | 192.168.1.193 – 192.168.1.206 | 192.168.1.207 | 255.255.255.240 | | Management | 10 | /28 | 192.168.1.208 | 192.168.1.209 – 192.168.1.222 | 192.168.1.223 | 255.255.255.240 | | *unallocated* | — | /27 | 192.168.1.224 | 192.168.1.225 – 192.168.1.254 | 192.168.1.255 | 255.255.255.224 | Walking it with the three-step method: 1. The /25 has block size `256 − 128 = 128`, so it starts at 0 and its broadcast is `0 + 128 − 1 = 127`. Usable 1 to 126, which is 126 addresses, enough for 100 workstations with room left. The cheat sheet's /25 row agrees: 128 total, 126 usable. 2. The next free address is 128. The /26 has block size 64, and 128 is a multiple of 64, so it fits: network 192.168.1.128, broadcast `128 + 64 − 1 = 191`, usable 129 to 190. That is 62 usable for 50 phones. Cheat sheet /26 row: 64 total, 62 usable. 3. Next free is 192. The /28 has block size 16, and `192 = 16 × 12`, so it fits: network 192.168.1.192, broadcast 207, usable 193 to 206, which is 14 addresses for 12 servers. Cheat sheet /28 row: 16 total, 14 usable. 4. Next free is 208, and `208 = 16 × 13`, so the second /28 lands at 192.168.1.208 with broadcast 223 and usable 209 to 222. That accounts for `128 + 64 + 16 + 16 = 224` of the 256 addresses, leaving 192.168.1.224 through 192.168.1.255. Those 32 addresses happen to be exactly one aligned /27, which is where the point-to-point links would come from later: sixteen /31s fit inside it, one per router link. **Why largest first, and what it costs when you do not.** Suppose you place the two /28s at the bottom instead: 192.168.1.0/28 and 192.168.1.16/28. The next free address is 192.168.1.32, and a /25 must start on a multiple of 128, so it cannot start there. It has to skip forward to 192.168.1.128. The addresses from 32 to 127 are not lost, but they are only usable as smaller aligned pieces (a /27 at 192.168.1.32 and a /26 at 192.168.1.64), so the leftover space ends up scattered instead of sitting in one contiguous block at the top. Add one more segment to the request and the same manoeuvre stops fitting at all. Ordering by size avoids that. After you place a block, the next free address is a multiple of that block's size, and a multiple of a larger power of two is automatically a multiple of every smaller one, so every subsequent, smaller block is aligned wherever the previous one ended. You never have to skip. Before committing a plan like this to a switch, running it through the [subnet calculator](/tools/subnet-calculator) division table is faster than checking the alignment of every segment by hand. ## Five mistakes that survive into production **1. Treating the address you typed as the network address** **Symptom:** a firewall rule matches nothing, or a route covers the wrong half of a segment. **Cause:** `192.168.1.130/26` was read as network 192.168.1.0, broadcast 192.168.1.255: the /24 boundary, because that is the one people see in decimal. **Fix:** apply step 2. Block size 64, `130 ÷ 64` rounds down to 2, so the network is `2 × 64 = 128`. The block is 192.168.1.128 to 192.168.1.191, and 192.168.1.0 is a different subnet entirely. Any time a prefix is longer than /24, assume the address you were given is a host address until you have masked it. **2. Applying `2ⁿ − 2` to a /31** **Symptom:** an IPAM tool or a spreadsheet reports 0 usable hosts for a point-to-point link that is up and passing traffic. **Cause:** the minus-two rule assumes a network and a broadcast address exist to be subtracted. On a /31 they do not. **Fix:** treat /31 and /32 as the boundary conditions of the formula rather than as anomalies. RFC 3021 makes both addresses of a /31 assignable, and a /32 is a single host route with one address. Anything that reports 0 or −1 has applied the formula outside its domain. Check /31 support on the specific interface first, since the exception only holds on genuinely point-to-point links. **3. Treating 172.16.0.0/12 as `172.16.x.x` only** **Symptom:** an internal service is unreachable from one office, or a "block all private ranges" rule leaks. **Cause:** the /12 was read as if it were a /16. **Fix:** the range is 172.16.0.0 to 172.31.255.255. Write ACLs and allowlists against the prefix 172.16.0.0/12 rather than against an octet pattern, and remember that 172.15.x.x and 172.32.x.x sit outside it on the public internet. If you are matching by hand, the second octet is the boundary octet and it steps by 16. **4. Pasting a wildcard into a field that wants a subnet mask** **Symptom:** an ACL permits far more, or far less, than intended, and nothing in the config looks wrong. **Cause:** IOS ACLs and OSPF network statements take the wildcard while the ASA takes the subnet mask, and 0.0.0.63 and 255.255.255.192 are visually interchangeable at a glance. **Fix:** check the field before pasting, not after. A useful tell: for any prefix of /8 or longer, a subnet mask begins with 255 and a wildcard begins with 0. If a value sitting in an ACL or an OSPF network statement begins with 255, it is a subnet mask in a wildcard field. **5. Writing a non-contiguous mask, or generating one by accident** **Symptom:** a device rejects a configuration line, or a home-grown script produces masks that look plausible and are wrong. **Cause:** a value like 255.0.255.0 is not a valid subnet mask — it has a hole. The scripted version is subtler: in JavaScript, shift operators take their right operand modulo 32, so an out-of-range prefix silently produces a plausible-looking wrong mask (33 becomes /1, −1 becomes /31) instead of throwing. **Fix:** validate the prefix range before shifting, and reject any mask that is not a solid run of 1s followed by 0s. A library that raises on bad input is worth more here than one that guesses, because both failure modes are silent. ## FAQ ### What is the difference between a subnet and a VLAN? A VLAN is a layer 2 broadcast domain configured on switches; a subnet is a layer 3 range of addresses. They are usually mapped one to one, but nothing enforces that: you can put two subnets on one VLAN, or trunk one VLAN across sites. Renumber the subnet and the VLAN ID does not change. ### How many subnets do I get if I split a /24 into /26s? Four. The count is 2 raised to the number of borrowed bits, and /26 is two bits longer than /24, so `2² = 4` subnets of 64 addresses each. Each child reserves its own network and broadcast address, so the four /26s hold 248 usable addresses against the parent /24's 254. ### Does CIDR notation work the same way in IPv6? The slash still counts leading network bits, so /64 means 64 network bits out of 128. What does not carry over is the minus-two rule: IPv6 has no broadcast address, so nothing is subtracted from the total. The cheat sheet above and the calculator behind it are IPv4 only. ### What does 0.0.0.0/0 mean? Zero network bits, so it matches every IPv4 address. In a routing table it is the default route, used when no more specific prefix matches. As a bind address it means "all interfaces", which is why a service listening on 0.0.0.0 is reachable from every network the machine is attached to. ### What happens if two subnets on the same network overlap? Routers pick the more specific route, since forwarding always prefers the longest matching prefix, while hosts inside the overlap disagree about which destinations are local. The symptom is partial: some destinations work and some do not, and which ones changes depending on where you test from. ### Why were Class A, B and C addresses replaced by CIDR? Because the classes only offered three sizes: /8, /16 and /24. An organisation needing 300 addresses had to take a class B and waste most of it or run two class C routes. CIDR let a prefix be any length, which slowed address exhaustion and let providers summarise many customer blocks into one route. ### Can I subnet a private range like 192.168.0.0/16 however I want? Yes. RFC 1918 space is yours to divide at any prefix length and nobody outside your network sees it. The constraint is internal: overlapping with a partner network or a cloud VPC you later peer with is expensive to unwind, which is why plans tend to avoid the blocks every home router already uses. **What to carry away.** The prefix counts network bits, and block size is `256 − the boundary mask octet`. Round the address down to a multiple of the block size for the network, and add block size minus one for the broadcast. Subtract two for the reserved pair, but only when a /31 or /32 has not already removed the reason to subtract. Keep the wildcard and the subnet mask straight by their shape. Allocate large blocks before small ones. None of it needs a tool once the arithmetic is in your head, which is the reason to work through it by hand at least once. For checking a plan before it reaches a router, or for reading a block's binary boundary at a glance, the [subnet calculator](/tools/subnet-calculator) runs locally in your browser. --- ### cm to inches: exact formula, height & screen charts (2026) URL: https://go-tools.org/blog/cm-to-inches-length-conversion-guide Convert cm to inches (centimeters to inches) with the exact 1 in = 2.54 cm factor. Mental-math, height, screen, paper charts plus code. Free in browser. # cm to inches conversion guide: exact formula, height & screen charts (2026) `1 inch = 2.54 cm`. That number is exact, not a rounded approximation. The 1959 International Yard and Pound Agreement pinned the inch to that value, and every legal-for-trade ruler since then traces back to it. To convert cm to inches, divide by 2.54. To go the other way, multiply by 2.54. ```text inches = cm ÷ 2.54 cm = inches × 2.54 ``` > Need a number right now? Open the free [length converter](/tools/length-converter). 16 length units, instant results, runs entirely in your browser with full IEEE 754 precision. This guide answers four questions in order. Where the 2.54 factor actually comes from, four mental-math tricks matched to four precision tiers, scenario charts (height, screens, paper, mm), and how centimeters to inches connects to the rest of the metric and imperial length family. JavaScript and Python snippets with a roundtrip assert close it out. --- ## The exact formula and where 2.54 comes from The number `2.54` is not measured; it is defined. On 1 July 1959, the United States, United Kingdom, Canada, Australia, New Zealand and South Africa signed the International Yard and Pound Agreement, fixing the international yard at exactly `0.9144 m`. From that single definition every smaller imperial length falls out: 1 yard = 36 inches, so `1 in = 0.0254 m = 25.4 mm = 2.54 cm` exactly. NIST publishes the same factor in Handbook 44, which is what every certified caliper in a US machine shop is calibrated to. The metric side hardened on 20 May 2019. The BIPM redefined the meter by fixing the speed of light at exactly `c = 299,792,458 m/s`, so 1 meter is now the distance light travels in `1/299,792,458` of a second in vacuum. The inch inherits that definition through the yard and meter chain. Practical effect for daily work: zero. What changed is that any lab with an iodine-stabilized laser can realize a meter from first principles, no platinum bar required. Going the other direction, `1 cm = 0.3937007874… in`. That is an infinite non-repeating decimal in any base ten representation, which means the cm to in formula is asymmetric. cm-to-inch is divisive (clean), inch-to-cm is multiplicative (also clean), but neither direction has a "nicer" inverse. A worked example: `30 cm ÷ 2.54 = 11.811024 in`. Round to 11.81 in for shopping, keep all six digits for engineering drawings. Precision warning. Replacing `0.3937007874` with `0.39` for speed introduces 0.6 mm of error per meter. Fine for picking out a curtain rod, fatal for a CNC tool path where ±0.05 mm is normal tolerance. When the work matters, use the full factor or the [length converter](/tools/length-converter), which carries the canonical `2.54` through to the result. --- ## 4 mental-math tricks that match your precision The eight-digit factor is precise but useless in a furniture aisle or a shoe shop. These four tricks cover the realistic precision tiers, so pick the one that matches what's at stake. ### Method 1: halve, then subtract 20% (~1.6% error) Divide the cm by 2, then knock off 20% of that halved number. - 30 cm → `15 - 3 = 12 in` (exact 11.81 in). - 50 cm → `25 - 5 = 20 in` (exact 19.69 in). - 100 cm → `50 - 10 = 40 in` (exact 39.37 in). The math: `0.5 - 0.1 = 0.4`, which is 1.6% high relative to the true `0.3937`. Use this for clothing sizes, bag dimensions, "will this fit on the shelf" questions. ### Method 2: multiply by 4, divide by 10 (~1.6% error, cleanest integers) If you'd rather not handle fractional cm, multiply by 4 and shift the decimal once. - 27 cm → `108 ÷ 10 = 10.8 in` (exact 10.63 in). - 55 cm → `220 ÷ 10 = 22 in` (exact 21.65 in). - 75 cm → `300 ÷ 10 = 30 in` (exact 29.53 in). Same 1.6% bias as Method 1 but easier when the cm value is awkward to halve. Useful for screen sizing in a store where you just need to know whether a 27" monitor will look bigger than the 24" you have at home. ### Method 3: divide by 2.54 (full precision) When 1.6% error is intolerable, just do the division. CNC paths, medical-device tolerances, customs declarations on cross-border parcels, and engineering drawings all need the full factor. A pocket calculator handles `÷ 2.54` in two keystrokes; a spreadsheet handles a thousand rows in milliseconds. This is also the cm to in formula that any conversion API will use under the hood. ### Method 4: roundtrip sanity check Whichever shortcut you took, run the result back through `× 2.54` and confirm it lands close to where you started. Converted 75 cm to "about 30 in"? Multiply: `30 × 2.54 = 76.2 cm`. Within 1.6%, so the shortcut held. If the roundtrip differs by more than 5%, you dropped a factor of ten, usually a slipped decimal between mm and cm. Pilots use the same defensive trick converting fuel uplift between liters and gallons. --- ## Quick reference charts: cm and inches Bookmark this section when you need a number without thinking. All values use the exact 2.54 factor and round to two or three decimals depending on use. ### Small scale (0.1 to 10 cm) | cm | mm | inches | Reference | | ---- | --- | ------- | ---------------------------- | | 0.1 | 1 | 0.0394 | 1 mm = 0.0394 in | | 0.5 | 5 | 0.1969 | 5 mm board thickness | | 1 | 10 | 0.3937 | width of a fingernail | | 2 | 20 | 0.7874 | thumb width | | 2.54 | 25.4| 1.0000 | 1 inch (the anchor) | | 3 | 30 | 1.1811 | A4 short edge / 30 cm rounds | | 5 | 50 | 1.9685 | typical lipstick length | | 10 | 100 | 3.9370 | 100 mm = 3.937 in | ### Height chart: cm to ft + in The conversion every traveler needs eventually. Formulas: ```text total_inches = cm ÷ 2.54 feet = floor(total_inches ÷ 12) inches = total_inches − feet × 12 ``` | cm | ft + in | Notes | | ------ | ------- | ----------------------------------------- | | 152.4 | 5'0" | exact | | 157.5 | 5'2" | | | 160.0 | 5'3" | global female-average reference | | 162.6 | 5'4" | | | 165.1 | 5'5" | exact | | 167.6 | 5'6" | | | 170.18 | 5'7" | exact (67 in × 2.54) | | 172.7 | 5'8" | global male-average reference | | 175.3 | 5'9" | | | 177.8 | 5'10" | exact | | 180.3 | 5'11" | | | 182.88 | 6'0" | exact (72 in × 2.54) | | 185.4 | 6'1" | | | 187.96 | 6'2" | exact (74 in × 2.54) | | 190.5 | 6'3" | exact | | 193.04 | 6'4" | exact (76 in × 2.54) | | 200.0 | 6'6.7" | basketball-roster threshold | For height conversion the trick is to do all arithmetic in one go. Convert cm to total inches first, then split into feet and inches at the end. Splitting first and rounding twice causes rounding drift (covered in the mistakes section below). ### Screen size chart: diagonals 11" to 85" TV and monitor size always means the diagonal. The visible width and height of a 16:9 panel are smaller than the diagonal, much smaller than buyers expect. | diagonal | cm | 16:9 width | 16:9 height | Typical use | | -------- | ------ | ---------- | ----------- | ---------------------- | | 11" | 27.94 | 24.36 cm | 13.70 cm | netbook / iPad mini | | 13.3" | 33.78 | 29.45 cm | 16.57 cm | 13" laptop | | 15.6" | 39.62 | 34.55 cm | 19.43 cm | 15" laptop | | 24" | 60.96 | 53.15 cm | 29.90 cm | budget desktop monitor | | 27" | 68.58 | 59.78 cm | 33.62 cm | popular desktop tier | | 32" | 81.28 | 70.85 cm | 39.85 cm | small TV / large monitor| | 43" | 109.22 | 95.21 cm | 53.55 cm | mid-tier TV | | 55" | 139.70 | 121.76 cm | 68.49 cm | living-room TV | | 65" | 165.10 | 143.94 cm | 80.96 cm | large living-room TV | | 85" | 215.90 | 188.21 cm | 105.87 cm | flagship TV | The width formula is `diagonal × cos(arctan(9/16)) ≈ diagonal × 0.8716`; the height is `diagonal × 0.4903`. Measure the wall before buying. A 65" TV needs about 144 cm of clear horizontal space plus stand or mount clearance. ### Paper & document chart (A4 vs US Letter) Cross-border printing trips up almost every remote team. The two standard sizes are close but never identical: | Format | mm × mm | cm × cm | inches × inches | | --------- | ---------- | ----------- | --------------- | | A4 | 210 × 297 | 21.0 × 29.7 | 8.27 × 11.69 | | US Letter | 215.9 × 279.4 | 21.59 × 27.94 | 8.5 × 11.0 | | A3 | 297 × 420 | 29.7 × 42.0 | 11.69 × 16.54 | | Legal | 215.9 × 355.6 | 21.59 × 35.56 | 8.5 × 14.0 | A4 is 0.59 cm narrower but 1.76 cm taller than Letter. Print a Letter PDF on A4 paper without "fit to page" and the bottom line of every page can clip; print A4 on Letter and the right margin shrinks. For a wider tour of the metric and imperial families, see our [unit conversion complete guide](/blog/unit-conversion-complete-guide). --- ## 5 real-world scenarios when cm and inches matter ### Height on a medical form: 5'7" to cm US clinical intake forms still ask for feet and inches; the WHO, ICD coding, and almost every non-US hospital chart in centimeters. Switching between the two has one safe pattern. Combine into total inches first, then multiply. ```text height_in = feet × 12 + inches height_cm = height_in × 2.54 ``` Worked example: 5'7" → `5 × 12 + 7 = 67 in` → `67 × 2.54 = 170.18 cm`. Reverse direction (cm to ft + in) for a US patient handed a metric chart: `175 cm ÷ 2.54 = 68.898 in → 5 ft + 8.898 in ≈ 5'8.9"`. Round to the nearest half-inch only at the very end. Skip the arithmetic entirely with the [length converter](/tools/length-converter), which handles inches to cm in either direction without intermediate rounding. ### Buying a TV: 55" diagonal vs wall width A 55" TV is 139.7 cm on the diagonal, but its actual width on a 16:9 panel is `139.7 × 0.8716 = 121.76 cm`. Add 2 to 3 cm of bezel and the visible footprint is roughly 124 cm. Subtract that from the wall width and you want at least 20 cm of breathing room on each side, otherwise the TV looks crammed. Sound bars, console shelves, and side speakers eat the rest. The same arithmetic applies at every size: a 65" diagonal is `165.1 cm`, but the real width on the wall is `144 cm`. ### International apparel: EU 38, US 8 and the 81 cm waist Apparel sizing is where centimeters to inches errors cost real money. Cross-border denim sells two ways: EU and Asian brands print waist in cm, US and UK brands print in inches. A "US 32" waist is `81.28 cm`, which European retailers usually round to `81` or `82`. Off-the-shelf shoes use the foot length directly: a Japanese or Chinese 27 cm shoe size is roughly US men's 9 (foot length plus 7 cm gives the US size). Get this wrong by one centimeter and the shoe goes back. ### CNC and 3D printing: why 0.39 kills tolerances CNC mills and 3D printers run on tolerances of `±0.05 mm` for metal and `±0.2 mm` for FDM plastic. Speed-converting a 1000 mm part with `× 0.39` instead of `× 0.3937007874` produces an inch value that is 0.37 mm short over the full length. That already eats the metal tolerance budget on its own, before any machine-induced error. The rule for any path that ends up on a tool: divide by 2.54 directly, or carry the full factor `0.3937007874`. Anything else compounds. Keep a [length converter](/tools/length-converter) tab open while you read drawings. It carries the canonical 2.54 with no manual rounding. ### Cross-border e-commerce: DHL and FedEx box limits DHL Express and FedEx International cap a single parcel at length + girth ≤ `419 cm (165 in)`, with a single longest dimension ≤ `274 cm (108 in)`. USPS Priority Mail International caps total length + girth at `108 in (274.32 cm)`. Hit the cap by 1 cm and the package is rejected at the warehouse, not the door. Some carriers also charge by dimensional weight using cm or in depending on origin country, so a `60 × 40 × 40 cm` box quoted in centimeters is not the same as `24 × 16 × 16 in` quoted in inches. The first is `27.5 in × 15.7 in × 15.7 in` and lands in a different fee bracket. --- ## Beyond cm and inches: mm, m, ft, yd in one chain The `2.54` anchor unlocks the rest of the metric and imperial length family. ### mm to inches: sub-millimeter precision `1 mm = 0.03937 in`, exact within the same 2.54 chain. Inversely, `1/64 in = 0.396875 mm`, which is the standard step on US machinist scales. Common engineering thicknesses: | metric | imperial | typical use | | ------ | ----------------- | ------------------- | | 1 mm | 0.0394 in | thin sheet metal | | 3 mm | 0.1181 in (≈ 1/8")| acrylic sheet | | 6 mm | 0.2362 in (≈ 1/4")| plywood, plate glass| | 10 mm | 0.3937 in | thick bar stock | | 25.4 mm| 1.0000 in | the anchor | The mm to inches lookup matters most when you order metric stock from a US supplier or vice versa. One decimal slip turns a 6 mm board into a 60 mm slab. ### m to ft: architecture and real estate `1 m = 3.28084 ft`, and `1 ft = 0.3048 m` exactly. A standard US 8 ft ceiling is `2.4384 m`; a European 2.5 m ceiling is `8.20 ft`. Real-estate listings between continents always quote both, but the conversion factor is the same `2.54` chain underneath: `1 ft = 12 in = 12 × 2.54 cm = 30.48 cm = 0.3048 m`. ### yd and m: sports fields `1 yd = 0.9144 m` exactly. That is the very definition that anchors the whole 1959 agreement. A 100 m sprint is `109.36 yd`; a 100 yd US football field is `91.44 m`. UEFA pitches are sized in metres, NFL fields in yards. ### Fractional inches: when decimals aren't enough US woodworking, plumbing and machinist drawings still default to `1/16, 1/32, 1/64`. To convert cm to a fraction: ```text in_decimal = cm × 0.3937007874 fraction = round(in_decimal × 64) ÷ 64 // nearest 1/64" ``` Worked example: `3 cm × 0.3937 = 1.1811 in → 0.1811 × 64 ≈ 11.59 → round to 12/64 = 3/16"`. So 3 cm ≈ `1 3/16 in`. Always reduce the fraction at the end (`12/64 = 3/16`). ### The whole length family at a glance | 1 mile | = 1.609344 km exact | | ------ | ------------------------ | | 1 yd | = 0.9144 m exact | | 1 ft | = 0.3048 m = 30.48 cm | | 1 in | = 25.4 mm = 2.54 cm exact| The same 1959 international agreement that pins inches to centimeters also defines the kilogram and pound chain. For the weight side of the same story, see our [kg to lbs conversion guide](/blog/kg-to-lbs-pounds-kilograms-conversion-guide). Volume runs on a different definition entirely, covered in the [ml to oz conversion guide](/blog/ml-to-fl-oz-fluid-ounces-conversion-guide). Temperature has its own three-unit chain in our [temperature conversion guide](/blog/temperature-conversion-celsius-fahrenheit-kelvin-guide). For a single-tab quick converter across all four families, the [weight converter](/tools/weight-converter), [volume converter](/tools/volume-converter) and [temperature converter](/tools/temperature-converter) sit alongside the length tool. --- ## Common mistakes to avoid ### Confusing cm with mm (the 10× error) A "30 cm display" is a 12 in laptop screen; a "30 mm display" is a smartwatch face. Japanese product listings often quote dimensions in mm, European listings in cm, and machine translations sometimes drop the suffix entirely. When in doubt, run the result through an inches to cm sanity check. 30 mm → 1.18 in, too small for any laptop, so the source must have meant cm. ### Diagonal vs width / height (the screen trap) Every monitor and TV size refers to the diagonal, not the width. A 27" monitor is `68.58 cm` corner-to-corner but only `59.78 cm` wide on a 16:9 panel. Confusing the two before drilling a wall mount is expensive. Width formula for any 16:9 screen: `diagonal × 0.8716`; height: `diagonal × 0.4903`. ### The 0.39 speed factor in an engineering context `0.39` and `0.3937` look interchangeable but compound. Across 1 m of CNC tool path the gap is `0.7 mm`, which already eats most of a precision-class tolerance budget. The fix: never type `0.39` into a CAM file. Use the canonical 2.54 and divide, or use the [length converter](/tools/length-converter) to generate the value once and copy it across. ### Rounding mid-calculation A height of 5'7" should be converted in one shot: `(5 × 12 + 7) × 2.54 = 170.18 cm`. Splitting it as `5 × 30.48 + 7 × 2.54 = 152.40 + 17.78 = 170.18 cm` happens to land on the same number here, but split rounding to fewer decimals (`5 × 30.5 + 7 × 2.5 = 152.5 + 17.5 = 170.0`) accumulates error fast. Rule: keep at least four decimal places in intermediate steps, then round at the very end. --- ## Code examples: JavaScript and Python The same canonical factor that powers the [length converter](/tools/length-converter) drops directly into any codebase. Both snippets below include a roundtrip assertion to catch precision drift before it ships. ### JavaScript ```javascript // 1959 International Yard and Pound Agreement: 1 in = 2.54 cm exactly const CM_PER_INCH = 2.54; const cmToInches = (cm) => cm / CM_PER_INCH; const inchesToCm = (inches) => inches * CM_PER_INCH; // Height split for medical / travel forms const cmToFeetAndInches = (cm) => { const totalInches = cmToInches(cm); const feet = Math.floor(totalInches / 12); const inches = +(totalInches - feet * 12).toFixed(1); return { feet, inches }; }; console.log(cmToInches(30)); // 11.811023622047244 console.log(inchesToCm(67)); // 170.18 console.log(cmToFeetAndInches(170.18)); // { feet: 5, inches: 7 } // Roundtrip sanity check — should match to ~15 sig figs const back = inchesToCm(cmToInches(170.18)); console.assert(Math.abs(back - 170.18) < 1e-10, "cm roundtrip drift"); ``` `CM_PER_INCH` is the single source of truth. Define it once and derive everything else; never copy `2.54` into a second file, because the day someone "fixes" one and not the other, you ship a unit-conversion bug. ### Python (pandas batch + roundtrip assert) ```python import pandas as pd CM_PER_INCH = 2.54 # exact, by 1959 international agreement df = pd.DataFrame({"cm": [10, 30, 100, 170.18, 215.9]}) df["inches"] = df["cm"] / CM_PER_INCH df["cm_back"] = df["inches"] * CM_PER_INCH df["roundtrip_error"] = (df["cm"] - df["cm_back"]).abs() assert (df["roundtrip_error"] < 1e-10).all(), "roundtrip drift detected" print(df.round(4)) # cm inches cm_back roundtrip_error # 0 10.00 3.9370 10.00 0.0 # 1 30.00 11.8110 30.00 0.0 # 2 100.00 39.3701 100.00 0.0 # 3 170.18 67.0000 170.18 0.0 # 4 215.90 85.0000 215.90 0.0 ``` The `assert` is the load-bearing line. IEEE 754 double-precision floats round-trip to within machine epsilon (`~1e-15`); the `1e-10` threshold leaves headroom while still catching a typo like `0.394` instead of the full factor. --- ## FAQ ### How many cm in an inch exactly? `1 inch = 2.54 cm`, exact by definition since the 1959 International Yard and Pound Agreement, not a rounded approximation. The inverse is `1 cm = 0.3937007874… in`, an infinite non-repeating decimal. For everyday work `0.39` is fine (1.6% error); for engineering, medical or customs work use the full factor or divide by 2.54. ### How do I convert cm to inches in my head? Fastest method: halve the cm and subtract 20%. Example: 30 cm → `15 - 3 = 12 in` (exact 11.81, error 1.6%). Cleaner integers: multiply by 4 and shift one decimal. 50 cm → `200 ÷ 10 = 20 in` (exact 19.69). Both methods stay within 1.6% of the true value, fine for furniture, clothing and screens. ### What is 5'7" in cm? `5'7" = 170.18 cm`. The clean way: combine feet and inches into a single inch count first (`5 × 12 + 7 = 67 in`), then multiply by 2.54 (`67 × 2.54 = 170.18 cm`). Quick references: 5'0" = 152.4 cm, 5'10" = 177.8 cm, 6'0" = 182.88 cm, 6'2" = 187.96 cm. ### How many inches is 30 cm? `30 cm = 11.811 in`, more precisely `11.811024 in`. This shows up constantly because A4 paper is 29.7 cm tall (≈ 11.69 in), one centimeter shy of 30. Speed-converted as "about 12 inches" the error is 1.6%, which is acceptable for desk-organizer shopping but not for cabinet-making. ### What is the formula for cm to inches? `inches = cm ÷ 2.54`. The reverse is `cm = inches × 2.54`. The factor `2.54` was set by the 1959 international agreement signed by the US, UK, Canada, Australia, New Zealand and South Africa, which fixed `1 yard = 0.9144 m` exactly; dividing by 36 inches per yard yields `1 in = 0.0254 m = 2.54 cm`. ### How do I convert mm to inches? `inches = mm ÷ 25.4`, since `1 in = 25.4 mm` exactly. Common results: 6 mm = 0.236 in, 10 mm = 0.394 in, 25 mm = 0.984 in. Watch out for the cm vs mm confusion: a 30 mm board is `1.18 in` (less than a thumb-knuckle) while a 30 cm board is `11.8 in`, an order of magnitude apart. ### Why is the inch defined as exactly 2.54 cm? The 1959 International Yard and Pound Agreement fixed the international yard at exactly `0.9144 m`. Since `1 yard = 36 inches`, that pegs `1 inch = 0.0254 m = 2.54 cm` by simple division. The agreement deliberately made the inch a metric quantity so that engineering tolerances could be specified once, in SI, and traced anywhere on the planet. ### How many inches is 100 cm? 100 cm equals 39.3700787402 inches when divided by the exact 2.54 factor. For everyday use, 100 cm is just under 39.4 inches, or about 3 feet 3.4 inches tall. Round to 39 inches only when precision below 0.4% does not matter. ### Is 1 cm bigger than 1 inch? No, 1 inch is bigger than 1 cm. One inch equals 2.54 cm, so an inch is about 2.54 times longer than a centimeter. A common quick check: 1 cm fits inside a single inch about two and a half times. ### How do I convert cm to inches in Excel or Google Sheets? Use the CONVERT function: `=CONVERT(A1, "cm", "in")` returns the inches equivalent of the cm value in cell A1, accurate to spreadsheet precision. Wrap with ROUND for cleaner output: `=ROUND(CONVERT(A1, "cm", "in"), 2)`. Both Excel and Google Sheets accept this formula identically. --- ### Code Minification Guide: CSS, JS & HTML Explained URL: https://go-tools.org/blog/code-minification-guide-css-js-html What code minification is, how minifying CSS, JS, and HTML works, and why minify and gzip/brotli are different. Learn the order and minify your code free. # Code Minification Guide: CSS, JS & HTML Explained Code minification removes characters that a machine doesn't need (whitespace, comments, line breaks) from your CSS, JavaScript, and HTML source, and rewrites verbose patterns into shorter equivalents. The behavior stays the same; the file just gets smaller and loads faster. One thing to get straight up front: minification is not compression. Minify operates on your source code, stripping syntactic redundancy. Gzip and Brotli operate on the bytes in transit, encoding repeated patterns. They run at different stages and remove different kinds of redundancy, which is why you should still minify even when your server already serves Brotli. This guide explains why. Want to compress something right now? Go straight to the [CSS minifier](/tools/css-formatter), the [JavaScript minifier](/tools/js-formatter), or the [HTML minifier](/tools/html-formatter); each one runs entirely in your browser. But understanding the mechanics is what lets you decide *where* to compress and *whether* you even need to do it by hand. The rest of this guide covers what minification does, how CSS, JS, and HTML each get minified, how minify stacks with gzip and Brotli, when your build tool already handles it, and how source maps keep minified code debuggable. ## What minification is (and what it is not) Minification does two things. It deletes characters that carry no meaning for the parser, and it rewrites your source into a shorter form that means the same thing. The output is equivalent to a machine and nearly unreadable to a human. Nothing about how the code runs changes, only its surface. That last point is the invariant to hold onto for the rest of this guide: minify only edits the surface of your source (whitespace, comments, identifier names, redundant syntax), never the behavior or output. It is the mirror image of formatting. Formatting adds whitespace to make code readable; minifying strips it to make code small. Both sit on the same "semantically equivalent" axis, just pointing in opposite directions. People constantly confuse three operations that sound similar. This table sorts them out: | Dimension | Format (beautify) | Minify | Compress (gzip/Brotli) | |-----------|-------------------|--------|------------------------| | What it changes | Adds whitespace, line breaks, indentation | Removes whitespace and comments, shortens syntax | Byte-level encoding of repeated patterns | | Which layer | Source code | Source code | Transfer / storage | | Still source code? | Yes (readable) | Yes (runnable, hard to read) | No (binary, must be decoded) | | Who does it | Developer / editor | Build tool / minifier | Server + browser | | Reversible? | Semantically | Semantically (behavior unchanged) | Fully (decompress restores the bytes) | Format and minify live on one axis, the semantic-equivalence axis. Compression lives on a different one. A formatted file and a minified file are both valid source; a compressed file is a binary blob that has to be decoded before anything can run. This is where a costly misconception creeps in: "my server already does gzip, so minifying is pointless." It isn't, and the numbers later in this guide show why. Minification and compression remove different redundancy, so doing one does not make the other redundant. It helps to think about *why* the bytes a minifier removes exist in the first place. You write whitespace, comments, and descriptive names for yourself and your teammates, since they make code reviewable and maintainable. The machine that parses your CSS, runs your JavaScript, or builds your DOM ignores every one of them. Minification throws away the human-only material once the humans are done with the source. That's also why minification is a *production* concern and never a development one: you keep the readable version in your repository and ship the stripped-down version to browsers. The readable copy is the source of truth; the minified copy is a build artifact you can regenerate at any time. ## How CSS minification works CSS is the gentlest of the three to minify because its grammar leaves little room for ambiguity. A minifier strips comments, collapses runs of whitespace into nothing, drops the final semicolon in each block, and removes spaces around `{`, `}`, `:`, and `;`. That alone clears most of the bytes. CSS also allows a set of equivalence rewrites that no other language shares. A good minifier applies them safely: - Shorten colors: `#ffffff` becomes `#fff`, and `#ff0000` collapses to `red` (or the reverse, whichever is shorter to write). - Drop units on zero: `0px` becomes `0`, and `margin: 0 0 0 0` becomes `margin: 0`. - Strip leading zeros: `0.5em` becomes `.5em`. - Merge shorthands: four separate `margin-top`, `margin-right`, `margin-bottom`, and `margin-left` declarations fold into one `margin`. - Combine rules: adjacent rules with identical selectors or declarations can be merged, and duplicate declarations dropped. Every one of these keeps the rendered result identical, which is the boundary a compliant minifier never crosses. But CSS is order-sensitive: a later rule overrides an earlier one through the cascade. So a safe minifier will not blindly reorder rules that could change which declaration wins. Shrinking bytes is allowed; changing the cascade is not. That constraint is more subtle than it sounds. Two declarations that look mergeable might not be, because something between them references the same property at the same specificity. Consider: ```css .btn { color: #ff0000; } .alert .btn { color: blue; } .btn { color: #f00; } ``` The first and third rules share a selector and could merge, but only if doing so doesn't move the declaration past the middle rule in a way that changes which wins for an element matching both. A naive merge that reorders these could break the cascade. This is the kind of edge case a production-grade engine like CSSO is built to reason about, and it's why you shouldn't hand-roll your own "delete the whitespace" minifier with a regex. The transforms look mechanical, but the safety analysis behind them is not. Our [CSS minifier](/tools/css-formatter) uses the CSSO engine for this kind of lossless minification, and it runs entirely in your browser with a byte-savings readout so you can see the payload impact of each pass. The same tool also formats in the other direction, so you can take a minified stylesheet you copied off a live site and expand it back into readable, indented rules. Reach for it when you've copied a snippet of CSS and want to check its compressed size, or when you're shipping a static page with no build step to do it for you. ## How JavaScript minification works JavaScript minification goes much further than CSS, and that's where both the savings and the traps live. To see why, look at a small function before and after Terser: ```js // before function calculateTotal(items, taxRate) { let runningTotal = 0; for (const item of items) { runningTotal += item.price * item.quantity; } return runningTotal * (1 + taxRate); } ``` ```js // after function calculateTotal(t,a){let n=0;for(const o of t)n+=o.price*o.quantity;return n*(1+a)} ``` The function name `calculateTotal` survives because it's exported (or could be called from elsewhere); the parameters and the loop variables collapse to single letters. That's the core of it, but a JS minifier does several distinct things: - Identifier mangling: local variables and parameters get renamed to single letters, so `getUserPreferences` becomes `a`. Only locals are mangled; globals and exported names stay intact by default, because renaming them would break code that references them from outside. - Dead-code elimination: unreachable branches and unused variables are removed, working alongside tree-shaking at the bundler level. - Constant folding and syntax compression: expressions get shortened, so `true` becomes `!0`, `false` becomes `!1`, and `return undefined;` becomes `return;`. The most important thing to know about JS minification is the automatic semicolon insertion (ASI) trap. JavaScript lets you omit semicolons, and the parser inserts them for you under specific rules. When a minifier deletes the line breaks those rules depend on, code can change meaning. The classic failure is a statement that begins with `(` or `[` getting silently glued onto the previous line: ```js const x = getValue() [1, 2, 3].forEach(handle) ``` Without semicolons, this parses as `getValue()[1, 2, 3]`, an indexing expression rather than two statements. Once minified onto one line, the bug is locked in. The same hazard appears with a line starting in `(`, where the previous expression gets called like a function. Modern Terser handles most real-world cases gracefully because it parses the code into an abstract syntax tree first and re-emits semicolons where they're needed, rather than doing blind text deletion. But bad source plus aggressive minification is a genuine source of production bugs, and the failures are nasty precisely because they only appear in the minified build, not in development. The fix is on your side: write code with explicit semicolons and unambiguous syntax, and the minifier stays safe. A linter rule or an auto-formatter that inserts semicolons at the source level removes the risk entirely. A compliant minifier preserves behavior, but only if the input is valid, standard JavaScript. Terser parses ECMAScript; it does not understand TypeScript or JSX. Those have to be transpiled to plain JS first, otherwise minification fails at the parse step. If you paste a `.ts` file into a JS minifier and get an error, that's why. One naming question comes up a lot: minify versus uglify. They mean effectively the same thing. "Uglify" comes from UglifyJS, the early popular JS minifier; Terser is its modern fork that supports ES2015 and later. Today "minify" is the generic term across all three languages, and "uglify" survives as an older, JS-specific name for the same process. Our [JavaScript minifier](/tools/js-formatter) runs Terser in the browser, renaming locals, dropping dead code, and stripping comments, and reports how many bytes it saved on each pass. ## How HTML minification works HTML minification starts with the basics: remove comments (keeping the `` declaration and any conditional comments you still rely on), collapse whitespace between tags, and trim redundant spaces inside attribute lists. A small fragment shows the shape of it: ```html

      ``` becomes: ```html ``` The comment is gone, the indentation between tags is collapsed, the optional `
    • ` closing tags are dropped, and the unquoted attribute values lose their quotes. From there a minifier can apply a few more HTML-specific tricks: - Remove optional closing tags: the HTML spec allows omitting ``, `

      `, ``, and several others, so a minifier can drop them. - Remove attribute quotes: when a value has no spaces or special characters, `class="x"` becomes `class=x`. - Collapse boolean attributes: `disabled="disabled"` becomes just `disabled`, and `checked="checked"` becomes `checked`. - Minify embedded CSS and JS: the contents of ` ``` Apply `.preview-protanopia` to your page wrapper to see what a protanope sees. Repeat with the deuteranopia and tritanopia matrices (their coefficients are documented in the Brettel paper, and bundled in most CVD simulator libraries). Chrome DevTools' Rendering panel has the same simulators built in under "Emulate vision deficiencies", useful for quick checks, less useful for capturing screenshots in CI. One rule sits beneath all the simulation work: never let color be the only difference between two states. Icons should differ in shape (`×` vs `✓`), states in text label ("Error" vs "Success"), categories in pattern (solid vs hatched). Color is a multiplier on those other signals, not a substitute. There's a class of failures that contrast checkers don't catch but CVD simulators do. Pie chart segments distinguished only by hue, map legends that color-code countries, status pills that rely on a green/yellow/red gradient: all of them can clear WCAG 2 contrast against the background while being unreadable to a deuteranope reading them against each other. The rule is to check legibility *between adjacent colors in the design*, not just against the surrounding canvas. Two slate-500 segments next to each other at the same lightness are indistinguishable regardless of hue rotation. Add a luminance step between adjacent regions and the chart survives every CVD variant. Achromatopsia and cone monochromacy are rare but worth designing for explicitly because they collapse all hue distinctions. A user with achromatopsia perceives only luminance; your color-coded UI looks like a grayscale photograph to them. If your design holds up when you run a `filter: grayscale(1)` over the whole page (try it in DevTools), you've passed the strictest version of the "color is not the only signal" rule. It's the cheapest accessibility test you can run, and it surfaces a surprising number of failures the moment you toggle it. ## Auditing Tailwind and Material palettes Most front-end work uses a pre-built palette: Tailwind v4, Material 3, Radix, or shadcn. The accessibility question becomes "at which stop of this ramp does the text become readable?", and the answer is more rule-of-thumb than the docs admit. For Tailwind v4's slate ramp against pure white, the WCAG and APCA numbers fall like this: | Tailwind class | Approx hex | WCAG vs white | AA body | AAA body | APCA Lc (approx) | | -------------- | ------------ | ------------- | -------- | -------- | ---------------- | | slate-400 | `#94a3b8` | 2.56:1 | ✗ | ✗ | ~38 ✗ | | slate-500 | `#64748b` | 4.76:1 | ✓ | ✗ | ~60 ✗ body | | slate-600 | `#475569` | 7.58:1 | ✓ | ✓ | ~78 ✓ body | | slate-700 | `#334155` | 10.35:1 | ✓ | ✓ | ~90 ✓ body | The practical rules that fall out: - **Body text needs slate-600 or darker.** Slate-500 passes WCAG AA but fails APCA's body-text threshold, so it's compliant but uncomfortable. Slate-600 is the safe floor. - **UI labels and secondary text can use slate-500.** UI components only need 3:1; the 4.76:1 from slate-500 is comfortable, and the text usually carries supporting visual context. - **Placeholders should use slate-400 or fade lighter.** Placeholder text is WCAG 1.4.3-exempt as decorative *only if* you keep a visible label above the field. Inline-label-as-placeholder patterns must hit the body-text threshold. - **Pure black on slate-100 / slate-200 is wasted ink.** You're at 17:1+; consider using slate-700 or slate-800 as the text color for a softer feel without losing readability. Material 3 uses a similar tonal palette structure. Its `surface-container-high` lives around tone 92 (very light); its `on-surface` lives around tone 10 (near-black). The contrast between any `on-surface` and any `surface-*` token in the same family is guaranteed by Material's spec to clear AA. Don't pair `on-surface-variant` (tone 30) against `surface-container` (tone 94) without checking; that's a real-world miss I've seen ship. If you have an existing palette that fails contrast, OKLCH gives you the cleanest fix path. Instead of nudging hex codes blindly, convert your color to OKLCH, hold C (chroma) and H (hue) fixed, and reduce L (lightness) until the contrast passes. Because OKLCH's L channel is genuinely perceptual, the brand recognition stays intact while the contrast tightens. The [HEX to OKLCH tool](/tools/hex-to-oklch) does this conversion in one step; the [OKLCH explained](/blog/oklch-color-space-explained-tailwind-v4) sister post covers the math in depth. Dark mode deserves its own treatment. WCAG 2's symmetric ratio passes the same combinations in light and dark mode by definition. APCA, being polarity-aware, frequently flags dark-mode body text as harder to read than the same hex pair would be in light mode. Light-on-dark always loses some perceived contrast relative to dark-on-light at the same numeric ratio; it's a known effect of how the eye adapts. Re-run APCA on every dark-mode pair before shipping. ## Contrast checkers and CI workflows Designers and engineers each have a favorite checker; the practical question is which combination of tools you stitch into a real workflow. Here is the field as of 2026: | Tool | WCAG 2 | APCA | CVD sim | Palette audit | | ------------------------------- | ------ | ---------- | ----------- | -------------- | | WebAIM Contrast Checker | ✓ | ✗ | ✗ | ✗ | | Adobe Color | ✓ | ✗ | ✓ | ✓ | | Stark (Figma plugin) | ✓ | ✓ | ✓ | ✓ | | Polypane (browser) | ✓ | ✓ | ✓ | ✓ | | Chrome DevTools color picker | ✓ | ✓ (exp.) | ✗ | ✗ | | axe DevTools | ✓ | ✗ | ✗ | ✓ (page-level) | | Go Tools Color Converter | ✓ | ✓ | ✓ (8 types) | ✓ (Tints/Shades) | A workflow that holds up under real product pressure looks like this: 1. **In Figma**, designers run Stark on each frame to surface failing pairs early. Stark catches the obvious offenders before any hex code reaches the codebase. 2. **At hex-code handoff**, engineers paste the value into the [Color Converter](/tools/color-converter) to get WCAG ratio + APCA Lc + gamut classification + CVD preview in one row. If the pair is dark-mode or saturated-brand, the dual metric catches the WCAG-passes-APCA-fails cases Stark might miss. 3. **At PR time**, `axe-core/playwright` scans the built pages for any contrast violation on the rendered DOM, including dynamic states. This catches focus rings, hover states, and disabled affordances that static design files miss. 4. **In QA**, Chrome DevTools' Rendering tab simulates protanopia/deuteranopia/tritanopia for spot checks on critical flows. The Color Picker in DevTools also surfaces a WCAG ratio inline when you hover any element. Pa11y, Lighthouse CI, and `@axe-core/playwright` all expose contrast assertions as part of their broader accessibility audits. None of them check APCA today; they all check WCAG 2. The realistic compromise is "enforce WCAG 2 AA in CI, sanity-check APCA Lc manually for brand colors and dark mode." A pattern worth borrowing from larger design-system teams: bake the contrast check into your token validation step, not just into page-level QA. If your design tokens compile from a source file (JSON, YAML, or a TypeScript module), add a script that enumerates every `--text-*` × `--surface-*` pairing the system allows and asserts a minimum WCAG ratio. The script runs in milliseconds, catches regressions when someone tweaks a token value, and produces a contrast matrix that doubles as documentation for the design team. The check is independent of any rendered page; it operates purely on the tokens, so it catches the failure before any UI ships. For ad-hoc conversions during this workflow (converting between hex, RGB, HSL, and OKLCH while you debug), the [HEX to RGB](/tools/hex-to-rgb), [HEX to HSL](/tools/hex-to-hsl), [HEX to OKLCH](/tools/hex-to-oklch), and [RGB to HEX](/tools/rgb-to-hex) spokes cover round-trips into and out of any color format your toolchain expects. ## Common mistakes and how to fix them After years of accessibility audits, the same six failures keep showing up. Each has a tidy fix: 1. **Placeholder text in light gray.** `#999999` on white is 2.85:1, fails AA. Either deepen to `#666666` (5.74:1, passes AA) or, better, replace placeholder-as-label patterns with a persistent visible label above the field. Placeholders should not carry information. 2. **Brand orange button with white text.** `#FFA500` on white is 1.97:1, fails AA badly. The fix that preserves brand is to invert the contrast direction: dark text (e.g. `#451a03`) on the orange background, or keep the white text but darken the button to a deep saturated brown-orange. Verify in the [Color Converter](/tools/color-converter) before shipping. 3. **Bright blue link in dark mode.** `#3b82f6` on `#000000` is 5.71:1, a WCAG AA pass, but APCA Lc ~65, *below* the Lc 75 body threshold. Reach for OKLCH and bump L from ≈ 0.63 to 0.75, holding C and H fixed; you'll land near `#7aa5f8` with a comfortable APCA Lc 80+ and the same hue. 4. **Disabled text in `#CCCCCC`.** 1.61:1 against white. WCAG 1.4.3 exempts purely decorative text from the ratio rule, *but* disabled UI controls are not decorative; they communicate "this is currently unavailable." Pair the muted color with a non-color cue (strikethrough, lock icon, "Disabled" tooltip) so a CVD or low-vision user still understands the state. 5. **Status icons that differ only in hue.** A red `×` and a green `✓` is fine because the shape already distinguishes them. A red dot vs a green dot is not. Use shape and color together; the [Color Converter's](/tools/color-converter) CVD preview makes the failure case obvious in a second. 6. **Text over a gradient background.** A gradient that runs from `#3b82f6` to `#a78bfa` against white text passes contrast in the middle and fails near the lavender end. The fix is to enforce the contrast against the *worst-case* point of the gradient, or to overlay a semi-transparent dark scrim so the effective background luminance is always under a known threshold. Each fix takes minutes. The audit cycle they avoid takes weeks. ## FAQ ### What is WCAG AA contrast ratio? WCAG AA requires ≥ 4.5:1 between body text and background, or ≥ 3:1 for large text (≥ 18pt regular or ≥ 14pt bold) and UI components like form borders. AA is the legal baseline under ADA, EAA, and Section 508. Most commercial sites target AA because it covers the regulatory bar without forcing brand colors toward grayscale. ### What contrast ratio do I need for AAA? WCAG AAA requires ≥ 7:1 for normal body text and ≥ 4.5:1 for large text. AAA is recommended for medical, educational, and government sites where the user base skews toward higher accessibility needs. Brand colors often need flattening toward grayscale-adjacent values to pass AAA, which is why many commercial products stop at AA. ### What is APCA and is it WCAG 3.0? APCA (Advanced Perceptual Contrast Algorithm), designed by Andrew Somers / Myndex, is a candidate algorithm under the WCAG 3 Silver project. It uses polarity-sensitive Lc scores from -108 to +108 instead of symmetric ratios. WCAG 3 is still in early draft and APCA has not been formally ratified. WCAG 2.1 / 2.2 AA remains the regulatory standard you must hit today. ### Does dark mode help contrast accessibility? Sometimes, but not automatically. WCAG 2's symmetric ratio passes the same combinations in light and dark mode, but APCA (which is polarity-sensitive) often flags dark-mode body text as harder to read than the same hex pair in light mode. Always re-test dark mode against both WCAG and APCA before shipping; light-on-dark loses perceived contrast in ways the symmetric ratio cannot see. ### Why does my brand color fail WCAG AA? Saturated mid-luminance colors (most oranges, yellows, lime greens, light blues) have relative luminance values too close to white to clear 4.5:1. The fix: keep the brand hue for accents and large headlines, but pair body text with a darker tone from the same hue family. Use OKLCH to lower the L channel without shifting hue. The [Color Converter](/tools/color-converter) finds the closest passing shade in one step. ### Are WCAG 2 ratios and APCA scores compatible? No. WCAG 2 returns a symmetric ratio (1–21); APCA returns a polarity-signed Lc score (-108 to +108). The relationship is non-linear: a pair that's 4.5:1 in WCAG might score Lc 60 or Lc 75 in APCA depending on which color is on top. Treat them as two independent checks, not as translations of one another. ### Can I use color contrast for small UI icons? Yes, with caveats. WCAG 2.1 §1.4.11 requires ≥ 3:1 for UI components and graphical objects. For decorative icons paired with a visible text label, contrast requirements relax because the label carries the meaning. For stand-alone icons (e.g., a search magnifier with no label), enforce the full 3:1 against the surrounding background. ### How do I test color blindness without simulating? Use Chrome DevTools → Rendering → "Emulate vision deficiencies" for protanopia, deuteranopia, tritanopia, and achromatopsia. Combine with the [Color Converter's](/tools/color-converter) 8-type CVD preview for the anomalous trichromacy variants (deuteranomaly being the most common at 5% of men). For audit reporting, capture screenshots under each simulation so reviewers can see the failure modes inline. ### Is 4.5:1 contrast ratio enough for accessibility? Yes — for normal body text on standard commercial sites. The 4.5:1 WCAG contrast ratio is the AA threshold for body text under 18pt regular / 14pt bold. Government, medical, and educational sites should target AAA (7:1). Anything below 4.5:1 fails accessibility audits and ADA compliance baselines. ## Conclusion Five takeaways carry the whole guide: - **AA 4.5:1 is the legal floor.** Hit it for all body text or expect compliance noise. - **AAA 7:1 is for healthcare, education, and government.** Most commercial brands stop at AA by design. - **APCA Lc is the real-readability sanity check.** Run it in parallel with WCAG 2, especially for dark mode and saturated brand colors. - **Color is never the only signal.** Pair every color cue with shape, text, or pattern. Deuteranomaly alone is 5% of male users. - **OKLCH L is the right knob.** When a color fails contrast, reduce L (not S, not B) to fix it without drifting hue. Drop any two hex codes into the [Color Converter](/tools/color-converter) to see WCAG ratio, APCA Lc, gamut classification, and the 8-type CVD preview side by side. That single view replaces six separate tools and is the fastest way to close out the audits this guide describes. --- ### Webhook Signature Verification Failed: Causes and Fixes URL: https://go-tools.org/blog/webhook-signature-verification-failed-hmac-guide Webhook signature verification failed? Usually it's the raw body, the digest encoding, or a missing timestamp prefix. Debug yours with a free HMAC tool. # Webhook Signature Verification Failed? Find Your Cause A webhook signature verification failed error means one thing: the digest your code computed does not equal the digest in the request header. That is the entire message. It says nothing about permissions or expiry, and it is almost never a bug in the provider's SDK. Something differs between the bytes the provider hashed and the bytes you hashed. Four inputs decide the outcome: which bytes were signed, which key bytes were used, which hash algorithm ran, and which text encoding you compared in. Get any one wrong and the failure looks identical. The error carries no hint about which one it was, so the job is narrowing the input space rather than reading the message more carefully. Pick a starting branch: ``` Signature doesn't match? Three branches: ├─ Did your framework parse the JSON before you saw it? → Section 3 ├─ Does the header value carry a prefix, or look like base64? → Section 4 └─ Does the provider's header contain a timestamp? → Section 2 ``` ## 1. What a signature mismatch tells you Verification is a comparison of two byte strings. When it fails, one of four things is wrong, and they are independent of each other. **Which bytes got signed.** The provider hashed a specific sequence of bytes. Maybe that is the request body alone, maybe it is a timestamp glued to the front of the body. If your framework parsed the JSON and handed you an object, you no longer have those bytes and cannot reconstruct them reliably. This is Section 3, and it is the most common cause by a wide margin. **Which key bytes got used.** The same secret string can be interpreted as UTF-8 text, as hex, or as base64, and each reading produces a different key. So does a secret with an extra newline the config loader kept. A second failure hides in this dimension: the secret may be the wrong secret entirely rather than the wrong reading of the right one, which is Section 6. **Which encoding you compared in.** A digest is 32 raw bytes for SHA-256. Hex and base64 are two ways of writing those same bytes down as text, and they never look alike. Compare one against the other and you get a permanent hmac signature mismatch even though the underlying bytes agree. **Which hash algorithm ran.** Most providers use SHA-256 and document it, so this dimension usually costs you nothing. GitHub is the exception worth knowing about: every delivery carries `X-Hub-Signature` (HMAC-SHA1) next to `X-Hub-Signature-256` (HMAC-SHA256), and GitHub's own docs say the SHA-1 header "is only included for legacy purposes" while recommending the 256 variant. Read the wrong one and the length gives it away before the bytes do. The body from Section 2, signed with the same secret under SHA-1, is `sha1=ba2954d180839d8170b08b32cd38483775aaae96` — 40 hex characters against the 64 of its SHA-256 digest. Keep those four separated while you debug. The fastest way to isolate a dimension is to compute the digest outside your application from inputs you control: paste a body and a secret into the [HMAC generator](/tools/hmac-generator) and see what you get. It runs entirely in your browser and the secret never leaves the page, so a production signing secret is safe to paste into it. HMAC runs the same SHA-256 primitive as a plain [SHA-256 hash](/tools/sha-256-generator), just keyed with your secret, so if you can reproduce the provider's value by hand, the cryptography is fine and the bug is in your request handling. ## 2. What the four big providers actually sign The assumption that sinks most integrations is that every provider signs the request body and nothing else. Two of the four biggest do not. What each one hashes, verified against the current provider documentation: | Provider | Header | Signed string | Encoding | Value prefix | Secret | Timestamp tolerance | |---|---|---|---|---|---|---| | Stripe | `Stripe-Signature` | `{timestamp}` + `.` + rawBody | hex | `t=…,v1=…,v0=…` | endpoint signing secret (`whsec_` prefix) | 5 minutes (300 seconds) | | GitHub | `X-Hub-Signature-256` | rawBody (no prefix) | hex | `sha256=` | webhook secret token | none (no timestamp sent) | | Slack | `X-Slack-Signature` + `X-Slack-Request-Timestamp` | `v0:` + `{timestamp}` + `:` + rawBody | hex | `v0=` | signing secret | 5 minutes | | Shopify | `X-Shopify-Hmac-SHA256` | rawBody | **base64** | none | **app client secret** (not a separate webhook secret) | none | Those four happen to cover three orthogonal axes. The signed string is either the body alone or a timestamp concatenation, and even the separator differs: Stripe uses `.` while Slack uses `:`. The encoding is hex for three and base64 for one. The secret comes from a dedicated webhook credential for three, and from the app's client secret for Shopify, which is the detail people get wrong most often because there is a field labelled "webhook" in the admin UI that is not the thing you want. The same body signed four ways with one secret: ``` body : {"id":42,"event":"user.created"} secret : whsec_test_secret ts : 1700000000 ``` | Shape | Value | |---|---| | GitHub style | `sha256=09dd9fef34ca68915e1ba93eb7515cbc33e7e753806767f81abc6409480c846b` | | Shopify style | `Cd2f7zTKaJFeG6k+t1FcvDPn51OAZ2f4GrxkCUgMhGs=` | | Stripe style | `t=1700000000,v1=4b56de5a58122bab8ebbadbed663fbc17d810096d57498f5b24a72f5123b2375` | | Slack style | `v0=3faf37337484c62dcd1a6c1ff308d1345c31291e4aac9b44554a99e8e35a1f9c` | Read the first two rows together, because they are the same 32-byte digest written twice. Sixty-four hex characters, or forty-four base64 characters including padding. Nothing about the two strings suggests they are equal, which is why comparing across encodings produces a mismatch that survives every "but the secret is right" check you can think of. The last two rows prove the other half of the point. Same body, same secret, same algorithm, and neither digest resembles the GitHub one, because the string being hashed now starts with a timestamp. Most reports of a Stripe webhook signature verification failed error come down to this row: the code hashed the body on its own and never prepended the `t` value and the dot. Reproduce all four in the [HMAC generator](/tools/hmac-generator) by editing only the message field and switching the output format, and the mechanism stops being abstract. One practical consequence of the timestamp column: a Stripe or Slack digest is only valid for a few minutes, so you cannot capture a signature today and replay it in a test tomorrow. GitHub and Shopify signatures are stable forever, which makes them far easier to debug and also means you have to think about replay protection yourself. ## 3. The raw body problem Most reports of webhook signature verification failed trace back to a framework that read and parsed the body before your handler ever saw it. ### Your framework already destroyed the bytes Web frameworks are built to save you from parsing. That convenience is what breaks signature verification, because by the time your handler runs, the original bytes are gone. `express.json()` reads the request stream, parses it, and replaces `req.body` with a JavaScript object. The stream is consumed and cannot be read again. In FastAPI, declaring a Pydantic model or a `dict` body parameter means the framework reads and parses before your function is entered. Rails populates `params` from the JSON body through a middleware that runs before your controller action. Spring's Jackson converter turns the body into your DTO class, and by default the underlying `HttpServletRequest` input stream can only be read once. Nothing here is a bug. Every one of these is doing what it was configured to do. The problem is that a signature covers bytes, an object is not bytes, and turning the object back into bytes is a different operation from the one the provider performed. ### Why re-serializing sometimes works, and that's the trap The usual advice is that re-serializing changes the bytes. That is incomplete, and the missing half is what makes this failure so hard to diagnose. Sometimes it changes nothing at all. `JSON.stringify(JSON.parse(body)) === body`, measured across payload shapes: | Payload shape | Bytes after round-trip | Change | |---|:-:|---| | `{"id":42,"event":"user.created"}` | **identical** | none, which is why local tests pass | | `{"amount":1.0}` | changed | → `{"amount":1}` | | `{"n":1e3}` | changed | → `{"n":1000}` | | `{"id":12345678901234567890}` | changed | → `{"id":12345678901234567000}` (precision lost) | | `{"name":"caf\u00e9"}` | changed | → `{"name":"café"}` (6 bytes become 2) | | `{"a":1}\n` | changed | trailing newline swallowed | | `{ "a" : 1 }` | changed | interior whitespace swallowed | | `{"v":-0.0}` | changed | → `{"v":0}` | | `{"p":0.1000000000000000055511151231257827}` | changed | → `{"p":0.1}` | Look at the first row. A flat object with an integer and a short ASCII string round-trips byte for byte, so a parse-then-restringify verifier passes every test you wrote against a fixture like that. Then you deploy, and the first payload carrying a monetary amount of `1.0`, an ID beyond 2^53, or a customer name with an accent fails. Not all of them. Just those. That is the mechanism behind "works locally, intermittent 401 in production", and it is considerably worse than a verifier that fails all the time. A verifier that always fails gets fixed in an hour. One that fails on 3% of events gets blamed on the provider, retried, escalated, and lived with for weeks. If your failure rate is somewhere strictly between zero and one hundred percent, this table is where to look first. Key order is the cause people expect and the least likely one in practice, because `JSON.parse` preserves insertion order for string keys. Numbers and whitespace are the real culprits. ### Getting the raw body in each framework Express, with the route-specific parser registered before the global JSON parser: ```js const express = require('express'); const crypto = require('crypto'); const app = express(); // This route must be registered BEFORE app.use(express.json()). // body-parser marks the request as parsed, so a later raw() silently yields {}. app.post('/webhooks/github', express.raw({ type: 'application/json' }), (req, res) => { const raw = req.body; // a Buffer, not an object const digest = crypto .createHmac('sha256', process.env.WEBHOOK_SECRET) .update(raw) // hash the Buffer directly, no toString() .digest('hex'); console.log('bytes:', raw.length, 'digest:', digest); res.sendStatus(200); }); app.use(express.json()); // every other route still gets parsed JSON app.listen(3000); ``` If you cannot reorder middleware, keep a copy during parsing instead: ```js app.use(express.json({ verify: (req, res, buf) => { req.rawBody = Buffer.from(buf); }, })); ``` FastAPI. Starlette caches the body, so `await request.body()` returns the original bytes even in a handler that also receives a parsed model: ```python import hashlib, hmac, os from fastapi import FastAPI, HTTPException, Request app = FastAPI() @app.post("/webhooks/github") async def github(request: Request): raw = await request.body() # bytes, exactly as received expected = "sha256=" + hmac.new( os.environ["WEBHOOK_SECRET"].encode("utf-8"), raw, hashlib.sha256 ).hexdigest() received = request.headers.get("X-Hub-Signature-256", "") if not hmac.compare_digest(expected, received): raise HTTPException(status_code=401, detail="bad signature") return {"ok": True} ``` Rails, where `request.raw_post` gives you the unparsed body as a string: ```ruby class WebhooksController < ApplicationController skip_before_action :verify_authenticity_token def shopify raw = request.raw_post digest = Base64.strict_encode64( OpenSSL::HMAC.digest('sha256', ENV['SHOPIFY_CLIENT_SECRET'], raw) ) unless OpenSSL.secure_compare(digest, request.headers['X-Shopify-Hmac-SHA256'].to_s) return head :unauthorized end head :ok end end ``` Go, where you read the body yourself and must remember it is drained afterwards: ```go func handler(w http.ResponseWriter, r *http.Request) { raw, err := io.ReadAll(r.Body) if err != nil { http.Error(w, "unreadable body", http.StatusBadRequest) return } mac := hmac.New(sha256.New, []byte(os.Getenv("WEBHOOK_SECRET"))) mac.Write(raw) expected := mac.Sum(nil) got, err := hex.DecodeString( strings.TrimPrefix(r.Header.Get("X-Hub-Signature-256"), "sha256=")) if err != nil || !hmac.Equal(expected, got) { http.Error(w, "bad signature", http.StatusUnauthorized) return } // Unmarshal from raw, never from r.Body — it has no bytes left. w.WriteHeader(http.StatusOK) } ``` Spring, where asking for `byte[]` skips Jackson entirely: ```java @PostMapping(path = "/webhooks/github", consumes = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity github(@RequestBody byte[] payload, @RequestHeader("X-Hub-Signature-256") String header) throws GeneralSecurityException { Mac mac = Mac.getInstance("HmacSHA256"); mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256")); String expected = "sha256=" + HexFormat.of().formatHex(mac.doFinal(payload)); boolean ok = MessageDigest.isEqual(expected.getBytes(StandardCharsets.UTF_8), header.getBytes(StandardCharsets.UTF_8)); return ok ? ResponseEntity.ok().build() : ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); } ``` `ContentCachingRequestWrapper` is the alternative when a filter has to do the check and you cannot change the controller signature. It has a trap of its own: `getContentAsByteArray()` returns bytes only after something downstream has read the stream, so calling it before `chain.doFilter(...)` gives you an empty array. ## 4. Encoding mismatches: hex, base64, and the key itself Three separate encoding decisions sit between your digest and the header value, and any of them can break the comparison on its own. **The digest encoding.** HMAC-SHA256 output is 32 bytes. Written as lowercase hex it is 64 characters; written as standard base64 it is 44 including the `=` pad. The two rows from Section 2 are one digest written in both: | Encoding | Characters | Same 32 bytes written as | |---|:-:|---| | hex | 64 | `09dd9fef34ca68915e1ba93eb7515cbc33e7e753806767f81abc6409480c846b` | | base64 | 44 | `Cd2f7zTKaJFeG6k+t1FcvDPn51OAZ2f4GrxkCUgMhGs=` | A quick heuristic when you are staring at an unfamiliar header: if the value is 64 characters of `0-9a-f`, it is hex. If it is 44 characters ending in `=`, or contains `+`, `/`, or uppercase letters, it is base64. When you want to confirm rather than guess, run the base64 value through the [Base64 decoder](/tools/base64-decode-encode) and check that it yields 32 bytes; if it does, both strings describe the same digest and you were comparing text formats, not signatures. **The value prefix.** GitHub sends `sha256=` in front of the hex. Slack sends `v0=`. Stripe wraps everything in a comma-separated list of `key=value` pairs. None of those characters are part of the digest, so either strip the prefix from the header or add it to your own value. Doing neither is the most common reason an otherwise correct implementation reports an hmac signature mismatch, and in Node it does not even report a mismatch, as Section 7 explains. **The key encoding.** The secret is bytes too, and the same string read as UTF-8, hex, or base64 gives three different keys. Providers that hand you a text token like `whsec_...` want UTF-8, but plenty of internal systems distribute base64 or hex secrets that must be decoded before signing. This failure mode is identical in shape to the JWT version of the problem and is covered in depth in [JWT invalid signature: every cause and how to fix it](/blog/jwt-invalid-signature-troubleshooting-guide), including how to tell whether a given secret is base64 or plain text. ## 5. Timestamp, tolerance, and replay windows You can compute a digest that matches perfectly and still be rejected. Providers that include a timestamp expect you to check it, and a stale timestamp is a valid signature you must refuse anyway. | Provider | Where the timestamp lives | Window | |---|---|---| | Stripe | `t=` inside `Stripe-Signature` | 5 minutes (300 seconds) | | Slack | `X-Slack-Request-Timestamp` header | 5 minutes | | GitHub | not sent | not applicable | | Shopify | not sent | not applicable | Both directions of getting the window wrong hurt. Too generous, and a captured request stays replayable for as long as you allow, which defeats most of the point of checking the timestamp. Too tight, and ordinary clock drift starts rejecting real deliveries. Five minutes is what both providers chose, and copying that is a sound default. Before you widen a tolerance, check the clock. Container images do not run NTP, and a VM resumed from a snapshot can be minutes behind wall time with nothing in the logs to say so. A host that drifts steadily produces failures that begin as occasional and become total, which reads like a code regression and is not one. The other clock bug is a unit mismatch. Every provider in the table sends epoch seconds. Compare one against a millisecond value like JavaScript's `Date.now()` and the difference is roughly a thousand times the real age, so every event is outside every plausible window. The symptom is a tolerance check that rejects one hundred percent of deliveries while the digest itself matches. If you are unsure which unit you are holding, the length is the tell, and [epoch seconds versus milliseconds](/blog/unix-timestamp-guide-epoch-seconds-ms-timezone-dst) covers the conversions and the timezone traps around them. Use the raw timestamp string from the header when you build the signed string, not a parsed and reformatted number. Parsing `1700000000` to a float and printing it back can yield `1700000000.0`, and that is a different byte sequence. ## 6. Wrong secret, and secrets that rotate Before you go any further into encodings, rule out the plainest cause: the secret may not be the right secret. Stripe's docs are explicit that "Stripe generates a unique secret key for each endpoint," and that if you point the same URL at both test and live keys, "the secret is different for each one." Three versions of one mistake follow from that. Test mode and live mode hold separate secrets, so a value copied while the dashboard was in test mode fails every live delivery. Each endpoint holds its own, and the docs add that "if you use multiple endpoints, you must obtain a secret for each one you want to verify signatures on" — aim two endpoints at one handler with one secret in the environment and half your traffic fails. And `stripe listen` prints a signing secret for the CLI's local forwarding, which is a separate endpoint from anything registered in the dashboard, so the two are not interchangeable. None of these look like encoding bugs from the outside. The digest is well formed, the comparison is correct, and the value in your environment is a real Stripe secret — just not the one that signed this delivery. Rotation is the same dimension moving under you. It looks least like an encoding problem and gets misdiagnosed as a code bug most often. Nothing in your code changed, verification worked yesterday, and now a fraction of events fail. The overlap window is deliberate. Stripe keeps the old endpoint secret valid for up to 24 hours after you rotate, and during that period the `Stripe-Signature` header carries one `v1` signature for each active secret. Shopify goes the other way: after rotation it can take up to an hour before it starts using the new secret to compute digests, so the old one is what you need in the meantime. The Stripe behaviour is what breaks code, because the header looks like it has one signature in it. Splitting on `,` and taking the first `v1` you find works right up until there are two, at which point you match roughly half the time depending on which secret signed which event. Iterate over all of them: ```js const crypto = require('crypto'); function verifyStripe(header, rawBody, secret, toleranceSec = 300) { let t = null; const v1 = []; for (const pair of header.split(',')) { const idx = pair.indexOf('='); const key = pair.slice(0, idx); const value = pair.slice(idx + 1); if (key === 'v1') v1.push(value); else if (key === 't') t = value; // keep the original string } if (t === null || v1.length === 0) return false; const age = Math.abs(Math.floor(Date.now() / 1000) - Number(t)); if (!Number.isFinite(age) || age > toleranceSec) return false; const signedPayload = Buffer.concat([Buffer.from(`${t}.`, 'utf8'), rawBody]); const expected = crypto.createHmac('sha256', secret).update(signedPayload).digest(); return v1.some((sig) => { const received = Buffer.from(sig, 'hex'); return received.length === expected.length && crypto.timingSafeEqual(received, expected); }); } ``` Two details in there matter beyond the loop. The timestamp goes into the signed payload as the string it arrived as, and the body is concatenated as bytes rather than through template interpolation, which would decode it as UTF-8 first. The same shape applies when you rotate on your side: accept both the old and the new secret for the length of the overlap, then drop the old one. Whatever you rotate to needs full entropy, so generate it rather than typing it, using something like the [signing secret generator](/tools/jwt-secret-generator) for a 256-bit random value. ## 7. Comparing signatures without leaking timing Once you have two digests, how you compare them is a security decision. String equality returns as soon as it finds a differing byte, so the time it takes reveals how many leading bytes were correct. An attacker who can submit many requests uses that to recover a valid signature one byte at a time. It is slow and noisy over the internet, and entirely practical on a local network. Every runtime ships a fixed-time comparison: | Language | Constant-time compare | When lengths differ | |---|---|---| | Node | `crypto.timingSafeEqual(a, b)` | **throws** | | Python | `hmac.compare_digest(a, b)` | returns `False` | | Go | `hmac.Equal(a, b)` | returns `false` | | PHP | `hash_equals($known, $user)` | returns `false` | | Ruby | `OpenSSL.secure_compare(a, b)` | returns `false` | That last column is where a whole class of confusing incidents comes from. Node is the outlier, and it does not fail politely: ``` RangeError [ERR_CRYPTO_TIMING_SAFE_EQUAL_LENGTH]: Input buffers must have the same byte length ``` It fires on a trivial slip. A hex SHA-256 digest is 64 characters. The value in `X-Hub-Signature-256` is 71, because `sha256=` is seven characters. Forget to strip the prefix and the two buffers have different lengths, so `timingSafeEqual` throws instead of returning false. Uncaught, that exception propagates out of your handler and Express turns it into a 500. From the outside that looks like something else entirely. You are looking for a webhook 401 unauthorized response, you get a server error, so you go read your handler and your event dispatcher. The bug is one line above the comparison. Comparing a 64-character hex digest against a 44-character base64 one throws for the same reason, which means an encoding mismatch in Node also surfaces as a 500 rather than a clean rejection. The fix is to check the length yourself and return false: ```js function safeEqualHex(receivedHex, expectedHex) { const a = Buffer.from(receivedHex, 'hex'); const b = Buffer.from(expectedHex, 'hex'); if (a.length !== b.length) return false; // guard before the call return crypto.timingSafeEqual(a, b); } ``` Leaking the length is harmless; a digest length is fixed by the algorithm and public. What you must not leak is which prefix matched. The Verify tab of the [HMAC generator](/tools/hmac-generator) folds the length difference into the same constant-time accumulator instead of returning early, so a length mismatch comes back as a plain false rather than an exception, and you can check a header value against your computed digest without writing throwaway code. ## 8. When the transport layer changed your bytes You have ruled out the signed string, the raw body, the encodings, the clock, and rotation. What is left is the possibility that the bytes arriving at your process are not the bytes that left the provider. **Compression.** A provider or proxy may send the body gzipped with `Content-Encoding: gzip`. The signature covers the uncompressed payload, so you must hash after decompression. Some frameworks decompress transparently and some hand you the compressed bytes, and a body that looks like binary garbage in your log is the giveaway. **Chunked transfer.** With `Transfer-Encoding: chunked` there is no `Content-Length`, and code that trusts that header to size a read buffer truncates the body. The digest of a truncated body is valid nonsense: it will never match, and nothing looks wrong. **Proxies and WAFs.** Any layer that reads and rewrites the body can change it. AWS API Gateway can base64-encode the body before it reaches a Lambda, so you must decode before hashing. Application load balancers, service meshes, and web application firewalls can all normalize or re-encode a payload on the way through. Test by comparing the byte length your handler sees against the `Content-Length` the provider sent. **Character encoding and BOM.** Payloads can contain non-ASCII characters, and GitHub's documentation is explicit that the payload must be handled as UTF-8. Decoding the body to a string in the wrong charset and re-encoding it destroys every multi-byte character. A UTF-8 byte order mark, `EF BB BF`, prepended by a well-meaning editor or serializer adds three bytes that were never signed. **Line endings and stray whitespace.** A body that crossed a text-mode file boundary can arrive with `LF` rewritten to `CRLF`. Read the provider's spec for the exact signing string too: some append a character of their own, and Typeform documents a trailing newline as part of what gets hashed. When a provider's docs mention any extra character, take it literally. ## 9. A repeatable debugging workflow Run these in order. Each step either finds the bug or eliminates a branch, and stopping early is the point. 1. **Log the raw bytes before any middleware runs.** Write the body to a file, or log its byte length plus its SHA-256, from the earliest point in the request lifecycle you can reach. Length alone resolves a surprising number of cases: a value one greater than expected is a trailing newline, three greater is a BOM. 2. **Compute the digest by hand.** Paste those exact bytes and your secret into the [HMAC generator](/tools/hmac-generator), pick SHA-256, and set the output format to match the header. Doing this before anything else splits the problem cleanly in two. 3. **Compare the hand-computed value with the header.** Equal means the bytes and the secret are both correct and the bug is somewhere in your code path, so go read your comparison. Not equal means one of the inputs is wrong, so continue. 4. **Check the signed string against the table in Section 2.** Does this provider prepend a timestamp? With which separator? Add the prefix in the tool and recompute. 5. **Switch the digest encoding.** Recompute as hex and as base64 and compare both against the header. A 44-character header value with an `=` on the end is base64, whatever your code assumed. 6. **Switch the key encoding.** Try the secret as text, then hex, then base64. One of the three usually produces a match, and that tells you what the provider expects. 7. **Check the clock and the rotation state.** Compare your server's time against a known source, confirm you are handling epoch seconds, and check the provider's dashboard for a rotation in the last 24 hours. Two habits make this loop much faster. First, capture one failing payload and work from it offline instead of waiting for the next delivery. Second, replay that captured body against your endpoint with a fixed signature so the input never varies between attempts. The [cURL command builder](/tools/curl-builder) assembles the request with the exact headers and a body read from a file, which keeps the bytes stable across runs. Reproducing the failure on demand is what turns an intermittent webhook signature verification failed report into a five-minute fix. If you still need to file a support ticket, include the byte length of the body you hashed, the header value verbatim, the signed string construction you used, and the digest encoding. Never include the secret itself. ## FAQ ### Why does my webhook signature work locally but fail in production? Your test payload probably survives a JSON round-trip unchanged, so re-serializing it is harmless. Real payloads contain floats, large integers, Unicode escapes, or extra whitespace, and those do change the bytes. Sign the raw body instead of a re-serialized copy; the table in Section 3 shows which shapes break. ### Should I include the sha256= prefix when comparing signatures? Strip it, or add it to your own value so both strings match exactly. Your computed hex digest is 64 characters and the header value is 71 with the prefix. Some comparison functions return false on a length mismatch, and Node's `timingSafeEqual` throws instead of returning false. ### Can I verify the signature after my framework parsed the JSON? Not reliably. Re-serializing reproduces the original bytes only for payloads with no floats, no integers beyond 2^53, no Unicode escapes, and no extra whitespace. The moment one appears the digest changes, so verification passes in testing and fails on a fraction of production events. ### Why do Stripe and GitHub produce different signatures for the same payload? Because they hash different strings. GitHub signs the raw body alone. Stripe signs the timestamp, a literal `.`, then the body, so one payload delivered at two different times yields two different digests. Slack prepends `v0:` and its own timestamp. Same algorithm, different input. ### How long should the timestamp tolerance be? Five minutes is what Stripe and Slack use, and copying it is a reasonable default. Shorter windows reject legitimate deliveries as soon as your server clock drifts. Longer windows widen the period in which a captured request can be replayed. Sync clocks with NTP before loosening the tolerance. ### Does timingSafeEqual return false when the lengths differ? No. Node throws `RangeError [ERR_CRYPTO_TIMING_SAFE_EQUAL_LENGTH]: Input buffers must have the same byte length`. Uncaught, that becomes a 500 instead of a 401, which sends you debugging your handler rather than the line above the comparison. Compare lengths first and return false yourself. ### My provider rotated the secret, so why do some webhooks still fail? Rotation windows overlap. Stripe keeps the old secret valid for up to 24 hours and sends one `v1` signature per active secret, so code that reads only the first `v1` fails on roughly half the events. Shopify can take up to an hour to start using the new secret. ## Conclusion Verification is a byte comparison, so webhook signature verification failed always resolves to a disagreement about bytes rather than to anything cryptographic. Keep the three dimensions apart while you debug. Capture the raw body before any parser touches it, and never hash a re-serialized object: it matches often enough to pass your tests and not often enough to work in production. Read the secret the way the provider reads it, since text, hex and base64 readings of one string give three different keys. Then check the encoding you compared in, because hex is 64 characters and base64 is 44 and both can describe the same 32 bytes. After those, in rough order of likelihood: the timestamp prefix, the value prefix, the tolerance window, the rotation overlap, and the transport layer. However you write the comparison, guard the length first, then hand both values to your runtime's constant-time function. When you want a value you can trust to compare against, compute it outside your application: paste the body and the secret into the [HMAC generator](/tools/hmac-generator) and let it tell you which side is wrong. --- ### WebP vs AVIF vs JPEG: Which Image Format Wins in 2026? URL: https://go-tools.org/blog/webp-vs-avif-vs-jpeg-image-format-guide AVIF is 20–30% smaller than WebP and 30–50% smaller than JPEG, but encodes 5–20× slower. 2026 browser support, real benchmarks, and fallback patterns. Try free. # WebP vs AVIF vs JPEG: Which Image Format Wins in 2026? **TL;DR.** AVIF is 20–30% smaller than WebP and 30–50% smaller than JPEG. WebP is roughly 25–35% smaller than JPEG. AVIF encodes 5–20× slower than WebP, while WebP decodes the fastest of the three. The 2026 winning workflow: serve AVIF first, fall back to WebP, keep JPEG as the universal safety net, and let the browser pick via the `` element. That answer covers most readers. The interesting question is when *not* to reach for AVIF. Skip it on tight CI budgets where encoding time matters more than bytes. Hold it back for real-time user uploads, since browser-side AVIF encoding is still patchy. Keep JPEG primary if you must support iOS 16.0–16.3 or older Edge in the wild. Everywhere else, AVIF wins on file size, provided your `` markup is correct and your CDN sends the right MIME type. The rest of this guide is the working detail: 2026 support numbers, a real-photo benchmark, a use-case decision tree, copy-paste `` patterns, conversion commands, and the four traps that cost teams time. If you just want the conversion done, our [free image compressor](/tools/image-compressor) handles JPEG, PNG, WebP, and AVIF in the browser without uploading anything. ## 1. 2026 browser support: WebP at 97%, AVIF at 93% Two and a half years after Edge shipped AVIF, the support gap has narrowed to a few percent of global traffic. The question is no longer "is it supported" but "what do we serve to the last 3%?" ### 1.1 WebP: the safe default WebP shipped in Chrome back in 2012, Firefox 65 (2019), Edge 18 (2018), and Safari 14 (2020). As of May 2026, caniuse puts global support at roughly 97%. WebP has graduated from "modern alternative" to "viable fallback." If you serve a single non-JPEG format, WebP is it. ### 1.2 AVIF: now usable as the primary format AVIF arrived in Chrome 85 (August 2020) and Firefox 93 (October 2021). Safari 16.4 enabled it on macOS and iOS in March 2023. Edge was the laggard, only adding decode support in 121 (January 2024). Global coverage in May 2026 is roughly 93–95%. One sharp edge worth flagging: iOS 16.0–16.3 has a known AVIF decode bug that can crash Safari on certain images. Those builds are still alive on devices held back by enterprise MDM, so AVIF without a working WebP or JPEG fallback is a real outage risk. Treat it as "primary with insurance," not "primary alone." ### 1.3 Quick compatibility matrix | Format | Global support (May 2026) | Key limitation | |--------|---------------------------|----------------| | JPEG | 100% | Largest files; no transparency; no HDR | | WebP | ~97% | No HDR or 10-bit color | | AVIF | ~93–95% | Slow encode; iOS 16.0–16.3 decode bug; needs Edge 121+ | ## 2. Core differences: compression, speed, features ### 2.1 Compression efficiency on a real photo One same-source benchmark. A 4000×3000 landscape photograph, original PNG around 24 MB, re-encoded at perceptually matched quality: | Encoding | Size | Vs JPEG baseline | |----------|------|------------------| | JPEG q75 (mozjpeg) | ~2.1 MB | 0% (baseline) | | WebP q75 (libwebp) | ~1.4 MB | 33% smaller | | AVIF q60 (libavif, cpu-used 4) | ~1.0 MB | 52% smaller | AVIF q60 here is visually equivalent to JPEG q80 and WebP q75. Quality scales are not interchangeable across codecs. Re-encoding "JPEG q75 to AVIF q75" is the classic beginner mistake; you end up with an oversized AVIF that looks identical to the source. ### 2.2 Encoding speed: the 5–20× tax AVIF compresses harder because it does more work. Using libavif at the default `cpu-used 4`, a 4000×3000 image takes 5–20× longer than libwebp and roughly 50× longer than mozjpeg. That cost compounds across CI builds and any pipeline that re-encodes thousands of assets, including Lambda cold starts. Two escape valves. `cavif --speed 9` (or libavif `cpu-used 9`) gets within 3× of libwebp at the price of 5–8% larger files. And cache aggressively: a content-addressed asset pipeline that skips re-encoding unchanged sources turns a slow codec into a one-time cost. ### 2.3 Color depth and HDR JPEG and WebP top out at 8-bit sRGB. AVIF handles 10- and 12-bit color, Rec. 2020, DCI-P3, and PQ/HLG transfer functions natively. For HDR video thumbnails, pro photo galleries, or browser-side print proofs, AVIF is the only standardized option today. ### 2.4 Transparency and animation JPEG has neither. WebP and AVIF both support alpha and animation. For transparent-PNG replacement specifically, AVIF encodes alpha more compactly: a UI illustration that shrinks 30–40% as WebP often shrinks 50–70% as AVIF. ## 3. Decision tree: which format for which job ### 3.1 Static sites: blogs, marketing pages, docs AVIF primary, WebP fallback, JPEG safety net. Encoding happens once in CI, so the 5–20× tax is paid in build time, not user time. This is the canonical case for the three-source `` pattern in section 4. ### 3.2 User uploads: avatars, UGC, form attachments Compress to WebP in the browser, re-encode to AVIF async on the server. Browser-side `canvas.toBlob('image/avif')` works only on Chrome 99+ today, so AVIF can't be the upload path. WebP compresses fast in the browser and saves bandwidth on the upload itself. For a deeper comparison of client-side libraries (Squoosh, browser-image-compression, Compressor.js) and how they pair with server-side Sharp or Imagemin, see the sister [browser-based image compression guide](/blog/image-compression-browser-vs-node). Format choice and processing location are orthogonal; this guide covers the format axis. ### 3.3 HDR and high-fidelity photography AVIF only. Nothing else on the open web stack supports 10- or 12-bit color today. Skip the fallback for HDR-only assets, or accept that the JPEG fallback will be SDR. ### 3.4 Legacy-browser-heavy audiences JPEG primary, WebP optional, no AVIF. Government portals, certain East Asian enterprise environments, and B2B tools with long device tails sometimes show 5–10% IE/old-Edge traffic in analytics. WebP is now safe; AVIF is not yet. ### 3.5 Real-time CDN delivery Server-side libvips or Sharp streaming WebP, with AVIF generated on a background worker and served on cache hit. Don't block the response on AVIF encoding. Cloudflare Polish, Vercel Image Optimization, and Cloudinary `f_auto` automate the pattern. ## 4. The `` fallback, done right The `` element exists exactly so the browser can choose the best supported source. Three layers, listed AVIF first, give you the optimal byte budget without breaking on older clients. ### 4.1 The three-layer baseline ```html Mountain landscape at sunset ``` Three things to call out. The browser walks `` elements top-down and picks the first one whose `type` it understands; everything else is ignored, not downloaded. The `` tag is the actual rendered element and inherits attributes (alt, sizes, classes) regardless of which source wins. And `width`/`height` on the `` are not optional in 2026; they reserve space and prevent Cumulative Layout Shift. For above-the-fold images, swap `loading="lazy"` for `loading="eager" fetchpriority="high"`. Lazy-loading the LCP image is one of the most common Core Web Vitals foot-guns. ### 4.2 Responsive plus format: the full pattern When you also need responsive resolutions, repeat `srcset` inside each ``: ```html Mountain landscape at sunset ``` Yes, that's nine generated files per image. A build step is non-negotiable at this scale; section 5.3 covers what to plug in. ### 4.3 Server-side `Accept` negotiation as an alternative If your CDN supports it, content negotiation collapses the markup to a single `` tag. The browser sends `Accept: image/avif,image/webp,image/apng,*/*` and the CDN responds with the best supported format at the same URL. Cloudflare Polish, Vercel Image Optimization, Cloudinary `f_auto`, and CloudFront with Lambda@Edge all implement this. The trade-off: smaller HTML and one URL per image, but CDN lock-in and harder local debugging. A useful split is CDN negotiation for marketing pages, explicit `` for product UI where deterministic behavior matters. ## 5. How to convert images between formats ### 5.1 In the browser, no upload The fastest path for a one-off or small batch is browser-only. Drop a JPEG into the [free image compressor](/tools/image-compressor), pick WebP or AVIF as the output, and the file never leaves your machine. AVIF input is supported everywhere; AVIF output uses native browser encoding on Chrome 85+ and transparently falls back to WebP on browsers that can't encode AVIF yet. ### 5.2 Command line for build pipelines For a production pipeline, you want deterministic output and reproducible builds. These three commands are real: ```bash # AVIF: cavif (Rust, easy install via cargo or brew) cavif --quality 60 --speed 4 input.jpg -o output.avif # WebP: cwebp (official Google libwebp) cwebp -q 75 -m 6 input.jpg -o output.webp # Both, plus resize, via libvips (fastest for batches) vips webpsave input.jpg output.webp[Q=75,effort=6] vips heifsave input.jpg output.avif[Q=60,compression=av1,effort=4] ``` `cavif --speed` runs 0 (slowest, smallest) to 10 (fastest, largest). Default 4 is the sweet spot for nightly builds; bump to 9 for PR previews where speed beats bytes. ### 5.3 Build pipeline integration Most static-site frameworks already wrap these encoders. Pick the one that matches your stack: ```js // Next.js — next.config.js module.exports = { images: { formats: ['image/avif', 'image/webp'], deviceSizes: [640, 828, 1080, 1200, 1920, 2400], }, }; ``` ```ts // Astro — astro.config.mjs import { defineConfig } from 'astro/config'; export default defineConfig({ image: { service: { entrypoint: 'astro/assets/services/sharp' }, }, }); ``` ```js // Programmatic — Sharp (Node.js) import sharp from 'sharp'; await sharp('input.jpg') .resize({ width: 1600 }) .avif({ quality: 60, effort: 4 }) .toFile('output.avif'); await sharp('input.jpg') .resize({ width: 1600 }) .webp({ quality: 75, effort: 6 }) .toFile('output.webp'); ``` For tiny assets (icons under about 5 KB, inline avatars, email headers), skip the network entirely and inline as a data URI using [base64 encoding](/tools/base64-decode-encode). That swaps an HTTP request for a few extra bytes of HTML, usually a win below ~5 KB. If you're picking between client-side and Node-side compression libraries (Sharp vs Squoosh vs browser-image-compression), the [browser-based image compression guide](/blog/image-compression-browser-vs-node) goes deeper on benchmarks and trade-offs. ## 6. Pitfalls and misconceptions ### 6.1 "AVIF always wins" — not for screenshots and line art AVIF's strengths are continuous-tone photographs. On screenshots with sharp text, UI captures, and pixel art, WebP often produces smaller files at equivalent quality. AVIF's deblocking can introduce subtle banding on flat color regions. Run conversions both ways and pick the winner per asset class. ### 6.2 "Lossless WebP perfectly replaces transparent PNG" — close, not identical WebP lossless is genuinely smaller than PNG, typically about 26%. The catch: "lossless" applies to the compressed image, but the encoder may still alter alpha-channel rounding in high-gradient regions. For pixel-exact reproduction (medical imagery, archival assets, anything legally required to match the source), keep PNG. For everything else, WebP lossless is a net win. ### 6.3 "Just upload .avif files" — your CDN may not know what they are Older nginx and Apache configs predate AVIF. If `/etc/nginx/mime.types` doesn't list `image/avif avif;`, nginx serves AVIF as `application/octet-stream`. The browser sees the wrong Content-Type, refuses to render the image, and quietly falls back to JPEG, defeating the entire optimization. Curl your asset URL after deploy and check the Content-Type header. Five seconds of paranoia saves a week of "why is AVIF broken in production." ### 6.4 The iOS 16.0–16.3 AVIF crash Some AVIF files trigger a Safari decode crash on iOS 16.0–16.3. The bug is fixed in 16.4 (March 2023), but enterprise MDM and slow OEM updates keep older devices alive. Mitigation: never ship AVIF without a working WebP source in the same ``, and never set an AVIF as the `` `src` directly. Following section 4's patterns already protects you. ## 7. The 2026 cheat sheet | Scenario | Primary | Fallback | Encoder | |----------|---------|----------|---------| | Static marketing pages | AVIF q60 | WebP q75 + JPEG q80 | cavif + cwebp in CI | | Blog posts and docs | WebP q75 | JPEG q80 | [free image compressor](/tools/image-compressor) (manual) or Sharp (build) | | User uploads | WebP (client-side) | JPEG (browser without WebP encode) | browser-image-compression + server-side Sharp for AVIF | | HDR / pro photography | AVIF 10-bit q70 | (none — drop SDR fallback or skip AVIF entirely) | cavif + libavif HEIF mode | | Real-time CDN delivery | Negotiated (AVIF or WebP) | JPEG | Cloudflare Polish, Cloudinary `f_auto`, Vercel Image | Two reminders before shipping. Always set `width` and `height` on the `` to prevent CLS. Always verify the production Content-Type header on at least one AVIF response; broken MIME config silently kills AVIF in production more often than any other failure on this list. ## FAQ ### Do I still need a JPEG fallback in 2026? Yes. AVIF reaches roughly 93% of global users and WebP about 97%. That leaves a small but real population (old Edge, Firefox below 93, iOS before 16.4, plus the iOS 16.0–16.3 bug zone) who need JPEG. Drop JPEG only if your analytics show effectively zero traffic from those browsers and you can prove it. ### How do I keep CI builds fast when AVIF encoding is slow? Three levers. Use `cavif --speed 6` or higher (defaults to 4) to trade ~5% size for ~3× speed. Parallelize across cores with GNU parallel or your build tool's worker pool. And cache by content hash so unchanged source images skip the encoder entirely. Combined, these usually cut AVIF build time below WebP's old single-threaded baseline. ### Is WebP decoding really faster than AVIF? Yes. libwebp decodes roughly 2–3× faster than libavif, and the gap widens on low-end mobile. If your performance bottleneck is decode (a long image gallery on a budget Android phone, for example) rather than network, WebP is the better primary format. For most web traffic, network savings dominate and AVIF still wins overall. ### Can iPhone users see AVIF? iPhones running iOS 16.4 (March 2023) or later support AVIF natively in Safari. Devices on iOS 16.0–16.3 have a documented decode bug that can crash Safari on certain AVIF files; older iOS versions don't support AVIF at all. Always ship a WebP or JPEG fallback inside `` so affected users see something. ### Should I use `` or Accept-header negotiation? Smaller projects benefit from explicit `` markup: behavior is deterministic, you can debug locally, and it adds maybe 80 bytes of HTML per image. High-traffic sites win with CDN-side `Accept` negotiation: one URL per image, automatic format upgrades, and the CDN caches each format separately. A common hybrid is `` for app UI and CDN negotiation for marketing assets. ### Why did my PNG become larger after converting to WebP? Almost always because the source PNG was already aggressively optimized by pngquant, oxipng, or ZopfliPNG, and the browser's Canvas-based encoder can't match those tools. Re-encode from the original (Photoshop export, design-tool master, RAW) instead of from the optimized PNG. If the optimized PNG is your only source, the original is already near-optimal; leave it alone. ### Does AVIF support transparency? Yes. AVIF supports a full 8- to 12-bit alpha channel, and its alpha encoding is generally more compact than WebP's. A transparent PNG illustration converted to AVIF typically shrinks 50–70%, versus 30–40% as WebP. AVIF is the strongest replacement for transparent PNG in any context that doesn't demand pixel-exact lossless output. ### Can browsers encode AVIF natively via toBlob('image/avif')? Only Chrome 99+ at the moment. Safari and Firefox can decode AVIF but cannot encode it via Canvas APIs as of May 2026. For client-side AVIF encoding you currently need WebAssembly libraries like libavif-wasm or jsquash, which add 1–2 MB of payload. Most production stacks compress to WebP in the browser and hand off AVIF generation to a server worker. --- ### What Exactly Lives Inside a PostgreSQL timestamp Column? URL: https://go-tools.org/blog/what-is-stored-in-pg-timestamp-column A plain-English guide to how PostgreSQL stores timestamp vs timestamptz, why timezones bite, and how to choose the right type for your use case. # PostgreSQL timestamp vs timestamptz: What's Actually Stored Under the Hood? PostgreSQL maintains both `timestamp` and `timestamptz` as a single 64-bit integer: the number of microseconds since 1970-01-01 00:00:00 UTC. The distinction emerges only during data formatting for human consumption. ## Why Does This Trip People Up? - Two columns, one date... two different query results - Your app inserts `2025-07-29 10:00`, but another team sees `02:00` - The frontend renders an ISO string that doesn't match the backend log ## Two Cans of Peaches: One Plain, One Labeled | Data Type | Official Name | Stored Value | What Happens on SELECT | |-----------|---------------|--------------|------------------------| | `timestamp` | timestamp **without** time zone | raw microsecond count | Sent back unchanged — Postgres never guesses a timezone | | `timestamptz` | timestamp **with** time zone | same microsecond count | Postgres applies the session `TimeZone` setting just before sending the text | ### Analogy - **`timestamp`** = a jar of peaches with no origin label. You know it's fruit, but not where it was canned. - **`timestamptz`** = a jar proudly stamped "Made in UTC+8." Anyone opening it can decide whether to convert the nutrients panel. ## Under the Hood: It's Just a Giant Number ``` 2000-01-01 00:00:00 UTC → 0 2000-01-01 00:00:01 UTC → 1 000 000 ``` - **Unit**: microseconds (one-millionth of a second) - **Range**: 4713 BC – 294276 AD — Indiana Jones approved - Storage for `timestamp` and `timestamptz` is **identical**; interpretation differs ## A 15-Second Demo ```sql -- Client thinks in Shanghai time SET TimeZone = 'Asia/Shanghai'; CREATE TABLE demo ( created_ts timestamp, created_tz timestamptz ); INSERT INTO demo VALUES ('2025-07-29 10:00', '2025-07-29 10:00'); ``` | Query | Result | Why | |-------|--------|-----| | `SELECT created_ts FROM demo;` | 2025-07-29 10:00:00 | Raw value, no TZ math | | `SELECT created_tz FROM demo;` | 2025-07-29 10:00:00+08 | Tag applied on output | | `SET TimeZone = 'UTC';` then select | 2025-07-29 02:00:00+00 | Same instant, new lens | ## Timestamp Arithmetic and Intervals One of the most practical aspects of PostgreSQL timestamps is interval arithmetic. Because both types store microsecond counts, you can add and subtract intervals directly: ```sql -- Add 3 hours and 30 minutes SELECT '2025-07-29 10:00'::timestamptz + INTERVAL '3 hours 30 minutes'; -- → 2025-07-29 13:30:00+08 -- Find the difference between two timestamps SELECT '2025-07-30 09:00'::timestamptz - '2025-07-29 10:00'::timestamptz; -- → 23:00:00 (an interval) -- Extract specific fields SELECT EXTRACT(EPOCH FROM '2025-07-29 10:00:00+08'::timestamptz); -- → 1753768800 (Unix timestamp in seconds) -- Truncate to day boundary (useful for daily aggregations) SELECT date_trunc('day', '2025-07-29 15:42:19+08'::timestamptz); -- → 2025-07-29 00:00:00+08 ``` The `EXTRACT(EPOCH FROM ...)` function is particularly useful when you need to pass timestamps to external systems that expect Unix epoch seconds. Conversely, you can convert an epoch back to a timestamp: ```sql SELECT to_timestamp(1753768800); -- → 2025-07-29 10:00:00+08 (in Asia/Shanghai session) ``` A subtle but important point: interval arithmetic with `timestamp` (without timezone) ignores DST transitions entirely, while `timestamptz` respects them. This means adding `INTERVAL '1 day'` to a `timestamptz` value that crosses a DST boundary will correctly return the same wall-clock time — not exactly 24 hours later. ## Indexing and Performance Considerations Both `timestamp` and `timestamptz` are stored as 8-byte integers, so there is no performance difference between them for storage or indexing. B-tree indexes work identically on both types because the underlying comparison is just integer comparison. However, there are a few practical considerations: - **Range queries**: `WHERE created_at > '2025-07-01'` works efficiently with an index on either type. With `timestamptz`, PostgreSQL converts the literal to UTC before comparison, so the index is still used. - **Partition keys**: When using range partitioning on timestamp columns, `timestamptz` is generally safer because partition boundaries are unambiguous (always UTC). With `timestamp`, a boundary like `'2025-07-01 00:00'` could mean different things to different sessions. - **Functional indexes**: If you frequently query by date only (ignoring time), consider an index on `date_trunc('day', created_at)` to speed up daily aggregation queries. ## Common Pitfalls & Quick Fixes ### 1. Different users, different clocks - **Cause**: clients use different `TimeZone` settings with `timestamptz` - **Fix**: either keep everything `timestamp` + agree on one zone, **or** enforce `SET TimeZone = 'UTC'` at connection init A common pattern in application code is to set the timezone once at connection pool initialization: ```sql -- In your connection setup (e.g., pg pool config) SET timezone = 'UTC'; ``` This ensures all sessions see the same UTC representation, and your application layer handles the conversion to local time for display. ### 2. Storing "wall time" but picked the wrong type - Business calendars (store hours, due dates) should use `timestamp` - Cross-border workflows (orders, logs) should store **UTC** in `timestamptz` The test is simple: if the question is "what moment in time did this happen?" use `timestamptz`. If the question is "what does the clock on the wall say?" use `timestamp`. ### 3. APIs that drift - Always ship `timestamptz` as ISO-8601 strings with the offset (`Z` or `+08:00`) - Let the UI format locally ### 4. Comparing timestamps across types Mixing `timestamp` and `timestamptz` in comparisons or joins is a common source of subtle bugs: ```sql -- Dangerous: implicit cast applies session timezone SELECT * FROM orders o JOIN schedules s ON o.created_tz = s.start_ts; -- PostgreSQL casts s.start_ts to timestamptz using session timezone -- Different sessions can get different join results! ``` **Fix**: always cast explicitly when comparing across types, or standardize on one type per domain. ### 5. ORM default pitfalls Many ORMs (Django, SQLAlchemy, ActiveRecord) default to `timestamp` without timezone. Check your migration files — if your app serves users across timezones, override the default to `timestamptz`. In Django, set `USE_TZ = True` in settings. In SQLAlchemy, use `DateTime(timezone=True)`. ## Cheat Sheet: Which One Should I Use? ``` Local calendar only → timestamp Anything global → timestamptz (store UTC) ``` - Financial reports, class schedules → `timestamp` - Audit logs, e-commerce orders → `timestamptz` ## Verify in Seconds with Go Tools | Need | Tool | How-to | |------|------|--------| | Inspect the epoch value from SQL | [Epoch Converter](/tools/unix-timestamp-converter) | Paste `1690622400`, hit Convert | | Tidy bulk JSON with time fields | [JSON Formatter](/tools/json-formatter) | Drop in the payload, prettify & scan | All utilities run entirely in your browser — no data ever leaves your machine. ## Frequently Asked Questions ### What is the difference between timestamp and timestamptz in PostgreSQL? `timestamp` (without time zone) stores a date-time value as-is, with no timezone context. `timestamptz` (with time zone) converts the input to UTC for storage and converts back to the session's timezone on retrieval. Use `timestamptz` for almost all cases — it prevents timezone-related bugs across distributed systems. ### Does PostgreSQL actually store the timezone in timestamptz? No — despite the name, PostgreSQL does not store the timezone itself. It converts the input to UTC and stores only the UTC value (a microsecond count from 2000-01-01). On retrieval, it converts from UTC to whatever timezone your session's `timezone` setting specifies. The original timezone information is discarded. ### How do I change the timezone for a PostgreSQL session? Run `SET timezone = 'America/New_York';` to change the session timezone. This affects how `timestamptz` values are displayed and interpreted. For server-wide defaults, set `timezone` in `postgresql.conf`. Always use IANA timezone names (like `Asia/Shanghai`) rather than abbreviations (like `CST`) to avoid ambiguity. ### Should I use timestamp or timestamptz for storing event times? Use `timestamptz` for nearly everything — user actions, API calls, audit logs, and scheduled events. Only use `timestamp` (without timezone) for abstract times that aren't tied to a specific moment, like "store opens at 09:00" which means 9 AM in whatever the local timezone is, not a specific UTC instant. ### How does PostgreSQL handle daylight saving time with timestamptz? PostgreSQL handles DST correctly when using `timestamptz` because it stores everything in UTC internally. When you retrieve a value, PostgreSQL converts from UTC using the current DST rules for your session timezone. This means the same stored UTC instant correctly shows different local times before and after a DST transition. For a comprehensive guide to Unix timestamps — including precision handling, timezone best practices, and code examples in JavaScript, Python, and Go — see our [Unix Timestamp Guide](/blog/unix-timestamp-guide-epoch-seconds-ms-timezone-dst). ## Wrap-up - Both Postgres time types are **microsecond counters**; the label is the whole difference - Choosing the wrong one means puzzling timestamps and broken math - Test, convert, and sanity-check with the right tools to save hours of debugging --- ### What Is a ULID? Sortable Unique Identifier Guide URL: https://go-tools.org/blog/what-is-ulid-sortable-unique-identifier-guide What is a ULID? How the sortable 128-bit ID works: its timestamp-plus-randomness structure, Crockford Base32 encoding, and when to pick it over a UUID. # What Is a ULID? The Sortable Unique Identifier, Explained Every random UUIDv4 you insert as a primary key lands at an unpredictable spot in the database index. Do that a few million times and the index fragments, the cache thrashes, and writes slow down. A ULID fixes that without giving up what you liked about UUIDs: you can still mint one anywhere, with no central coordinator, but it lands in time order instead of scattering. So how does a 26-character string sort itself by time? That is the whole trick, and it is worth understanding before you reach for one. A ULID (Universally Unique Lexicographically Sortable Identifier) is a 128-bit identifier written as 26 Crockford Base32 characters. The first 10 characters encode a millisecond timestamp and the last 16 encode random bits, so ULIDs created later always sort after earlier ones when compared as plain strings. It is a **sortable unique identifier** you can generate offline. This guide takes that apart: the anatomy decoded character by character, the proof it really sorts, the B-tree math behind the database win, and an honest look at what the embedded timestamp leaks. You can follow along with a live value in the [ULID generator](/tools/ulid-generator) — generate one, decode it, convert it to a UUID — while you read. ## What Is a ULID? A ULID (Universally Unique Lexicographically Sortable Identifier) is a 128-bit identifier designed as a more sortable, more compact alternative to a UUID. It is written as 26 characters of Crockford Base32: the first 10 hold a 48-bit timestamp in milliseconds since the Unix epoch, and the remaining 16 hold 80 bits of randomness. Because the time comes first, the string sorts chronologically. That last property is the reason the format exists. UUIDv4 is fully random, which is great for uniqueness but means two IDs created a second apart have no relationship to each other. ULIDs keep the coordination-free, generate-anywhere model and add time-ordering on top, so a column of them is naturally sorted by creation time with nothing extra. Here is the format at a glance: | Property | Value | |----------|-------| | Bits | 128 | | Encoding | 26 Crockford Base32 characters | | Layout | 48-bit timestamp + 80-bit randomness | The rest of this article fills in how each piece works. The encoding and the sortability each get their own section below. First, the layout. ## Anatomy of a ULID: 48 Bits of Time + 80 Bits of Randomness A ULID's 26 characters split cleanly into two halves. The first 10 characters are the timestamp; the last 16 are the random part. Lay the canonical example out and the boundary is obvious: ``` 01ARYZ6S41 TSV4RRFFQ69G5FAV └────────┘ └──────────────┘ 10 chars 16 chars 48-bit ms 80-bit random timestamp ``` Two components, two jobs. One records *when* the ULID was created; the other guarantees *uniqueness*. Each is decoded below. ### The 48-bit timestamp (first 10 characters) The leading 10 characters encode a 48-bit integer: the number of [milliseconds since the Unix epoch](/blog/unix-timestamp-guide-epoch-seconds-ms-timezone-dst) at the moment the ULID was created. Take the canonical example straight from the spec: ``` 01ARYZ6S41 -> 1469918176385 ms -> 2016-07-30T22:36:16.385Z ``` That is a real, reversible decode — paste `01ARYZ6S41TSV4RRFFQ69G5FAV` into a decoder and you get exactly `2016-07-30T22:36:16.385Z` back. The time component is plain data, not a hash, so reading it costs nothing. One small detail that trips people up: the first character of a ULID is always between `0` and `7`. A Crockford character holds 5 bits, and 48 bits is not a multiple of 5. The timestamp occupies the low 48 of the 50 bits that 10 characters can carry, which leaves the top 2 bits of the first character permanently zero. Two zero bits cap that character's value at 7. If you ever see a ULID starting with `8` or higher, it is malformed. ### The 80 bits of randomness (last 16 characters) The remaining 16 characters carry 80 bits of randomness, and this half is where uniqueness comes from. The bits should come from a cryptographically secure source — `crypto.getRandomValues` in the browser, not `Math.random`. The difference matters: `Math.random` is predictable enough that an attacker could guess or collide values, while a CSPRNG is not. How much room is 80 bits? Roughly 1.2 × 10²⁴ possible values, and that is *per millisecond*. Even if you mint millions of ULIDs inside a single millisecond, the odds that two draw the same 80 bits stay vanishingly small. Unlike the timestamp, this half carries no decodable meaning — it is noise whose only purpose is to make every ULID distinct. ## Crockford's Base32: Why ULIDs Drop I, L, O, and U ULIDs are encoded with Crockford's Base32, an alphabet of 32 symbols: the digits `0`–`9` and the letters `A`–`Z` with four removed. ``` 0123456789ABCDEFGHJKMNPQRSTVWXYZ ``` The missing letters are **I, L, O, and U**. Three are dropped because they look like digits — `I` and `L` resemble `1`, `O` resembles `0` — so a human reading a ULID off a screen can't confuse a letter for a number. The flip side is forgiving input: a compliant decoder maps `I` and `L` back to `1` and `O` to `0`, and treats the whole string case-insensitively. `U` is excluded separately, to avoid accidentally spelling out offensive words. The bit math is the other reason. Each Base32 character encodes 5 bits, where a hexadecimal character encodes only 4. Pack 128 bits at 5 bits per character and you need 26; pack the same 128 bits at 4 bits each — the way a UUID does — and you need 32, plus four hyphens, for 36 characters. So a ULID is meaningfully shorter than a UUID and, with no hyphens, drops straight into a URL, a filename, or a header without escaping. Crockford's Base32 is an alphabet of 32 symbols (`0`–`9` and `A`–`Z` minus I, L, O, U) that encodes 5 bits per character. ULIDs use it to pack 128 bits into 26 case-insensitive, URL-safe characters, and — crucially — the alphabet is in ascending order, which is what lets the encoded string sort the same way as the raw bits. ## Why ULIDs Sort by Time Lots of articles tell you ULIDs sort by time. Fewer show *why*. The reason rests on two facts you already have: the timestamp is the most significant part of the value, and Crockford's alphabet is laid out in ascending order. Put those together and you get a chain of equivalences: ``` string compare == 128-bit integer compare == creation-time compare ``` Comparing two ULIDs character by character (the way a string sort works) gives the same answer as comparing their underlying 128-bit integers, *because* the alphabet is order-preserving: a "higher" character always means a higher value. Comparing the 128-bit integers gives the same answer as comparing creation times, *because* the timestamp sits in the most significant bits, so it dominates the comparison; the random tail only breaks ties within the same millisecond. String order, bit order, and time order are the same order. A quick demonstration. Two ULIDs minted one millisecond apart: ``` 01ARYZ6S41... (created at T) 01ARYZ6S42... (created at T + 1 ms) ``` The tenth character ticks from `1` to `2`, and a plain text sort puts the second after the first — no timestamp column, no special comparator. The practical payoff, which the next section expands, is one line: `ORDER BY id` returns rows in chronological order with no extra index. ## ULIDs as Database Primary Keys: B-Tree Locality Most relational databases store a primary-key index as a B-tree, and where a new key lands in that tree decides how expensive the insert is. This is where ULIDs earn their keep. A random UUIDv4 lands somewhere unpredictable on every insert: > **UUIDv4:** each new key targets a random leaf page. The page is often full, so the engine splits it, copies half the rows elsewhere, and dirties pages all over the tree. Across millions of rows this fragments the index, evicts useful pages from the buffer cache, and drags down insert throughput. (For the hard [index page-split numbers](/blog/uuid-v4-v7-ulid-snowflake-id-comparison) — typically a 2–10× difference on write-heavy tables — see the comparison guide.) A time-prefixed ULID lands at the end every time: > **ULID:** because the high bits are a timestamp, each new key is greater than the last, so it appends at or near the right edge of the index. Inserts stay sequential, page splits nearly disappear, the index stays compact, and a range scan over a time window reads a contiguous run of pages. You get the coordination-free generation of a UUID with the insert locality of an auto-increment integer — without exposing a guessable sequential counter, since the random tail still hides the exact next value. **Storage tip:** store the 128 bits as 16 binary bytes — a `uuid` column in PostgreSQL, `BINARY(16)` in MySQL — not as a 26-character text field, which wastes space and bloats the index. Encode to the Base32 string only at the edges where a human or a URL sees it. The generator's Convert tab will [convert a ULID to a UUID](/tools/ulid-generator) for exactly this, since the two forms are the same 128 bits. ## Monotonic ULIDs: Strict Order Within a Millisecond The sortability proof has one honest gap: within a single millisecond, plain ULIDs are *not* strictly ordered. They share the same 10-character time prefix, but their 80-bit random tails are drawn independently, so which of two same-millisecond ULIDs sorts first is essentially a coin flip. For most uses that is fine. When you need strict order even at sub-millisecond rates, it is not. Monotonic generation closes the gap. The rule is simple: the first ULID in a given millisecond gets fresh randomness as usual, and every later ULID in that same millisecond is produced by taking the previous 80-bit random value and incrementing it by one (treated as a big-endian integer, carrying into higher bits as needed). Each value is therefore strictly greater than the one before it. You can see it in a batch generated inside one millisecond — only the final character moves: ``` 01KVT0F720ZK9N4T2QX7VR8WMC 01KVT0F720ZK9N4T2QX7VR8WMD 01KVT0F720ZK9N4T2QX7VR8WME ``` `…WMC` < `…WMD` < `…WME`, guaranteed. This matters whenever rows can be created faster than the millisecond clock ticks: high-throughput inserts, event logs, message IDs in a tight loop. When the clock advances to the next millisecond, generation reverts to fresh randomness and the cycle repeats. ## ULID vs UUID: When to Use Which The question most people actually arrive with is **ULID vs UUID**. Here is the focused comparison — ULID against the two UUID versions you'd realistically weigh it against. (For the full five-way decision matrix including Snowflake and NanoID, see the [full comparison of ULID, UUID and Snowflake](/blog/uuid-v4-v7-ulid-snowflake-id-comparison).) | Property | ULID | UUIDv4 | UUIDv7 | |----------|------|--------|--------| | Length | 26 chars | 36 chars | 36 chars | | Encoding | Crockford Base32 | Hyphenated hex | Hyphenated hex | | Sortable by time? | Yes | No | Yes | | Embeds timestamp? | Yes (48-bit ms) | No | Yes (48-bit ms) | | Standardized? | Community spec | RFC 9562 | RFC 9562 | | Best for | Short sortable IDs | Opaque random IDs | Sortable IDs in UUID format | In prose: reach for a **ULID** when you want the shortest, URL-safe, sortable string. Reach for **UUIDv4** when you want an opaque, fully random identifier with no embedded time — for example a public token where you'd rather not reveal when it was created. Reach for [UUIDv7](/tools/uuid-generator) when you need time-ordering but must stay inside the standard UUID format, with version and variant bits in their fixed positions and a native `uuid` column to drop it into. All three are 128 bits, so ULID ↔ UUID conversion is lossless either way. The relationship between ULID and **ulid vs uuid v7** is closer than it looks: UUIDv7 is essentially the IETF-standardized take on the same time-prefixed idea ULID pioneered. If you're [new to UUIDs](/blog/what-is-uuid-guide-format-versions-use-cases) altogether, start with the fundamentals first, then come back to this comparison. ## The Privacy Trade-Off: ULIDs Leak Their Creation Time The embedded timestamp is a feature and a leak, depending on who reads the ID. Anyone holding a ULID can [decode the timestamp](/tools/ulid-generator) in one step and learn the exact millisecond the record was created — no access to your database required. Inside your own systems that is pure upside: instant auditing, free ordering, easy debugging. On a *public-facing* identifier it is a real disclosure. The creation time can be business-sensitive on its own, and a handful of ULIDs sampled over time leak your creation *rate*: how many orders, accounts, or messages you mint per second. That is the kind of thing competitors and scrapers like to estimate. To be fair, this is a narrower leak than UUIDv1, which historically embedded the generating machine's MAC address; a ULID exposes only time, never hardware identity. Still, weigh it. The simple mitigation: keep ULIDs internal and hand out a fully random UUIDv4 for public-facing IDs where ordering doesn't matter. ## Common Pitfalls with ULIDs Most ULID trouble is a handful of avoidable engineering decisions, not bugs in the format. The recurring ones: - **Assuming same-millisecond plain ULIDs are ordered.** They share a time prefix but have independent random tails, so their order is undefined. *Fix:* use monotonic mode when you need strict ordering at sub-millisecond rates. - **Storing a ULID as 26-char text.** That wastes space and inflates the index. *Fix:* store the 128 bits as 16 bytes (`uuid` / `BINARY(16)`) and encode to Base32 only at the edges. - **Expecting a ULID→UUID conversion to report as v4 or v7.** Conversion re-encodes the same bits; it does not set the UUID version and variant fields, so a library inspecting them won't see a tagged version. *Fix:* treat the result as an opaque 128-bit value, or generate a real UUIDv7 when you need the tag. - **Filling the randomness with `Math.random`.** It is predictable and can collide. *Fix:* always use a CSPRNG like `crypto.getRandomValues`. - **Exposing ULIDs publicly without weighing the timestamp leak.** See the privacy section above. *Fix:* internal ULIDs, random UUIDv4 for public IDs. - **Hand-typing `I`, `L`, `O`, or `U` into a ULID.** Those letters aren't in the alphabet, and retyping invites errors. *Fix:* copy ULIDs, don't retype them. ## FAQ ### Is ULID an official standard like UUID? No. ULID is a community specification published on GitHub, not an IETF RFC. It is widely implemented and stable, but it has no standards body behind it. If you need a standardized, time-ordered identifier, UUIDv7 (RFC 9562) applies the same idea inside the official UUID format. ### How many characters is a ULID, and why is it shorter than a UUID? 26 characters, versus a UUID's 36. ULID uses Crockford Base32, which packs 5 bits per character; a UUID's hexadecimal packs only 4 bits and adds four hyphens. The same 128 bits therefore need fewer characters in Base32 — and none of them need URL escaping. ### Can two ULIDs ever collide? Practically never. Within one millisecond a ULID has 80 random bits — about 1.2 × 10²⁴ possibilities — so even generating millions per millisecond keeps the collision odds vanishingly small. The one requirement is that a cryptographically secure RNG fills the randomness; `Math.random` voids the guarantee. ### Can I store ULIDs in PostgreSQL or MySQL? Yes. A ULID is 128 bits, so convert it to UUID form and store it in a `uuid` column (PostgreSQL) or `BINARY(16)` (MySQL), then render the Base32 string only at the edges. There is no native ULID column type, but the UUID representation costs the same 16 bytes and keeps the index compact. ### Are ULIDs case-sensitive? The canonical form is uppercase, but Crockford Base32 is case-insensitive on input: a decoder reads lowercase letters the same way, and maps `I`/`L` to `1` and `O` to `0`. To avoid surprises in equality checks and indexes, normalize to a single case before you store or compare. ### Will the 48-bit timestamp ever run out? Not for a very long time. 48 bits of milliseconds reach the year 10889 before the counter overflows, so the timestamp component is effectively future-proof for any real application. You will replace the system, the language, and the database long before the format runs out of room. ### Can I generate ULIDs in the browser or on mobile without a server? Yes — that's a core benefit. ULIDs need no central coordinator, so any node, edge worker, browser, or device can mint one from its clock plus a secure RNG. Values created on different machines still sort together by time afterward, because the timestamp lives in the ID itself. ## Conclusion ULIDs solve a specific, real problem — random keys fragmenting your index — without taking away decentralized generation. The mechanics are worth keeping in mind: - A ULID is a **48-bit millisecond timestamp + 80 bits of randomness**, encoded as 26 Crockford Base32 characters. - It sorts by time because the timestamp is the most significant component and the alphabet is order-preserving — string order equals time order. - That ordering gives a B-tree the insert locality a random UUIDv4 lacks, keeping writes fast and the index compact. - Use monotonic mode when you need strict ordering for IDs minted in the same millisecond. - Weigh the timestamp leak before exposing ULIDs on public-facing identifiers. - Pick UUIDv7 instead when you must stay inside the standard UUID format. When you're ready to put it to work, open the [ULID generator](/tools/ulid-generator) to generate, decode, and convert ULIDs entirely in your browser — no server, no upload, nothing stored. --- ### What Is a UUID? Guide to Format, Versions & Use Cases URL: https://go-tools.org/blog/what-is-uuid-guide-format-versions-use-cases UUIDs from the ground up: 128-bit structure, hex format, how v1/v3/v4/v5/v7 work internally, collision math, real-world use cases and code examples. # UUID Explained: 128-Bit Structure, Versions & Real-World Use Cases Every time you sign up for a service, a unique identifier is created for your account. Every API request carries a trace ID. Every row in a distributed database needs a primary key that won't collide with keys generated on other machines. The solution behind all of these? **UUID — Universally Unique Identifier.** This guide explains what UUIDs are, how they're structured, what each version does under the hood, and when to use (or avoid) them. ## UUID at a Glance A UUID is a **128-bit (16-byte) identifier** designed to be globally unique without requiring a central authority. It's written as 32 hexadecimal digits in the canonical **8-4-4-4-12** format: ``` 550e8400-e29b-41d4-a716-446655440000 |------| |--| |--| |--| |----------| 8 hex 4 4 4 12 hex ``` That's 32 hex characters + 4 hyphens = 36 characters total. The hyphens are purely cosmetic — they don't carry data. **Key facts:** - **128 bits** = 2¹²⁸ ≈ 3.4 × 10³⁸ possible values - Standardized by **RFC 9562** (May 2024, supersedes RFC 4122) - Also called **GUID** (Globally Unique Identifier) in Microsoft ecosystems — same format, different name - Supported natively by PostgreSQL (`uuid` type), MySQL (`BINARY(16)` or `CHAR(36)`), and virtually every programming language ## Anatomy of a UUID Every UUID encodes two metadata fields in fixed bit positions, regardless of version: ``` 550e8400-e29b-41d4-a716-446655440000 ^ ^ | | Version-┘ └-Variant ``` ### Version Field (Bits 48–51) The 13th hex digit (first digit of the third group) identifies the UUID version: | Hex Digit | Version | Method | |---|---|---| | `1` | v1 | Timestamp + MAC address | | `3` | v3 | MD5 hash of namespace + name | | `4` | v4 | Cryptographically random | | `5` | v5 | SHA-1 hash of namespace + name | | `6` | v6 | Reordered timestamp (RFC 9562) | | `7` | v7 | Unix timestamp + random (RFC 9562) | | `8` | v8 | Custom / implementation-specific | ### Variant Field (Bits 64–65) The 17th hex digit (first digit of the fourth group) identifies the variant. For RFC 4122/9562 UUIDs, the first bits are `10`, which means this hex digit is always `8`, `9`, `a`, or `b`. ### Example Breakdown ``` 550e8400-e29b-41d4-a716-446655440000 ↑ ↑ 4 → v4 a → RFC 4122 variant This is a UUID v4 (random), RFC 4122/9562 variant. ``` ## UUID Versions Explained ### Version 1: Timestamp + MAC Address UUID v1 was the original design. It encodes: - **60-bit timestamp** — 100-nanosecond intervals since October 15, 1582 (the Gregorian calendar reform) - **14-bit clock sequence** — monotonicity counter to prevent duplicates on clock rollback - **48-bit node** — typically the machine's MAC address ``` | Timestamp | Ver | Clk |Var| Node (MAC) | | 60 bits | 4b | 14b |2b | 48 bits | ``` **Problems:** - Exposes the generation time and hardware identity (privacy risk) - MAC addresses can be spoofed, undermining uniqueness - The 1582 epoch is confusing and requires conversion **Verdict:** Deprecated by RFC 9562. Use v7 instead for time-based UUIDs. ### Version 3: MD5 Name-Based (Deterministic) UUID v3 hashes a **namespace UUID** and a **name string** using MD5. The same inputs always produce the same UUID. ```python import uuid # namespace = DNS, name = "example.com" print(uuid.uuid3(uuid.NAMESPACE_DNS, "example.com")) # → "9073926b-929f-31c2-abc9-fad77ae3e8eb" (always this value) ``` Four standard namespaces are defined: - **DNS**: `6ba7b810-9dad-11d1-80b4-00c04fd430c8` - **URL**: `6ba7b811-9dad-11d1-80b4-00c04fd430c8` - **OID**: `6ba7b812-9dad-11d1-80b4-00c04fd430c8` - **X.500**: `6ba7b814-9dad-11d1-80b4-00c04fd430c8` **Verdict:** Functional but prefer v5 — SHA-1 is stronger than MD5. ### Version 4: Random — The Most Popular UUID v4 fills **122 bits** with cryptographically secure random data (the remaining 6 bits are reserved for the version and variant fields). ``` | Random | Ver | Random |Var| Random | | 48 bits | 4b | 12 bits |2b | 62 bits | ``` With 2¹²² ≈ 5.3 × 10³⁶ possible values, the probability of collision is astronomically low. To reach a 50% chance of at least one collision, you'd need approximately **2.71 × 10¹⁸ UUIDs** — that's 2.71 quintillion. ```javascript // Every modern browser and Node.js supports this const id = crypto.randomUUID(); console.log(id); // → "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d" ``` **Strengths:** simple, private, universally supported, no coordination needed. **Weakness:** random distribution causes B-tree index fragmentation when used as database primary keys. For database-heavy use cases, consider v7. ### Version 5: SHA-1 Name-Based (Deterministic) Identical to v3 but uses SHA-1 instead of MD5. Same inputs always produce the same UUID. ```python import uuid print(uuid.uuid5(uuid.NAMESPACE_DNS, "example.com")) # → "cfbff0d1-9375-5685-968c-48ce8b15ae17" (always this value) ``` **Use cases:** - Generating stable IDs from URLs or DNS names - Content-addressable storage keys - Reproducible test fixtures **Important:** v3 and v5 are NOT meant for security. They are deterministic — anyone who knows the namespace and name can reproduce the UUID. ### Version 7: Unix Timestamp + Random (Recommended for New Projects) UUID v7 is the newest version, introduced in **RFC 9562 (May 2024)**. It encodes: - **48-bit Unix timestamp** in milliseconds — monotonically increasing - **74 bits** of cryptographic randomness ``` | Unix timestamp (ms) | Ver | rand_a |Var| rand_b | | 48 bits | 4b | 12 bits |2b | 62 bits | ``` This means v7 UUIDs are **naturally sorted by creation time** — newer UUIDs are always lexicographically greater than older ones. This property makes them ideal for database primary keys, where B-tree indexes stay sequential rather than fragmenting randomly. ```javascript import { v7 as uuidv7 } from "uuid"; const id1 = uuidv7(); // generated at T₁ const id2 = uuidv7(); // generated at T₂ (T₂ > T₁) console.log(id1 < id2); // → true (lexicographic comparison) ``` **Why it matters for databases:** v7's sequential property reduces index page splits by up to 90% compared to v4, resulting in faster inserts, smaller indexes, and better cache performance. ## UUID vs GUID — What's the Difference? There is no functional difference. **GUID** (Globally Unique Identifier) is Microsoft's name for UUID, used in Windows, .NET, COM, and SQL Server. The format is identical: 128 bits, 8-4-4-4-12 hex. The only cosmetic difference: Microsoft tools sometimes display GUIDs in **uppercase with curly braces**: ``` UUID: 550e8400-e29b-41d4-a716-446655440000 GUID: {550E8400-E29B-41D4-A716-446655440000} ``` If someone asks about the "difference between UUID and GUID," the answer is: branding. ## Special UUID Values RFC 9562 defines two special UUIDs: | Name | Value | Purpose | |---|---|---| | **Nil UUID** | `00000000-0000-0000-0000-000000000000` | Represents absence of value (like `null`) | | **Max UUID** | `ffffffff-ffff-ffff-ffff-ffffffffffff` | Boundary marker or sentinel value | Never use these as actual identifiers — they are not unique by definition. ## Collision Probability: The Birthday Problem The "birthday problem" calculates how many UUIDs you need before a collision becomes likely. For UUID v4 (122 random bits): | UUIDs Generated | Collision Probability | |---|---| | 1 million | ~10⁻²² (virtually impossible) | | 1 billion | ~10⁻¹⁶ (still negligible) | | 2.71 × 10¹⁸ | 50% (the "birthday bound") | To put it in context: if you generated **1 billion UUIDs per second**, it would take **86 years** to reach a 50% chance of a single collision. In practice, hardware failure, software bugs, and cosmic rays are all more likely to cause a duplicate than UUID v4 math. The formula: p(n) ≈ n² / (2 × 2¹²²) ## How to Validate a UUID A valid UUID matches this regex pattern (case-insensitive): ``` ^[0-9a-f]{8}-[0-9a-f]{4}-[1-7][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$ ``` This checks: 1. The 8-4-4-4-12 hex format 2. Version digit is 1–7 (position 15) 3. Variant nibble starts with 8, 9, a, or b (position 20) ```javascript function isValidUUID(str) { return /^[0-9a-f]{8}-[0-9a-f]{4}-[1-7][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(str); } isValidUUID("550e8400-e29b-41d4-a716-446655440000"); // → true isValidUUID("not-a-uuid"); // → false ``` ## Generating UUIDs in Every Language ### JavaScript / TypeScript ```javascript // Browser & Node.js — built-in v4 crypto.randomUUID(); // npm uuid package — supports v1, v3, v4, v5, v7 import { v4, v7 } from "uuid"; v4(); // random v7(); // time-ordered ``` ### Python ```python import uuid uuid.uuid4() # random uuid.uuid5(uuid.NAMESPACE_DNS, "example.com") # deterministic # uuid.uuid7() planned for Python 3.14+ ``` ### Go ```go import "github.com/google/uuid" uuid.New() // v4 random uuid.Must(uuid.NewV7()) // v7 time-ordered ``` ### Java ```java import java.util.UUID; UUID.randomUUID(); // v4 random // UUID v7: use com.fasterxml.uuid or java.util.UUID in JDK 21+ ``` ### SQL (PostgreSQL) ```sql -- v4 (PostgreSQL 13+) SELECT gen_random_uuid(); -- v7 (PostgreSQL 18+) SELECT uuidv7(); ``` ## Common Use Cases ### Database Primary Keys UUIDs let you generate IDs anywhere — in the application, on the client, at the edge — without a database round trip. This enables offline-first architectures and simplifies distributed systems. Use **v7** for best index performance, or **v4** if you don't care about ordering. ### API Request Tracing Assign a UUID to every API request at the entry point (gateway, load balancer). Pass it through all downstream services in a header like `X-Request-ID`. This makes it trivial to correlate logs across microservices. ### Idempotency Keys APIs use UUIDs as idempotency keys to ensure that retried requests don't create duplicate resources. The client generates a UUID before the first attempt and sends the same UUID on retries. ### Session Identifiers UUIDs provide sufficient uniqueness to prevent session collisions across large user bases. Unlike auto-increment integers, they can't be enumerated — an attacker can't guess valid session IDs by incrementing a number. ### Content-Addressable Storage UUID v5 generates deterministic IDs from content. Given the same input, you always get the same UUID — useful for deduplication, caching, and reproducible builds. ## Security Considerations ### UUIDs Are NOT Security Tokens UUIDs are designed for **uniqueness**, not **secrecy**. Key issues: - **UUID v1** leaks the generation timestamp and MAC address - **UUID v4** has 122 random bits but a predictable structure (version/variant bits are fixed) - **UUID v3/v5** are deterministic — anyone who knows the namespace and name can reproduce the UUID For security tokens, API keys, or session secrets, use a dedicated CSPRNG with 128+ bits of pure randomness: ```javascript // For security tokens — NOT a UUID, but fully random const token = Array.from(crypto.getRandomValues(new Uint8Array(32))) .map(b => b.toString(16).padStart(2, "0")) .join(""); ``` ### UUID v7 Exposes Creation Time The first 48 bits of a UUID v7 encode the creation timestamp in milliseconds. Anyone who receives a v7 UUID can extract when it was created: ```javascript const hex = "01906b5e-4a3e-7234-8f56-b8c12d4e5678".replace(/-/g, "").slice(0, 12); new Date(parseInt(hex, 16)); // → 2024-07-01T12:34:56.000Z ``` If creation time is sensitive information, use v4 instead. ### Don't Use UUIDs to Prevent Enumeration While UUIDs are harder to guess than sequential integers, they shouldn't be your only access control mechanism. Always enforce authorization checks — don't rely on URL obscurity. ## Frequently Asked Questions ### Why are there hyphens in UUIDs? The hyphens in the 8-4-4-4-12 format are purely for human readability. They carry no data and are ignored during parsing. Some systems store UUIDs without hyphens (32 hex characters), which is equally valid. ### Can two UUIDs ever be the same? Theoretically yes, practically no. For UUID v4 with 122 random bits, the probability of generating two identical UUIDs is approximately 1 in 5.3 × 10³⁶ for any given pair. At real-world generation rates, you are more likely to be struck by lightning while winning the lottery than to encounter a UUID collision. ### Are UUIDs sequential? Only some versions. UUID v1, v6, and v7 contain timestamps and sort chronologically. UUID v4 is fully random with no ordering. UUID v3 and v5 are deterministic but not ordered. ### How much storage does a UUID use? - **Binary**: 16 bytes (128 bits) — the most efficient storage - **String (with hyphens)**: 36 bytes (ASCII) - **String (without hyphens)**: 32 bytes (ASCII) Most databases store UUIDs in binary format internally. PostgreSQL's native `uuid` type uses exactly 16 bytes. ### Should I use UUID or auto-increment for primary keys? Auto-increment is simpler for single-database applications (smaller, faster, sequential). UUID is better for distributed systems (generate anywhere, no coordination, merge-safe). If using UUID, prefer v7 for best database performance. ### What is RFC 9562? RFC 9562, published in May 2024, is the latest UUID standard. It supersedes RFC 4122 and formally introduces UUID versions 6, 7, and 8. It deprecates v1 in favor of v6/v7 and defines the nil and max UUID values. If you're implementing UUID generation or validation, RFC 9562 is the authoritative reference. ### Can I use UUIDs across different programming languages? Yes. The UUID format (128-bit, 8-4-4-4-12 hex) is language-agnostic. A UUID generated in JavaScript will be correctly parsed in Python, Go, Java, or any other language with UUID support. This interoperability is one of UUID's greatest strengths. --- *Generate, decode, and validate UUIDs instantly with our [UUID Generator](/tools/uuid-generator) — supports v1, v4, v5, and v7 with batch generation, 100% in your browser.* *Choosing between UUID versions for your next project? Read our [UUID v4 vs v7 vs ULID vs Snowflake comparison](/blog/uuid-v4-v7-ulid-snowflake-id-comparison) for a practical selection guide with database benchmarks and code examples.* --- ### XML to JSON: Conventions, Pitfalls & Code (2026 Guide) URL: https://go-tools.org/blog/xml-to-json-conversion-guide Convert XML to JSON the right way: how attributes, arrays, and namespaces map, why values stay strings, plus code for JavaScript, Python and the browser. # XML to JSON Conversion: Conventions, Pitfalls & Code Examples You pull a response off a SOAP endpoint, an RSS feed, or a `sitemap.xml`, and it's XML. Your stack is JSON-native: JavaScript on the front end, REST in the middle, a document store at the bottom. So you need to convert XML to JSON, and you reach for a parser expecting it to be a one-liner. It usually is — until the output bites you. An array you expected turns out to be a single object. An `id` attribute vanishes. A ZIP code like `01234` comes back as the number `1234`. None of these are bugs in your parser. They're the consequence of mapping two data models that don't line up, and the only way to convert XML to JSON reliably is to understand the conventions that bridge the gap. This guide covers why those conventions exist, four ways to do the conversion (browser, JavaScript, Python, CLI), the `@_` and `#text` rules every major library shares, the five pitfalls that cause silent data loss, and how to convert JSON back to XML for a clean round-trip. Paste the examples into Node, Python, or a shell and they produce the output shown in the comments. ## Why XML-to-JSON Needs Conventions (Not Just a Reformat) XML and JSON look similar at a glance: both are trees of named, nested data. But their underlying models diverge. XML elements can carry attributes, hold mixed content (text interleaved with child elements), and live under namespaces. JSON has none of those concepts. It has objects, arrays, and four scalar types. Converting one to the other isn't reformatting; it's translating between two grammars, and one of them has words the other can't spell. Before you convert anything, it pays to confirm the source is actually valid. A stray unescaped `&` or a mismatched tag will reject at the parser, so running the input through an [XML Formatter](/tools/xml-formatter) to check well-formedness first saves a round of confusing errors. Here is where the two models pull apart: | Dimension | XML | JSON | |-----------|-----|------| | Node types | elements, attributes, text, mixed content | objects, arrays, string, number, boolean, null | | Root constraint | exactly one root element required | no root constraint | | Attributes | yes (`id="P01"`) | none (needs an `@_` convention) | | Repeated elements | same-named siblings are legal | object keys can't repeat (needs an array convention) | | Type system | text is untyped — everything is a string | native types | | Namespaces | yes (`xmlns`) | none | Because the models don't match, every XML-to-JSON conversion is convention-driven, not lossless reformatting. The conventions aren't arbitrary, though: `fast-xml-parser` (Node.js), `xmltodict` (Python), and JAXB (Java) all landed on the same two markers, `@_` for attributes and `#text` for mixed-content text. Learn them once and they transfer across runtimes. Data-shape mismatches like this show up in other conversions too, such as the type-inference questions in the [CSV to JSON conversion guide](/blog/csv-json-conversion-guide). ## How to Convert XML to JSON: 4 Methods Pick the method that fits your context: a quick one-off paste, a Node service, a Python pipeline, or a shell script in CI. ### Method 1 — Browser-Based Tool (Zero Setup, Privacy-First) For a one-off conversion, or for XML you'd rather not paste into a random website, an in-browser converter is the fastest path. Paste XML into the [XML to JSON Converter](/tools/xml-to-json), and the JSON appears instantly — no install, no account, no upload. Everything runs in your browser's JavaScript engine, so the data never leaves the machine. That detail matters here. SOAP envelopes carry WS-Security tokens, internal configs carry connection strings, and exports carry customer records. Because nothing is transmitted, the tool is safe for XML containing credentials or sensitive payloads. You can confirm it yourself: open the Network tab and watch zero requests fire as you convert. ### Method 2 — JavaScript / Node.js (fast-xml-parser) In Node, `fast-xml-parser` is the standard choice. The defaults will surprise you, though — attributes are ignored and values get coerced — so the options below are the ones you actually want for a faithful conversion: ```javascript // Convert XML to JSON in Node.js using fast-xml-parser import { XMLParser } from 'fast-xml-parser'; const xml = ` Wireless Headphones 79.99 `; const parser = new XMLParser({ ignoreAttributes: false, // keep attributes (default drops them!) attributeNamePrefix: '@_', // attributes become @_-prefixed keys textNodeName: '#text', // mixed-content text goes under #text parseAttributeValue: false, // no type coercion on attributes parseTagValue: false, // no type coercion on element text }); const result = parser.parse(xml); console.log(JSON.stringify(result, null, 2)); // { // "catalog": { // "product": { // "@_id": "P01", // "name": "Wireless Headphones", // "price": { // "@_currency": "USD", // "#text": "79.99" // } // } // } // } ``` The two settings people forget are `ignoreAttributes: false` and `parseTagValue: false`. The first keeps your `id` and `currency` attributes; the second stops the parser from turning `"79.99"` into a float and `"01234"` into `1234`. We'll come back to why string preservation is the safe default in the pitfalls section. If you want zero dependencies in the browser, the native `DOMParser` does the parsing for you, and you walk the DOM yourself: ```javascript // Zero-dependency XML to JSON in the browser using DOMParser function xmlToJson(node) { // Text-only element → string value const children = Array.from(node.children); if (children.length === 0 && node.attributes.length === 0) { return node.textContent.trim(); } const obj = {}; // Attributes → @_ prefix for (const attr of node.attributes) { obj['@_' + attr.name] = attr.value; } // Element with attributes AND text → #text if (children.length === 0) { obj['#text'] = node.textContent.trim(); return obj; } // Recurse into children, collecting same-named siblings into arrays for (const child of children) { const value = xmlToJson(child); if (obj[child.tagName] === undefined) { obj[child.tagName] = value; } else { if (!Array.isArray(obj[child.tagName])) obj[child.tagName] = [obj[child.tagName]]; obj[child.tagName].push(value); } } return obj; } const doc = new DOMParser().parseFromString( 'Wireless Headphones', 'text/xml' ); const json = { [doc.documentElement.tagName]: xmlToJson(doc.documentElement) }; console.log(JSON.stringify(json, null, 2)); // { "catalog": { "product": { "@_id": "P01", "name": "Wireless Headphones" } } } ``` `DOMParser` is XML 1.0 compliant, handles CDATA and entity references, and reports well-formedness errors — all without a package install. The trade-off is that you own the traversal logic, including the array-collection rule shown above. ### Method 3 — Python (xmltodict) In Python, `xmltodict` collapses the whole job into a short pipeline. It uses `@` as its attribute prefix and `#text` for mixed content by default: ```python # Convert XML to JSON in Python using xmltodict import json import xmltodict xml = """ Wireless Headphones 79.99 """ data = xmltodict.parse(xml) print(json.dumps(data, indent=2)) # { # "catalog": { # "product": { # "@id": "P01", # "name": "Wireless Headphones", # "price": { # "@currency": "USD", # "#text": "79.99" # } # } # } # } ``` By default `xmltodict` keeps every value as a string, which is the behavior you want. The one option worth knowing up front is `force_list`, which fixes the single-versus-many array problem before it reaches your code: ```python # force_list guarantees is always a list, even when there is one data = xmltodict.parse(xml, force_list={'product'}) products = data['catalog']['product'] # always a list now for p in products: print(p['name']) ``` Without `force_list`, one `` yields a dict and two yield a list — and your loop crashes on the single-item case. That's pitfall #1, which we cover below. ### Method 4 — CLI (yq / Python one-liner) For shell scripts and CI pipelines, two one-liners cover most cases. Mike Farah's `yq` reads XML and emits JSON directly: ```bash # Using yq (Mike Farah's Go version) yq -p=xml -o=json '.' input.xml # Pipe from stdin cat sitemap.xml | yq -p=xml -o=json '.' ``` If `xmltodict` is already in your environment, the Python one-liner needs no extra binary: ```bash python3 -c "import sys, xmltodict, json; print(json.dumps(xmltodict.parse(sys.stdin.read()), indent=2))" < input.xml ``` Both stream from stdin, so they drop straight into a pipeline — useful for converting an API response mid-script or normalizing a batch of files in a build step. ## The @_ Attribute and #text Conventions Explained Most converter pages skip the part that actually matters: what the odd-looking `@_` and `#text` keys mean and why they exist. Once these click, the output stops looking arbitrary. **Attributes map to `@_`-prefixed keys.** An attribute has no JSON equivalent — there's no slot in an object for "metadata about this object" that's distinct from a child. The convention is to give attributes a key prefixed with `@_`: ``` → { "user": { "@_id": "42", "@_role": "admin" } } ``` Why `@_` specifically? Because no valid XML element name can start with `@`, the prefix can never collide with a real child-element key. The character is reserved for free. (`xmltodict` uses bare `@`; `fast-xml-parser` uses `@_` by default. The principle is identical.) **Mixed content maps to `#text`.** When an element has both an attribute and a text value, the text needs somewhere to live alongside the attribute keys. That's `#text`: ``` 29.99 → { "price": { "@_currency": "USD", "#text": "29.99" } } ``` **Plain-text elements become a direct string value.** No attributes, no children, just text — so there's no need for the `#text` indirection. `Alice` becomes `"name": "Alice"`. The `#text` key only appears when attributes force the element value to be an object. This asymmetry is the source of a subtle bug. The same element name can produce a plain string in one document and an `@_`/`#text` object in another, depending on whether that particular instance carried an attribute. A `` with no `currency` attribute is the string `"29.99"`; the same `` is `{ "@_currency": "USD", "#text": "29.99" }`. Code that reads `node.price` directly works for one shape and silently breaks on the other. The defensive accessor is to check the type: `const amount = typeof node.price === 'object' ? node.price['#text'] : node.price;`. **CDATA becomes plain text content.** A `` section is just an escaping mechanism, so the delimiters are stripped and the inner text is preserved: `"if (a < b) return;"`. Nothing special survives into the JSON. Once you have output, paste it into a [JSON Formatter](/tools/json-formatter) to validate the JSON output and confirm the structure matches what your consumer expects before you wire it into code. ## 5 XML-to-JSON Pitfalls & How to Avoid Them These are the failures that get past code review and show up in production. Each one traces back to the model mismatch from the start of this guide. **1. Array ambiguity (one vs. many).** A single `` becomes an object; two or more become an array. The JSON shape depends on how many siblings happened to be in that particular document. Consumer code like `result.items.item.forEach(...)` works in testing — where your fixture has three items — and throws `TypeError: not a function` in production when a record has exactly one. ```javascript // Two siblings → array // AB // → { "library": { "book": ["A", "B"] } } // One → object, NOT an array // A // → { "library": { "book": "A" } } // Normalize so both cases behave identically const books = [].concat(result.library?.book ?? []); books.forEach(b => console.log(b)); // safe for 0, 1, or many ``` The `[].concat(x ?? [])` idiom is worth memorizing: a missing value becomes `[]`, a single object becomes `[object]`, and an existing array passes through unchanged. In Python, pass `force_list={'book'}` to `xmltodict.parse()` and the value is always a list, so you skip the normalization entirely. **2. Attributes silently dropped.** Several libraries default to ignoring attributes — `fast-xml-parser` does exactly this until you set `ignoreAttributes: false`. The conversion looks like it worked, the JSON parses fine, and your `id`, `currency`, and `status` values are simply gone. Always set the flag explicitly rather than trusting the default. **3. Namespace flattening.** An `xmlns` declaration becomes an ordinary `@_xmlns` key, and the prefix in `` survives only as part of the string key `"soap:Body"`. The *semantics* — that two prefixes might bind to the same URI — are lost. ``` ... → { "soap:Envelope": { "@_xmlns:soap": "http://schemas.xmlsoap.org/soap/envelope/", "soap:Body": "..." } } ``` The prefix `soap:` is now just text in a key name; nothing knows it's a namespace. If two elements from different namespaces share a local name, they can collide. When precise namespace handling is part of the requirement, keep the data in a namespace-aware parser and don't flatten it into JSON at all. **4. No type coercion — and that's correct.** `01234` must not become `1234`. Account codes, postal codes, padded identifiers, and precision-sensitive decimals all break under silent coercion. A good converter keeps everything as a string and lets you coerce deliberately: ```javascript // Don't rely on implicit coercion if (config.timeout > 25) { /* fragile: "30" > 25 happens to work */ } // Coerce explicitly, only where you know the type if (parseInt(config.timeout, 10) > 25) { /* safe */ } ``` **5. Lossy: comments, processing instructions, and mixed-content order.** XML comments (``) and processing instructions (``) have no JSON home and are discarded. The relative order of text interleaved with child elements may not round-trip. If you need every byte preserved — for re-emitting the exact source document — don't convert at all; use an [XML Formatter](/tools/xml-formatter) to reformat or minify without touching the data model. ## Converting JSON Back to XML (Round-Trip) Going the other direction has its own twist, because JSON has no root-element rule and XML requires exactly one. The companion [JSON to XML Converter](/tools/json-to-xml) applies the same `@_`/`#text` conventions in reverse, so a JSON → XML → JSON trip preserves attributes, text, and structure. The interesting part is root normalization. The converter resolves the single-root requirement with four rules: - **Single-key object** → that key becomes the root: `{ "config": {...} }` → `...`. - **Multi-key object** → wrapped in ``: `{ "a": 1, "b": 2 }` → `12`. - **Top-level array** → wrapped as `...`, with `` as a fixed fallback name. - **Primitive value** → `value`. Everything else mirrors the forward direction. `@_` keys become attributes, `#text` becomes text content, and a JSON array under a key produces repeated same-named siblings — the key name is reused, never singularized: ```javascript // Convert JSON to XML in Node.js using fast-xml-parser import { XMLBuilder } from 'fast-xml-parser'; const data = { catalog: { product: { '@_id': 'P01', name: 'Wireless Headphones', price: { '@_currency': 'USD', '#text': '79.99' }, }, }, }; const builder = new XMLBuilder({ attributeNamePrefix: '@_', // @_ keys become attributes textNodeName: '#text', // #text key becomes text content ignoreAttributes: false, // process @_ keys format: true, // pretty-print }); console.log(builder.build(data)); // // // Wireless Headphones // 79.99 // // ``` One detail the builder handles for you: special characters in text and attribute values (`<`, `>`, `&`, `"`) are escaped to their entity references, so the output stays well-formed. ## FAQ ### How do XML attributes map to JSON? Attributes become keys prefixed with `@_`, so `id="42"` turns into `"@_id": "42"`. This is the shared convention of `fast-xml-parser` and `xmltodict`, and the prefix never collides with element names because no valid element name starts with `@`. ### Why does XML to JSON keep numbers as strings? Because the converter does no type coercion. Forcing `01234` into `1234` would drop a meaningful leading zero from ZIP codes, account numbers, and padded IDs. Keeping every value as a string is the safe default; coerce deliberately downstream where you know the type. ### Is XML to JSON conversion lossless? No. Comments and processing instructions are discarded, namespace semantics are only partially preserved, and mixed-content ordering may not round-trip. When you need every byte preserved, use an XML Formatter to reformat the XML instead of converting it to JSON. ### How are repeated XML elements handled in JSON? A single same-named child becomes an object; two or more become an array. Because the shape depends on sibling count, your consumer code should always normalize to an array so it handles both the one-item and many-item cases without crashing. ### What happens to XML namespaces when converting to JSON? An `xmlns` declaration becomes an ordinary `@_xmlns` key, and the prefix stays inside the element-name string, as in `"soap:Body"`. The semantic binding of a prefix to a URI is not interpreted, so distinct namespaces can flatten together. ### How do I convert JSON back to XML? Use the companion JSON to XML Converter. It applies the same `@_` and `#text` conventions in reverse, so attributes, text content, and arrays map back symmetrically. That symmetry is what makes a clean JSON → XML → JSON round-trip possible. ### Can I convert XML with multiple root elements? No. Multiple top-level elements are not well-formed XML, so the parser rejects the input. Wrap the fragments in a single root element first — turn `` into `` — then convert. ## Conclusion XML-to-JSON conversion is convention-driven, not a reformat. The rules are consistent across runtimes: attributes map to `@_` keys, mixed-content text to `#text`, repeated siblings to arrays, and values stay strings so leading zeros and precision survive. The traps to remember are the single-versus-array shape shift, silently dropped attributes, and the loss of comments and namespace semantics. None of those are bugs; all of them are predictable once you know the model mismatch behind them. When you need a quick, private conversion, paste into the [XML to JSON Converter](/tools/xml-to-json) — it runs entirely in your browser. Validate the source first with the [XML Formatter](/tools/xml-formatter), and go the other direction with the [JSON to XML Converter](/tools/json-to-xml) when you need round-trip XML. For more on how data-format models shape conversion behavior, see the notes on [YAML and JSON differences](/blog/yaml-norway-problem-and-json-yaml-differences). --- ### The YAML Norway Problem and JSON-YAML Differences for Engineers URL: https://go-tools.org/blog/yaml-norway-problem-and-json-yaml-differences Why YAML reads "no" as false. Real K8s outages from string quoting. JSON vs YAML choices, indent rules & K8s manifest conversions explained. # The YAML Norway Problem and JSON ↔ YAML Differences Engineers Should Know It was a routine Helm deployment. The team had spent two days tuning a values.yaml file for a multi-region rollout. The chart templated a Kubernetes ConfigMap with locale metadata — including the country code for their Norwegian data center. Someone typed `country: NO` and committed it. The CI pipeline went green. The deployment went out. Then the alerts came in. The ConfigMap contained `country: false` instead of `country: "NO"`. Every downstream service that read the country field got a boolean instead of a string. The string comparison broke. The routing logic fell through to a default. Traffic that should have stayed in Norway ended up processed by the wrong regional endpoint. The root cause was a single unquoted string in a YAML file. YAML 1.1 — the version that virtually all Kubernetes tooling uses — treats `NO` as a boolean `false`. It treats `YES`, `ON`, `OFF`, `Y`, `N`, `no`, `yes`, `on`, `off`, `y`, `n`, and a dozen more variants the same way. No warning. No error. Silently wrong. JSON does not have this problem. `{"country": "NO"}` is always a string. YAML's implicit type coercion is both its greatest convenience and its most dangerous footgun. This guide covers the full picture: why the Norway problem exists, what changed in YAML 1.2 (and why most tooling ignores it), how to write correct quoting strategies, the indentation rules that trip up newcomers, number precision traps, and four real-world conversion scenarios from Kubernetes manifests to Terraform plans. When you need to safely flatten a JSON value into YAML without this trap, our JSON to YAML converter auto-quotes Norway-prone strings automatically. ## JSON vs YAML — When to Use Which Before diving into the Norway problem, it helps to understand what each format is actually optimized for. They are not interchangeable — each has a design center that makes it the better choice in specific contexts. | Dimension | JSON | YAML | |-----------|------|------| | Syntax | Strict — braces, quotes, commas required | Flexible — indentation-driven, minimal punctuation | | Type system | Explicit: string, number, boolean, null, array, object | Implicit — YAML 1.1 infers types from value shape | | Human readability | Developer-friendly, machine-verifiable | Human-friendly, easy to hand-edit | | Quote requirement | Strings always quoted | Most scalars can be unquoted (the source of Norway) | | Comments | Not supported | Supported with `#` | | Primary use | APIs, data exchange, modern config systems | Kubernetes, Docker Compose, Ansible, CI pipelines | | Surprising parses | None — strict parsing | Yes — Norway, octal, timestamps | | Schema enforcement | JSON Schema ecosystem | YAML Schema (less tooling) | **JSON wins** when your data crosses system boundaries — REST APIs, message queues, database serialization. Machines parse it, machines generate it, and the strict syntax makes validation straightforward. Use a JSON Formatter to validate structure before sending. **YAML wins** when humans are the primary authors. Kubernetes manifests, GitHub Actions workflows, Helm charts, Ansible playbooks — these are files developers read and edit dozens of times. The reduced punctuation and support for comments make them genuinely more maintainable than their JSON equivalents. The problem arises at the boundary: when a tool generates JSON (like `kubectl get deploy -o json` or `terraform show -json`) and a human needs to version-control or edit the result as YAML. That conversion is where the Norway problem lives. Our YAML to JSON converter handles the reverse direction when you need to go back. ## The Norway Problem — Deep Dive The Norway problem is not a bug. It is a feature of the YAML 1.1 specification behaving exactly as designed. Understanding why it was designed this way — and why so many systems still implement 1.1 — is the key to avoiding it. ### Why "no", "yes", "on", "off", "y", "n" Misparse The YAML 1.1 specification defined a broad boolean type that was intended to be human-friendly. It recognized all of the following as `true` or `false`: **True:** `y`, `Y`, `yes`, `Yes`, `YES`, `true`, `True`, `TRUE`, `on`, `On`, `ON` **False:** `n`, `N`, `no`, `No`, `NO`, `false`, `False`, `FALSE`, `off`, `Off`, `OFF` The intent was good: config files often use `yes`/`no` instead of `true`/`false` in English, and YAML wanted to support the natural way people write configuration. The problem is that `yes`, `no`, `on`, `off`, `y`, and `n` are also perfectly legitimate string values that mean something entirely different in most applications. Here is the mismatch in concrete YAML: ```yaml # YAML 1.1 (what most parsers implement) country: NO # parses as: country: false ← DANGER enabled: yes # parses as: enabled: true restart: off # parses as: restart: false language: y # parses as: language: true shell: n # parses as: shell: false # Correct — explicit string quotes override type inference country: "NO" # parses as: country: "NO" ← safe enabled: "yes" # parses as: enabled: "yes" restart: "off" # parses as: restart: "off" language: "y" # parses as: language: "y" shell: "n" # parses as: shell: "n" ``` And the JSON comparison: ```json {"country": "NO"} ``` In JSON, `NO` inside quotes is always and unconditionally a string. There is no implicit type inference. The strictness that makes JSON feel verbose is also what makes it safe. Beyond boolean coercion, YAML 1.1 also implicitly converts: - `123e4` → the number `1230000` (scientific notation) - `0x1A` → the number `26` (hexadecimal) - `0755` → the number `493` (octal — this one breaks Unix file permission strings) - `2024-05-04` → a date object in many parsers (not just a string) - `1_000_000` → the number `1000000` (underscore separator) The Norway problem is really just the most famous member of a whole family of YAML implicit type coercions. ### YAML 1.1 vs 1.2 — What Changed YAML 1.2 was published in 2009 — four years after YAML 1.1. Its primary goal was to bring YAML into strict alignment with JSON (since JSON is actually a valid YAML 1.2 subset) and to reduce the surprising implicit type conversions. In YAML 1.2: - Boolean is narrowed to exactly **`true` and `false`** (case-sensitive). That is it. `yes`, `no`, `on`, `off` are plain strings. - Octal literals require the `0o` prefix (`0o755`) — the old `0755` form is a string. - Timestamps are not implicitly parsed — `2024-05-04` stays a string unless you tag it explicitly. - The specification itself is a JSON superset, meaning every valid JSON document is valid YAML 1.2. On paper, YAML 1.2 solves the Norway problem entirely. In practice, the ecosystem barely moved. | Library | Default spec | Norway risk | |---------|-------------|-------------| | PyYAML (Python) | YAML 1.1 | Yes — `yaml.safe_load` still parses `NO` as `False` | | ruamel.yaml (Python) | YAML 1.2 (optional) | Configurable — safer by default | | js-yaml (Node.js) | YAML 1.1 | Yes in older versions; newer versions have `FAILSAFE_SCHEMA` option | | eemeli/yaml (Node.js) | YAML 1.2 | No — 1.2 by default, or explicitly version-selectable | | gopkg.in/yaml.v2 (Go) | YAML 1.1 | Yes | | gopkg.in/yaml.v3 (Go) | YAML 1.2 | Significantly safer | | Kubernetes / Helm | YAML 1.1 (via Go yaml.v2) | Yes — historical, very difficult to migrate | | Ansible | YAML 1.1 (via PyYAML) | Yes | The reason migration is slow is backward compatibility. Systems that have relied on `yes`/`no` parsing as booleans for a decade cannot silently change that behavior without breaking existing configs. Kubernetes in particular is a massive installed base where changing YAML parsing semantics would be a cluster-wide breaking change. **The practical conclusion:** assume YAML 1.1 semantics in any tool you did not explicitly configure otherwise. Always quote strings that could be misread as booleans, timestamps, or numbers. ### How Production Systems Get Bitten The Norway country code is the most-cited example because it is counterintuitive — `NO` looks like an obvious abbreviation, not a boolean. But the pattern repeats across many real-world scenarios: **IATA airport codes.** The Norwegian airport Harstad/Narvik has code `EVE`. Safe. Oslo Gardermoen is `OSL`. Also safe. But any application using YAML to store regional airport codes is one `no` route code away from a boolean false in production. **Environment variable names.** `ON` is a perfectly valid environment variable value meaning "enabled" in some legacy systems. `OFF` is its counterpart. Migrating configs from shell scripts to YAML without quoting these values introduces silent type coercion. **Email user fields.** A user whose first name or username is literally `n`, `y`, or any of the trigger words will serialize incorrectly if the application dumps YAML without proper quoting. This is particularly insidious because it fails for only a subset of users. **Docker Compose restart policies.** The `restart_policy` field's value `"no"` means "do not restart." If it loses its quotes in a YAML round-trip, the value becomes `false`, and Docker Compose may interpret it as "no restart policy specified" or throw a validation error — either way, the container restart behavior is wrong. **GitHub Actions `shell:` field.** The valid shell values are `bash`, `pwsh`, `python`, `sh`, `cmd`, `powershell`. None of these are Norway words. But someone who types `shell: yes` or `shell: on` as a placeholder during draft editing may be surprised when YAML turns it into a boolean before the validator even sees it. The fix in all cases is the same: quote strings that are semantically strings, regardless of whether a human would recognize them as keywords. Our JSON to YAML converter applies this automatically — any value in the Norway-word list gets quoted in the output. ## String Quoting Strategy Once you understand why Norway words mismatch, the solution is choosing the right quoting strategy for your use case. YAML supports three modes, each with different tradeoffs. ### Auto vs Double vs Single **Auto quoting** (recommended for most conversions) lets the library decide when quotes are necessary. Values that would be misread without quotes — Norway words, numbers, timestamps, strings that look like YAML syntax — get quoted automatically. Everything else stays as a plain scalar. This produces the most readable output while remaining safe. ```yaml # Auto mode output name: Alice # plain — no ambiguity country: "NO" # quoted — Norway word age: 30 # plain — unambiguous number created: "2024-05-04" # quoted — would otherwise parse as a date port: "8080" # depends on library — some quote numeric-looking strings ``` **Double-quoted strings** wrap all string values in double quotes. This is explicit and auditable — any reader can see that all these values are strings without reasoning about the spec. The tradeoff is verbosity and reduced human readability, especially for deeply nested configs. ```yaml # Double-quote mode name: "Alice" country: "NO" replicas: "3" # even numbers become strings — may cause schema errors ``` Be careful: if your target schema expects a number and you serialize it as a quoted string, the YAML parser will correctly type it as a string, but Kubernetes or another strict consumer may reject the field as the wrong type. **Single-quoted strings** are a YAML-only feature — JSON has no single-quote syntax. Single quotes are literal: no escape sequences inside them. The only special case is that a single quote inside a single-quoted string must be doubled (`''`). Single quotes are ideal for strings that contain backslashes or special characters that would need escaping in double quotes. ```yaml # Single-quote mode pattern: 'C:\Users\alice\Documents' # no escape needed regex: '\d+\.\d+' # backslashes literal ``` For JSON-to-YAML conversions intended to round-trip back to JSON, prefer Auto or Double mode. Single-quoted strings introduce a YAML-specific syntax that requires a YAML-aware parser on the way back. ### Block Scalars (| and >) YAML's block scalar syntax is genuinely useful for multi-line strings — something JSON handles awkwardly with `\n` escape sequences. **Literal block scalar `|`** preserves newlines exactly: ```yaml # Literal block — newlines kept script: | #!/bin/bash set -euo pipefail echo "Starting deployment" kubectl apply -f manifest.yaml # Equivalent JSON representation (unreadable) # {"script": "#!/bin/bash\nset -euo pipefail\necho \"Starting deployment\"\nkubectl apply -f manifest.yaml\n"} ``` **Folded block scalar `>`** joins lines with spaces, turning each newline into a space (except blank lines, which become newlines): ```yaml # Folded block — newlines become spaces description: > This service handles authentication for the entire platform. It supports OAuth2, SAML, and API key authentication. # Result: "This service handles authentication for the entire platform. It supports OAuth2, SAML, and API key authentication.\n" ``` Block scalars shine for embedding TLS certificates, multi-line shell scripts, or SQL queries in YAML configs — scenarios where the JSON equivalent would be a long, escaped, one-liner that no human can read. When converting from JSON to YAML, most converters (including ours) use Auto mode and represent multi-line strings with block scalars only when they detect embedded newlines. Single-line strings get flow scalars (quoted or plain). Use our JSON to YAML converter to see the output before committing it to a manifest. ## Indentation — 2 vs 4 Spaces, Tabs Forbidden YAML's indentation rules are stricter than they look. The spec has one absolute rule and one convention that varies by ecosystem. **The absolute rule: tabs are forbidden.** Every indentation level must use spaces. A tab character in a YAML file is a parse error in most parsers: ```yaml # WRONG — tabs cause parse errors apiVersion: apps/v1 kind: Deployment metadata: name: my-app # ← tab character here → ParseError # CORRECT — spaces only apiVersion: apps/v1 kind: Deployment metadata: name: my-app # ← two spaces ``` The error message you will see varies by library. In Python's PyYAML: ``` yaml.scanner.ScannerError: while scanning for the next token found character '\t' that cannot start any token ``` In Go's yaml.v3: ``` yaml: line 4: found character that cannot start any token ``` Configure your editor to expand tabs to spaces for YAML files. In VS Code, add to your workspace settings: `"[yaml]": { "editor.insertSpaces": true, "editor.tabSize": 2 }`. **The convention: 2 vs 4 spaces.** Both are valid. Ecosystem conventions differ: | Ecosystem | Convention | Reason | |-----------|-----------|--------| | Kubernetes manifests | 2 spaces | Official docs and examples use 2 | | Helm charts | 2 spaces | Follows K8s convention | | Docker Compose | 2 spaces | Official compose spec examples | | GitHub Actions | 2 spaces | Official workflow examples | | Ansible playbooks | 2 spaces | Official documentation | | Traditional configs | 4 spaces | Matches JSON beautify default | For any file that will be consumed by Kubernetes or Docker Compose, use 2 spaces. For standalone config files that will only be read by humans and custom tooling, either works — just be consistent within a file. Our JSON to YAML converter defaults to 2-space indentation and lets you switch to 4 for projects that prefer it. One more rule: child elements must be indented more than their parent, but the number of additional spaces can be any positive integer (1, 2, 3, 4...) — as long as it is consistent within a block. In practice, always use 2 or 4 for readability. ## Number Handling Across JSON ↔ YAML Both formats support numbers, but the edge cases differ enough to cause production bugs. ### Precision Loss for Big Numbers JavaScript's `Number` type is a 64-bit IEEE 754 float. It can represent integers exactly up to 2^53 − 1 = 9,007,199,254,740,991. Beyond that, integer precision is lost: ```js // JavaScript precision loss — this is not a YAML problem, but it affects JSON parsing JSON.parse('{"v": 9007199254740993}').v // → 9007199254740992 (the 3 became 2 — one bit lost) // Safe — within 2^53 range JSON.parse('{"v": 9007199254740991}').v // → 9007199254740991 (exact) ``` This matters for JSON-to-YAML conversion in JavaScript environments because the precision is already lost before YAML serialization begins. Kubernetes `metadata.resourceVersion` is a string field specifically because resource versions can exceed the safe integer range. Other fields that look like small numbers — `observedGeneration`, `uid` components — are safer, but any int64 field in a K8s response is potentially affected. **Workarounds:** - Use Python or Go for conversion pipelines involving large numbers — both handle arbitrary integers natively. - In Node.js, use a JSON parser that supports BigInt: `JSON.parse(text, (_, v) => typeof v === 'number' && !Number.isSafeInteger(v) ? BigInt(v) : v)`. - For fields that must round-trip without loss, serialize them as strings at the source. - When reviewing converted YAML, look for fields like `resourceVersion`, `generation`, and timestamp-derived values. ### Octal & Hex Quirks YAML 1.1 treats certain number-like strings as non-decimal integers: ```yaml # YAML 1.1 parsing surprises permissions: 0755 # parses as octal 493, not decimal 755 value: 0x1A # parses as hex 26, not string "0x1A" # YAML 1.2 behavior permissions: 0755 # stays as integer 755 (decimal) — octal requires 0o prefix permissions: 0o755 # parses as octal 493 in both 1.1 and 1.2 # Safe for both specs — quote any leading-zero value permissions: "0755" # always the string "0755" ``` The octal trap is particularly dangerous for Unix file permissions, IP address components with leading zeros (some network devices), and any numeric code that uses leading zeros for padding (ZIP codes, product codes). Always quote these values when writing YAML by hand, or ensure your converter quotes them — our JSON to YAML converter detects numeric strings from JSON and preserves their string type. ## Real-World Conversions The Norway problem and quoting strategies become concrete when you apply them to real conversion scenarios. ### Kubernetes Manifest from JSON The canonical workflow: `kubectl get deploy my-app -o json` gives you the live object as JSON. You want to clean it up (remove `status`, `creationTimestamp`, managed fields) and check it into git as a YAML manifest. **Source JSON (abbreviated):** ```json { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "name": "my-app", "namespace": "production", "labels": { "app": "my-app", "region": "NO" } }, "spec": { "replicas": 3, "selector": { "matchLabels": { "app": "my-app" } }, "template": { "spec": { "containers": [{ "name": "app", "image": "registry.example.com/my-app:v1.2.3", "env": [ { "name": "REGION", "value": "NO" }, { "name": "ENABLE_FEATURE", "value": "yes" } ] }] } } } } ``` **Expected YAML output (with Norway protection):** ```yaml apiVersion: apps/v1 kind: Deployment metadata: name: my-app namespace: production labels: app: my-app region: "NO" # quoted — Norway word spec: replicas: 3 selector: matchLabels: app: my-app template: spec: containers: - name: app image: registry.example.com/my-app:v1.2.3 env: - name: REGION value: "NO" # quoted — Norway word - name: ENABLE_FEATURE value: "yes" # quoted — Norway word ``` Notice that `replicas: 3` is left unquoted — it is a legitimate integer that Kubernetes expects as a number. The Norway words in `labels` and `env` values are quoted. A naive converter that does not handle YAML 1.1 booleans would silently produce `region: false` and `value: false`. After converting, validate with: `kubectl apply --dry-run=client -f manifest.yaml`. This catches schema errors without touching the cluster. Try the conversion in our JSON to YAML converter — paste the JSON above and see Norway-safe output instantly. Use our YAML to JSON converter to verify the round-trip. ### Docker Compose from JSON CI/CD pipelines sometimes generate Docker Compose configs programmatically from a JSON configuration store, then write them to disk as YAML for developers to read. **Critical trap — restart policy:** ```json {"restart_policy": "no"} ``` In Compose, `restart_policy: "no"` is a valid value meaning "never restart the container." Without quotes in YAML, this becomes `restart_policy: false`, which Docker Compose may either treat as the same semantic (falsy = no restart) or reject with a type validation error — behavior varies by Compose version. The quoting is mandatory. **Also watch for:** Compose v3 `deploy.restart_policy.condition: "on-failure"` — the `on-failure` value contains the word `on`, but it is hyphenated and not in the trigger list, so it is actually safe. However, `condition: on` (without the `-failure`) would mismatch. Quote environment variable values in the `environment:` block if they could be Norway words. Validate Compose files after conversion: `docker-compose config` parses and re-outputs the canonical form, surfacing type errors. ### GitHub Actions Workflow GitHub Actions workflows are YAML files hand-edited by developers. The most common conversion scenario is reading workflow data from the GitHub API (which returns JSON) and converting it to a local YAML file for editing. The key fields to watch: ```yaml # SAFE — no Norway words in standard GitHub Actions on: # "on" is a YAML key here, not a value — handled differently push: branches: [main] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Run tests run: | npm install npm test env: NODE_ENV: production # safe — not a Norway word DEBUG: "off" # Norway word in value — needs quoting ``` Note: `on:` as a YAML key is special — the Norway problem applies to values, not keys. But `on` as a value (like `DEBUG: on`) would trigger the coercion. The `env:` block deserves particular scrutiny because environment variable values are strings, but many of them are short flags that could collide with Norway words. For workflows that include `shell:` specifications, valid values (`bash`, `pwsh`, `sh`, `python`) are all safe from Norway coercion. Custom values should be quoted proactively. ### Terraform JSON Plan → YAML `terraform show -json tfplan > plan.json` outputs a detailed JSON representation of what Terraform plans to create, modify, or destroy. Converting this to YAML makes it more readable for pull request reviews and compliance audits. ```bash terraform plan -out=tfplan terraform show -json tfplan > plan.json # Then convert with our tool or a library ``` The Terraform plan JSON is complex and deep. Key concerns when converting: 1. **Large integer IDs.** Cloud resource IDs (AWS account IDs, GCP project numbers) and computed attribute values can be large numbers. Convert via Python or Go to avoid float64 precision loss. 2. **Version constraint strings.** Terraform uses `~>`, `>=`, `<=` in provider version constraints. These are string values that YAML handles correctly as long as they are not Norway words — but `~>` is safe. 3. **Provider configuration values.** Terraform plan outputs can include configuration values for resources. If a boolean field defaults to `false` and is represented as `"no"` in some provider schema, that is a Norway risk on the way back to YAML. 4. **The `.sensitive_values` block.** Sensitive values are redacted as `true` booleans in the plan JSON. These survive conversion cleanly since `true` is not a Norway word in either YAML version. The Terraform-to-YAML conversion is primarily for human review, not for feeding back into Terraform. Do not use YAML manifests as Terraform input — Terraform's native format is HCL, and its JSON input format is specific and documented separately. ## Code Examples — 4 Languages ### Node.js (eemeli/yaml + js-yaml) The Node.js ecosystem has two dominant YAML libraries with meaningfully different Norway handling: ```js // eemeli/yaml — recommended, YAML 1.2 by default, Norway-safe import { stringify } from 'yaml'; import { readFileSync } from 'fs'; const jsonInput = readFileSync('input.json', 'utf8'); const data = JSON.parse(jsonInput); // Default: YAML 1.2 — "NO" stays as "NO", no boolean coercion const yamlOutput = stringify(data); console.log(yamlOutput); // region: NO ← safe in 1.2, but for maximum compatibility quote it explicitly // Force YAML 1.1 behavior (for K8s/Helm environments that parse 1.1) const yamlForK8s = stringify(data, { version: '1.1' }); // region: 'NO' ← auto-quoted because 1.1 would parse NO as false console.log(yamlForK8s); ``` ```js // js-yaml — widespread, but YAML 1.1 semantics, Norway-risky without care import yaml from 'js-yaml'; import { readFileSync } from 'fs'; const data = JSON.parse(readFileSync('input.json', 'utf8')); // Default dump — Norway words may not be quoted const unsafe = yaml.dump(data); // region: NO ← will parse as false if re-read by a 1.1 parser! // Safer: use a custom schema or force quoting const safer = yaml.dump(data, { schema: yaml.JSON_SCHEMA, // restricts to JSON-compatible types noCompatMode: false, lineWidth: -1, quotingType: '"', forceQuotes: false, // only quotes when necessary per JSON schema }); ``` For new projects, prefer `eemeli/yaml`. Its YAML 1.2 default is safer, its Document API gives fine-grained control over quoting, and it handles the round-trip fidelity better. For projects already using `js-yaml`, use the `JSON_SCHEMA` option to restrict to JSON-safe types. For a deeper look at filtering and transforming JSON before conversion, see the jq command-line cheat sheet for pre-processing patterns. ### Python (PyYAML + ruamel.yaml) Python is the dominant language for Kubernetes tooling, Ansible, and data engineering pipelines — all heavy YAML users. ```python import json import yaml import sys # PyYAML — simple, standard, but YAML 1.1 by default with open('input.json') as f: data = json.load(f) output = yaml.dump(data, default_flow_style=False, allow_unicode=True) # country: 'NO' ← PyYAML is actually smart enough to auto-quote Norway words # But it does NOT quote "yes", "no" (lowercase) in all configurations: # enabled: 'yes' ← quoted # tag: y ← may or may not be quoted depending on version print(output) ``` ```python import json import sys from ruamel.yaml import YAML # ruamel.yaml — round-trip fidelity, supports YAML 1.2, recommended for production yaml_rt = YAML() yaml_rt.default_flow_style = False yaml_rt.width = 4096 # prevent unwanted line wrapping yaml_rt.best_map_flow_style = False with open('input.json') as f: data = json.load(f) yaml_rt.dump(data, sys.stdout) # Preserves key order, handles Norway words correctly, supports anchors on round-trip ``` For Ansible and Kubernetes automation scripts where you are converting JSON API responses to YAML manifests, `ruamel.yaml` is the safer choice. PyYAML is fine for simple scripts where you control the input data and have verified no Norway words appear. If you use JSON5 or JSONC config files (with comments) before conversion, strip the extensions first — see the JSON5 and JSONC formatting guide for compatible parsers. ### Go (gopkg.in/yaml.v3) Go is the language of the Kubernetes ecosystem itself — `kubectl`, Helm, Argo, Flux, and most K8s operators are written in Go. ```go package main import ( "encoding/json" "fmt" "os" "gopkg.in/yaml.v3" ) func main() { // Read JSON input jsonBytes, err := os.ReadFile("input.json") if err != nil { panic(err) } // Unmarshal JSON into a generic map var data map[string]interface{} if err := json.Unmarshal(jsonBytes, &data); err != nil { panic(err) } // Marshal to YAML — yaml.v3 uses YAML 1.2 semantics yamlBytes, err := yaml.Marshal(data) if err != nil { panic(err) } fmt.Println(string(yamlBytes)) // country: "NO" ← yaml.v3 quotes Norway words correctly // replicas: 3 ← integers stay integers // enabled: true ← booleans stay booleans } ``` `yaml.v3` is a significant improvement over `yaml.v2` for Norway safety. The v2 library followed YAML 1.1 and would write `NO` without quotes; v3 quotes ambiguous values correctly. If you are maintaining an older Go project that uses v2, upgrade to v3 — the API is largely compatible and the safety improvement is worth the migration. For type-safe conversion with Go structs (rather than `map[string]interface{}`), use struct tags: ```go type DeploymentLabels struct { App string `yaml:"app" json:"app"` Region string `yaml:"region" json:"region"` } // yaml.Marshal on a struct field containing "NO" will quote it correctly in v3 ``` ### Bash CLI (yq + jq) For shell scripts and quick one-off conversions, `yq` (Mike Farah's version, `mikefarah/yq`) converts JSON to YAML in a single command: ```bash # Install yq brew install yq # macOS sudo wget -qO /usr/local/bin/yq \ https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64 chmod +x /usr/local/bin/yq # Linux # Convert JSON file to YAML yq -P < input.json > output.yaml # Convert from kubectl JSON output kubectl get deploy my-app -o json | yq -P > manifest.yaml # Pipe through jq first to filter/transform, then convert to YAML kubectl get deploy my-app -o json \ | jq 'del(.status, .metadata.creationTimestamp, .metadata.managedFields)' \ | yq -P > clean-manifest.yaml ``` The `jq | yq` pipeline is a powerful pattern: use `jq` for JSON manipulation (filtering fields, reshaping structure, querying values) and `yq -P` as the final YAML serializer. For `jq` patterns, see the jq command-line cheat sheet for 30 real-world patterns including `kubectl` and `aws` integrations. **Norway caution with yq:** `yq` (mikefarah) respects the input type from JSON — a JSON string `"NO"` in the input will serialize as a YAML string with quotes. But if you generate YAML directly with `yq` (not from JSON input), you must quote Norway-word values explicitly. Use our YAML to JSON converter to validate the round-trip after `yq` output. ## Edge Cases & Gotchas Beyond the Norway problem, JSON ↔ YAML conversion has several edge cases that trip up experienced engineers: 1. **Multi-document YAML (`---` separator).** A single YAML file can contain multiple documents separated by `---`. JSON has no equivalent concept. When converting multi-document YAML to JSON, most tools either take the first document only, merge all documents into an array, or error out. When converting JSON to YAML, a single `---` document header is added by convention. Decide and document your behavior explicitly for pipelines that may encounter multi-document files. 2. **YAML anchors and aliases.** YAML supports `&anchor` definitions and `*alias` references for DRY configs. When converting YAML to JSON, anchors must be expanded — the resulting JSON may be much larger than the source YAML. When converting JSON to YAML, the converter cannot reconstruct anchors that did not exist in the original. Aliases are a YAML-only feature. 3. **Timestamp implicit parsing.** YAML 1.1 parsers convert `2024-05-04` and `2024-05-04T12:00:00Z` to language-native date objects, not strings. When this date object is serialized back to JSON, the output depends on the library: some output ISO strings, some output Unix timestamps, some output null. Round-tripping dates through YAML without explicit string quoting (`"2024-05-04"`) can silently change the format. 4. **The `!!binary` tag.** YAML can embed base64-encoded binary data with the `!!binary` tag. JSON has no binary type — binary must be a base64 string. When converting YAML with `!!binary` fields to JSON, decode to base64 string. When converting back, you cannot reconstruct the binary tag without knowing the schema. Kubernetes uses `!!binary` for some secret values. 5. **Key type collisions.** JSON requires object keys to be strings. YAML allows keys of any type — integer keys, boolean keys, even complex object keys. A YAML file with `true: value` or `1: value` as keys cannot be faithfully represented as JSON. Most converters stringify the keys, but the semantics change. 6. **Null representation variance.** In YAML, `null`, `~`, `Null`, `NULL`, and an empty value all mean null. In JSON, only `null` is null. When converting YAML to JSON, all of these normalize to `null`. But when converting JSON back to YAML, the null representation choice matters — `~` is more compact, `null` is more explicit. Pick one and stick to it. 7. **Sort order changes.** JSON objects technically have no defined key order (though most parsers preserve insertion order). YAML mappings similarly have no required order. But some YAML libraries sort keys alphabetically by default when serializing. This can cause large diffs in version control if the source JSON used a different order. Configure `sort_keys=False` in PyYAML (`default_flow_style=False` alone does not prevent sorting) and equivalent options in other libraries. ## When NOT to Convert Conversion is not always the right answer. Here are the scenarios where staying in the original format is the better choice: **Do not convert YAML to JSON if the YAML contains comments that document business logic.** YAML comments are not part of the data model — they disappear in any serialization to JSON. If a Kubernetes manifest has comments explaining why a specific resource limit was chosen or why a security policy exception was made, converting to JSON destroys that documentation. Keep the YAML. **Do not auto-convert configs in CI pipelines without round-trip tests.** If your pipeline converts JSON to YAML and then applies the YAML to a cluster, add a round-trip validation step: YAML back to JSON, then compare with the original. This catches type coercion surprises before they reach production. **Do not convert just because a tool outputs JSON.** `kubectl`, `aws`, `terraform`, and `docker inspect` all output JSON, but most of these tools also accept YAML as input. Before building a conversion step, check whether the target tool can directly accept YAML input — most modern DevOps tools can. Our YAML to JSON converter is most useful when you specifically need JSON for a tool that does not accept YAML. **Do not convert if the schemas differ.** If your JSON uses `camelCase` keys and your YAML consumer expects `snake_case` (or vice versa), you need a transform step in addition to a format conversion. A bare format conversion will produce syntactically correct but semantically wrong YAML. Address the schema mapping explicitly. **Do not keep both formats in sync manually.** If you are maintaining a `config.json` and a `config.yaml` that are supposed to be equivalent, you will drift. Pick one canonical format and derive the other automatically — or better, pick one format and eliminate the duplication. ## FAQ ### Does the YAML Norway problem still affect modern systems? Yes — it is pervasive in the ecosystem. Kubernetes and Helm use Go's `yaml.v2` library (YAML 1.1 semantics) in significant parts of their codebases. Ansible uses PyYAML (YAML 1.1). GitHub Actions workflows are parsed by GitHub's internal YAML parser which has its own behavior. Most CI/CD YAML files in the wild are processed by YAML 1.1 parsers. Assume 1.1 semantics until you have verified otherwise. ### Why would I convert JSON to YAML if YAML is harder to parse? The conversion is not about parser difficulty — it is about human editability. JSON is ideal for machines; YAML is ideal for humans who need to read, edit, and review configuration files. A Kubernetes manifest checked into git, reviewed in pull requests, and hand-tuned by engineers should be YAML. The same manifest retrieved from the API for programmatic processing should be JSON. Our JSON to YAML converter bridges the two. ### Can I round-trip JSON ↔ YAML losslessly? With caveats, yes — for JSON-compatible data. JSON is a subset of YAML 1.2, so any valid JSON document is valid YAML 1.2. Going JSON → YAML → JSON should be lossless for any data without implicit type coercion. The Norway problem means a JSON string `"NO"` could survive the forward pass only if the converter quotes it, and then survive the return pass only if the YAML parser respects the quotes. Use a YAML 1.2 library for both directions to guarantee lossless round-trips. ### What is the safest YAML library for production? For Python: `ruamel.yaml` configured for YAML 1.2. For Node.js: `eemeli/yaml` (the `yaml` package on npm). For Go: `gopkg.in/yaml.v3`. All three implement YAML 1.2 semantics or have explicit YAML 1.2 modes and handle Norway words correctly. Avoid YAML 1.1 libraries in new projects. If you must use a 1.1 library (PyYAML, js-yaml, yaml.v2) for compatibility reasons, always quote Norway-prone strings explicitly. ### Does Kubernetes manifest YAML support comments after JSON conversion? No — comments cannot be recovered from JSON. JSON has no comment syntax, so there is nothing to convert. When you run `kubectl get deploy -o json` and convert the output to YAML for git storage, the resulting YAML will have no comments. Comments in a Kubernetes manifest must be written by a human after the conversion. This is one reason why keeping the hand-authored YAML as the canonical source is often preferable to round-tripping through the JSON API. ### How do I handle big integers like resourceVersion or nanosecond timestamps? Kubernetes `metadata.resourceVersion` is a string field deliberately — the Kubernetes team knew that JSON parsers in JavaScript and other float64-based runtimes would lose precision on large integers. Always treat it as a string. For genuinely numeric large integers (like nanosecond epoch timestamps in some tracing systems), use Python's `int` type, Go's `int64`, or Node.js `BigInt` for parsing. Never pass them through `JSON.parse()` in JavaScript without a custom reviver function. When converting to YAML, these large integers are safe — YAML has no precision limit for integers. The danger is in the round-trip back through JavaScript's JSON parser. ### Is YAML 1.2 widely adopted yet? Unevenly. The major language libraries have been migrating: Go's yaml.v3, Python's ruamel.yaml, and Node.js's eemeli/yaml all support or default to YAML 1.2. But Kubernetes, Ansible, and much of the DevOps ecosystem still runs on YAML 1.1 parsers due to the backward-compatibility cost of migration. YAML 1.2 adoption in new projects is recommended, but assume 1.1 for any system you did not configure yourself. ### Should our team standardize on JSON or YAML for configs? Standardize on purpose, not on format. Use JSON for configs consumed by code (API request bodies, SDK config files, programmatic tooling). Use YAML for configs consumed by humans (Kubernetes manifests, CI pipelines, deployment configs, Ansible playbooks). Avoid mixing the two for the same config — pick one representation per config type and automate the conversion if you need both. When you do need to convert, both our JSON to YAML and YAML to JSON converters run entirely in your browser — no data leaves your device. ## Try It Now Ready to convert a real file? Try our JSON to YAML converter for sanitizing JSON into safe Kubernetes YAML — it auto-quotes Norway words (`NO`, `yes`, `on`, `off`, and the full YAML 1.1 boolean list) and lets you choose 2-space or 4-space indentation. For the reverse direction, our YAML to JSON converter handles anchors, aliases, and multi-document YAML. Both tools run entirely in your browser — your data never leaves your device, which matters when you are working with production Kubernetes manifests or Terraform plans that contain sensitive resource configurations.