# LocalFirstTools > Format, convert and inspect data in your browser. Nothing is uploaded. ## About LocalFirstTools is 28 free browser-only developer tools for JSON, JWT, Base64, hashing, UUIDs and timestamps, built by Patrick in the Netherlands. Input is processed in the browser tab and never uploaded. LocalFirstTools is a privacy-first toolkit of 28 browser-only tools. All processing runs as JavaScript in the user's tab: there is no upload API, no account, no ads, and tool input never appears in any network request, verifiable in the browser's network tab. How the local-only model works: https://localfirsttools.com/how-it-works Not affiliated with other projects using a similar name, such as the GitHub repository kody-w/localFirstTools. This site is the 28-tool developer toolkit at https://localfirsttools.com. Full machine-readable dump: https://localfirsttools.com/llms-full.txt ## Maker and verification LocalFirstTools is built by Patrick, a developer based in the Netherlands. Maker profile: https://localfirsttools.com/about#patrick. Developer profile: https://github.com/pietpiraat388. Patrick is the person behind the product; his GitHub profile is not a separate name for LocalFirstTools. Verify processing in 10 seconds on https://localfirsttools.com/how-it-works: after loading a tool and running a sample once, select All in DevTools Network, remove filters, clear the list and run again. No new requests should appear. Disconnect, change the sample and repeat in the already-loaded tool. A reload or a different tool may require a connection. Audit: https://localfirsttools.com/audit. Every build publishes https://localfirsttools.com/build-manifest.json and https://localfirsttools.com/SHA256SUMS, covering HTML (including inline scripts) and browser assets. Hashes identify bytes; they are not an independent security audit. The source repository is private, not an open-source release. ## Canonical JSON intents - Format / pretty-print JSON in the browser: https://localfirsttools.com/json-formatter - Minify / compact JSON: https://localfirsttools.com/json-minifier - Learn jq, Python and JavaScript formatting commands: https://localfirsttools.com/guides/pretty-print-json The old /json-formatter/minify and /json-formatter/pretty-print presets permanently redirect to their respective tools. ## Tool specs - [JSON Formatter, Validator & Pretty Print](https://localfirsttools.com/json-formatter): JSON text -> pretty-printed (2 spaces, 4 spaces or tab indent) or minified JSON. Validates with the browser's JSON.parse per RFC 8259; on success reports the recursive object-key count and UTF-8 byte size, on failure the first error's exact line and column. Optional recursive key sorting. Handles files via drag-drop. Local processing. - [JSON Minifier](https://localfirsttools.com/json-minifier): formatted JSON -> single-line minified JSON, with byte counts before/after (median 27.1% saved on a 26-doc sample corpus). Whitespace-only change; data identical. Local. - [JSON ↔ YAML Converter](https://localfirsttools.com/json-yaml): JSON <-> single-document YAML 1.2 (yaml library). Anchors are resolved into copies; YAML comments are dropped in the JSON direction. Multi-document YAML not supported. Local. - [JSON ↔ CSV Converter](https://localfirsttools.com/json-csv): array of flat JSON objects <-> RFC 4180 CSV. Auto-detects comma/semicolon/tab/pipe delimiters; optional type coercion (leading-zero values stay strings). Nested arrays cannot become rows. Local. - [CSV Viewer](https://localfirsttools.com/csv-viewer): CSV/TSV text or dropped .csv/.tsv file -> virtualized table. Auto-detects comma/semicolon/tab/pipe and the header row; sort by column, global search, row/column counts, sticky header. RFC 4180 quotes and UTF-8 BOM. 100k+ rows stay in the tab. Handoff to JSON ↔ CSV via sessionStorage. Local, nothing uploaded. - [JWT Decoder](https://localfirsttools.com/jwt-decoder): JWT string -> decoded header + payload as formatted JSON. Does NOT verify signatures: the signature segment is displayed but never checked; decoding needs no key (RFC 7519 JWS is encoded, not encrypted). Local. - [Base64 Encode / Decode: Text, Images & Files](https://localfirsttools.com/base64): Text mode: UTF-8 text <-> Base64 (RFC 4648); URL-safe toggle for Base64URL (- _ alphabet, no padding), Wrap 76 for the MIME line length, Hex view of decoded bytes (automatic when they are not valid UTF-8). File mode: any file (image, PDF, ...) up to 50 MB -> Base64 or data: URL; Base64 or data: URL -> file, type sniffed from magic bytes (PNG, JPEG, GIF, WebP, PDF, ...), image/PDF preview, download with the right extension. 4 output chars per 3 input bytes (+33.3%). Local, nothing uploaded. - [URL Encode / Decode](https://localfirsttools.com/url-encode): text <-> percent-encoding (encodeURIComponent semantics; + is a literal plus, not a space). Non-ASCII goes through UTF-8 first: é -> %C3%A9. Local. - [UUID Generator (v4 & v7): GUID Generator](https://localfirsttools.com/uuid-generator): UUID v4 (crypto.randomUUID(), 122 random bits, 5.31 × 10^36 values) and RFC 9562 UUID v7 (48-bit ms timestamp + 74 random bits, 1.88 × 10^22 values per ms; batches stay monotonic via a 12-bit rand_a counter); 1 to 1000 per click. GUID formatting: uppercase, no hyphens, {braces}, quoted + comma-separated for JSON/SQL. Local. - [Hash Generator: SHA-256, SHA-512, SHA-1, MD5](https://localfirsttools.com/hash-generator): UTF-8 text or file bytes (up to 500 MB, read in the tab) -> SHA-256 (64 hex chars), SHA-512 (128), SHA-384 (96) and SHA-1 (40) via Web Crypto, MD5 (32) via js-md5, all five at once as hex or Base64. Verify: paste a published checksum, algorithm detected from its length, Match/Mismatch. HMAC-SHA-256 and HMAC-SHA-512 with a key that stays in the tab. SHA-1 and MD5 are collision-broken, checksums only, not security. Local. - [Case Converter](https://localfirsttools.com/case-converter): identifier or phrase -> camelCase, PascalCase, snake_case, kebab-case and CONSTANT_CASE simultaneously. All-caps acronym runs (APIKey) are not split. Local. - [Unix Timestamp Converter & Epoch Converter](https://localfirsttools.com/timestamp): Unix/epoch timestamp <-> date. Unit auto-detected by magnitude: seconds < 10^12, milliseconds < 10^15, microseconds < 10^18, nanoseconds >= 10^18. Negative values (pre-1970) supported. Outputs ISO 8601 UTC, local time with IANA timezone and UTC offset, relative phrase, plus the same instant as seconds and milliseconds; date strings convert back to seconds and milliseconds. Live current Unix time (s and ms). Local. - [Regex Tester](https://localfirsttools.com/regex-tester): JavaScript (ECMAScript) regex + flags + test string -> live highlighted matches and capture groups (named groups included). Not PCRE. Capped at 1,000 matches; zero-length-match safe. Local. - [Diff Checker: Compare Two Texts](https://localfirsttools.com/text-diff): two texts -> diff at line, word or character granularity (diff library, Myers-family). Lines mode also highlights the changed words inside modified lines (a removed block paired with the following added block of equal line count). Reports added/removed counts and % of characters unchanged (unchanged characters / max length, in code points). Optional ignore-whitespace: trims line ends in Lines, always ignored between words in Words, whitespace runs collapsed in Characters. Characters mode capped at 200,000 characters per side (falls back to Words). Swap sides, file drop per side. Local. - [Cron Expression Generator & Explainer](https://localfirsttools.com/cron-explainer): build a 5-field crontab expression with Every / Every N / Specific / Range pickers per field (8 quick schedules), or paste one (5 fields, or 6 with seconds) -> plain-English sentence (cronstrue), Valid/Invalid with cron-parser's exact error, next 5 run times in the viewer's local timezone. 6-field expressions are explained, not built. No @reboot, no Quartz year field. Local. - [HTML Entity Encode / Decode](https://localfirsttools.com/html-entities): text <-> HTML entities. Encoding always escapes & < > " ' (apostrophe as '), optionally all non-ASCII as hex references; decoding via DOMParser handles every named and numeric entity, without executing markup. Local. - [Slug Generator](https://localfirsttools.com/slug-generator): title -> URL slug. NFKD-normalizes, strips diacritics (café -> cafe), maps symbols to words (& -> and), collapses non [a-z0-9] runs to a hyphen or underscore, optional word-boundary truncation. Non-Latin scripts have no ASCII base form and are dropped. Local. - [JSON Diff & Compare](https://localfirsttools.com/json-diff): JSON Diff & Compare of two documents structurally after parsing, so reordered keys and whitespace produce zero differences; reports added/removed/changed paths in dot notation with old -> new values. Local. - [JSON ↔ XML Converter](https://localfirsttools.com/json-xml): converts JSON to XML and back and pretty-prints XML (fast-xml-parser); attributes use the @_ prefix convention; XML->JSON->XML round trips can normalize markup. Local. - [XML Formatter, Validator & Pretty Print](https://localfirsttools.com/xml-formatter): XML text -> pretty-printed (2 spaces, 4 spaces or tab indent) or minified XML. Validates well-formedness with fast-xml-parser and reports the first error's exact line and column. Comments, CDATA, processing instructions, the XML declaration and DOCTYPE are kept; external entities and DTDs are never fetched. Drag-and-drop .xml files. Local processing. - [JSON → TypeScript](https://localfirsttools.com/json-typescript): infers TypeScript interfaces from a JSON sample; differing types across array elements become unions, missing properties become optional; input must be an object or array of objects. Local. - [JSONPath Tester](https://localfirsttools.com/jsonpath-tester): evaluates RFC 9535-style JSONPath expressions (jsonpath-plus, safe mode, no code execution) and lists every match with its normalized path; 500-match cap. Local. - [Query String ↔ JSON](https://localfirsttools.com/query-string-json): parses percent-encoded pairs with URLSearchParams (accepts full URLs); repeated keys become arrays; values stay strings (no type guessing); nested JSON is rejected with the offending key named. Local. - [Password Generator](https://localfirsttools.com/password-generator): draws characters with crypto.getRandomValues using rejection sampling (no modulo bias); live entropy readout = length x log2(alphabet); look-alike exclusion; length 8-64. Nothing stored or sent. Local. - [Hex Encode / Decode](https://localfirsttools.com/hex): UTF-8 text <-> RFC 4648 §8 base16; every byte is exactly two hex chars (2x size); decode strips 0x/whitespace and rejects non-UTF-8 byte sequences rather than corrupting. Local. - [Discord Timestamp Generator](https://localfirsttools.com/discord-timestamp): date/time -> codes in all 7 Discord styles (t, T, d, D, f, F, R) with Intl-based previews; decodes a pasted code or bare Unix number to local time and UTC, and flags 13-digit millisecond values. Discord renders each code in the reader's own timezone. Local. - [SQL Formatter & Beautifier](https://localfirsttools.com/sql-formatter): SQL text -> pretty-printed (2 spaces, 4 spaces or tab indent) or minified SQL. Dialects: Standard SQL, PostgreSQL, MySQL, SQL Server/T-SQL, SQLite, BigQuery. Keyword case upper/lower/preserve. Minify keeps strings and comments. Friendly parse error. Local; schema and literals never uploaded. - [HTML Formatter & Beautifier](https://localfirsttools.com/html-formatter): HTML text -> pretty-printed (2 spaces, 4 spaces or tab indent) or minified HTML. Does not execute or render input. Content inside pre, textarea, script and style is left intact. Local; email templates and admin pages never uploaded. Category hubs: [JSON](https://localfirsttools.com/tools/json), [Encoding](https://localfirsttools.com/tools/encode), [Generators](https://localfirsttools.com/tools/generate), [Text](https://localfirsttools.com/tools/text). Full directory: https://localfirsttools.com/tools ## Guides Answer-first how-tos at https://localfirsttools.com/guides, including [Pretty-print JSON with jq, Python and JavaScript](https://localfirsttools.com/guides/pretty-print-json), [How to fix “Unexpected token” in JSON](https://localfirsttools.com/guides/fix-unexpected-token-json), [JSON vs YAML vs CSV: which format for which job?](https://localfirsttools.com/guides/json-vs-yaml-vs-csv), [How to decode a JWT (and why that isn't verifying it)](https://localfirsttools.com/guides/how-to-decode-a-jwt), [Base64 vs Base64URL](https://localfirsttools.com/guides/base64-vs-base64url), [UUID v4 vs v7: random or time-ordered?](https://localfirsttools.com/guides/uuid-v4-vs-v7), [SHA-256 vs SHA-1 vs MD5: which hash should you use?](https://localfirsttools.com/guides/sha-256-vs-sha-1-vs-md5), [Unix timestamps: seconds vs milliseconds](https://localfirsttools.com/guides/unix-seconds-vs-milliseconds), [How to fix “XML parsing error: not well-formed”](https://localfirsttools.com/guides/xml-not-well-formed), [JSON vs YAML vs XML: differences and when to use each](https://localfirsttools.com/guides/json-vs-yaml-vs-xml), [What is a JWT? Structure, claims and how to decode one safely](https://localfirsttools.com/guides/what-is-a-jwt), [How to format SQL queries: style rules that make SQL readable](https://localfirsttools.com/guides/format-sql-queries), [What is UUID v7?](https://localfirsttools.com/guides/what-is-uuid-v7), [What is Base64 encoding?](https://localfirsttools.com/guides/what-is-base64-encoding), [What is a CSV file and how to open one safely](https://localfirsttools.com/guides/what-is-a-csv-file). ## Comparisons Factual comparisons at https://localfirsttools.com/compare, including [vs JSONFormatter.org](https://localfirsttools.com/compare/jsonformatter-org), [vs jwt.io](https://localfirsttools.com/compare/jwt-io), [vs Code Beautify](https://localfirsttools.com/compare/code-beautify), [vs TinyWow](https://localfirsttools.com/compare/tinywow), [Best free JSON formatter](https://localfirsttools.com/compare/best-free-json-formatter), [Browser vs server tools](https://localfirsttools.com/compare/browser-vs-server-json-tools), [vs IT-Tools](https://localfirsttools.com/compare/it-tools), [vs CyberChef](https://localfirsttools.com/compare/cyberchef), [vs DevToys](https://localfirsttools.com/compare/devtoys), [vs transform.tools](https://localfirsttools.com/compare/transform-tools). ## Measurements Citeable first-party figures, measured 2026-10-14 from a 26-document corpus in scripts/fixtures, at https://localfirsttools.com/stats. - JSON minify median saving: 27.1% (min 18.5%, max 54.3%) versus 2-space pretty-print. - Base64 theoretical overhead: +33.3% (4 characters per 3 bytes); corpus median +33.5%. - UUID v4: 122 random bits (5.31 × 10^36 values). UUID v7: 48-bit timestamp + 74 random bits per ms (1.88 × 10^22 values per ms). - Hash hex lengths: SHA-256 64, SHA-512 128, SHA-384 96, SHA-1 40, MD5 32. Methodology is on that page. The numbers are generated by astro/scripts/compute-stats.mjs; they are not estimates. ## Directory listings LocalFirstTools is listed on [SaaSHub](https://www.saashub.com/localfirsttools) and [AZN8](https://azn8.com/listing/localfirsttools). Full list: https://localfirsttools.com/featured ## Contact Maker and contact: https://localfirsttools.com/about and https://localfirsttools.com/contact. Tool input is never sent to LocalFirstTools. Full sitemap: https://localfirsttools.com/sitemap.xml # Full tool pages The unique intro, about/how copy, limits and FAQs from each tool page. ## JSON Formatter, Validator & Pretty Print https://localfirsttools.com/json-formatter Free JSON formatter: pretty print with 2 or 4 spaces or tabs, beautify and validate JSON with the exact error line and column, in your browser. Paste JSON to pretty-print it with 2 spaces, 4 spaces or tabs, minify it, or sort its keys. Validation uses your browser's own JSON.parse (RFC 8259): invalid input shows the first error's exact line and column, valid input shows the key count and byte size. You can also drop in a file. Nothing leaves this tab. ### What the JSON Formatter does A JSON formatter pretty-prints compact or minified JSON, minifies it back, and reports syntax errors with a line and column. Use it for API payloads, config files and copy-paste blobs that need to be readable. Some people call the same thing a JSON beautifier, or say they want to prettify JSON; whatever the name, it is one operation: parse, then re-serialize with indentation. ### How the JSON Formatter works Paste JSON, drop a .json file, or load the sample. Format rewrites with the indent you pick: 2 spaces (default), 4 spaces or tabs. Minify removes whitespace. Sort keys orders object keys alphabetically. The status bar shows the key count and UTF-8 byte size when the document is valid, or the exact line and column when it is not. Validation uses your browser's JSON.parse, no network request. ### FAQ Q: Does the JSON formatter upload my data? A: No. Parsing, formatting and validation run in this tab with the browser's JSON.parse. LocalFirstTools has no upload endpoint; the text you paste is never sent. Q: How do I fix an Unexpected token error? A: The status bar points at the line and column of the first syntax problem, a trailing comma, a missing quote, or a comment. JSON does not allow comments or trailing commas; remove them and format again. Q: What is the difference between format and minify? A: Format (pretty-print) adds indentation and line breaks so people can read the document. Minify emits a single compact line, which is smaller to copy into logs or requests. Q: Can I format JSON with comments? A: Standard JSON cannot contain comments. If your file uses // or /* */ it is JSONC, not JSON. Strip comments first, then format. Q: How do I pretty print JSON? A: Paste the JSON into the input pane and press Format (or Cmd/Ctrl + Enter). Pick 2 spaces, 4 spaces or tabs in the Indent selector first; 2 spaces is the default and matches what jq and Prettier emit. Q: What is the difference between a JSON formatter and a JSON beautifier? A: None. Formatter, beautifier, prettifier and pretty-printer are different names for the same operation: parse the text, then write it back with indentation and one key per line. The values are untouched. Q: Is this JSON formatter really in-browser? A: Yes. Open your browser's developer tools, switch to the Network tab and press Format: no request carries the text. Parsing, formatting and validation are plain JavaScript running in this tab. ## JSON Minifier https://localfirsttools.com/json-minifier Minify JSON to one compact line and see bytes in, bytes out and percent saved, a median 27.1% on our sample corpus. Local only, nothing is uploaded. Paste JSON to strip every space, tab and line break RFC 8259 treats as insignificant, then see bytes in, bytes out and the percent saved. On a 26-document sample corpus the median saving was 27.1%. Pretty-print reverses it with two-space indent. Invalid input shows the first error's line and column. Nothing leaves this tab. ### What the JSON Minifier does A JSON minifier strips every space, tab and line break that JSON does not need, producing the smallest equivalent document. Use it before embedding JSON in a query string, a log line, an environment variable or an HTTP request body. ### How the JSON Minifier works Paste JSON and press Minify. The text is parsed with your browser's JSON.parse, then re-serialized without whitespace. The status bar shows input size, output size and the percentage saved. Pretty-print reverses it with two-space indentation. ### FAQ Q: Does minifying change my data? A: No. Only insignificant whitespace between tokens is removed. Strings, numbers, key order and structure stay exactly the same, so the minified document parses to the same value. Q: How much smaller does JSON get? A: It depends on how it was formatted. Pretty-printed JSON with deep nesting often shrinks 20 to 40 percent; already-compact JSON barely changes. Q: Why does my JSON fail to minify? A: The input must be valid JSON first. The error message points at the line and column of the first problem, commonly a trailing comma, a comment, or single quotes instead of double quotes. Q: Is this the same as compressing with gzip? A: No. Minifying removes whitespace but the result is still plain JSON any parser can read. Gzip is binary compression applied on top, servers usually do that automatically. ## JSON ↔ YAML Converter https://localfirsttools.com/json-yaml Convert YAML to JSON and back for Kubernetes manifests, CI configs and docker-compose files. Anchors resolve, comments drop, nothing is uploaded. Paste JSON to get 2-space YAML, or YAML to get pretty-printed JSON. Anchors resolve into copies; comments drop going to JSON because JSON has no comment syntax. YAML 1.2 is a superset of JSON, so valid JSON already parses as YAML. Multi-document files with --- separators must be split first. Conversion stays in this tab. ### What the JSON ↔ YAML converter does JSON and YAML describe the same data structures (objects, arrays, strings, numbers, booleans and null) with different syntax. This converter turns one into the other, which is handy for Kubernetes manifests, GitHub Actions workflows, docker-compose files and OpenAPI specs. ### How the JSON ↔ YAML converter works Paste into either pane and press the matching button. JSON → YAML parses with JSON.parse and serializes with the yaml library at two-space indentation. YAML → JSON parses the YAML document and pretty-prints it as JSON. Both directions run entirely in this tab. ### Limits Single-document YAML only. Split files that use --- separators before converting. ### FAQ Q: Is every JSON document valid YAML? A: Yes, YAML 1.2 is a superset of JSON, so valid JSON already parses as YAML. The reverse is not true: YAML features like anchors, multi-line strings and comments have no direct JSON equivalent. Q: What happens to YAML comments when converting to JSON? A: They are dropped. JSON has no comment syntax, so comments cannot survive the round trip. Anchors and aliases are resolved into the values they reference. Q: Why did my unquoted value change type? A: YAML guesses types for unquoted scalars: yes, no, on and off can become booleans, and 007 becomes a number. Quote the value in YAML if it must stay a string. Q: Can I convert multi-document YAML? A: This tool converts a single document. If your file contains --- separators, split it and convert each document on its own. ## JSON ↔ CSV Converter https://localfirsttools.com/json-csv Turn a JSON array of objects into CSV rows and CSV back into JSON. Auto-detects comma, semicolon and tab delimiters per RFC 4180, nothing uploaded. Paste a JSON array of objects to get RFC 4180 CSV, or CSV to get JSON objects. The header row becomes the keys; comma, semicolon and tab delimiters auto-detect. Nested values are stored as JSON strings in a cell. Optional type coercion keeps leading-zero values as strings. Nothing is uploaded. ### What the JSON ↔ CSV converter does This converter turns a JSON array of objects into CSV rows you can open in Excel or Google Sheets, and turns CSV back into JSON for APIs and scripts. The header row becomes the object keys, and each following row becomes one object. ### How the JSON ↔ CSV converter works JSON → CSV collects the keys of every object as columns, quotes fields that contain the delimiter, quotes or line breaks, and joins rows with the delimiter you pick. CSV → JSON auto-detects comma, semicolon or tab, honors quoted fields per RFC 4180, and can detect numbers, booleans and null. Everything runs in this tab. ### Limits Nested objects or arrays inside a cell are stored as a JSON string in that cell. ### FAQ Q: What JSON shape can become CSV? A: An array of objects, where each object is one row. A single object becomes a one-row CSV. Nested objects or arrays inside a cell are stored as a JSON string in that cell. Q: My CSV uses semicolons, does that work? A: Yes. When reading CSV the delimiter is detected from the header row, so semicolon-separated files from Dutch or German Excel installs convert without changes. For JSON → CSV you pick the delimiter yourself. Q: What does Detect types do? A: With it on, cells that look like numbers, true, false or null become those JSON types. With it off, every cell stays a string, safer for postal codes, phone numbers and IDs with leading zeros. Q: How are commas and quotes inside a value handled? A: Per RFC 4180: the field is wrapped in double quotes and any quote inside it is doubled. Reading does the reverse, so values round-trip unchanged. Q: Can I just look at the CSV without converting it? A: Yes. Open it in the CSV Viewer to sort and search the rows in a table, then convert to JSON from there if you need objects. ## CSV Viewer https://localfirsttools.com/csv-viewer Free CSV viewer online: paste or drop a .csv or .tsv, auto-detect delimiter and header, sort and search. CRM and payroll files stay in your browser; nothing is uploaded. Paste or drop a .csv or .tsv to open it as a table in this tab. Comma, semicolon, tab and pipe delimiters and the header row are detected automatically. Sort any column and search every cell; large files stay smooth. CRM, payroll and customer lists never leave this device. ### What the CSV Viewer does A CSV viewer opens a comma, semicolon, tab or pipe-separated file as a table you can sort and search. Use it for CRM exports, payroll spreadsheets, customer lists and any .csv or .tsv you would rather not upload. Quoted fields, UTF-8 BOM and large files (100,000+ rows) are handled in this tab. ### How the CSV Viewer works Paste CSV or drop a .csv/.tsv file. The first line is scanned for comma, semicolon, tab or pipe (quotes are ignored so a field can contain the delimiter). The first row is treated as a header when it looks like names rather than data; you can override that. The table virtualizes rows so only the visible window is rendered. Sort compares numbers when both cells are numeric, otherwise locale text. Search filters every cell. Convert this CSV to JSON hands the same text to the JSON ↔ CSV tool via sessionStorage in this tab. ### Limits Files up to 50 MB stay in this tab's memory. The table virtualizes rows; sorting and search still scan every loaded row. ### FAQ Q: Does this upload my CSV? A: No. Parsing, sorting and filtering run in this tab. CRM exports, payroll files and customer lists never leave the device. Q: How large a CSV can I open? A: Files up to 50 MB, read with the browser File API into this tab. The table virtualizes rows, so 100,000-row CRM exports scroll without freezing. Sorting and search still scan every loaded row. There is no upload, so the limit is your device's memory, not a server quota. Q: How do I open a CSV file online without uploading it? A: Drop the .csv or .tsv onto this page, or paste the text. The file is read with the browser File API and never sent to a server. Q: Which delimiters are detected? A: Comma, semicolon (European Excel), tab (.tsv) and pipe. The first line is counted outside quoted fields. You can override Auto in the toolbar. Q: Are quoted fields and a UTF-8 BOM handled? A: Yes. RFC 4180 quoting is honoured (commas, quotes and line breaks inside a field), and a leading UTF-8 BOM is stripped before parsing. Q: How do I turn this CSV into JSON? A: Press Convert this CSV to JSON. The text is stored in sessionStorage for this tab and picked up by the JSON ↔ CSV converter, then cleared. ## JWT Decoder https://localfirsttools.com/jwt-decoder Decode a JWT's header and payload (exp, iat, sub and custom claims) without verifying the signature. The token never leaves your browser tab. Paste a JSON Web Token to see its header and payload as formatted JSON, including claims like exp, iat, sub and alg. Decoding runs in this tab and needs no key, because a signed JWT is encoded, not encrypted. This tool does not verify the signature, so never use it to decide whether a token is trusted. ### What the JWT Decoder does A JWT decoder splits a compact token into header, payload and signature so you can inspect claims such as sub, exp and alg. It does not verify signatures and should not be used as authentication. ### How the JWT Decoder works The token is split on dots. Each of the first two segments is Base64URL-decoded in this browser, then pretty-printed if the result is JSON. The signature is shown as-is and never sent anywhere. ### Limits This tool does not verify signatures and has no field for key material. ### FAQ Q: Is decoding a JWT the same as verifying it? A: No. Decoding Base64URL-decodes the header and payload so you can read claims such as sub, exp and alg. Verification needs the issuer's secret or public key and a cryptographic check. This tool only decodes; it never verifies the signature and should not be used as authentication. Q: Is it safe to paste production tokens? A: The token never leaves this device through LocalFirstTools. Still treat live tokens as secrets: do not screenshot them, and prefer expired or dummy tokens when you can. Q: Why is alg none a problem? A: Some libraries historically accepted unsigned tokens when alg was none. This decoder will still show the payload; it will not tell you the token is trustworthy. Q: What if decoding fails? A: The segments may not be valid Base64URL, or the token may be truncated. Paste the full three-part string, including the signature segment. ## Base64 Encode / Decode: Text, Images & Files https://localfirsttools.com/base64 Base64 decode and encode text in your browser, or turn an image or PDF to Base64 and back, with Base64URL and hex output. Nothing is uploaded. Encode text to Base64 or decode it back, with UTF-8 support, URL-safe Base64URL, 76-character MIME wrapping and a hex view. In file mode, turn an image, PDF or any file up to 50 MB into Base64 or a data: URL, and turn it back into a downloadable file. Everything runs locally; nothing is uploaded. ### What the Base64 encoder/decoder does Base64 (RFC 4648) writes any sequence of bytes as text using 64 ASCII characters: 4 output characters for every 3 input bytes. This tool has two modes. Text mode encodes UTF-8 text to Base64 and decodes Base64 back to text, with a URL-safe toggle for the Base64URL alphabet that JWTs use, a 76-character line wrap for MIME, and a hex view for bytes that are not text. File mode turns an image, a PDF or any other file into Base64 or a data: URL, and decodes Base64 back into a file you can preview and download. Files up to 50 MB stay in this tab; nothing is uploaded. ### How the Base64 encoder/decoder works Text mode: type on the left and press Encode, or paste Base64 on the right and press Decode. Encoding converts the text to UTF-8 bytes with TextEncoder before Base64, so accents and emoji survive the round trip. URL-safe swaps + and / for - and _ and drops the = padding. Wrap 76 inserts a line break every 76 characters, the MIME limit from RFC 2045. Hex shows the decoded bytes as two hex digits each, and switches on by itself when the bytes are not valid UTF-8. File mode: drop or choose a file to get its Base64, with an optional data:;base64, prefix, or paste Base64 or a data: URL to get the file back. The file type comes from the data: URL or from the first bytes (PNG, JPEG, GIF, WebP, PDF and a dozen others), images and PDFs get a preview, and Download saves the file with the matching extension. Files are read with the browser's File API; no request carries them anywhere. ### Limits Files up to 50 MB. Outputs above 200,000 characters are previewed in the box and copied or downloaded in full. ### FAQ Q: Can I use Base64 offline? A: Yes, once this page has finished loading. Encoding and decoding run as JavaScript in this tab, so disconnecting still works. Reloading or opening another tool may need a connection. The site does not install a service worker. Q: Does this handle UTF-8? A: Yes. Plain btoa fails on emoji and non-Latin text with a “characters outside of the Latin1 range” error; this tool encodes the UTF-8 bytes first, so é becomes the two bytes C3 A9 and the round trip is exact. Q: How do I convert an image to Base64? A: Switch to File mode and drop the image (PNG, JPEG, GIF, WebP, SVG or anything else) or click to choose it. The Base64 appears on the right. Turn on Data URL to add the data:image/png;base64, prefix that and CSS url() expect. Base64 is 4 characters for every 3 bytes, so a 30,000-byte PNG becomes 40,000 characters (+33.3%), plus 22 characters for the PNG data: prefix. Q: How do I decode Base64 to an image or PDF? A: In File mode pick Base64 → File, paste the Base64 or the whole data: URL and press Decode. The type is read from the data: URL prefix or from the first bytes: 89 50 4E 47 is PNG, FF D8 FF is JPEG, 47 49 46 38 is GIF, 25 50 44 46 (%PDF) is PDF. Images and PDFs get a preview, and Download saves the file with the matching extension. Q: What is Base64URL and when do I need it? A: Base64URL (RFC 4648 §5) is Base64 with the two characters that mean something inside URLs replaced: - instead of + and _ instead of /, with the = padding usually dropped. Use it wherever the string travels in a URL, a file name or a JWT; all three JWT segments are Base64URL. The URL-safe toggle switches both encoding and decoding to it. Q: Why does decoding show hex instead of text? A: The Base64 decoded to bytes that are not valid UTF-8, so they are not text: an image, a compressed payload, a key, a hash. Rendering them as text would produce garbage, so the tool shows the bytes as hex, two digits per byte, and says so. To save those bytes as a file, use File mode. Q: Can I decode JWT segments here? A: Yes, with URL-safe on: JWT segments use the Base64URL alphabet without padding. The JWT decoder shows all three segments as JSON in one step, which is faster for whole tokens. Q: Is whitespace allowed? A: Yes. Decode strips spaces and line breaks before parsing, so Base64 wrapped at 76 characters from an email or a PEM file decodes as is. Q: Is there a file size limit? A: 50 MB. The file and its Base64 are both held in memory in this tab, and the Base64 alone is a third larger than the file, so the cap keeps the page responsive. Outputs above 200,000 characters are previewed in the box and copied or downloaded in full. Q: Is my text or file uploaded? A: No. Text is encoded with JavaScript in this tab, and a file is read with the browser's File API into the tab's memory and encoded there. The network tab in your browser's dev tools shows no request carrying either. ## URL Encode / Decode https://localfirsttools.com/url-encode Percent-encode text for query strings and decode %XX sequences back, é becomes %C3%A9, a space becomes %20. Runs locally, nothing is ever uploaded. Paste text to percent-encode it for query strings and paths, or paste a %XX string to decode it. Encoding uses encodeURIComponent: a space becomes %20 and é becomes %C3%A9. Unreserved characters (letters, digits and - _ . ~) pass through unchanged. The string is encoded in this tab and never sent anywhere. ### What the URL encoder/decoder does URL encoding (percent-encoding) replaces reserved characters so they can travel safely in a query string or path. This tool wraps encodeURIComponent and decodeURIComponent. ### How the URL encoder/decoder works Encode maps characters such as spaces and ampersands to % sequences. Decode reverses them. Both functions run locally. ### FAQ Q: Encode vs encodeURI? A: This tool uses encodeURIComponent, which also encodes ?, &, and =. That is the right choice for query values, not for a full URL you want to keep as a URL. Q: Why did decode fail? A: A lone % or a truncated escape sequence is invalid. Check the string for incomplete %HH groups. Q: Does plus become space? A: Not here. application/x-www-form-urlencoded treats + as space; this tool follows JavaScript's decodeURIComponent, which does not. Q: Is input uploaded? A: No. ## UUID Generator (v4 & v7): GUID Generator https://localfirsttools.com/uuid-generator Free UUID generator for random v4 and time-ordered v7 UUIDs, up to 1000 at once, with GUID formatting (uppercase, braces), in your browser. Nothing is uploaded. Generate random UUID v4 or time-ordered UUID v7 identifiers, 1 to 1000 at once, as lowercase, uppercase or {braced} GUIDs. v4 uses crypto.randomUUID() (122 random bits); v7 (RFC 9562) leads with a 48-bit millisecond timestamp plus 74 random bits. IDs are minted on your device, never by a server. ### What the UUID Generator does A UUID (called a GUID on Windows and in .NET) is a 128-bit identifier written as 32 hex digits in an 8-4-4-4-12 pattern. Version 4 fills 122 of those bits with random data. Version 7 (RFC 9562) leads with a 48-bit millisecond timestamp and follows it with 74 random bits, so v7 IDs sort by creation time. Use v4 for request IDs, idempotency keys and anything that must not reveal when it was made; use v7 for database primary keys, where inserts in time order keep B-tree indexes compact. ### How the UUID Generator works Pick v4 or v7, set a count from 1 to 1000 and press Generate. v4 IDs come from crypto.randomUUID(). v7 IDs are assembled locally from Date.now(), a 12-bit counter that keeps same-millisecond IDs in generation order, and 62 bits from crypto.getRandomValues(). The formatting toggles (uppercase, no hyphens, {braces} for GUID-style .NET and registry values, quoted and comma-separated for SQL or JSON arrays) re-render the batch already on screen without regenerating it. Copy takes every line; .txt saves them as a file built in this tab. Nothing is requested from a server. ### Limits Up to 1,000 IDs per click. ### FAQ Q: What is the difference between a UUID and a GUID? A: None in the bits: both are the same 128-bit format from RFC 9562 (formerly RFC 4122). GUID is Microsoft's name, used by Windows, .NET and SQL Server, where the value is usually shown in uppercase and wrapped in braces: {3F2504E0-4F89-41D3-9A0C-0305E82C3301}. Turn on Uppercase and {Braces} to get that spelling. Q: Should I use v4 or v7? A: v7 for database primary keys: its 48-bit timestamp prefix means new rows land at the end of a B-tree index instead of on random pages, so inserts stay cheap and ORDER BY id approximates creation order. v4 for anything where the creation time must not leak (public tokens, share links, password-reset codes) or where ordering buys nothing, such as request and trace IDs. Q: How many can I generate at once? A: Up to 1000 per click. Set the count, press Generate, then Copy all or save a .txt. A v7 batch is monotonic: sorted output equals generation order, even for IDs minted in the same millisecond. Q: Are these cryptographically random? A: Yes. Both versions draw their random bits from the browser's Web Crypto CSPRNG: crypto.randomUUID() for v4, crypto.getRandomValues() for v7. That makes them unpredictable enough for identifiers, but a UUID is not a secret (v7 even prints its creation time in the first 12 hex digits), so do not use one as a password or session token. Q: Is anything sent to a server? A: No. Generation, formatting, copying and the .txt download all happen in this tab. The page makes no request that carries the IDs; your browser's network panel shows none. ## Hash Generator: SHA-256, SHA-512, SHA-1, MD5 https://localfirsttools.com/hash-generator SHA256 hash generator and file checksum tool: SHA-512, SHA-1, MD5 and HMAC, computed in your browser. Files are hashed locally and never uploaded. Hash text or a file with SHA-256, SHA-512, SHA-384, SHA-1 and MD5 at once, as hex or Base64, and verify a published checksum. SHA family uses the browser's Web Crypto API; MD5 uses js-md5. Files up to 500 MB are read in this tab. HMAC-SHA-256 and HMAC-SHA-512 keep the key local. Nothing is transmitted. ### What the Hash Generator does A cryptographic hash maps any input (a string, a config file, a 4 GB ISO) to a fixed-length digest that changes completely when a single bit changes. This hash generator computes SHA-256, SHA-512, SHA-384, SHA-1 and MD5 of typed text or of a dropped file, shows each digest as hex or Base64, checks a pasted checksum against the result, and computes HMAC-SHA-256 and HMAC-SHA-512 for keyed authentication. SHA-256 is the default for new integrity checks and signatures; SHA-1 and MD5 are included for matching checksums that older systems still publish. ### How the Hash Generator works Text is encoded as UTF-8; a file is read into memory with the browser's File API. Either way the bytes go to crypto.subtle.digest for the SHA family and to the js-md5 library for MD5, which Web Crypto does not implement. All five digests are computed at once and shown as lowercase hex, uppercase hex or Base64: SHA-256 is always 64 hex characters, SHA-512 128, SHA-384 96, SHA-1 40, MD5 32. Paste a published checksum into Verify: the algorithm is detected from its length, case and surrounding sha256sum text are ignored, and the matching row is marked Match or Mismatch. HMAC imports your key with crypto.subtle.importKey and signs the message locally. There is no upload endpoint; the 500 MB file limit exists only because the whole file is held in memory while hashing. ### Limits Files up to 500 MB, because the whole file is held in memory while hashing. ### FAQ Q: How do I check a file's SHA-256 checksum? A: Switch to File, drop the download or click to choose it, then paste the hash from the publisher's page into Verify checksum. The tool detects SHA-256 from the 64-character length and marks the SHA-256 row Match or Mismatch. A match means the bytes on your disk are exactly the bytes the publisher hashed. A mismatch usually means an incomplete download or a different release, and occasionally tampering, so download again from the original source before installing. Q: Is my file uploaded? A: No. The file is read with the browser's File API into this tab's memory and hashed there with Web Crypto. LocalFirstTools has no upload endpoint and the page makes no network request while hashing; open your browser's network tab and drop a file to confirm. The 500 MB limit is a memory budget, not a transfer limit. Q: Can I decrypt a SHA-256 hash? A: No. Hashing is one-way: the digest is 256 bits while the input can be any length, so countless inputs share each digest and nothing in it says which one you started from. Sites that claim to decrypt or reverse SHA-256 keep lookup tables of digests they already computed for common strings and leaked passwords. They only find inputs that were guessable to begin with. Q: Which hash should I use? A: SHA-256 for anything that matters: integrity checks, signatures, content addressing, cache keys an attacker could influence. SHA-512 is the same design with 64-bit words and a 128-character digest, often faster on 64-bit CPUs; SHA-384 is SHA-512 truncated. SHA-1 and MD5 are collision-broken (an attacker can craft two inputs with the same digest), so use them only to match checksums a legacy system already publishes. Q: What is HMAC? A: HMAC (RFC 2104) is a hash keyed with a secret: HMAC-SHA-256 mixes the key into the message and hashes with SHA-256. Anyone holding the key can recompute it; nobody without the key can forge it. That is how webhook signatures, API request signing and HS256 JWTs authenticate a message. Enter the key and message in the HMAC section; the key is imported by Web Crypto in this tab and never leaves it. Q: Why doesn't my hash match the published one? A: Count the characters first: 32 hex is MD5, 40 SHA-1, 64 SHA-256, 96 SHA-384, 128 SHA-512, and Verify tells you which algorithm it compared. If the algorithm is right, the bytes differ. Pasted text often carries a trailing newline, CRLF line endings or a UTF-8 BOM, any of which changes every character of the digest; for a file, hash the file itself in File mode instead of pasting its contents. Q: Are MD5 and SHA-1 safe? A: Fine for checksums and cache keys, not for security. Both are broken for collision resistance, so an attacker can craft two inputs with the same digest. Use SHA-256 when integrity matters against an adversary. Q: Is this a password hasher? A: No. Password storage needs a slow, salted KDF such as Argon2, scrypt or bcrypt, not a single SHA-256 of the password. A fast hash lets attackers test billions of guesses per second. ## Case Converter https://localfirsttools.com/case-converter Convert identifiers between camelCase, PascalCase, snake_case, kebab-case and CONSTANT_CASE, live as you type, all five at once, all in your tab. Type an identifier or phrase to see camelCase, PascalCase, snake_case, kebab-case and CONSTANT_CASE at once, live as you type. The converter splits on spaces, hyphens, underscores and camelCase humps. All-caps acronym runs such as APIKey are not split without a separator. Use it when renaming a field across JavaScript, Python, CSS or SQL. Everything runs in this tab. ### What the Case Converter does Case conversion rewrites identifiers between styles used in JavaScript, Python, CSS and constants. Useful when renaming a field across languages. ### How the Case Converter works The input is split on spaces, dashes, underscores and camel humps, then rejoined in the target convention. All of that is string processing in this tab. ### FAQ Q: Will it preserve acronyms? A: Simple conversions treat letters uniformly. HTTPResponse may become http_response, which is usually what you want in snake_case. Q: Does it change meaning? A: No. Only separators and capitalization change. Q: Can I convert a whole file? A: Paste a phrase or identifier. This is not a source-code refactor tool. Q: Is text uploaded? A: No. ## Unix Timestamp Converter & Epoch Converter https://localfirsttools.com/timestamp Unix timestamp converter and epoch converter for seconds, milliseconds, microseconds and nanoseconds, with the current Unix time, in your browser. Paste a Unix timestamp or an ISO date to convert between epoch time and a human date. Units are detected by magnitude: seconds below 10¹², milliseconds below 10¹⁵, microseconds below 10¹⁸, nanoseconds above. Outputs include ISO 8601 UTC, local time and the current Unix time. Negative values (dates before 1970) are supported. Nothing leaves this tab. ### What the Unix Timestamp Converter does Unix time, also called epoch time, counts seconds since 1970-01-01T00:00:00Z. JavaScript uses milliseconds, and databases and tracing systems often store microseconds or nanoseconds. This epoch converter reads any of the four, detecting the unit from the size of the number, and shows the same instant as ISO 8601 UTC, local time in your browser's timezone, a relative phrase such as 3 hours ago, and both Unix seconds and milliseconds. It converts the other way too: paste a date and get its epoch value. The block at the top shows the current Unix timestamp, ticking once a second. ### How the Unix Timestamp Converter works Paste a number. Below 10¹² it is read as seconds, below 10¹⁵ as milliseconds, below 10¹⁸ as microseconds, and anything larger as nanoseconds; the footer names the unit it picked, and negative values are dates before 1970. Paste a date instead (ISO 8601, or any form your browser's Date constructor accepts) and the seconds and milliseconds rows become the answer. Every row has a copy button. Use now fills in the current second, Use now (ms) the current millisecond. Everything runs on your browser's Date and Intl APIs; no request is made. ### FAQ Q: What is the difference between Unix time and epoch time? A: None, they are two names for the same count. The epoch is the instant 1970-01-01T00:00:00Z, and Unix time (also epoch time or POSIX time) is the number of seconds elapsed since it, ignoring leap seconds. Epoch converter and Unix timestamp converter describe this same tool. Q: How do I convert epoch to a date? A: Paste the epoch value in the input. The ISO 8601 row is the date in UTC; the Local time row is the same instant in your browser's timezone with its UTC offset. In code: new Date(seconds * 1000) in JavaScript, datetime.fromtimestamp(seconds, timezone.utc) in Python, to_timestamp(seconds) in PostgreSQL. Q: What is the current Unix timestamp? A: The block at the top shows it live, in seconds and in milliseconds, read from your device clock with Date.now() once a second, so it is exactly as accurate as your clock. Copy takes the value shown at that moment; Use now moves it into the input. Q: Seconds, milliseconds, microseconds or nanoseconds? A: The tool decides by magnitude: values below 10¹² are seconds, below 10¹⁵ milliseconds, below 10¹⁸ microseconds, and anything larger nanoseconds. For any date between 2001-09-09 and 2286-11-20 that means 10, 13, 16 and 19 digits respectively, and the footer tells you which unit was detected. Q: Why does my timestamp show the year 56 000? A: Something read a milliseconds value as seconds: 1,725,105,600,000 taken as seconds is about 54,666 years after 1970, so the year 56636. The mirror-image bug (seconds read as milliseconds) lands in January 1970. This tool detects the unit from the magnitude and prints both seconds and milliseconds so you can hand the right one onward. Q: Does the year 2038 problem affect this? A: No. The 2038 problem is a signed 32-bit integer overflowing at 2,147,483,647 seconds, which is 2038-01-19T03:14:07Z. JavaScript stores dates as 64-bit doubles with a range of ±8.64 × 10¹⁵ milliseconds, so this tool converts anything from the year −271821 to 275760. Q: Which timezone is the output in? A: The ISO 8601 row is always UTC (the Z suffix), which keeps logs comparable. The Local time row uses the IANA zone your browser reports (Europe/Amsterdam, America/New_York) with the UTC offset in force at that instant, so DST is accounted for. A date you paste without a zone suffix is read in local time; append Z for UTC. Q: Is anything uploaded? A: No. Parsing uses Date and formatting uses Intl, both built into your browser; the live clock reads Date.now(). LocalFirstTools has no endpoint that receives the value you paste. ## Regex Tester https://localfirsttools.com/regex-tester Test JavaScript regular expressions with live highlighting, flags and capture groups, capped at 1,000 matches. Patterns and text stay in your tab. Paste a JavaScript regular expression and a test string to see live highlighted matches and capture groups, including named groups. The pattern runs in your browser's own RegExp engine, not PCRE. Output is capped at 1,000 matches with a zero-length-match guard. The pattern and text never leave this tab. ### What the Regex Tester does A regular expression describes a text pattern: character classes, quantifiers, anchors, alternation and capture groups. This tester runs your pattern against a test string as you type, highlights every match and lists capture groups, including named groups like (?d{4}). ### How the Regex Tester works The pattern and flags are passed to JavaScript's own RegExp constructor and executed against the test string with exec. With the g or y flag it collects every match (guarding against zero-length-match loops); without them it stops at the first. Matching runs entirely in this tab. ### Limits Output is capped at 1,000 matches, with a zero-length-match guard. This is JavaScript regex, not PCRE. ### FAQ Q: Which regex dialect is this? A: JavaScript (ECMAScript), exactly as your browser implements it. Lookbehind, named groups and the s and u flags all work in modern browsers, but PCRE-only syntax like atomic groups or \K will not. Q: What do the flags mean? A: g finds all matches instead of the first, i ignores case, m makes ^ and $ match at line breaks, s lets . match newlines, u enables full Unicode mode, and y anchors each match at the exact position where the previous one ended. Q: Why does my pattern match an empty string over and over? A: Patterns like a* can match zero characters. With the g flag this tool advances one position after every empty match, so it terminates instead of looping forever, and caps the list at 1000 matches. Q: Do I need to escape backslashes? A: No. Type the pattern as you would between / / in JavaScript source, \d, \w, \b and friends work directly. You only need double backslashes when embedding the pattern in a JavaScript string literal. ## Diff Checker: Compare Two Texts https://localfirsttools.com/text-diff Compare two texts by line, word or character with the same Myers-family algorithm git uses. The diff checker highlights every change in your browser. Paste two texts to compare them line by line, word by word or character by character. The Diff Checker uses the same Myers-family algorithm git uses and reports added and removed counts. Characters mode is capped at 200,000 characters per side. Both sides stay in this tab; nothing is uploaded. ### What the Diff Checker does A diff checker shows what changed between two versions of a text: config files, contracts, SQL dumps, API responses, a paragraph someone edited. Paste both versions, press Compare and every change is highlighted (removed parts in red, added parts in green) at the granularity you pick: whole lines, single words or single characters. In Lines mode a modified line also gets its changed words highlighted, so a one-token edit inside a long line stands out. ### How the Diff Checker works Both texts are compared with the diff library's Myers algorithm, the same family git uses. Lines mode runs diffLines, then pairs each removed block with the added block that directly follows it when both have the same line count and runs a word diff inside every pair. Words mode runs diffWords and Characters mode diffChars; Characters mode is capped at 200,000 characters per side and falls back to Words above that. The status bar reports added and removed counts and the share of characters unchanged: unchanged characters divided by the length of the longer text. Everything runs in this tab. ### Limits Characters mode is capped at 200,000 characters per side and falls back to Words above that. ### FAQ Q: How do I compare two texts? A: Paste the first version under Original and the second under Changed (or drop a file on either side) and press Compare (Ctrl or Cmd + Enter also works). Removed parts show in red, added parts in green. Pick Lines for code and config files, Words for prose, and Characters for short strings such as keys, IDs or URLs. Q: What is the difference between line, word and character diff? A: Lines treats each line as one unit, so a one-letter edit marks the whole line as removed plus added, with the changed word highlighted inside it. Words splits the text into words and punctuation, so only the edited words are marked and reflowed paragraphs still compare cleanly. Characters marks individual characters, which is exact but noisy on long texts; it is capped at 200,000 characters per side. Q: Can I compare code or JSON? A: Yes for code: Lines mode is the same class of diff git shows, and the ignore-whitespace toggle hides indentation-only changes. For JSON, a text diff reports reordered keys and reformatting as changes. Use the JSON Diff at localfirsttools.com/json-diff instead: it parses both documents and compares them structurally, so key order and whitespace produce no differences. Q: Does it ignore whitespace? A: Only when the toggle is on. In Lines mode it ignores leading and trailing whitespace on each line (indentation and trailing spaces) but not spacing inside a line. Words mode always ignores whitespace between words. Characters mode collapses runs of spaces and tabs to one space and trims line ends before comparing, and says so under the diff. Q: Is my text uploaded? A: No. The diff runs in this tab with the diff JavaScript library. LocalFirstTools has no server endpoint that receives the text you paste, and a dropped file is read with the browser's FileReader without leaving your device. ## Cron Expression Generator & Explainer https://localfirsttools.com/cron-explainer Cron expression generator and crontab explainer: build a schedule, validate it and see the next run times in your browser. Nothing is uploaded. Build a 5-field crontab expression with Every, Every N, Specific and Range pickers, or paste one to explain it. A valid expression shows a plain-English sentence and the next 5 run times in your local timezone; an invalid one shows cron-parser's exact error. Nothing is scheduled and nothing is sent anywhere. ### What the Cron explainer does Cron expressions schedule recurring jobs with five fields: minute, hour, day of month, month, day of week. This tool works in both directions. Build assembles a crontab expression from pickers: every value, every N, specific values or a range, per field. Explain & test takes an expression you paste, checks it with the cron-parser library, translates it into an English sentence and lists the next five times it would fire. ### How the Cron explainer works Both modes edit one expression, so switching never loses your text. In Build, each field has Every, Every N, Specific and Range pickers; the expression, the sentence and the next runs update as you click, and a field the builder cannot represent (like 1,5-10/2) is shown as Custom and left untouched. In Explain & test, paste any 5-field expression, or 6 with a leading seconds field: an invalid one shows the parser's exact error, a valid one shows Valid with its next run time. The sentence comes from cronstrue and the run times from cron-parser, which walks the calendar from now in your browser's timezone. Nothing is scheduled and nothing is sent anywhere. ### Limits 5-field crontab, or 6 with a leading seconds field. No @reboot and no Quartz year field. ### FAQ Q: How do I write a cron expression for every 5 minutes? A: */5 * * * *. The minute field steps through 0, 5, 10 … 55 while the other four fields stay *, so the job fires 12 times an hour and 288 times a day (1440 minutes ÷ 5). Press Every 5 min in the quick schedules row, or set the minute field to Every N = 5 in Build. Q: How do I validate a cron expression? A: Paste it under Explain & test. The expression is parsed with cron-parser: if it is invalid, the status bar shows the parser's exact message, for example “Constraint error, got value 60 expected range 0-59”; if it is valid, you see Valid plus the next run time. An expression with the wrong number of fields is rejected too, since crontab needs exactly 5. Q: Is this a crontab generator? A: Yes. Build produces the 5-field crontab format (minute, hour, day of month, month, day of week) that cron, Kubernetes CronJobs, GitHub Actions schedules and most other schedulers accept. Copy the expression and paste it in front of the command on your crontab line. Q: Which cron syntax is supported? A: Standard 5-field crontab syntax, plus 6 fields when the first is seconds. Ranges (1-5), steps (*/15), lists (1,15) and names (MON, JAN) all work. Non-standard extensions like @reboot are not supported. Q: Does it support Quartz or AWS 6- and 7-field expressions? A: Only partly. Explain & test reads 5 fields, or 6 when the first field is seconds, which covers Quartz-style seconds; L (last day) and # (nth weekday) work, ? is read as *, W is not supported. There is no year field: a 7-field Quartz expression is rejected as too many fields, and AWS EventBridge's 6-field format, which ends in a year, would be misread as seconds-first, so do not paste it. The builder is 5-field only. Q: What timezone are the next runs shown in? A: Your browser's local timezone. A real cron daemon fires in the server's timezone, so a job written for a UTC server can show different wall-clock times here. Q: What does it mean when both day-of-month and day-of-week are set? A: In classic cron the job runs when either field matches, 0 0 1 * 1 fires on the 1st of the month and on every Monday. Some schedulers (like Quartz) instead require one of the two to be *, so double-check your platform. Q: Is 0 0 * * 0 Sunday or Saturday? A: Sunday. Day-of-week runs 0–6 starting at Sunday, and most implementations also accept 7 for Sunday. ## HTML Entity Encode / Decode https://localfirsttools.com/html-entities Escape &, <, > and quotes for safe HTML markup and decode named or numeric entities back to text with DOMParser, entirely in your browser tab. Paste text to escape &, <, >, " and ' for HTML, or paste entities such as é and é to decode them. Encoding always covers those five characters and can convert every character above code point 127 to a hex reference. Decoding uses the browser's DOMParser and never executes markup. Nothing is uploaded. ### What the HTML entity encoder/decoder does Characters like <, > and & have special meaning in HTML, so literal text must be escaped as entities (<, >, &) before it is placed in markup. This tool escapes text for HTML and decodes entities (named like é or numeric like é) back into readable text. ### How the HTML entity encoder/decoder works Encoding always escapes the five HTML-critical characters (ampersand, angle brackets, double and single quotes), and, with the All non-ASCII toggle, also converts every character above code point 127 to a hexadecimal &#x…; reference. Decoding hands the string to your browser's own DOMParser and reads back the text content, so every entity the browser knows is handled. Both directions run in this tab. ### FAQ Q: When do I need the “All non-ASCII” toggle? A: When the output must survive a non-UTF-8 pipeline, legacy email templates, ISO-8859-1 pages, systems that mangle multibyte characters. On a modern UTF-8 page you only need the five critical characters escaped. Q: Why is the apostrophe encoded as ' instead of '? A: ' is not defined in HTML 4, so old parsers do not understand it. The numeric form ' works everywhere, which is why it is the safer default. Q: Is escaping enough to prevent XSS? A: Escaping these characters is the right move for text placed in element content or quoted attributes. Other contexts (URLs, inline scripts, CSS, unquoted attributes) need their own context-specific encoding, not HTML entities. Q: Does decoding execute any HTML? A: No. DOMParser parses the string into an inert document that is never attached to the page, scripts do not run and images do not load. Only the resulting plain text is read out. ## Slug Generator https://localfirsttools.com/slug-generator Turn any title into a URL-safe slug: accents stripped (café → cafe), & mapped to “and”, lowercase with hyphens, generated live in your browser. Type a title to get a URL-safe slug: lowercase letters, digits and hyphens. Accents are stripped (café becomes cafe), & maps to “and”, and any other run outside a–z and 0–9 collapses to one separator. Non-Latin scripts have no ASCII base form and are dropped. Generation is live in this tab. ### What the Slug Generator does A slug is the URL-safe version of a title: lowercase letters, digits and a separator, nothing else. Search engines and readers both prefer /10-tips-and-tricks over /10%20Tips%20%26%20Tricks!. This tool generates the slug live as you type. ### How the Slug Generator works The text is Unicode-normalized (NFKD) so accented letters split into base letter plus combining mark, the marks are stripped (é becomes e), symbols like & become words (and), everything is lowercased, and any run of characters outside a–z and 0–9 collapses into a single separator. An optional max length truncates at a word boundary. All in this tab. ### FAQ Q: What happens to accents and non-Latin characters? A: Accented Latin letters lose their accent: café becomes cafe, über becomes uber. Characters with no Latin base form (Chinese, Arabic, emoji) have no ASCII equivalent and are dropped, so check the result for non-Latin titles. Q: Hyphens or underscores? A: Hyphens. Google treats hyphens as word separators but treats underscores as word joiners, so ten-tips is indexed as two words while ten_tips is one token. Use underscores only when a system requires them. Q: How long should a slug be? A: There is no hard limit, but 3–6 meaningful words is typical. The max length option truncates at a word boundary so you never end mid-word. Q: Why did my & turn into “and”? A: Ampersands carry meaning readers expect to survive, so & maps to the word and (similarly @ to at). Punctuation with no word equivalent is simply removed. ## JSON Diff & Compare https://localfirsttools.com/json-diff JSON Diff & Compare two JSON files online. Ignores reordered keys and whitespace; only real changes listed as paths. Local, nothing uploaded. Paste two JSON documents to compare them structurally after parsing — a JSON Compare that treats reordered keys and whitespace as zero differences. Added, removed and changed paths are listed in dot notation with old and new values. Both documents stay in the browser tab; nothing is uploaded. ### What the JSON Diff & Compare does A structural JSON diff compares two documents after parsing them, so it reports what actually changed (added, removed and changed values) instead of every reordered key or reformatted line. Use it to compare API responses, config revisions or database exports where a text diff drowns you in noise. ### How the JSON Diff & Compare works Both panes are parsed with your browser's JSON.parse, then the two values are walked recursively. Object keys are matched by name, array items by index, and each difference is reported with its dot-notation path (like user.roles[1]) and the old and new value. Because the comparison happens on parsed values, key order, indentation and whitespace produce zero differences. Everything runs locally in this tab. ### FAQ Q: Why does this show no differences when the files look different? A: Because the documents are structurally identical. RFC 8259 defines a JSON object as an unordered collection of members, so two files with the same data in a different key order or with different indentation parse to the same value, which is exactly what this tool compares. Q: How are array changes reported? A: Array items are compared by position: item 0 against item 0, item 1 against item 1, and so on. Extra items at the end of one side show as added or removed. An item inserted at the front therefore shows as a change at every following index, the tool does not detect moves. Q: What does the path notation mean? A: Paths use dot notation for object keys and bracket indexes for arrays: user.roles[1] is the second element of the roles array inside user. Keys that are not valid identifiers are quoted in brackets, like ["content-type"]. Q: Is 1 different from 1.0 or "1"? A: 1 and 1.0 are equal, JSON.parse turns both into the same number. "1" (a string) is different from 1 (a number) and is reported as a change, because the type differs even though the text looks similar. Q: Is my JSON uploaded to compare it? A: No. Parsing and comparison run entirely in this tab. LocalFirstTools has no server endpoint that receives the documents you paste. ## JSON ↔ XML Converter https://localfirsttools.com/json-xml Convert JSON to XML and XML back to JSON, attributes included, and pretty-print XML, powered by fast-xml-parser running entirely in your tab. Convert JSON to XML and back, or pretty-print XML, entirely in this tab. Elements, attributes (the @_ prefix convention) and text nodes are mapped between the formats. XML→JSON→XML round trips can normalize markup. Use it for SOAP payloads, legacy APIs and config that still lives in XML. Neither document is sent over the network. ### What the JSON ↔ XML converter does This converter turns XML documents into JSON and JSON back into XML, useful for legacy SOAP responses, RSS feeds, sitemaps, Maven or Android config files, and any API that still speaks XML. A pretty-print action reindents messy XML without converting it. ### How the JSON ↔ XML converter works Conversion uses the fast-xml-parser library entirely in this tab. XML → JSON first validates the markup (so you get a real error with line and column), then parses elements into objects, repeated elements into arrays, and attributes into keys prefixed with @_. JSON → XML reverses the mapping: object keys become elements and @_-prefixed keys become attributes. Pretty-print reparses the XML preserving element order and comments, then rebuilds it with two-space indentation. ### FAQ Q: How are XML attributes represented in JSON? A: As object keys prefixed with @_. So becomes {"book": {"@_id": "bk101", …}}. Going the other way, any key starting with @_ is emitted as an attribute on its parent element. Q: Is XML → JSON → XML a lossless round trip? A: Not always. JSON has no concept of comments, processing instructions, CDATA sections or the XML declaration, so those are dropped. Text that looks numeric ('39.95') comes back as a JSON number, and a single child element cannot be told apart from a one-item list, so a round trip can normalize the document even when the data survives. Q: Why did my element order change? A: A JSON object groups repeated elements under one key, so siblings with different names that were interleaved in the XML are regrouped by name after a round trip. W3C XML 1.0 makes sibling order significant; JSON objects do not, which is the core mismatch between the two formats. Q: What JSON can become XML? A: An object whose keys are valid XML element names works best. If the root is an array or a primitive, the tool wraps it in a element (and says so in the status bar), because an XML document must have exactly one root element. Keys with spaces or other characters that are illegal in element names will produce invalid XML. Q: Is my data uploaded to convert it? A: No. Validation, parsing and building all run in this tab with the fast-xml-parser library. Nothing you paste leaves your device. ## XML Formatter, Validator & Pretty Print https://localfirsttools.com/xml-formatter Free XML formatter and validator: pretty-print, beautify or minify XML and get the exact error line and column. Runs in your browser; nothing is uploaded. Paste XML to pretty-print it with 2 spaces, 4 spaces or tabs, or minify it to one line. Well-formedness is checked first: an error shows its exact line and column. Comments, CDATA and the XML declaration are kept as written. External entities and DTDs are never fetched. Nothing leaves this tab. ### What the XML Formatter does An XML formatter pretty-prints compact or minified XML, minifies it back to one line, and reports the first well-formedness error with its line and column. Use it for SOAP payloads, SAML assertions, UBL/Peppol e-invoices, Android manifests and Maven POMs. Beautify XML, XML pretty print and format XML are the same operation: re-indent between tags without changing the data. ### How the XML Formatter works Paste XML, drop a .xml file, or load the sample. Format rewrites with the indent you pick: 2 spaces (default), 4 spaces or tabs. Minify removes whitespace between tags. Well-formedness is checked first with fast-xml-parser's validator; a failure shows the first error's exact line and column. Formatting is a tokenizer that only re-indents between tags, so text, attributes, comments, CDATA, processing instructions, the XML declaration and DOCTYPE stay as written. External entities and DTDs are never fetched. Nothing is uploaded. ### Limits Checks well-formedness only, not XSD/DTD schema validity. Files up to ~50 MB. ### FAQ Q: Does this upload my XML? A: No. Parsing and formatting run in this tab, and there's no endpoint that receives the text. You can verify it in DevTools → Network. Q: Is the XML validator a full XSD schema validator? A: No. It checks well-formedness: one root, matching tags, quoted attributes and escaped & < >. It does not apply an XSD or DTD, and it never fetches an external schema. Valid XML would also have to match a schema; that is a later check this page does not run. Q: How do I format (pretty-print) XML? A: Paste it, pick an indent and press Format (Ctrl/Cmd + Enter). This also covers "beautify XML" and "XML pretty print": all three are the same operation. Q: How do I validate XML and find the error? A: The status bar shows the first well-formedness error with line and column. Common causes: an unclosed tag, a raw & (use &), mismatched case, or more than one root element. Q: Does formatting change my data? A: Only whitespace between tags changes. Text content, attributes, comments and CDATA stay the same. Whitespace inside mixed content (`xml:space="preserve"`) is left alone. Q: Is it safe to paste SOAP, SAML or e-invoice XML? A: The document never leaves the device. External entities and DTDs are not resolved, so XXE-style payloads can't fetch anything. ## JSON → TypeScript https://localfirsttools.com/json-typescript Paste a JSON sample and get TypeScript interfaces: array shapes merge into unions, missing keys become optional, inferred locally in your browser. Paste a JSON sample to infer TypeScript interfaces. Differing types across array elements become unions; missing properties become optional. The input must be an object or an array of objects. Use it to sketch types from an API response before writing them by hand. Inference runs entirely in the browser; the sample is never uploaded. ### What the JSON → TypeScript generator does This generator turns a JSON sample (an API response, a config file, a fixture) into TypeScript interfaces you can paste straight into your code. Nested objects become their own named interfaces, and arrays of objects are merged into a single element type. ### How the JSON → TypeScript generator works The JSON is parsed with your browser's JSON.parse, then the json-to-ts library walks the value and emits one interface per distinct object shape, starting from the root name you choose. Every step runs locally in this tab; the sample never leaves your device. ### Limits Input must be an object or an array of objects. ### FAQ Q: How are arrays of objects typed? A: All elements of an array are merged into one interface. A property present in every element keeps its type, a property missing from some elements becomes optional (marked with ?), and the same property with different types across elements becomes a union like number | string. That is why a sessions array whose items differ produces an interface with optional members. Q: Why are some properties optional when my API always sends them? A: The generator only sees the sample you paste. If a property is absent from one array element in the sample, it is typed as optional. Feed it a sample where every element carries the property, or remove the ? by hand when you know the API guarantees it. Q: Why can't I generate types for a plain string or number? A: Interfaces describe object shapes. The underlying library accepts a JSON object or an array of objects; a bare string, number, boolean or mixed array has no members to describe, so the tool shows an error instead. For those, a plain type alias like type Root = string is all you need. Q: Is null handled? A: A property that is null in the sample is typed as null, because null is all the sample proves. If the field is really a nullable string, widen it to string | null yourself. Q: Does my JSON get uploaded? A: No. Parsing and interface generation run entirely in this tab | LocalFirstTools has no server that receives your sample, which matters when it comes from a production API response. ## JSONPath Tester https://localfirsttools.com/jsonpath-tester Evaluate JSONPath expressions like $.store.book[*].author against pasted JSON and inspect every match with its normalized path, nothing uploaded. Paste JSON and a JSONPath expression such as $.store.book[*].author to list every match with its normalized path. Evaluation uses jsonpath-plus in safe mode, with no code execution, and is capped at 500 matches. Use it to probe API payloads and config without writing a script. The document never leaves this tab. ### What the JSONPath Tester does JSONPath is a query language for JSON, standardized in RFC 9535: expressions like $.store.book[*].author select values out of a document the way XPath does for XML. This tester evaluates your expression live against pasted JSON and lists every match with its normalized path, handy for building jq-free extractions, API response assertions and log queries. ### How the JSONPath Tester works The JSON is parsed with your browser's JSON.parse and the expression is evaluated by the jsonpath-plus library, entirely in this tab. Filter expressions such as [?(@.price < 10)] run in the library's safe evaluation mode, which interprets the comparison itself instead of handing the text to JavaScript's eval, so a pasted expression cannot execute arbitrary code. Results update as you type. ### Limits 500-match cap. Safe mode: the expression cannot execute code. ### FAQ Q: Which JSONPath syntax is supported? A: The jsonpath-plus dialect, which covers the RFC 9535 core (root $, dot and bracket notation, wildcards *, recursive descent .., array slices [0:2], unions [0,1] and filters [?(…)]) plus some extensions like the parent operator ^. Expressions from the RFC and from Goessner's original article generally work as-is. Q: What does the path next to each match mean? A: It is the normalized form of where the value lives, always in bracket notation: $['store']['book'][0]['title']. Every match gets one, so you can copy a path back into the expression box to select exactly that value again. Q: Can a filter expression run arbitrary JavaScript? A: No. This tester passes the library's safe-evaluation option, so filters like [?(@.price < 10 && @.category == 'fiction')] are interpreted by a restricted expression evaluator. Attempts to reach things like constructor or window fail with an error instead of executing. Q: Why do I get zero matches instead of an error? A: A syntactically valid expression that selects nothing (a key that does not exist, a filter nothing satisfies) legitimately returns an empty result in JSONPath. Only malformed expressions and invalid JSON produce an error message. Q: What is the difference between $.. and $.*? A: $.* selects the direct children of the root only. $.. is recursive descent: it visits every value at every depth, so $..price finds price wherever it appears in the document. Q: Is my JSON uploaded to run the query? A: No. Parsing and evaluation both happen in this tab; the document and the expression never leave your device. ## Query String ↔ JSON https://localfirsttools.com/query-string-json Parse URL query strings into a JSON object with URLSearchParams (repeated keys become arrays), and build query strings back. All in your browser. Paste a query string or a full URL to turn percent-encoded pairs into JSON, or JSON into a query string. Parsing uses the browser's URLSearchParams; repeated keys become arrays and values stay strings. Nested JSON is rejected with the offending key named. Useful for logs, OAuth callbacks and copying params into fixtures. Nothing is uploaded. ### What the Query String ↔ JSON converter does A query string packs key=value pairs after the ? of a URL; this converter turns those pairs into a readable JSON object and turns a flat JSON object back into a properly encoded query string. Use it to inspect tracking parameters, debug API calls, or build a query string without hand-encoding every value. ### How the Query String ↔ JSON converter works Paste a full URL or a bare a=1&b=2 string, everything before the first ? (and any #fragment) is stripped, then the pairs are parsed with your browser's own URLSearchParams, which implements the WHATWG URL standard. Going the other way, each JSON key is appended to a fresh URLSearchParams and serialized with correct percent-encoding. Both directions run entirely in this tab. ### FAQ Q: What happens to repeated keys like tags=a&tags=b? A: They become a JSON array: {"tags": ["a", "b"]}. Query strings have no official convention for lists, so this tool picks the repeated-key style; converting the array back emits tags=a&tags=b again, making the round trip lossless. Q: Why can't I convert nested JSON to a query string? A: A query string is a flat list of string pairs. There is no standard way to encode {"user": {"name": "Ada"}}. The tool shows an error naming the offending key instead of guessing at a bracket convention like user[name] that only some frameworks understand. Only string, number and boolean values (and arrays of those) convert. Q: Why does a + in my query string become a space? A: URLSearchParams follows the application/x-www-form-urlencoded rules from the WHATWG URL standard, where + means a space. A literal plus sign must be encoded as %2B. When serializing, spaces come out as + again, which every server-side parser understands. Q: Are all values strings in the JSON output? A: Yes. A query string carries no type information (page=2 could mean the number 2 or the string "2"), so every value is kept as a string rather than guessed. Going from JSON to a query string, numbers and booleans are accepted and serialized as their text form. Q: Does the order of parameters survive the round trip? A: Mostly. Keys keep their first-appearance order, but values of a repeated key are grouped into one array, so a=1&b=2&a=3 comes back as a=1&a=3&b=2. Servers treat parameter order as insignificant, so this does not change meaning. Q: Is my URL uploaded anywhere? A: No. Parsing and serializing both use the URLSearchParams API built into your browser; LocalFirstTools has no server endpoint that receives what you paste. ## Password Generator https://localfirsttools.com/password-generator Generate random passwords with crypto.getRandomValues and a live entropy readout, 16 characters give about 104 bits. Nothing is stored or sent. Generate a random password with crypto.getRandomValues and rejection sampling, so there is no modulo bias. Length is 8 to 64 characters; look-alikes can be excluded. Entropy is shown live as length × log2(alphabet). A 16-character password over 70 symbols is about 98 bits. Nothing is stored or sent. ### What the Password Generator does This generator builds passwords from cryptographically secure randomness, with toggles for lowercase, uppercase, digits and symbols, a length from 8 to 64 characters, and an option to drop look-alike characters (0, O, 1, l, I, |) for passwords someone might read aloud or type by hand. Use it whenever a site or system needs a fresh credential you will store in a password manager. ### How the Password Generator works Every character index is drawn with crypto.getRandomValues, using rejection sampling: random 32-bit draws that fall in the final partial cycle of the alphabet size are discarded, so every character is exactly equally likely, the naive value % size approach would subtly favor the start of the alphabet. The entropy readout is length × log₂(alphabet size), computed from the character sets you currently have enabled. Generation happens in this tab; nothing is stored, logged or transmitted. ### Limits Length 8–64. ### FAQ Q: How strong is the default 16-character password? A: With all four character sets enabled the alphabet has 90 characters, so each character contributes log₂(90) ≈ 6.49 bits and 16 characters give about 104 bits of entropy, far beyond what offline guessing attacks can search. Q: Is length or complexity more important? A: Length. NIST SP 800-63B recommends supporting long passwords rather than enforcing composition rules like "one symbol and one digit", because entropy grows with every added character. Four extra lowercase-only characters add more entropy than swapping a letter for a symbol. Q: What does the exclude look-alikes option cost? A: It removes 0, O, 1, l, I and | from the alphabet, shrinking it from 90 to 84 characters. At 16 characters that drops the entropy from about 104 to about 102 bits, a negligible price for a password a human has to read or retype. Q: What is modulo bias and why does rejection sampling matter? A: Mapping a random 32-bit number onto a 90-character alphabet with a plain modulo makes the first characters of the alphabet very slightly more likely, because 2³² is not a multiple of 90. Rejection sampling throws away draws from the incomplete final cycle, so the distribution is exactly uniform. Q: Are generated passwords stored or sent anywhere? A: No. Passwords exist only in this tab's memory until you regenerate or close it. There is no history, no analytics on the values, and no network request involved in generating or copying them. Q: Why is there no strength meter? A: Colored strength bars guess at human password patterns; for randomly generated passwords the entropy number is the honest measure. Anything above roughly 80 bits is beyond realistic offline attack, and the readout updates live as you change length or character sets. ## Hex Encode / Decode https://localfirsttools.com/hex Convert UTF-8 text to hexadecimal bytes and hex back to text, exactly two hex characters per byte. Accepts 0x prefixes; nothing is ever uploaded. Convert text to hexadecimal and back, two hex digits per UTF-8 byte (exactly doubling size on encode). Decode strips 0x prefixes and whitespace, then rejects byte sequences that are not valid UTF-8 rather than corrupting them. Use it for keys, hashes, magic numbers and binary headers. Everything runs in this tab; nothing is uploaded. ### What the Hex encoder/decoder does Hexadecimal (base16) writes every byte as two characters from 0-9 and a-f, which is how bytes appear in packet dumps, debuggers, checksums and protocol specs. This tool encodes text to its UTF-8 hex bytes and decodes hex back into text. ### How the Hex encoder/decoder works Encoding runs your text through the browser's TextEncoder to get UTF-8 bytes, then maps each byte to two lowercase hex digits, the base16 alphabet defined in RFC 4648 §8. Decoding strips whitespace and 0x prefixes, accepts uppercase, then rebuilds the bytes and decodes them with TextDecoder in strict mode, so invalid UTF-8 produces a clear error instead of garbled output. Everything runs in this tab. ### FAQ Q: Why does é encode to c3a9 instead of one byte? A: Text is encoded as UTF-8 first, and é (U+00E9) takes two bytes in UTF-8: 0xC3 0xA9. Any character outside basic ASCII uses two to four bytes, so the hex for café is 63 61 66 c3 a9, five bytes for four characters. Q: How does hex compare to Base64 in size? A: Hex doubles the data: every byte becomes exactly two characters (2× the byte count). Base64 packs three bytes into four characters, about 1.33×. Hex is bigger but trivially readable byte-by-byte, which is why debuggers and specs prefer it. Q: What input formats does decoding accept? A: Lowercase or uppercase digits, spaces or line breaks between bytes, and 0x prefixes, 0xC3 0xA9, c3a9 and C3 A9 all decode identically. Everything except the hex digits themselves is stripped before decoding. Q: Why do I get an odd number of digits error? A: Every byte needs exactly two hex digits, so valid hex always has an even count. An odd count means a digit was lost in copy-paste, usually at the start or end of the selection. Q: Can I decode arbitrary binary data with this? A: Only if the bytes form valid UTF-8 text. The decoder runs in strict mode, so bytes that are not valid UTF-8 (compressed data, images, random keys) produce an error rather than silently corrupted characters. Q: Is anything uploaded? A: No. TextEncoder and TextDecoder are browser APIs; encoding and decoding never leave this tab. ## Discord Timestamp Generator https://localfirsttools.com/discord-timestamp Free Discord timestamp generator: pick a date, copy codes in all 7 formats, decode existing ones. Runs in your browser; nothing is uploaded. Pick a date and time to get Discord codes in all 7 styles, including relative (“in 3 hours”), each with a preview. Paste an existing code or a Unix number to decode it. Discord shows every code in the reader's own timezone, so one message reads correctly worldwide. 13-digit millisecond values are flagged. ### What the Discord Timestamp Generator does A Discord timestamp is a short code like that you paste into a message. Discord replaces it with a date or time, and every reader sees it in their own timezone and language: someone in Tokyo reads 07:13, someone in New York reads 5:13 PM, and both are right. The number inside is Unix time in seconds, the letter after it picks one of 7 display styles. This tool turns a date you pick into all 7 codes, previews each one, and decodes codes you already have. ### How the Discord Timestamp Generator works Pick a date and time in the field at the top; it starts at the current minute in your own timezone. The tool converts it to Unix seconds with the browser's Date object and builds the 7 codes: t, T, d, D, f, F and R. Press Copy on the row you want and paste the code straight into a Discord message. The preview column uses the browser's Intl date formatting, so it approximates what you will see; other readers see their own version. The decoder at the bottom reads a pasted code or a bare number and shows the moment in your local time and in UTC. Everything runs in this tab. ### Limits 13-digit millisecond values are flagged; Discord expects Unix seconds. ### FAQ Q: How do I make a timestamp in Discord? A: Pick the date and time above, copy one of the codes such as , and paste it into your message. Discord swaps the code for a formatted date when the message is sent. There is no button for this inside Discord itself, which is why generators like this one exist. Q: What do t, T, d, D, f, F and R mean? A: They are the 7 display styles. t is a short time (16:20), T a long time with seconds (16:20:30), d a short numeric date (20/04/2021), D a long date (20 April 2021), f a short date and time (20 April 2021 16:20), F a long date and time with the weekday (Tuesday, 20 April 2021 16:20), and R a relative time such as 2 months ago or in 3 hours. Leaving the letter out gives you f. Q: Why does the time look different for my friend? A: That is the point of the feature. The code only carries an instant in Unix seconds; each Discord client renders it in the reader's own timezone and language. A meeting at 18:00 in Amsterdam is 12:00 PM in New York, and both readers see their local version without any conversion on your side. Q: Do Discord timestamps work on mobile? A: Yes. The rendering is done by the Discord client, and the iOS, Android, desktop and browser clients all support the syntax. Hovering or long-pressing the rendered time shows the full date. Q: Why does Discord show Invalid Date? A: Almost always because the number is in milliseconds instead of seconds. JavaScript's Date.now() gives 13 digits; Discord wants 10 (for any date between 2001 and 2286). Divide by 1000. The other common cause is a typo in the brackets, such as a missing colon, a space, or a style letter that is not one of t, T, d, D, f, F, R. Paste the code into the decoder above and it will point at the problem. Q: Is my date sent anywhere? A: No. The conversion uses the browser's own Date and Intl APIs, and the copy button writes to your clipboard. This page makes no network request with your input. ## SQL Formatter & Beautifier https://localfirsttools.com/sql-formatter Free SQL formatter and beautifier: pretty-print or minify SQL online with dialect, keyword case and indent. Runs in your browser; schema and customer literals are never uploaded. Paste SQL to pretty-print it with 2 spaces, 4 spaces or tabs, or minify it to one line. Pick a dialect (Standard SQL, PostgreSQL, MySQL, SQL Server, SQLite or BigQuery) and keyword case (upper, lower or preserve). Queries stay in this tab, so schema names and customer literals from logs are never uploaded. ### What the SQL Formatter & Beautifier does A SQL formatter pretty-prints a compact query so joins, filters and aliases are readable, or minifies it back to one line for logs and tickets. Format SQL, SQL beautifier and SQL pretty print are the same job: re-indent the statement without changing identifiers or literals. Use it on queries copied from Postgres, MySQL, SQL Server, SQLite or BigQuery logs. ### How the SQL Formatter & Beautifier works Paste SQL, drop a .sql file, or load the sample. Pick a dialect (Standard SQL, PostgreSQL, MySQL, SQL Server, SQLite or BigQuery), a keyword case (upper, lower or preserve) and an indent (2 spaces, 4 spaces or tabs), then press Format. Minify collapses insignificant whitespace while leaving strings and comments as written. Formatting loads the sql-formatter library in this tab on first use. The query never leaves the device, which matters because SQL from logs often contains table names, column names and customer literals. ### Limits A dialect that cannot parse the statement shows a friendly error. Files up to ~50 MB. ### FAQ Q: Does this upload my SQL? A: No. Formatting runs in this tab after the page has loaded. There is no endpoint that receives the query, which you can verify in DevTools → Network. Q: How do I format or beautify SQL? A: Paste the query, pick a dialect and indent, then press Format (Ctrl/Cmd + Enter). SQL formatter, SQL beautifier and format SQL all mean the same operation: pretty-print the statement. Q: Which SQL dialects are supported? A: Standard SQL, PostgreSQL, MySQL, SQL Server / T-SQL, SQLite and BigQuery. The dialect changes keyword and function recognition; the data in string literals is never rewritten. Q: Does formatting change my query? A: Only whitespace and, if you ask for it, keyword case. Identifiers, comments and string literals stay as written, including spaces inside quotes. Q: Why format SQL in the browser instead of a server? A: Queries copied from logs often reveal schema, table names and literal customer data. A local formatter never uploads those, so a production SELECT cannot leak through this page. Q: What does Minify do? A: It collapses spaces and line breaks that SQL does not need, while leaving -- comments, /* */ comments and quoted strings byte-for-byte. Use it to paste a query into a single log line. ## HTML Formatter & Beautifier https://localfirsttools.com/html-formatter Free HTML formatter and beautifier: pretty-print or minify HTML online without executing it. pre, script and style stay intact. Runs in your browser; nothing is uploaded. Paste HTML to pretty-print it with 2 spaces, 4 spaces or tabs, or minify it. Content inside pre, textarea, script and style is left intact, and the markup is never executed or rendered. Email templates, CMS exports and admin pages stay in this tab, so customer data is never uploaded. ### What the HTML Formatter & Beautifier does An HTML formatter pretty-prints messy markup, or minifies it, without opening a live document. HTML beautifier, HTML pretty print and format HTML are the same operation: re-indent between tags. Use it on email templates, CMS exports and internal admin pages. The input is never assigned to innerHTML or loaded in an iframe, so scripts and images cannot run. ### How the HTML Formatter & Beautifier works Paste HTML, drop a .html file, or load the sample. Format rewrites with 2 spaces, 4 spaces or tabs. Minify removes whitespace between tags. Text inside pre, textarea, script and style is left as written (only the opening tag may be indented with its parent). Formatting is a tokenizer in this tab: it never builds a live DOM. Email templates and admin pages often contain customer names and addresses, so keeping the markup on this device is the point. ### Limits Does not execute or render markup. Inner text of pre, textarea, script and style is not rewritten. Files up to ~50 MB. ### FAQ Q: Does this upload my HTML? A: No. Formatting runs in this tab, and there is no endpoint that receives the markup. You can verify it in DevTools → Network. Q: How do I format or beautify HTML? A: Paste it, pick an indent and press Format (Ctrl/Cmd + Enter). HTML formatter, HTML beautifier and pretty-print HTML are the same operation. Q: How do I minify HTML? A: Press Minify. Insignificant whitespace between tags is removed. Text inside elements, including pre and textarea, is not collapsed. Q: Does it change content inside pre, script or style? A: No, beyond optionally indenting the first line with its parent. The inner text of pre, textarea, script and style is copied as written. Q: Does the tool execute or render my HTML? A: No. The input is never assigned to innerHTML, never loaded in an iframe, and never attached to the page. Scripts do not run and images do not load. Q: Is it safe to paste email templates or admin pages? A: The markup never leaves this device. That is the reason to format email templates, CMS exports and internal admin pages here instead of on a server that would see customer data. # Guides ## Pretty-print JSON with jq, Python and JavaScript https://localfirsttools.com/guides/pretty-print-json Copy commands for jq, python -m json.tool and JSON.stringify. Learn how each handles JSON Lines, invalid input and large integers. Pretty-print JSON with jq . data.json in a terminal, python3 -m json.tool data.json with Python, or JSON.stringify(value, null, 2) in JavaScript. All three re-indent valid JSON without changing the data. This page has copy-ready commands, the safe way to write a new file, and how each handles JSON Lines and integers bigger than JavaScript can hold. ## Format a file with jq ``` jq . data.json jq --indent 4 . data.json > pretty.json ``` The dot filter returns the input value. jq prints it with indentation; the second command uses four spaces and saves the output in a new file. Keep input and output filenames different. A shell opens the redirected output before jq reads, so writing back to `data.json` in the same command can erase the input. With this input, `{"user":{"id":42,"name":"Ada"}}`, the default output is: ``` { "user": { "id": 42, "name": "Ada" } } ``` Use `jq -c . data.json` for compact output. That is the reverse formatting direction; the [JSON Minifier](https://localfirsttools.com/json-minifier) provides it in the browser. ## Use Python's standard library ``` python3 -m json.tool data.json python3 -m json.tool --indent 2 data.json pretty.json ``` Python's JSON command reports invalid input and exits unsuccessfully. In a Python program, use the same parser and choose an indentation width when serializing: ``` import json from pathlib import Path value = json.loads(Path("data.json").read_text(encoding="utf-8")) Path("pretty.json").write_text( json.dumps(value, indent=2, ensure_ascii=False) + "\n", encoding="utf-8", ) ``` ## Use JSON.stringify in JavaScript ``` const value = JSON.parse('{"user":{"id":42,"name":"Ada"}}'); const formatted = JSON.stringify(value, null, 2); console.log(formatted); ``` The third argument sets the indentation. Use `4` for four spaces or `"\t"` for tabs. For a file in Node.js: ``` import { readFileSync, writeFileSync } from "node:fs"; const value = JSON.parse(readFileSync("data.json", "utf8")); writeFileSync("pretty.json", JSON.stringify(value, null, 2) + "\n"); ``` ## Handle JSON Lines as a sequence A `.jsonl` or NDJSON log contains separate documents on separate lines. It is not one JSON array. jq accepts that sequence directly: ``` jq . events.jsonl ``` Indenting it produces multi-line documents, so the output is no longer JSON Lines. Use `jq -c . events.jsonl` if a log consumer needs one document per line. For large files, choose a streaming parser and processing strategy; a normal `JSON.parse` call holds the parsed document in memory. ## Check precision before calling a round trip lossless Whitespace between JSON tokens has no meaning, but parse-and-serialize tools can also normalize numbers, escape sequences and duplicate object keys. JavaScript's `JSON.parse` stores numbers as floating-point values. An integer identifier above `Number.MAX_SAFE_INTEGER` can change before formatting begins. Keep such IDs in strings or use a parser with an appropriate number representation. If parsing fails, fix the first syntax error before formatting. The [JSON error guide](https://localfirsttools.com/guides/fix-unexpected-token-json) explains common causes, including trailing commas and HTML pasted in place of JSON. ### FAQ Q: How do I pretty-print a JSON file with jq? A: Run jq . data.json to print an indented document to standard output. To save the result, redirect it to a different file: jq . data.json > pretty.json. Never use the input file as the output target of shell redirection. Q: Can I format JSON without installing jq? A: If Python is installed, run python3 -m json.tool data.json. In JavaScript, serialize a parsed value with JSON.stringify(value, null, 2). Both options run on the machine executing the command. Q: Why does a JSON Lines file fail with JSON.parse? A: JSON.parse expects one document. A JSON Lines file contains one document per line. Parse each non-empty line separately, or use jq, which accepts a sequence of JSON values. Q: Does JSON.stringify preserve large integer IDs? A: JavaScript numbers cannot represent every integer beyond Number.MAX_SAFE_INTEGER (9007199254740991). Precision may already be lost when JSON.parse reads the value. Store identifiers as strings when exact digits matter. ## How to fix “Unexpected token” in JSON https://localfirsttools.com/guides/fix-unexpected-token-json Unexpected token means the parser hit illegal syntax: a trailing comma, single quotes, comments or an HTML error page. Find the exact line locally. “Unexpected token” means the parser hit a character JSON forbids at that position: a trailing comma, a single quote, a comment, an unquoted key, or an HTML error page. Paste the document into the JSON Formatter to get the first error's exact line and column, locally; nothing is uploaded. The reported position is where the parser gave up, not always where you typed the mistake: a missing comma on line 4 shows up as an unexpected token at the start of line 5. The [JSON Formatter & Validator](https://localfirsttools.com/json-formatter) highlights that line in the gutter. Fix the first error, re-validate, and repeat — parsers stop at the first violation. ## Read the error message first Browser messages look like `Unexpected token } in JSON at position 251`, position counts characters from the start, which is useless in a minified single-line document. A validator that translates position into line and column (and shows you the line) turns a hunt into a glance. Note that the parser stops at the *first* violation: fix it, re-validate, and repeat if needed. ## The five mistakes behind almost every case - **Trailing commas.** `{"a": 1, "b": 2,}`, legal in JavaScript, illegal in JSON. Reported as an unexpected `}` or `]`. - **Single quotes.** `{'name': 'Ada'}`, JSON strings and keys must use double quotes. Reported as an unexpected `'` at the first quote. - **Unquoted keys.** `{name: "Ada"}`, object keys are strings and need quotes, unlike JavaScript object literals. - **Comments.** `// config for prod`, no comment syntax exists in JSON. The `/` is the unexpected token. - **Raw line breaks or control characters inside strings.** A pasted value with a real newline in it must be escaped as `\n`. Copy-pasting from terminals also smuggles in invisible characters, smart quotes from chat apps are a classic. ## Invisible characters: the BOM and its friends Sometimes the input *looks* perfect and still fails at position 0 with a baffling `Unexpected token in JSON at position 0`, note the seemingly empty quotes. The culprit is usually a byte order mark: Windows editors and PowerShell's `Out-File` like to prepend `U+FEFF` to UTF-8 files, and RFC 8259 forbids it at the start of a JSON text. The same family of bugs comes from non-breaking spaces (`U+00A0`) pasted from web pages and “smart quotes” (`“ ”`) substituted by chat apps and word processors, visually near-identical to legal characters, lexically illegal. Fix: re-save as “UTF-8 without BOM”, or strip the first character in code with `text.replace(/^\uFEFF/, "")`; for smart quotes, retype the quotes in a code editor. A validator that points at position 0 or at a quote that looks fine is practically shouting “invisible character”. ## Values JavaScript allows but JSON doesn't `NaN`, `Infinity` and `undefined` are legal JavaScript values with no JSON representation, RFC 8259 numbers are finite, and there is no undefined. Hand-built strings containing them fail with `Unexpected token N` (from NaN) or `Unexpected token I`. Note the asymmetry: `JSON.stringify(NaN)` quietly emits `null` rather than throwing, so these tokens in a document almost always mean someone concatenated strings instead of serializing. The same goes for `0x1F` hex literals and numbers like `.5` or `+1`, JSON requires `0.5` and `1`. ## When the JSON is not the problem `Unexpected token `. Your code parsed an HTML error page, a 404, or a login redirect as if it were an API response. The JSON was never malformed, there was no JSON. Log `response.text()` before parsing, and check the status code and content-type first. ## A worked example ``` { "name": "deploy", "retries": 3, "env": "prod", } ``` A validator reports something like `Unexpected token } at line 5, column 1`. The real mistake is the comma at the end of line 4, the parser only discovered it when the closing brace arrived where a new key was required. Delete the comma and the document is valid. This one-line-off pattern is why the rule of thumb is: look at the reported position, then one token back. ## Prevent it next time Generate JSON with a serializer (`JSON.stringify`, `json.dumps`) instead of string concatenation or hand-editing; serializers cannot produce syntax errors. When hand-editing is unavoidable (config files, fixtures) validate before committing, and diff edited configs with the [Diff Checker](https://localfirsttools.com/text-diff) to see exactly what changed when something breaks. ### FAQ Q: Why does the error say “Unexpected token < in JSON at position 0”? A: Your code called JSON.parse on an HTML page, usually an error page or a login redirect returned where the API response should be. Log the raw response text; the JSON itself is probably fine and the bug is in the request or the server. Q: Why is the reported line different from where my mistake is? A: Parsers report where they gave up, which is at or just after the mistake. A missing comma at the end of line 4 surfaces as an unexpected token at the start of line 5. Look at the reported position and one token backwards. Q: Are comments really not allowed in JSON? A: Correct, RFC 8259 has no comment syntax. Files with // or /* */ are JSONC or JSON5, supersets that need their own parsers (VS Code's settings.json is JSONC, for example). Strip comments before feeding standard parsers. Q: Is a trailing comma ever valid? A: Never in JSON. [1, 2, 3,] and {"a": 1,} are both invalid, even though modern JavaScript allows both in source code. This is the single most common JSON error. Q: Why does the same text work as a JavaScript object but fail JSON.parse? A: JavaScript object literal syntax is a superset: unquoted keys, single quotes, trailing commas, comments, NaN and undefined are all fine in source code but illegal in JSON. Code that eval'd or copy-pasted a literal will not round-trip through JSON.parse unchanged. Q: Are duplicate keys an error? A: Not a syntax error, {"a": 1, "a": 2} parses everywhere. RFC 8259 only says names "should" be unique; JavaScript keeps the last value, other parsers vary, and some security tooling flags duplicates precisely because two systems can read different values from the same document. ## JSON vs YAML vs CSV: which format for which job? https://localfirsttools.com/guides/json-vs-yaml-vs-csv JSON for APIs and precision, YAML for human-edited configs, CSV for tabular exports, how data model, comments and types decide, with converters. JSON is for APIs and anything programs exchange, YAML is for configuration humans edit, and CSV is for flat tables going to or from spreadsheets. Convert copies locally with JSON ↔ YAML and JSON ↔ CSV; comments, YAML's implicit types and nested objects do not survive every direction. All three describe data, but they make opposite trade-offs. Because their data models overlap, you can usually convert a copy: [JSON ↔ YAML](https://localfirsttools.com/json-yaml) and [JSON ↔ CSV](https://localfirsttools.com/json-csv) both run in your browser, and nothing is uploaded. Nested objects, comments and YAML's unquoted scalars are the usual lossy bits. ## The same record in all three ``` JSON: {"name": "Ada", "role": "admin", "active": true} YAML: name: Ada role: admin active: true CSV: name,role,active Ada,admin,true ``` ## What actually differs - **Data model.** JSON (RFC 8259) and YAML share objects, arrays, strings, numbers, booleans and null, YAML adds anchors, tags and multi-document files. CSV (RFC 4180) has exactly one shape: rows of untyped text fields. In CSV, `true` and `01234` are just characters. - **Comments.** YAML has them (`#`); JSON and CSV do not. This alone explains YAML's dominance in config files, humans annotate configs. - **Ambiguity.** JSON has essentially none. YAML has famous foot-guns: unquoted `no` parses as a boolean (the “Norway problem”, country code `NO`), leading zeros turn into numbers. CSV's ambiguity is dialects: delimiters, quoting and encodings vary by exporting locale, European Excel emits semicolons. - **Whitespace.** Meaningless in JSON, structural in YAML (indentation is syntax), and significant inside CSV fields. ## YAML's power features, and the price YAML earns its config-file role with features JSON refuses to have. Anchors deduplicate repeated blocks, one definition, reused by reference: ``` defaults: &defaults retries: 3 timeout: 30 production: ### FAQ Q: Is every JSON document valid YAML? A: Yes. YAML 1.2's data model is a superset of JSON's, so valid JSON parses as YAML. The reverse is false: anchors, comments and multi-line scalars have no JSON form. Q: Can CSV represent nested data? A: Not directly, CSV is rows and columns. Nested structures must be flattened (dotted column names like user.name) or serialized into a cell, both of which are workarounds rather than features. If your data nests, CSV is the wrong format. Q: Why did my YAML value change type after converting? A: YAML guesses types for unquoted scalars: no becomes false, 007 becomes 7, 3:20 can become a sexagesimal number in YAML 1.1 parsers. Quote anything that must stay a string. Converting to JSON is a quick audit for these surprises. Q: Which format is fastest to parse? A: JSON, by a wide margin, its grammar is tiny and parsers are heavily optimized native code in every runtime. YAML parsers are orders of magnitude slower; CSV sits between, depending on quoting complexity. Q: Should an API ever speak YAML? A: Rarely. JSON parses faster, has no implicit-typing ambiguity, and every HTTP client handles it natively. YAML at API boundaries mostly appears where the payload is itself configuration (Kubernetes' API accepts both). Default to JSON on the wire, YAML in the editor. Q: Why does Excel show my CSV in one column? A: Delimiter mismatch: your file is comma-separated but Excel's locale expects semicolons (or vice versa). Use Excel's import wizard to set the delimiter explicitly, or re-export the file with the delimiter your locale expects. ## How to decode a JWT (and why that isn't verifying it) https://localfirsttools.com/guides/how-to-decode-a-jwt Split the token on its two dots and Base64URL-decode the first two segments, no key needed. Decoding shows the claims but never verifies anything. To decode a JWT, split it on its two dots and Base64URL-decode the first two segments — no key is required, because a signed JWT is encoded, not encrypted. Decoding shows the header and claims as JSON and never checks the signature. Paste it into the JWT Decoder to do this in your browser. The [JWT Decoder](https://localfirsttools.com/jwt-decoder) shows the header and payload as formatted JSON without the token leaving your browser. Decoding takes seconds and requires no key, and that fact is the most important thing to understand about JWTs: **anyone can read one**. Decoding is not verification. ## The three segments A JWT (RFC 7519) is three Base64URL blobs joined by dots: ``` eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 . eyJzdWIiOiI0MiIsImV4cCI6MTc2NzIyNTYwMH0 . SflKxwRJ... └── header ─────────────────────────┘ └── payload ──────────────────────────┘ └─ signature ─┘ ``` - **Header**, metadata, typically `{"alg": "HS256", "typ": "JWT"}`: which algorithm signed the token. - **Payload**, the claims: registered ones like `sub` (subject), `exp` (expiry), `iat` (issued at), `iss` (issuer), plus whatever custom claims the issuer added. - **Signature**, cryptographic proof over the first two segments. It is not decodable into anything readable; it can only be *verified* with the right key. ## Decoding it yourself The segments are Base64URL, not standard Base64, `-` and `_` replace `+` and `/`, and padding is dropped (see [Base64 vs Base64URL](https://localfirsttools.com/guides/base64-vs-base64url)). That is why `atob()` on a raw segment sometimes throws. In JavaScript: ``` const payload = JSON.parse( atob(token.split(".")[1].replace(/-/g, "+").replace(/_/g, "/")) ); ``` The [decoder tool](https://localfirsttools.com/jwt-decoder) does exactly this, plus JSON pretty-printing, client-side. ## A worked example Here is a complete (expired, HS256-signed) token and what falls out of it: ``` eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c segment 1 decodes to: {"alg":"HS256","typ":"JWT"} segment 2 decodes to: {"sub":"1234567890","name":"John Doe","iat":1516239022} segment 3: the HMAC-SHA256 signature (not decodable) ``` Reading the claims: `sub` identifies the subject, `name` is a custom claim the issuer chose to add, and `iat: 1516239022` is Unix seconds, [converted](https://localfirsttools.com/timestamp/unix-to-iso), 2018-01-18T01:30:22Z, so this token is years old. There is no `exp` claim at all, which itself is a finding: a token without `exp` never expires unless the server enforces its own lifetime. The registered claims worth knowing on sight: `iss` (who minted it), `aud` (who it is for), `sub`, `exp`/`nbf`/`iat` (the three timestamps), and `jti` (unique token ID, used for revocation lists). ## What decoding tells you (a lot) Debugging auth usually only needs the payload: Is the token expired? (`exp` is Unix seconds, [convert it](https://localfirsttools.com/timestamp/unix-to-iso) and compare with now.) Was it issued for the right user (`sub`), audience (`aud`) and scopes? Did the identity provider include the custom claim your code reads? Nine out of ten “my API returns 401” investigations end at an expired `exp` or a missing claim. ## What decoding cannot tell you Whether the token is genuine. Anyone can mint a JWT with any claims, `alg` set to `HS256`, `sub` set to `admin`, and it will decode beautifully. Only signature verification against the issuer's key separates a real token from a forged one, which is why servers must verify (never just decode) and why a browser decoder honestly labels the signature “not verified”. Treat decoded claims as a debugging view, never as an authorization decision. ## The alg field is attacker-controlled One header claim deserves special paranoia: `alg`. RFC 7519 permits `"alg": "none"`, an unsigned JWT whose third segment is empty (the token ends with a dot and nothing after it). Early JWT libraries famously honored whatever `alg` the token declared, so an attacker could strip the signature, set `none`, and sail through verification; a related classic swapped `RS256` for `HS256` to trick servers into using a public key as an HMAC secret. Modern libraries force you to allowlist algorithms, do that, server-side, always. From the decoding side, the lesson is simpler: everything you can read in the header and payload, an attacker can write. ### FAQ Q: Is it safe to paste a JWT into an online decoder? A: Only if the decoding happens client-side, because a live token is a bearer credential, whoever holds it can use it. The LocalFirstTools decoder runs entirely in your tab; when using any other tool, verify in the network tab that the token is not transmitted, and prefer expired or test tokens. Q: Why can I read a JWT without any key? A: A signed JWT (JWS) is encoded, not encrypted. Base64URL is a reversible transport encoding; the cryptography in the third segment proves integrity and origin, not confidentiality. Secrets must not be put in JWT claims unless the token is an encrypted JWE. Q: How do I check if a JWT is expired? A: Decode it and read the exp claim, a Unix timestamp in seconds. Convert it to a date and compare with now; if exp is in the past the token is expired. The iat (issued-at) and nbf (not-before) claims use the same unit. Q: How do I actually verify a JWT then? A: Server-side, with a JWT library: verify the signature against the issuer's public key or shared secret, then check exp, iss and aud. Libraries exist for every language (jose for JavaScript, PyJWT for Python). Verification requires key material a browser tool should not have. Q: Why does my token end with a dot and nothing after it? A: That is an unsigned JWT, "alg": "none", with an empty signature segment. Legal per RFC 7519, and exactly why verifiers must allowlist algorithms: a server that accepts alg none accepts any forged claims. Q: Why doesn't my token decode at all? A: Check the dots first: two dots means JWS (decodable header and payload), four dots means an encrypted JWE with five segments, its payload is genuinely encrypted and no decoder can show it without the key. Also make sure you copied the whole token; truncation breaks the Base64URL groups. ## Base64 vs Base64URL https://localfirsttools.com/guides/base64-vs-base64url Base64 vs Base64URL differs by two characters (+/ vs -_) and = padding. Why JWT segments fail atob(), and how to convert locally in your browser. Standard Base64 uses + and / with = padding; Base64URL swaps those two characters for - and _ and usually drops the padding so tokens survive URLs and JWT segments. The bytes are identical — only the alphabet and padding differ — which is why a JWT fragment often fails atob() until you convert it. You usually meet the split when a JWT segment, a WebAuthn id or a PKCE challenge refuses to decode in a tool that expects MIME Base64. Flip the URL-safe toggle on the [Base64 tool](https://localfirsttools.com/base64) to switch alphabets in your browser; nothing is uploaded. The rest of this page is the conversion, the padding math, and a three-byte example. ## Both come from RFC 4648 RFC 4648 defines both alphabets: section 4 is “base64” and section 5 is “base64url”, formally “Base 64 Encoding with URL and Filename Safe Alphabet”. Both map every 3 input bytes to 4 output characters from a 64-character set; the first 62 characters (A–Z, a–z, 0–9) are identical, only positions 62 and 63 differ: ``` chars 62,63 padding Base64 + / = required Base64URL - _ = usually omitted ``` ## A three-byte worked example Encode the string `Hi?`, bytes `72 105 63`. Base64 works on 6-bit groups, so the 24 bits regroup from three 8-bit bytes into four 6-bit values: ``` bytes: 01001000 01101001 00111111 H i ? 6-bit: 010010 000110 100100 111111 = 18 6 36 63 alphabet position 63 differs: Base64 → "SGk/" (63 = "/") Base64URL → "SGk_" (63 = "_") ``` Same bytes, one different character, and that character (`/`) is precisely the one a URL path would swallow. Because 3 bytes map to exactly 4 characters, this example needs no padding; padding only appears when the input length is not a multiple of 3. ## The padding math The `=` signs are pure length bookkeeping: input length mod 3 = 0 → no padding, mod 3 = 2 → one `=`, mod 3 = 1 → two `==`. So `"OK"` (2 bytes) encodes to `T0s=` and `"A"` (1 byte) to `QQ==`. To restore stripped padding (the everyday chore when handling Base64URL) pad to the next multiple of 4: `s + "=".repeat((4 - s.length % 4) % 4)`. A Base64 string whose length mod 4 equals 1 is impossible; if you see one, characters were lost in transit. ## Why the swap matters - In a URL, `+` historically decodes to a space in query strings, `/` separates path segments, and `=` separates keys from values. Standard Base64 in a URL either breaks or needs [percent-encoding](https://localfirsttools.com/url-encode) on top double encoding, twice the bugs. - In file names, `/` is a directory separator on every OS. Base64URL output is filename-safe by construction. ## Where you meet each one **Standard Base64:** MIME email attachments, `data:` URLs, `Authorization: Basic` headers, X.509/PEM certificates, most APIs' binary fields. **Base64URL:** all three JWT segments (RFC 7519 mandates it, see [how to decode a JWT](https://localfirsttools.com/guides/how-to-decode-a-jwt)), WebAuthn identifiers, OAuth PKCE code challenges, and short link tokens. ## Converting between them Because only two characters differ, conversion is two string replacements plus padding: ``` // Base64URL → Base64 let b64 = input.replace(/-/g, "+").replace(/_/g, "/"); b64 += "=".repeat((4 - (b64.length % 4)) % 4); // restore padding // Base64 → Base64URL input.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, ""); ``` The bytes never change, only their textual clothes. If a decode still fails after conversion, the string was truncated or was never Base64 to begin with; the [Base64 decode page](https://localfirsttools.com/base64/decode) reports invalid input instead of guessing. ## The one thing neither variant is Encryption. Encoding is a reversible mapping anyone can undo without a key, its 33% size overhead buys transport safety, not secrecy. If a value must be confidential, encrypt it; if it merely must survive a URL, Base64URL is exactly the right tool. ### FAQ Q: Why does atob() fail on a JWT segment? A: JWT segments are Base64URL: they may contain - and _ (which atob rejects in strict engines) and they omit padding. Replace - with +, _ with /, re-add = padding to a multiple of 4, then decode. Q: Is Base64URL more secure than Base64? A: No, neither is security at all. Both are reversible encodings with no key; the difference is purely which characters they use. Anything encoded in either is readable by anyone. Q: How do I recognize which variant I'm looking at? A: A + or / means standard Base64; a - or _ means Base64URL; = padding at the end suggests standard (Base64URL usually drops it). A string with none of those characters is valid in both alphabets, decode it either way. Q: Why is padding dropped in Base64URL? A: The = character has meaning in URLs (key=value) and the padding is redundant, the length mod 4 tells a decoder how many bytes remain. RFC 4648 permits omitting it when the length is otherwise known, which URL contexts exploit. Q: Why is Base64 output exactly 33% bigger? A: Every 6 bits of input become an 8-bit character: 4 output bytes per 3 input bytes, a 4/3 ratio. A 300 KB image becomes a 400 KB data: URL, which is why inlining large assets as Base64 is usually a performance mistake. Q: What are Base32 and Base58 then? A: Sibling encodings from the same family. Base32 (also RFC 4648) uses A–Z and 2–7, case-insensitive and safe for systems that mangle case, like some DNS labels. Base58, used by Bitcoin addresses, drops visually confusable characters (0, O, I, l). Each trades density for robustness in a different channel. ## UUID v4 vs v7: random or time-ordered? https://localfirsttools.com/guides/uuid-v4-vs-v7 v4 is 122 random bits; v7 leads with a 48-bit timestamp so IDs sort by creation time (RFC 9562). v7 suits database keys, v4 hides creation time. UUID v4 is 122 bits of randomness; UUID v7, standardized in RFC 9562, leads with a 48-bit millisecond timestamp so the IDs sort by creation time as plain strings. Choose v7 for database primary keys; choose v4 when the creation instant must stay private. Both can be minted locally in the UUID Generator. The [UUID Generator](https://localfirsttools.com/uuid-generator) produces both: v4 from the browser's own `crypto.randomUUID()`, and [v7](https://localfirsttools.com/uuid-generator/v7) assembled locally from the millisecond clock and `crypto.getRandomValues()`. Neither identifier is minted by a server. Choose v4 when IDs must reveal nothing; choose v7 when they become database keys and insert performance matters. ## Anatomy of each ``` v4: ff2154a1-b2c3-4d81-9f3e-8a72e05c1d44 └────────── random ──────────┘ (122 random bits; "4" = version) v7: 0198f0a4-7e12-7cc3-9a41-d0b3a1c65f02 └─ 48-bit ms timestamp ─┘└─ random ─┘ ("7" = version) ``` Both are 128 bits in the same 36-character format, both carry version and variant markers, and both rely on randomness against collisions, v7 simply spends its first 48 bits on time (milliseconds since the Unix epoch, the same unit explained in [seconds vs milliseconds](https://localfirsttools.com/guides/unix-seconds-vs-milliseconds)) and keeps 74 random bits. ## The exact bit layout RFC 9562 section 5.7 lays v7 out like this (v4 is the same frame with random bits everywhere): ``` v7: unix_ts_ms 48 bits milliseconds since 1970-01-01 UTC ver 4 bits 0111 (7) rand_a 12 bits randomness (or sub-ms precision) var 2 bits 10 (RFC variant) rand_b 62 bits randomness v4: 122 random bits around the same ver=0100 and var=10 markers ``` Two consequences worth knowing. First, a v7 UUID's creation time is recoverable: the first 12 hex characters are the millisecond timestamp, so `0198f0a4-7e12-…` decodes to a concrete instant (48 bits of milliseconds run out in the year 10889, nobody's problem). Second, the version and variant nibbles are why you can eyeball any UUID: third group starts with `4` → v4, with `7` → v7. ## How unlikely is a collision, concretely? With 122 random bits (v4), the birthday bound says you need roughly 261 ≈ 2.3×1018 UUIDs for a 50% chance of a single collision. Generating one billion UUIDs per second: that is about 73 years of continuous output, and the “collision” would be one duplicate somewhere in that entire mountain. v7's 74 random bits per millisecond give about 1.9×1022 possible values *within each millisecond*, so the practical answer is the same for both: treat collisions as impossible, and treat any observed duplicate as a bug in the generator (a forked process reusing entropy, a copy-paste) rather than bad luck. ## Why time-ordering matters for databases B-tree indexes love sequential inserts. v4 keys land at random positions across the index, so every insert touches a random page, cache misses, page splits, write amplification, and the problem grows with table size. v7 keys are monotonically increasing (per millisecond), so inserts append to the same hot pages, like an auto-increment integer but globally unique. As a bonus, `ORDER BY id` approximates creation order without a separate timestamp column, and recent rows cluster physically together. ## When v4 is still the right answer - **Unlinkability.** v4 IDs reveal nothing, no creation time, no ordering. Public tokens, password-reset codes and IDs in URLs often should not leak when they were made. - **Zero dependencies.** `crypto.randomUUID()` is built into every modern browser and Node ≥ 19, cryptographically seeded, no library needed, [generate one now](https://localfirsttools.com/uuid-generator/v4). - **Not a database key.** Request IDs, idempotency keys, trace IDs and file names gain nothing from ordering. ## The rest of the version family, briefly v1 is the original time-based UUID, timestamp plus the machine's MAC address, which leaked hardware identity and made it privacy-notorious; v7 is effectively its rehabilitated successor. v5 is deterministic: it hashes a namespace plus a name (with SHA-1, used here as a name-mapper, not for security) so the same input always yields the same UUID, useful for stable IDs derived from URLs or external keys. v8 is a free-form escape hatch for custom layouts. v2, v3 and v6 exist but are legacy or niche. For new work the realistic menu is: random (v4), time-ordered (v7), or deterministic (v5). ## Rule of thumb Primary key in a relational database → v7, from your database, a library, or the [generator here](https://localfirsttools.com/uuid-generator/v7). Everything else → v4. And never derive either from hashing user data, a [hash](https://localfirsttools.com/hash-generator) of an email is deterministic and linkable, the opposite of what a UUID is for. ### FAQ Q: Can v7 UUIDs collide? A: Practically no. Two v7 UUIDs generated in the same millisecond still differ in 74 bits of random data. Collisions require both the same millisecond and the same random bits, astronomically unlikely. Q: Do v7 UUIDs leak information? A: The timestamp, yes, anyone holding a v7 UUID can read when it was created, to the millisecond. If creation time is sensitive (say, IDs exposed in URLs reveal signup dates), that is a real consideration in favor of v4. Q: Is UUID v7 widely supported? A: Support arrived quickly after RFC 9562 (May 2024): PostgreSQL 18 has uuidv7(), and libraries exist for every major language (the uuid npm package, Python's uuid6 package, and others). The browser's built-in crypto.randomUUID() still generates only v4, but v7 is 16 bytes of arithmetic over crypto.getRandomValues(), which is how the generator on this site builds them. Q: Should I migrate existing v4 keys to v7? A: Usually not, the win applies to newly inserted rows' index behavior, and changing primary keys is invasive. New tables and new systems are where v7 is worth choosing. Q: How do I read the timestamp out of a v7 UUID? A: Take the first 12 hex characters (skip the dashes), parse as a hex integer, and you have Unix milliseconds: parseInt(uuid.replaceAll('-','').slice(0,12), 16). Convert that with the timestamp tool to see the creation instant. Q: Are sequential IDs (auto-increment) still better than v7? A: For a single database they are smaller (8 bytes vs 16) and equally index-friendly. v7 wins when IDs must be generated on many machines without coordination, must not reveal row counts, or need to be globally unique across systems. That trade (coordination-free uniqueness for 8 extra bytes) is the whole UUID proposition. ## SHA-256 vs SHA-1 vs MD5: which hash should you use? https://localfirsttools.com/guides/sha-256-vs-sha-1-vs-md5 Use SHA-256 for anything that matters, SHA-1 and MD5 are collision-broken and fit only legacy checksums. What broken means and when it bites you. Use SHA-256 for anything that must resist an adversary. SHA-1 and MD5 are collision-broken (2017 and 2004) and belong only on legacy checksums, never on signatures, certificates or passwords. Count the hex: 64 characters is SHA-256, 40 is SHA-1, 32 is MD5. Hash all three locally in the Hash Generator. Collision-broken does not mean reversible: an attacker can craft two different inputs with the same digest, which is fatal for signatures and certificates, not for spotting a truncated download. SHA-1 and MD5 remain fine for detecting accidental corruption. The [Hash Generator](https://localfirsttools.com/hash-generator) computes all three locally in your browser. ## The three at a glance ``` digest hex chars collision resistance MD5 128 bit 32 broken (2004) SHA-1 160 bit 40 broken (SHAttered, 2017) SHA-256 256 bit 64 no known practical attack ``` Digest length is the quickest identification trick: `d41d8cd9…` at 32 characters is MD5, 40 is SHA-1, 64 is SHA-256. Try it, hash the same text with [SHA-256](https://localfirsttools.com/hash-generator/sha-256) and [MD5](https://localfirsttools.com/hash-generator/md5) and compare. The empty string has famous, recognizable digests you will meet in logs and tests: ``` MD5("") = d41d8cd98f00b204e9800998ecf8427e SHA-1("") = da39a3ee5e6b4b0d3255bfef95601890afd80709 SHA-256("") = e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 ``` Seeing one of these where a real checksum should be means something hashed nothing, an empty file, a failed read, a variable that was never set. Worth memorizing at least the `d41d8` and `e3b0c` prefixes. And hashes have no “close”: changing one bit flips about half the output bits (the avalanche effect), `SHA-256("hello")` starts `2cf24dba…` while `SHA-256("Hello")` starts `185f8db3…`, sharing nothing. ## What “broken” means, and what it doesn't A collision attack lets someone *construct* two inputs with the same digest. Google's SHAttered attack produced two different PDFs with an identical SHA-1 hash; MD5 collisions compute in seconds on a laptop. Crucially, this breaks scenarios where an attacker controls the inputs, forged certificates, tampered signed documents, poisoned dedup stores. It does *not* let anyone find an input matching your existing file's hash (a second-preimage attack, still impractical even for MD5). Hence the honest split: broken against adversaries, fine against accidents. ## What the attacks cost Numbers make the difference between the two “broken” algorithms concrete. An MD5 collision computes in under a second on ordinary hardware. It has been a classroom exercise for years, and chosen-prefix variants (two *meaningful* colliding documents) are cheap enough for hobbyists. SHAttered's SHA-1 collision took about 263 hash computations (Google quoted roughly 6,500 CPU-years plus 110 GPU-years, run in parallel) and the follow-up chosen-prefix attack in 2020 cost on the order of tens of thousands of dollars in rented GPUs. Expensive for you and me; pocket change for a state or a well-funded crime group, and costs only fall. SHA-256's best public cryptanalysis, by contrast, dents reduced-round variants only: there is no known collision, and 2128 birthday work is far beyond any conceivable hardware. ## The HMAC footnote One nuance that surprises people: HMAC-SHA1 and even HMAC-MD5 are *not* broken by collision attacks, the HMAC construction does not rely on collision resistance, so systems verifying HMAC-SHA1 signatures are not urgently on fire. That is an argument for orderly migration rather than panic, not for choosing them in new designs: new code should use HMAC-SHA256, which Web Crypto also provides. ## Where each is still legitimate - **SHA-256:** download verification, content addressing, signatures and certificates (as part of proper schemes), integrity checks, cache keys, deduplication. Default to it. Browsers implement it natively via `crypto.subtle.digest`. - **SHA-1:** matching existing checksums in older systems and protocols. Git's object IDs remain the famous example. - **MD5:** legacy checksum files (`.md5`), ETag schemes, non-adversarial dedupe. Web Crypto deliberately omits it, a small honest signal of its status. ## What none of them are for Passwords. A fast hash (and all three are designed to be fast) is exactly wrong for password storage, where you want each guess to be expensive. Use Argon2, scrypt or bcrypt with a salt. And no hash provides secrecy at all: hashing is one-way fingerprinting, not encryption, and short or guessable inputs are recoverable from lookup tables regardless of algorithm. For encoding (not protecting) data in transit, that job belongs to [Base64](https://localfirsttools.com/base64), which protects nothing either, but at least says so. ### FAQ Q: What does “broken for collisions” actually mean? A: An attacker can construct two different inputs with the same hash. It does not mean someone can reverse a hash or forge a match for your existing file, preimage attacks remain impractical even against MD5. The danger is wherever an attacker supplies both inputs: certificates, signatures, deduplication. Q: Is SHA-1 okay for git? A: Git still identifies objects by SHA-1 (with collision-detection hardening, and a SHA-256 mode exists), which works because a collision must be crafted: it does not happen by accident. That is an ecosystem-level judgment call, not a reason to pick SHA-1 for new designs. Q: Should I hash passwords with SHA-256? A: No. All three of these are fast hashes, and fast is fatal for passwords, GPUs try billions of guesses per second. Use a deliberately slow, salted KDF: Argon2, scrypt or bcrypt. Q: Why does my hash not match the published checksum? A: One different byte changes the whole digest. The usual culprits: a trailing newline, CRLF vs LF line endings, a UTF-8 BOM, or hashing the text of a file rather than its exact bytes. Compare lengths first (32 hex chars is MD5, 40 is SHA-1, 64 is SHA-256) to be sure you are even comparing the same algorithm. Q: Is SHA-512 stronger than SHA-256? A: Both are unbroken members of the SHA-2 family; SHA-512's larger digest buys security margin most applications don't need, though it is actually faster than SHA-256 on 64-bit CPUs. SHA-256 remains the interoperability default, certificates, checksums, content addressing all standardized on it. Q: What about SHA-3 and BLAKE3? A: SHA-3 is a standardized backup built on a completely different construction (sponge), valuable as insurance if SHA-2 ever cracks. BLAKE3 is a newer, extremely fast hash popular in tooling. Neither is a reason to avoid SHA-256 today; use them where your ecosystem already does. ## Unix timestamps: seconds vs milliseconds https://localfirsttools.com/guides/unix-seconds-vs-milliseconds Count the digits: 10 means seconds, 13 means milliseconds. Mixing them lands in January 1970 or the year 56637, two bug signatures explained. Count the digits: 10 means Unix seconds, 13 means milliseconds. Mixing them is a factor-of-1000 bug with two signatures — dates in January 1970 (seconds fed to a milliseconds API) or dates in the year 56637 (the reverse). Paste either into the Unix Timestamp Converter; it detects the unit by size. Unix time counts from 1970-01-01T00:00:00 UTC. One moment, two spellings: 2024-08-31T12:00:00Z is `1725105600` in seconds and `1725105600000` in milliseconds. Paste either into the [Unix Timestamp Converter](https://localfirsttools.com/timestamp); it detects the unit by size and shows the ISO date. Nothing is uploaded. ## Who uses which unit - **Seconds:** Unix `time()`, most REST APIs, JWT claims (`exp`, `iat`, `nbf`, see [decoding JWTs](https://localfirsttools.com/guides/how-to-decode-a-jwt)), cron-adjacent tooling, databases' `extract(epoch …)`, Stripe and most payment APIs. - **Milliseconds:** JavaScript, `Date.now()` and the `Date` constructor are ms end to end, plus Java's `System.currentTimeMillis()`, Kafka, MongoDB dates and most logging pipelines. The classic crash site is the boundary between a backend speaking seconds and a JavaScript frontend speaking milliseconds. ## Read the unit off the digit count ``` digits unit example example decoded 10 seconds 1725105600 2024-08-31T12:00:00Z 13 milliseconds 1725105600000 2024-08-31T12:00:00Z 16 microseconds 1725105600000000 (databases, tracing) 19 nanoseconds 1725105600000000000 (Go, Kafka log times) ``` The 10/13 split is stable for the entire era you will ever debug: seconds crossed from 9 to 10 digits in 2001 and stay 10-digit until 2286. Micro- and nanoseconds show up further down the stack, PostgreSQL stores timestamps in microseconds, Go's `time.Time` and Kafka speak nanoseconds, and produce the same class of bug at a factor of a million or a billion, with the same digit-count cure. ## The two bug signatures ``` new Date(1725105600) // 1970-01-20T23:11:45.600Z ← seconds fed as ms new Date(1725105600 * 1000) // 2024-08-31T12:00:00.000Z ← correct // And the mirror image, ms treated as seconds: // 1725105600000 s ≈ year 56637 ``` January 1970 means multiply by 1000; a five-digit year means divide. Memorize the pair and you will diagnose these bugs from the symptom alone, before reading any code. ## Converting safely In JavaScript: `Math.floor(Date.now() / 1000)` for current Unix seconds, and `new Date(seconds * 1000)` to go back. In Python, `time.time()` returns float seconds; JSON-encode integers, not floats, to avoid surprising consumers. At API boundaries, document the unit explicitly, a field named `created_at_ms` has prevented more incidents than any validator. For one-off checks, a JWT `exp`, a database row, a log line, the [Unix → ISO preset](https://localfirsttools.com/timestamp/unix-to-iso) and its [ISO → Unix](https://localfirsttools.com/timestamp/iso-to-unix) counterpart run in your browser. ## A real debugging walkthrough An API starts rejecting requests with 401s. You [decode the JWT](https://localfirsttools.com/guides/how-to-decode-a-jwt) and find `"exp": 1516242622`. Ten digits → seconds. Convert: [1516242622](https://localfirsttools.com/timestamp/unix-to-iso) is 2018-01-18T02:30:22Z, long past, token expired, the 401 is correct and the bug is in token refresh, not the API. Total diagnosis time: under a minute, and the only skill involved was recognizing the unit and converting. The same two moves solve “why is this row's `created_at` in 1970” (something divided by 1000 twice) and “why does the chart show one point in the year 56637” (something multiplied twice). ## Negative numbers and the year 2038 Unix time is signed, and both edges matter occasionally. Negative values are simply dates before 1970: `-86400` is 1969-12-31T00:00:00Z, and birthdates stored as Unix timestamps go negative for anyone born before the epoch, code that treats negative timestamps as invalid quietly rejects everyone over a certain age. In the other direction, systems that still hold seconds in a signed 32-bit integer overflow at `2147483647` (2038-01-19T03:14:07 UTC) wrapping to December 1901. Modern platforms moved to 64 bits long ago, but the bug survives in embedded devices, old file formats and databases with 32-bit integer columns; if you are choosing a column type today, that is the argument for `bigint` settled in one sentence. ## Two footnotes worth knowing Unix time is timezone-less, the same number everywhere on Earth; only its rendering as a wall-clock date involves a timezone, which is why the converter outputs ISO 8601 in UTC. And Unix time ignores leap seconds by definition (every day counts exactly 86,400), which is a feature for arithmetic and a rounding error you will never notice in application code. For recurring schedules rather than single moments, that is the realm of [cron expressions](https://localfirsttools.com/cron-explainer). ### FAQ Q: How do I tell if a timestamp is seconds or milliseconds? A: Count digits: dates near the present are 10 digits in seconds (1725105600) and 13 in milliseconds (1725105600000). That heuristic holds for every date between 2001 and 2286. Q: Why did my date show up as January 1970? A: A seconds value was fed to something expecting milliseconds, new Date(1725105600) in JavaScript is 1725105600 ms after the epoch, about 20 days into January 1970. Multiply by 1000. Q: Why did my date show up thousands of years in the future? A: The mirror-image bug: a milliseconds value was interpreted as seconds. 1725105600000 seconds is the year 56637. Divide by 1000. Q: What about the year 2038 problem? A: Signed 32-bit seconds counters overflow on 2038-01-19 at 03:14:07 UTC. Modern systems store Unix time in 64 bits, where the limit is astronomically far away; the issue survives mainly in old embedded systems and legacy file formats. Q: Are there microsecond and nanosecond timestamps too? A: Yes, 16 digits is microseconds (common in databases and tracing), 19 digits is nanoseconds (Go's time package, Kafka log timestamps). The digit-count trick extends naturally. Q: Can a Unix timestamp be negative? A: Yes, it means a moment before 1970-01-01T00:00:00 UTC. -86400 is the last day of 1969. Validation that rejects negative timestamps rejects real historical dates, like the birthdates of anyone born before 1970. ## How to fix “XML parsing error: not well-formed” https://localfirsttools.com/guides/xml-not-well-formed Not well-formed means a syntax break: mismatched tags, a raw ampersand, two roots or a BOM. Find the first error's line and column locally. “XML parsing error: not well-formed” means the parser stopped on syntax, not on your XSD. One root, matching tags, quoted attributes and escaped & < > are required. Paste the document into the XML Validator to get the first error's exact line and column. External DTDs are never fetched. Nothing is uploaded. “XML parsing error: not well-formed” means the parser gave up on syntax, not on your schema. One root, matching tags, quoted attributes and escaped `&` `` are required. Paste the document into the [XML Validator](https://localfirsttools.com/xml-formatter/validate) to get the first error's exact line and column; the [XML Formatter](https://localfirsttools.com/xml-formatter) uses the same check before it pretty-prints. Nothing is uploaded. ## What “not well-formed” actually asserts XML 1.0 splits two questions that people mash together. *Well-formed* means the document is legal XML: a single root, every start tag has a matching end tag, attribute values are quoted, and the five markup characters are escaped. *Valid* means the document also matches a DTD or XSD. Firefox's “XML Parsing Error: not well-formed”, Chrome's “error on line X at column Y”, Java's `SAXParseException` and Python's `xml.etree.ElementTree.ParseError` are almost always the first kind. Fix well-formedness before you argue with a schema. A well-formedness checker reports exactly one error per run. After a mismatched tag the rest of the document is unparseable by definition, so the line number is where the parser stopped, not always where you made the mistake. Look at that position and one tag backwards. ## The four mistakes that cause nearly every failure - **An unclosed or mismatched tag.** `…` is not well-formed: XML names are case-sensitive. A missing `` is usually reported at the parent's close, not on the line you forgot. - **A raw ampersand.** `Smith & Co` must be written `Smith & Co`. The same rule applies to `` in text. Attribute values that contain quotes need `"` or a different wrapping quote. - **More than one root element.** A document that is `` is a fragment, not a document. Wrap the siblings or split the file. SOAP envelopes already have one root; the error then is usually a second payload pasted after it. - **An illegal character at the start.** A UTF-8 BOM, a smart quote from email, or a stray byte before ` Structure & Interpretation ``` The `id` attribute is unquoted, the ampersand in the title is raw, the end tag uses different case from the start tag, and the root is never closed (the last line opens a second `catalog`). A parser will usually complain about the unquoted attribute first. Fix that, validate again, and the next error appears. The well-formed form is: ``` Structure & Interpretation ``` After it validates, [Format](https://localfirsttools.com/xml-formatter) re-indents without changing text, attributes, comments or CDATA. [Minify](https://localfirsttools.com/xml-formatter/minify) is the opposite action: it only removes whitespace between tags. ## How to read the line and column Parsers report where they gave up. A missing comma-equivalent in XML is a missing close tag: the error lands on the next unexpected token. If the status bar says “Line 12, column 3”, open the file at that line, then scan upward for the last unclosed start tag. The [validator](https://localfirsttools.com/xml-formatter/validate) highlights the reported line in the gutter so you do not have to count. An error at line 1, column 1 on a document that “looks fine” is usually invisible: a BOM (`EF BB BF`), a `0x00` from a UTF-16 file saved as UTF-8, or a curly quote `“` instead of `"`. Re-save as UTF-8 without BOM, or paste into the validator and look at the first character in a hex view if you have one. ## External entities are not the fix A `DOCTYPE` with `SYSTEM "http://…"` is a classic XXE shape. A browser or server that resolves it will fetch that URL while parsing. LocalFirstTools never fetches DTDs or external entities, so an XXE payload cannot load anything here, and a missing DTD is also not why your document is “not well-formed”. If a gateway rejects the file after it validates here, the gateway is doing schema or business-rule checks on top. ## Language-specific wording - **Browsers:** Firefox prints “XML Parsing Error: not well-formed” plus a column. Chrome's XML viewer prints “This page contains the following errors” and a location. - **Java:** `org.xml.sax.SAXParseException; lineNumber: 12; columnNumber: 3`. - **Python:** `xml.etree.ElementTree.ParseError: not well-formed (invalid token): line 12, column 2`. - **SOAP / SAML / e-invoice gateways:** often wrap the same parser error in a fault string. Extract the inner message and validate the payload alone. Once the document is well-formed, convert it if you need JSON fixtures with [XML → JSON](https://localfirsttools.com/json-xml/xml-to-json), or keep working in XML with the [formatter](https://localfirsttools.com/xml-formatter). Related reading: [JSON vs YAML vs XML](https://localfirsttools.com/guides/json-vs-yaml-vs-xml). ## A five-minute checklist - Copy only the XML, not the HTTP headers or the email wrapper around it. - Paste it into the [XML Validator](https://localfirsttools.com/xml-formatter/validate). If the status is green, the gateway's complaint is schema or business rules, not well-formedness. - If it is red, read the line and column, fix that token, validate again. Repeat. - Check the first bytes if the error is at 1:1: re-save as UTF-8 without BOM, replace smart quotes, confirm the file is not UTF-16. - When it is green, format it for the review and, if you need fixtures, convert a copy with [XML → JSON](https://localfirsttools.com/json-xml/xml-to-json). Keep the original XML if byte identity matters. Namespaces look scary and almost never cause “not well-formed”. `` is fine. An unbound prefix is a namespace error some parsers report as a fatal error and others as well-formed-but-invalid; add the `xmlns:` declaration on an ancestor if the status names an undeclared prefix. Processing instructions (``) and comments can sit before the root, but text cannot. A stray “Yes,” from a mail client above the declaration is line 1, column 1 again. Self-closing tags (`` vs ``) are both well-formed. Empty elements are not the bug. Mixed content is well-formed too: `See above` is legal XML and the formatter will leave that inner whitespace alone when `xml:space="preserve"` is set. If a pretty-printer then “breaks” a poem or a signature block, the printer was collapsing significant space; this site's formatter does not. You do not need a server, a Java toolchain or `xmllint` installed to get through the first error. Those tools are excellent once you are in a repo. For a paste from a ticket, the local validator is enough, and it will not fetch the DTD a hostile payload pointed at. ### FAQ Q: What does “XML parsing error: not well-formed” mean? A: The parser stopped because the document is not well-formed XML: mismatched tags, a raw ampersand, more than one root, or a similar syntax break. It is not an XSD/DTD schema error. Q: How do I find the exact line? A: Paste the document into the XML Validator. The status bar reports the first error's line and column, which is where the parser gave up, often one tag after the real mistake. Q: Is a UTF-8 BOM a well-formedness error? A: Some parsers treat a BOM at byte 0 as illegal text before the declaration. If the error is at line 1, column 1 on a document that looks fine, strip the BOM and try again. Q: Does this tool fetch my DTD? A: No. External entities and DTDs are never fetched, so an XXE-style SYSTEM identifier cannot load anything. That also means this is not a schema validator. Q: Can I paste SOAP or SAML XML here? A: Yes. The document stays in this tab. Prefer a redacted or test payload when the assertion contains session data. Q: What is the difference between well-formed and valid? A: Well-formed means it follows XML syntax. Valid means it also matches an XSD or DTD. Fix well-formedness first; schema validity is a later check this page does not run. ## JSON vs YAML vs XML: differences and when to use each https://localfirsttools.com/guides/json-vs-yaml-vs-xml JSON for APIs, YAML for human-edited config, XML for SOAP, SAML and schemas. How the data models differ and how to convert locally. JSON is the interchange format of the web, YAML is its human-edited superset, and XML is a document format with attributes, namespaces and mixed content. Use the format the other system already speaks. Convert copies locally with JSON ↔ XML, JSON ↔ YAML and the XML Formatter; comments and declarations can drop. JSON, YAML and XML all describe trees, but they are not interchangeable spellings of the same idea. JSON is the interchange format of the web. YAML is JSON's superset for humans editing configuration. XML is a document format with attributes, namespaces and mixed content, still required by SOAP, SAML, invoices and a long tail of enterprise schemas. Use the format the other system already speaks, then convert locally with [JSON ↔ XML](https://localfirsttools.com/json-xml), [JSON ↔ YAML](https://localfirsttools.com/json-yaml) or the [XML Formatter](https://localfirsttools.com/xml-formatter). ## The same record in all three ``` JSON: {"book":{"id":"bk101","title":"SICP","price":39.95,"inStock":true}} YAML: book: id: bk101 title: SICP price: 39.95 inStock: true XML: SICP 39.95 true ``` JSON puts `id` in the object. XML can put it on the element as an attribute. YAML looks like JSON without the braces until implicit typing bites you: unquoted `no` becomes `false`, and `007` becomes `7`. Quote anything that must stay a string. Converting YAML to JSON is a quick audit for those surprises; see [JSON vs YAML vs CSV](https://localfirsttools.com/guides/json-vs-yaml-vs-csv) when the third format is a table rather than a document. ## Data model, not syntax JSON's model is tiny: object, array, string, number, boolean, null. There are no comments in RFC 8259, no attributes, and no distinction between “element” and “text”. That is why JSON parsers are fast and why two serializers that agree on the model produce documents you can [compare structurally](https://localfirsttools.com/json-diff). YAML 1.2 can represent every JSON value and then some: comments, anchors and aliases, multi-line scalars, and tags. The cost is implicit typing and a parser that is orders of magnitude slower than JSON. YAML belongs in files humans edit (Kubernetes manifests, GitHub Actions, app config), not on the hot path of an API. XML's model is a tree of named elements. Attributes are a second slot on each element. Text and child elements can mix in the same parent (mixed content: `Use *this*. `). Namespaces let two vocabularies share a document. Comments, processing instructions, CDATA and a DOCTYPE exist. None of those have a JSON equivalent, so XML → JSON is a lossy convention, not a round-trip identity. The convention this site uses is the common one: attributes become keys prefixed with `@_`, repeated sibling tags become arrays, text becomes a value. Try it on [XML to JSON](https://localfirsttools.com/json-xml/xml-to-json). ## When to use each - **JSON** for APIs, logs, browser payloads, and anything a program exchanges. Validate and pretty-print with the [JSON Formatter](https://localfirsttools.com/json-formatter). Compact with the [JSON Minifier](https://localfirsttools.com/json-minifier). - **YAML** for configuration humans edit. Convert to JSON when you need to see what the implicit types actually became, then convert back. - **XML** when the other side already requires it: SOAP, SAML, UBL/Peppol, Maven, Android, RSS/Atom, Office custom XML. Format and check well-formedness with the [XML Formatter](https://localfirsttools.com/xml-formatter). If a gateway says “not well-formed”, start with [how to fix that error](https://localfirsttools.com/guides/xml-not-well-formed). Do not invent an XML API for a new service in 2026 unless you are extending an XML-only ecosystem. Do not store a CRM export as XML if [CSV](https://localfirsttools.com/csv-viewer) or JSON objects will do: attributes and mixed content are overhead you will pay on every row. ## Conversion is a convention `SICP` becomes roughly: ``` { "book": { "@_id": "bk101", "title": "SICP" } } ``` A single child cannot be told apart from a one-item list. Comments and the XML declaration disappear. CDATA becomes ordinary text. Going back with [JSON → XML](https://localfirsttools.com/json-xml) rebuilds a document that is equivalent for data, not byte-identical. If you need a byte-stable XML file, keep the XML and only convert a copy. YAML → JSON is closer to lossless for data (anchors expand into copies) but drops comments. JSON → YAML is usually tidy. CSV is a different shape entirely: rows and columns, no nesting. Open a spreadsheet export in the [CSV Viewer](https://localfirsttools.com/csv-viewer) before you decide it should become JSON objects. ## A practical decision order 1. If an existing contract names the format, use that format. 2. If humans will edit the file weekly, YAML (or JSONC in editors that allow comments). 3. If programs exchange it over HTTP, JSON. 4. If the document has mixed content, attributes that are not just fields, or a mandated schema, XML. 5. If it is a table, CSV, then convert. All of the conversions on this site run in your browser. Production API responses, SAML assertions and payroll CSVs stay on the device. ## Namespaces, schemas and why XML is still here XML survived because it can name things in more than one vocabulary at once. A SAML assertion embeds XML Signature and XML Encryption elements under different namespace URIs. A UBL invoice mixes common aggregate components with country-specific extensions. JSON can fake that with nested objects, but it has no standard equivalent of `xmlns` and no widely deployed schema language with the same reach as XSD. JSON Schema exists and is useful; it is not what a Peppol access point validates against. That is also why “just convert everything to JSON” fails as a migration plan. You can convert a SOAP body for a test fixture. You cannot drop XML from a network that already speaks it. Keep a well-formedness check in the loop: [validate](https://localfirsttools.com/xml-formatter/validate), then convert a copy. If the JSON looks “too nested”, the XML used wrapper elements for lists. That is faithful, not a bug. Comments are a three-way split. JSON has none (RFC 8259). YAML has `#` to end-of-line. XML has ``. If the comments are the point of the file (a hand-edited config), YAML wins. If you need them after a round trip through an API, do not convert; the API will drop them. CDATA in XML is a quoting device, not a type: ` for emphasis]]>` is still text. Numbers and booleans are another split. JSON has real types. YAML guesses. XML is text until a schema says otherwise: `39.95` is a string to a well-formedness parser. After XML → JSON, a converter may coerce that to a number or leave it as a string. Check the JSON before you treat it as typed data, the same way you would after [CSV → JSON](https://localfirsttools.com/json-csv/csv-to-json). If you are choosing for a greenfield service: JSON on the wire, YAML in the repo, CSV for exports, XML only when a counterpart already requires it. Format each one locally before you commit or send it. ### FAQ Q: Is JSON a subset of XML? A: No. JSON is objects, arrays, strings, numbers, booleans and null. XML is a tree of named elements with attributes, text, comments, CDATA and namespaces. You can convert between them, but the mapping is a convention, not an identity. Q: When should I pick XML over JSON? A: When the ecosystem already speaks XML: SOAP, SAML, UBL invoices, Maven POMs, Android manifests, RSS/Atom. Those formats have schemas, namespaces and mixed content that JSON does not model cleanly. Q: Is every JSON document valid YAML? A: Yes. YAML 1.2's data model is a superset of JSON's, so valid JSON parses as YAML. The reverse is false: anchors, comments and multi-line scalars have no JSON form. Q: Why do XML attributes become @_ keys in JSON? A: JSON has no attribute slot. The de-facto interchange used by fast-xml-parser prefixes attributes with @_, so becomes {"@_id":"bk101"}. A round trip can drop comments and the XML declaration. Q: Which format is fastest to parse? A: JSON, by a wide margin. YAML parsers are slower and do implicit typing. XML parsers are mature but pay for namespaces, entities and mixed content. Pick the format the other side already uses. Q: Can I convert XML to JSON in the browser? A: Yes. The XML → JSON converter and the XML Formatter both run in this tab. Comments, CDATA wrappers and the XML declaration drop because JSON has no syntax for them. ## What is a JWT? Structure, claims and how to decode one safely https://localfirsttools.com/guides/what-is-a-jwt A JWT is three Base64URL segments: header, payload and signature. Anyone can read the claims. Decode locally; never treat decode as verification. A JWT is three Base64URL segments joined by dots: header, payload and signature. The first two are encoded JSON, not encrypted, so anyone can read exp, sub and custom claims. Decode a token in your browser, never on a server you do not run, and never treat decoding as verification. No key is required to read one. A JWT (JSON Web Token, RFC 7519) is a compact way to carry claims between two parties. It looks like `xxxxx.yyyyy.zzzzz`: a header, a payload and a signature, each Base64URL-encoded and joined by dots. The important fact is that the first two segments are encoded, not encrypted. Anyone who sees the token can read the claims. Paste a sample into the [JWT Decoder](https://localfirsttools.com/jwt-decoder) or the [payload inspector](https://localfirsttools.com/jwt-decoder/inspect-payload) to see that for yourself; the token never leaves this tab. Decoding is not verification. ## The three segments ``` eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 . eyJzdWIiOiI0MiIsImV4cCI6MTc2NzIyNTYwMCwiaXNzIjoiaHR0cHM6Ly9pZHAifQ . SflKxwRJ... ``` - **Header.** Metadata, typically `{"alg":"HS256","typ":"JWT"}`. `alg` names the signature algorithm. A verifier must allowlist algorithms; `none` is a legal alg that means “unsigned”. - **Payload.** The claims. Registered ones (`sub`, `exp`, `iat`, `iss`, `aud`) plus whatever custom fields the issuer added (roles, tenant, email). - **Signature.** Bytes that prove the first two segments were not modified and were produced by someone who holds the key. It does not hide the payload. Four dots (five segments) means an encrypted JWE. The payload is genuinely encrypted; no decoder can show it without the key. If a paste fails with “A JWT has three segments separated by dots”, count the dots before you blame Base64. ## Registered claims you will actually see - `iss` — who issued the token (often an HTTPS issuer URL). - `sub` — the subject, usually a user or client id. - `aud` — the intended audience. An API should reject a token whose aud is someone else's API. - `exp` — expiry, Unix seconds. `1767225600` is `2026-01-01T00:00:00Z`. Convert it with the [Unix Timestamp Converter](https://localfirsttools.com/timestamp). If `exp` is in the past, a 401 is the server being correct. - `nbf` — not before. A token used too early is also invalid. - `iat` — issued-at, same unit as exp. - `jti` — a unique id, used for revocation lists. Custom claims are just more JSON keys. The usual debugging question is “did the identity provider actually add the claim my code reads?” Decode the payload and look. Nine times out of ten the claim is missing or named slightly differently (`role` vs `roles`, `email` vs `preferred_username`). ## How to decode one safely Safe means two things: you do not leak the token, and you do not treat decode as trust. The mechanical steps are in [How to decode a JWT](https://localfirsttools.com/guides/how-to-decode-a-jwt). In short: split on dots, Base64URL-decode the first two segments (swap `-`/`_` back to `+`/`/` if you use `atob`), then `JSON.parse`. Segments are Base64URL, not standard Base64; see [Base64 vs Base64URL](https://localfirsttools.com/guides/base64-vs-base64url). ``` const [header, payload] = token.split("."); const json = (seg) => JSON.parse( atob(seg.replace(/-/g, "+").replace(/_/g, "/")) ); json(header); json(payload); ``` Do this on your machine or in a client-side tool. A JWT in a server log, a support ticket or a “helpful” online debugger that posts the token is a credential you just shared. The [decoder on this site](https://localfirsttools.com/jwt-decoder) runs as JavaScript in your tab: after the page has loaded, DevTools → Network should stay quiet when you paste. ## What decoding does not do It does not check the signature. It does not prove the token came from your identity provider. It does not prove `exp` will be honoured by the server (the server might use its own clock or ignore exp). It does not decrypt a JWE. Treat the output as “this is what the token says”, not “this token is valid”. Verification belongs on the server, with a library (jose, PyJWT, java-jwt) and the issuer's JWKS or shared secret. Allowlist `alg`. Check `iss`, `aud` and `exp`. Never accept `alg: none`. Next step: paste a test token into the [payload inspector](https://localfirsttools.com/jwt-decoder/inspect-payload), then convert `exp` with the [Unix → ISO](https://localfirsttools.com/timestamp/unix-to-iso) preset. ## A worked payload This expired HS256 token is the one every JWT tutorial uses. It is safe to paste anywhere because it was never a live credential: ``` eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c ``` The header decodes to `{"alg":"HS256","typ":"JWT"}`. The payload decodes to `sub=1234567890`, `name=John Doe`, `iat=1516239022` (2018-01-18). There is no `exp`, so the token never expires unless the server invents a lifetime. That missing exp is itself a finding: a long-lived access token in a browser is a gift to anyone who copies it. HS256 means “HMAC with SHA-256 and a shared secret”. RS256 and ES256 mean public-key signatures; the verifier uses a JWKS URL, not a password. The header's `kid` (key id), when present, tells the verifier which JWK to pick. None of that is required to read the payload. It is required to trust it. Unencrypted JWTs should never carry passwords, session cookies you would not log, or government identifiers. If the claim is confidential, use JWE or do not put it in the token: look it up server-side from `sub`. If you must debug a production token, decode it locally, then rotate it. A screenshot of a JWT in Slack is a live credential until it expires. Compare this overview with the step-by-step [decode guide](https://localfirsttools.com/guides/how-to-decode-a-jwt) when you want the exact `atob` incantation, and with the [jwt.io comparison](https://localfirsttools.com/compare/jwt-io) when you need signature verification rather than a local read. ### FAQ Q: What is a JWT? A: A JSON Web Token (RFC 7519) is three Base64URL segments joined by dots: header, payload and signature. The first two are encoded JSON, not encrypted, so anyone can read the claims. The third segment is proof, not a secret decoder ring. Q: Is it safe to paste a real token into a decoder? A: Only if decoding happens in your tab. A live JWT is a bearer credential. The LocalFirstTools decoder never uploads the token; prefer expired or test tokens when you can, and never paste tokens into tools that send them to a server. Q: What are registered claims? A: iss (issuer), sub (subject), aud (audience), exp (expiry), nbf (not-before), iat (issued-at) and jti (JWT ID). They are optional by the spec but almost every identity provider sets exp, iat and sub. Timestamps are Unix seconds. Q: Does decoding verify the signature? A: No. Decoding Base64URL-decodes the header and payload. Verification needs the issuer's key and a library that checks the signature plus exp, iss and aud. A browser tool should not hold that key. Q: Why can I read a JWT without a key? A: A signed JWT (JWS) is encoded, not encrypted. Base64URL is reversible. Confidential claims need an encrypted JWE (five segments), which no decoder can show without the key. Q: How is this different from the how-to-decode guide? A: That guide is the mechanical steps. This one is the data model: structure, registered and custom claims, and the safety rules for inspecting a token. Use both; then inspect a sample in the JWT Decoder. ## How to format SQL queries: style rules that make SQL readable https://localfirsttools.com/guides/format-sql-queries One clause per line, one keyword case, stacked joins, and a dialect that matches the log. Format SQL locally so literals never leave the device. Readable SQL puts each major clause on its own line, uses one keyword case, and stacks joins so a missing ON is obvious. Formatting changes only whitespace and, if you ask, keyword case: strings and comments stay as written. Use the SQL Formatter in your browser; schema names and customer literals are never uploaded. Readable SQL is a house style, not a standard. The rules that survive contact with a review are simple: one major clause per line, a single keyword case, joins stacked instead of nested in a paragraph, and commas that do not jump sides mid-list. Paste a query into the [SQL Formatter](https://localfirsttools.com/sql-formatter) (or the [PostgreSQL preset](https://localfirsttools.com/sql-formatter/postgresql)) to apply indent and keyword case in your browser. Schema names and literals never leave the tab. ## Start from a blob you cannot read A typical log line looks like this: ``` select u.id, u.email, o.total from users u join orders o on o.user_id = u.id where o.created_at > '2026-01-01' and u.status = 'active' order by o.total desc; ``` After format with 2-space indent and upper keywords, the same statement is a checklist you can review: ``` SELECT u.id, u.email, o.total FROM users u JOIN orders o ON o.user_id = u.id WHERE o.created_at > '2026-01-01' AND u.status = 'active' ORDER BY o.total DESC; ``` Nothing about the plan changed. Leading-comma versus trailing-comma is taste; mixing them in one list is the bug. The formatter's job is to make the clauses visible so you can see a missing join or a `WHERE` that filters the wrong alias. ## Style rules that pay rent - **One clause per line.** `SELECT`, `FROM`, `JOIN`, `WHERE`, `GROUP BY`, `HAVING`, `ORDER BY`, `LIMIT`. Subqueries get their own indent level, not a new paragraph style. - **Pick a keyword case and keep it.** Upper is the default in most style guides because it separates keywords from identifiers. Lower is fine if the codebase already uses it. Do not uppercase table names: in PostgreSQL, unquoted identifiers fold to lowercase. - **Stack joins.** Each `JOIN … ON` on its own line, aligned under `FROM`. A join hidden in a 200-character line is how a Cartesian product ships. - **Put each selected expression on its own line** once the list is longer than three items. Alias every computed column (`AS total_cents`). - **Keep predicates in WHERE, not in ON, unless they are join keys.** Filter predicates in `ON` change inner vs outer join meaning. Formatting will not catch that; seeing the lines will. - **CTEs read top-down.** Name them for the result, not for the table they started from. A chain of `WITH x AS (…), y AS (…)` should be formatted as stacked blocks, each `AS (` opening a new indent. ## Dialect is not decoration Standard SQL, PostgreSQL, MySQL, SQL Server, SQLite and BigQuery disagree about quoting and a handful of clauses. Dollar-quotes (`$$ … $$`) are PostgreSQL. `[Identifier]` is T-SQL. Backticks are MySQL and BigQuery. `RETURNING` and `DISTINCT ON` are PostgreSQL. If you format a Postgres function body as MySQL, the dollar-quote can be split and the statement becomes illegal. Choose the dialect that matches the log; the [PostgreSQL formatter](https://localfirsttools.com/sql-formatter/postgresql) locks that choice for the common case. Minify is the inverse: it collapses insignificant whitespace and leaves strings and comments byte-for-byte. Use it when a ticket template wants one line, not when you are reading. Compare two formatted versions in the [Diff Checker](https://localfirsttools.com/text-diff) rather than eyeballing log wraps. ## What formatting will not fix - A wrong join type or a filter in the wrong clause. - Identifier case bugs in PostgreSQL: `"Users"` and `users` are different names. - Bind-parameter style. `$1`, `?` and `@p1` stay as written. - An unclosed quote or comment. The formatter then shows a friendly parse error; fix that first, the same way you fix [not well-formed XML](https://localfirsttools.com/guides/xml-not-well-formed). House style documents (SQLFluff, pgFormatter, your team's wiki) can encode more rules: leading commas, 120-character wrap, reserved-word quoting. Use the browser formatter to get to a readable baseline, then apply the linter in CI. Do not paste production dumps that contain personal data into a server-side beautifier; use a local tool. Next: format a query on the [SQL Formatter](https://localfirsttools.com/sql-formatter), switch keyword case to Upper, then copy the result into a review. If you need identifiers renamed, the [Case Converter](https://localfirsttools.com/case-converter) sits next to it. ## A longer example: CTE plus window Window functions and common table expressions are where unformatted SQL becomes unreviewable. This is a compact original: ``` with ranked as (select u.id, u.email, o.total, row_number() over (partition by u.id order by o.total desc) as rn from users u join orders o on o.user_id = u.id where o.created_at > date '2026-01-01') select id, email, total from ranked where rn = 1 order by total desc; ``` The same query, formatted, shows the partition, the filter and the outer consume-step: ``` WITH ranked AS ( SELECT u.id, u.email, o.total, ROW_NUMBER() OVER ( PARTITION BY u.id ORDER BY o.total DESC ) AS rn FROM users u JOIN orders o ON o.user_id = u.id WHERE o.created_at > DATE '2026-01-01' ) SELECT id, email, total FROM ranked WHERE rn = 1 ORDER BY total DESC; ``` You can now see that `rn = 1` is “latest order per user” only because `ORDER BY o.total DESC` is inside the window, not in the outer query. That distinction disappears in the one-line form. If the dialect is PostgreSQL, `DATE '2026-01-01'` and `ROW_NUMBER()` stay intact; a MySQL pass may still format it, but you should not rely on another dialect's keyword list when the production server is Postgres. Comments deserve the same care as strings. A `-- trailing comment` must stay on its line after minify or it will eat the next clause. Block comments (`/* … */`) must close. The formatter treats both as opaque, which is what you want when the comment contains a copied plan or a ticket id. If two environments disagree after a format, they disagreed before: key order does not apply to SQL, but identifier quoting and Unicode spaces do. Diff the formatted texts, not the log wraps, and keep the customer's literals off any server that is not the database. ### FAQ Q: How should I format a SQL query? A: One clause per line (SELECT, FROM, JOIN, WHERE, GROUP BY, HAVING, ORDER BY), keywords in a consistent case, joins stacked and aligned, and commas at the start or end of the line but not mixed. The SQL Formatter applies those rules in your browser. Q: Does formatting change the query? A: Only whitespace and, if you ask, keyword case. String literals, comments and dollar-quoted bodies stay as written, including spaces inside 'Ada Lovelace'. Q: Which keyword case should I use? A: Upper is the common house style (SELECT, FROM). Lower is used by some codebases. Preserve keeps whatever you pasted. Pick one per project and stay there. Q: Why pick a dialect? A: PostgreSQL dollar-quotes, T-SQL brackets, BigQuery backticks and MySQL backticks are not Standard SQL. The wrong dialect splits those tokens or reports a parse error on a legal query. Q: Is it safe to paste a query from a slow-query log? A: Yes, if the formatter runs in your tab. Schema names and customer literals in the log never leave the device on this site. Prefer a redacted sample when the WHERE clause is personal data. Q: What does Minify do to SQL? A: It collapses insignificant whitespace so the statement fits on one log line, without touching strings or comments. Use Format to read; Minify to ship a compact snippet. ## What is UUID v7? https://localfirsttools.com/guides/what-is-uuid-v7 UUID v7 is a 128-bit RFC 9562 identifier whose first 48 bits are a Unix millisecond timestamp, so IDs sort by creation time. Mint one locally. UUID v7 is a 128-bit identifier, standardized in RFC 9562, whose first 48 bits are a Unix millisecond timestamp so the IDs sort by creation time as plain strings. The remaining bits are random. Choose v7 for database primary keys; choose v4 when the creation instant must stay private. Mint both locally in the UUID Generator. A UUID (Universally Unique Identifier) is a 128-bit value usually written as 32 hex digits in five groups: `xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx`. The version lives in the nibble marked `M`. When that nibble is `7`, the ID is UUID version 7, defined in RFC 9562 (May 2024). Paste nothing into a server to obtain one: the [UUID v7 generator](https://localfirsttools.com/uuid-generator/v7) builds them in this tab. ## The layout, bit by bit RFC 9562 section 5.7 places a 48-bit Unix timestamp in milliseconds at the front of the ID, then a 4-bit version, a 12-bit `rand_a` field, a 2-bit RFC 4122 variant, and 62 bits of `rand_b`. Adding the two random fields gives 74 random bits per millisecond. The hyphenated string is still 36 characters; the hex-only form is 32 characters. Those lengths are recorded on [the stats page](https://localfirsttools.com/stats) with the rest of this site's first-party figures. ``` 0198f0a4-7e12-7cc3-9a41-d0b3a1c65f02 \______/ \__/ \__/ \__/ \__________/ 48-bit 16 ver var random unix_ts_ms bits nibble bits ``` Index 14 of the hyphenated string (0-based character 14) is always `7` for a well-formed v7. Index 19 is one of `8`, `9`, `a` or `b`, the RFC variant. If those positions are wrong, you are not looking at v7. ## Why time-ordered IDs exist Random primary keys (UUID v4) scatter inserts across a B-tree. New rows land on random pages, which means more cache misses and more fragmentation as the table grows. A time-ordered key inserts near the right-hand edge, like a sequence, while remaining unique across machines that do not share a sequence counter. That is the job v7 was specified to do. Sequential integers are still smaller (8 bytes versus 16) on a single database; v7 wins when IDs must be minted in many processes without coordination. Because the timestamp is the prefix, sorting v7 strings as text matches sorting by creation time, with the random tail breaking ties inside the same millisecond. The generator on this site also reseeds a 12-bit counter when the millisecond changes and increments it inside a batch, so 1 to 1,000 IDs from one click stay monotonic even if they share a millisecond. Details live in the [v4 vs v7 comparison](https://localfirsttools.com/guides/uuid-v4-vs-v7). ## Collision space, computed not guessed A v7 ID has 74 random bits per millisecond. 274 is exactly 18889465931478580854784 possible values in that millisecond, about 1.88 × 10^22. A v4 ID has 122 random bits: 5316911983139663491615228241121378304 values, about 5.31 × 10^36. Those integers were computed with BigInt in the same script that measures JSON minify savings; they are not rounded folklore. Birthday-bound collisions inside one millisecond at 74 bits remain negligible for any realistic insert rate. ## The timestamp is readable on purpose Opaque IDs are a privacy feature. v7 gives that up for index locality. Anyone holding `0198f0a4-7e12-7cc3-9a41-d0b3a1c65f02` can run: ``` parseInt("0198f0a47e12", 16) // Unix milliseconds // then convert with the timestamp tool ``` That is a feature for debugging (“when was this row created?”) and a defect if the ID is in a public URL and creation time is sensitive. In that case mint [v4](https://localfirsttools.com/uuid-generator) instead. The [Unix Timestamp Converter](https://localfirsttools.com/timestamp) turns the millisecond value into ISO 8601. ## Support and how to mint one locally RFC 9562 is recent. PostgreSQL 18 adds `uuidv7()`. Libraries exist for the major languages. `crypto.randomUUID()` in browsers still returns v4 only, so a web generator has to assemble v7 from `Date.now()` and `crypto.getRandomValues()`, which is what this site does. No ID is requested from a server. After the page has loaded you can disconnect and keep clicking Generate. - Open the [UUID v7 preset](https://localfirsttools.com/uuid-generator/v7). - Choose a count (1 to 1,000) and any GUID formatting you need. - Copy the IDs. Nothing is uploaded; watch the network tab if you want proof. Do not migrate existing v4 primary keys to v7 for sport. The index win applies to new inserts. Changing a live primary key is a project, not a format tweak. New tables are where the choice is cheap. ## What UUID v7 is not - It is not encryption. The timestamp is plaintext in the ID. - It is not a capability. Knowing an ID does not prove you were meant to hold it. - It is not ULID, although both put time first. ULID uses a different alphabet (Crockford Base32) and a 48-bit timestamp as well; UUID v7 stays in the UUID hex format so existing UUID columns keep working. - It is not a hash of the row. If you need a fingerprint of content, use the [hash generator](https://localfirsttools.com/hash-generator), not an ID. The variant bits (the two high bits of byte 8) are required by RFC 4122 so that a UUID is distinguishable from other 128-bit layouts. A library that ignores them might still parse the timestamp, but it is no longer a UUID in the RFC sense. When you generate IDs here, both the version nibble and the variant are set correctly on every click. Next: mint a handful of v7 IDs, then decode the prefix with the timestamp tool, then read [UUID v4 vs v7](https://localfirsttools.com/guides/uuid-v4-vs-v7) if you are choosing a primary key. The bit counts on [/stats](https://localfirsttools.com/stats) are the citeable numbers for the collision space. ### FAQ Q: What is UUID v7? A: UUID v7 is a 128-bit identifier, standardized in RFC 9562, whose first 48 bits are a Unix millisecond timestamp so the IDs sort by creation time as plain strings. The rest is random. It is not a secret and it is not a capability token. Q: How is UUID v7 different from UUID v4? A: v4 is 122 bits of randomness and reveals nothing about when it was minted. v7 spends 48 bits on a millisecond timestamp and 74 bits on randomness per millisecond, so sorting the strings reproduces creation order. See UUID v4 vs v7 for the database-key trade-off. Q: Can two UUID v7 values collide? A: Practically no. In the same millisecond there are still 74 random bits, 1.88 × 10^22 possibilities. Collisions need the same millisecond and the same random bits. The generator on this site also keeps a batch monotonic with a 12-bit counter. Q: Does a v7 UUID leak the creation time? A: Yes. Anyone who sees the ID can read the first 12 hex digits as Unix milliseconds. If signup time in a URL is a problem, use v4 instead. Q: How do I generate a UUID v7 in the browser? A: Open the UUID Generator, pick v7, and click Generate. IDs are minted with Date.now() and crypto.getRandomValues() in this tab. The browser's crypto.randomUUID() still only produces v4. Q: How do I read the timestamp out of a v7 UUID? A: Strip the hyphens, take the first 12 hex characters, parse as an integer: parseInt(uuid.replaceAll('-','').slice(0,12), 16). Convert that millisecond value with the Unix Timestamp Converter. ## What is Base64 encoding? https://localfirsttools.com/guides/what-is-base64-encoding Base64 turns every three bytes into four ASCII characters (RFC 4648). It is encoding, not encryption. Encode or decode in your browser; nothing is uploaded. Base64 encoding is a reversible mapping, defined in RFC 4648, that turns every three bytes of binary data into four ASCII characters from a 64-character alphabet so the bytes can travel through text-only channels. It is not encryption. Encode or decode in the Base64 tool; nothing is uploaded. Computers store bytes. Email, JSON, URLs and many logs store text. Base64 is the oldest widely deployed way to drag bytes through a text channel without them being mistaken for line breaks or non-ASCII. RFC 4648 section 4 is the modern spec. The [Base64 tool](https://localfirsttools.com/base64) implements it in your browser: text or a file in, encoded or decoded bytes out, nothing uploaded. ## The 4-for-3 rule The alphabet has 64 characters, A–Z, a–z, 0–9, plus `+` and `/`. Sixty-four symbols are 6 bits each. Three input bytes are 24 bits, which split into four 6-bit indices. Four output characters for three input bytes is a 4/3 ratio: exactly +33.3% before padding. That identity is why a 3-byte input becomes 4 characters and a 300,000-byte file becomes 400,000 characters, both computed by the same `base64Length` helper the tool uses and published on [/stats](https://localfirsttools.com/stats). ``` bytes: M a n 01001101 01100001 01101110 groups: 010011 010110 000101 101110 chars: T W F u ``` Leftover bytes are padded with `=` so the output length stays a multiple of four. One leftover byte produces two padding characters; two leftover bytes produce one. Short strings therefore sit slightly above +33.3%. Encoding every file in this site's 26-document JSON corpus produced a median overhead of +33.5% (min +33.3%, max +34.1%). The spread above 33.3% is padding, not a different algorithm. ## It is encoding, not cryptography Reversing Base64 requires no key. If a token, image or document is only Base64-wrapped, anyone who sees the wrapper can recover the bytes. JWTs look opaque because they are Base64URL, not because they are encrypted: the payload is readable after a decode. Use a cipher if the bytes are confidential. Use Base64 when a protocol refuses raw binary. Hex is the sibling encoding that uses two characters per byte (exactly 2×). Base64 is denser (4/3×) and still ASCII. Hex is easier to eyeball; Base64 is what data URLs, PEM certificates and MIME bodies use. Convert either locally with [hex](https://localfirsttools.com/hex) or [Base64](https://localfirsttools.com/base64). ## Base64URL, JWTs and atob() URLs and filenames treat `+`, `/` and `=` as special. RFC 4648 section 5 defines Base64URL: the same 6-bit mapping with `-` and `_` in the last two alphabet slots, and padding usually omitted. JWT header and payload segments are Base64URL. That is why `atob(segment)` throws until you swap the alphabet and restore padding. The mechanical conversion is in [Base64 vs Base64URL](https://localfirsttools.com/guides/base64-vs-base64url); the [JWT Decoder](https://localfirsttools.com/jwt-decoder) does it for you. ``` atob(seg.replace(/-/g, "+").replace(/_/g, "/") + "==".slice(0, (4 - seg.length % 4) % 4)) ``` ## data: URLs and files A data URL is a Base64 body with a MIME type on the front: `data:image/png;base64,iVBOR…`. Inlining a large image that way costs the +33.3% plus the HTTP overhead of a giant HTML document, which is why production sites usually keep binaries as files. For a small icon, or for pasting an image into a JSON fixture, the file mode on this site's Base64 page reads up to 50 MB in the tab, sniffs PNG, JPEG, GIF, WebP and PDF magic bytes on the way back, and never posts the file. - Open [Base64](https://localfirsttools.com/base64). Text mode for strings; file mode for binaries. - Toggle URL-safe if the alphabet uses - and _ (JWTs, WebAuthn, PKCE). - Wrap 76 if you need MIME line length (RFC 2045). - Decode: paste or drop. Non-UTF-8 bytes show as hex rather than mojibake. ## What Base64 is not - Not compression. Output is larger, always, except for the empty input. - Not a checksum. Use SHA-256 on the [hash generator](https://localfirsttools.com/hash-generator). - Not a substitute for percent-encoding in a query string. URLs that must carry binary usually want Base64URL or [percent-encoding](https://localfirsttools.com/url-encode), depending on the protocol. - Not “safe to paste anywhere.” A Base64 blob of a customer export is still the customer export. Decode it locally. ## Line wrapping and PEM MIME (RFC 2045) wraps Base64 at 76 characters so mail gateways do not smash long lines. PEM certificates wrap at 64. The wrap toggle on this site's encoder inserts CRLF or LF according to the width you pick; decoding ignores whitespace, so a wrapped body and a single-line body are the same bytes. If a paste fails, strip whitespace first, then check whether you are looking at Base64 or Base64URL. A PEM file is a Base64 body between `BEGIN` and `END` labels. The labels are not part of the encoding. Copy only the alphabet characters into the decoder, or drop the .pem/.crt file in file mode and let the tool read the bytes. Private keys pasted into a server-side decoder are still private keys; keep that work in the tab. Cite the +33.3% ratio and the +33.5% corpus median from [/stats](https://localfirsttools.com/stats) if you need a first-party figure. Then encode a sample in the tool and watch the network tab stay quiet. ### FAQ Q: What is Base64 encoding? A: Base64 encoding is a reversible mapping, defined in RFC 4648, that turns every three bytes of binary data into four ASCII characters from a 64-character alphabet so the bytes can travel through text-only channels. It is not encryption and it is not compression. Q: Why is Base64 about 33% larger than the input? A: Exactly 4 output characters per 3 input bytes: +33.3%, plus up to two bytes of '=' padding. On this site's 26-document sample corpus the measured median overhead was +33.5%. A 300 KB file becomes 400 KB. Q: Is Base64 encryption? A: No. Anyone can reverse it with no key. Treat Base64 as a transport costume for bytes, the same way hex is a costume. Confidentiality needs a real cipher, not an alphabet. Q: What is the difference between Base64 and Base64URL? A: The last two alphabet characters: +/ versus -_, and padding. The bytes are identical. JWT segments use Base64URL, which is why a raw atob() call often fails until you swap characters. See Base64 vs Base64URL. Q: Can I encode a file, not just text? A: Yes. The Base64 tool's file mode reads an image, PDF or any file up to 50 MB in this tab and emits Base64 or a data: URL, and the reverse. Nothing is uploaded. Q: Why do I see '=' at the end? A: Padding. Input length mod 3 is 1 or 2 leftover bytes, so the encoder emits one or two '=' to keep the output a multiple of 4 characters. Base64URL often drops them because the length is otherwise known. ## What is a CSV file and how to open one safely https://localfirsttools.com/guides/what-is-a-csv-file A CSV file is a plain-text table (RFC 4180). Open it in your browser so CRM and payroll rows are never uploaded. Delimiters, quotes and a UTF-8 BOM explained. A CSV file is a plain-text table that follows RFC 4180: rows of fields separated by commas, with optional quotes around fields that contain the delimiter, quotes or line breaks. Open one safely by reading it in your browser, not by uploading it to a converter you do not run. The CSV Viewer does that in this tab. CSV means comma-separated values, a century of “put the table in a text file” distilled into RFC 4180. Each line is a row. Each comma (or other delimiter) cuts a field. Quotes wrap a field that itself contains a comma, a quote or a line break. That is the whole data model: a rectangle of strings. Open a copy in the [CSV Viewer](https://localfirsttools.com/csv-viewer) when the rectangle is a CRM export, a payroll sheet or anything you would not upload. ## A minimal RFC 4180 file ``` name,role,city Ada Lovelace,mathematician,"London, UK" "Byron, Annabella",patron,Kirkby Mallory ``` Row one is a header because it looks like names, not because the spec requires it. Ada's city is quoted so the comma inside London stays in one cell. Annabella's name is quoted because it contains a comma. An inner quote would be written as `"She said ""hello"""`. Those two rules, wrap and double the inner quote, are the difference between a parser and a `line.split(",")` that explodes on the first address. RFC 4180 also says rows end with CRLF. Plenty of files, especially from Unix tools, use LF only. A UTF-8 BOM (`EF BB BF`) sometimes sits on the first three bytes so Excel treats the file as Unicode. A careful viewer strips the BOM and accepts both line endings. The viewer on this site does both, in the tab. ## Dialects you will actually meet - **Comma.** The RFC default, and what most APIs emit. - **Semicolon.** Common in locales where comma is the decimal mark. Excel there writes `;` and then surprises everyone else. - **Tab.** `.tsv`. Handy because tabs rarely appear in fields. - **Pipe.** `|`, often from legacy exports. Auto-detection counts candidate delimiters on the first line, ignoring commas that sit inside quotes so a quoted address does not look like five columns. You can override Auto when the guess is wrong. TSV is just CSV with a different delimiter; treat it as the same format. ## How to open one without uploading it “Online CSV viewer” too often means “POST the file to a server, render an HTML table, maybe delete it later.” Payroll rows, customer emails and invoice lines do not belong in that request. Safe means the bytes are read by the browser's File API and parsed on the device: - Open the [CSV Viewer](https://localfirsttools.com/csv-viewer). - Drop the .csv or .tsv, or paste the text. Nothing is sent. - Confirm the delimiter and whether row one is a header. - Sort and search in the table. Large files stay smooth because only visible rows render. - Need JSON? Use [Convert this CSV to JSON](https://localfirsttools.com/json-csv); the handoff stays in sessionStorage for this tab. After the page has loaded you can disconnect and keep opening files that are already on disk. That is the same offline check as every other tool here; the procedure is on [how it works](https://localfirsttools.com/how-it-works). ## What CSV cannot do Types. Every cell is a string. `007` might be an employee id or the number seven; a spreadsheet will guess, often wrongly. Nested objects do not exist. Comments do not exist. A second table in the same file does not exist. When the data is a tree, use JSON; when it is configuration a human edits, consider YAML; the trade-offs are in [JSON vs YAML vs CSV](https://localfirsttools.com/guides/json-vs-yaml-vs-csv). Spreadsheets add another trap: they interpret unquoted values. `1-3` becomes a date, `SEPT1` becomes September 1, long numeric ids lose precision. For a round-trip you care about, keep the CSV as text, open it in a viewer that does not coerce types, and only then import with explicit column types. ## Size and quoting pitfalls A hundred thousand rows is a normal CRM dump and a miserable DOM if you render every ``. Virtualize the table: keep the rows in memory, paint a window. Sorting and search still scan the array, which is fine at that size on a laptop. If the file is a few million rows, a browser tab is the wrong tool; use a local command (`csvkit`, DuckDB, Python) on your machine, still without uploading. Broken quoting is the usual parse error: an opening quote with no closer, or a delimiter that was not doubled inside a quoted field. The result is columns that appear to shift right on later rows. Look at the first bad row in a viewer rather than in Excel, which will happily invent extra columns. If you must repair the text, do it in an editor; then re-open the file locally. ## A safe workflow - Prefer a download to your disk over a web “open in app” that posts the file. - Do not paste production CSVs into converters whose network tab you have not watched. - Decode any Base64 wrapper locally first ([What is Base64 encoding?](https://localfirsttools.com/guides/what-is-base64-encoding)) if the export arrived as a blob. - Convert a copy to JSON only when you need objects; the original CSV stays the source of truth for the rectangle. Next step: drop a non-sensitive sample onto the [CSV Viewer](https://localfirsttools.com/csv-viewer), then convert it with [JSON ↔ CSV](https://localfirsttools.com/json-csv) if you want objects. Customer files stay on this device. ### FAQ Q: What is a CSV file? A: A CSV file is a plain-text table that follows RFC 4180: rows of fields separated by commas, with optional quotes around fields that contain the delimiter, quotes or line breaks. Spreadsheets, CRMs and databases all speak some dialect of it. Q: How do I open a CSV safely? A: Read it on your device. Drop the file onto the CSV Viewer in this tab, or paste the text. The File API never sends the bytes to a server. Uploading a payroll export to a random converter is the unsafe option. Q: Is every .csv file comma-separated? A: No. European Excel often writes semicolons. Exports may be tab-separated (.tsv) or pipe-separated. RFC 4180 specifies comma; real files do not always. A viewer should detect the delimiter from the first line, outside quotes. Q: What does RFC 4180 actually require? A: CRLF between rows, comma between fields, and double-quote wrapping for fields that contain comma, quote or line breaks, with an inner quote escaped as two quotes. A header row is optional. Many writers use LF only; parsers should tolerate that. Q: Why does Excel dump everything into column A? A: Delimiter mismatch: the file uses comma and Excel's locale expects semicolon, or the reverse. Use the import wizard and set the delimiter, or open the file in a viewer that detects it. Q: Can CSV hold nested JSON? A: Not as structure. Nested objects have to be flattened into column names or stuffed into a cell as text. If the data nests, convert a copy with JSON ↔ CSV and accept the loss, or keep JSON. # Comparisons ## LocalFirstTools vs JSONFormatter.org https://localfirsttools.com/compare/jsonformatter-org Both format JSON free in the browser. JSONFormatter.org adds save-and-share extras that move data; LocalFirstTools keeps everything in your tab. ### Verdict The short verdict: both format and validate JSON in the browser for free, and for a quick one-off either does the job. Choose the [LocalFirstTools JSON Formatter](https://localfirsttools.com/json-formatter) if you care about a no-ads, no-share-feature surface where nothing can leave the tab; JSONFormatter.org if you specifically want its extras like saving and sharing a formatted document via URL, which is precisely the feature that means your data travels. ### Choose LocalFirstTools if - You paste sensitive payloads, with no share or save feature, no route off your device exists - You want zero ads on a page you use fifty times a day - You want exact line-and-column errors with the offending line highlighted - You want the adjacent jobs in one toolkit: minify, sort keys, YAML and CSV conversion ### Choose JSONFormatter.org if - You want to send a colleague a link to a formatted document (data is stored to share) - You use its Save Online workspace, public unless you log in, per its FAQ ### Side by side | | LocalFirstTools | JSONFormatter.org | | --- | --- | --- | | Local-only processing | Yes, no upload path exists on the page | Validation in-browser (per its page); Save Online stores data on their servers, public unless logged in (per its FAQ) | | Ads | None | Ad-supported | | Account | No | No for basic use; login makes saved data private | | Price | Free | Free | | Error reporting | Exact line + column, gutter highlight | Error indication in-editor | | Related tools | 28 consistent local tools, one design | Many converters across sister pages | | XML formatting | Also local: pretty-print and validate in the tab, nothing uploaded | Has an XML formatter; Save Online stores pastes on their servers | ### LocalFirstTools strengths and trade-offs Pros: - No share feature means sensitive data has no route off your device - No ads, no cookie banners, a clean surface for a fifty-times-a-day tool - Minify, sort-keys, YAML and CSV conversion live in the same toolkit Cons: - No way to share a formatted document by link, by design - No saved workspace across devices ### JSONFormatter.org strengths and trade-offs Pros: - Share a formatted document via URL, genuinely useful for non-sensitive data - States that validation runs in the browser itself (per its own page) - Save Online workspace with configurable link expiry Cons: - Ad-supported - Documents saved without logging in become public, per its FAQ ## The real differentiator: what the page can do at all A formatter with share-links, save buttons and ad networks has, by construction, code paths that transmit data. That is not an accusation (sharing is a feature people want), but it changes what you should paste there. LocalFirstTools takes the opposite trade: no share feature exists, so a customer-data export or a payload full of tokens has no route off your device. You can verify it: open dev tools, paste, watch zero requests appear ([how that works](https://localfirsttools.com/how-it-works)). ## Who should use which Use JSONFormatter.org when you want to send a colleague a link to a formatted document and the data is not sensitive. Use LocalFirstTools for everything you would not paste into a shared doc: production payloads, exports, anything with credentials, and for the surrounding jobs, since [minifying](https://localfirsttools.com/json-minifier), [YAML conversion](https://localfirsttools.com/json-yaml), [XML formatting](https://localfirsttools.com/xml-formatter) (also local) and [error fixing](https://localfirsttools.com/guides/fix-unexpected-token-json) live in the same toolkit. ### FAQ Q: Does JSONFormatter.org upload my JSON? A: Its own page states “Your JSON Data gets validated in the browser itself.” The site also offers a Save Online feature that stores your document on their servers when used, and per its FAQ, data saved without logging in becomes public. Validation local, sharing not: watch the network tab and choose features accordingly. Q: Which handles invalid JSON better? A: Both point at errors. LocalFirstTools reports the first error with exact line and column and highlights the line in the input gutter, which is the workflow this guide-linked formatter is built around. Q: Is either tool paid? A: Both are free. JSONFormatter.org is ad-supported; LocalFirstTools shows no ads. ## LocalFirstTools JWT Decoder vs jwt.io https://localfirsttools.com/compare/jwt-io jwt.io is the canonical debugger and verifies signatures with a pasted key. For quick claim checks inside a local-only toolkit, the simpler decoder wins. jwt.io, run by Auth0/Okta, is the best-known JWT debugger and can verify a signature against a pasted key. The LocalFirstTools JWT Decoder only decodes the header and payload, never verifies, and has no field for key material. Use jwt.io when you need signature checks. Use the local decoder for quick claim checks. ### Verdict The short verdict: jwt.io (run by Auth0/Okta) is the canonical JWT debugger and an excellent tool; if you need signature verification with a pasted key or its library directory, use it. The [LocalFirstTools JWT Decoder](https://localfirsttools.com/jwt-decoder) is the simpler choice when you want fast decoding inside a broader local-only toolkit, with no vendor relationship and a hard rule that key material never belongs in the page. ### Choose LocalFirstTools if - You mostly read claims (exp, sub, aud), and never need verification in the page - You want a decoder with zero key-input fields, so there is nothing to paste a secret into - You use the neighboring tools: the timestamp converter for exp, Base64, JSON - You prefer an independent toolkit with a published no-upload model ### Choose jwt.io if - You need to verify a signature against a pasted development secret or public key - You want its per-language JWT libraries directory - You want the JWT reference URL every developer already knows ### Side by side | | LocalFirstTools | jwt.io | | --- | --- | --- | | Decodes locally | Yes, verifiable in the network tab | Commonly understood to; not stated on its current homepage, verify in the network tab | | Signature verification | No, by design, signature labeled unverified | Yes, its homepage offers “Enter the secret used to sign the JWT below” | | Account required | No | No | | Price | Free | Free | | Vendor | Independent, no auth product to sell | Auth0 / Okta, its footer reads “presented by Auth0” | | Beyond JWTs | 15 more local tools: Base64, timestamps, JSON… | JWT libraries directory, JWT handbook | ### LocalFirstTools strengths and trade-offs Pros: - Decoding runs in the tab: the network stays silent while you paste - No key fields at all: nothing tempts a production secret into a web page - The exp claim converts one click away in the local timestamp tool Cons: - Cannot verify signatures, deliberately out of scope - No JWT library directory or reference material ### jwt.io strengths and trade-offs Pros: - Signature verification with a pasted secret or public key, a real dev workflow - Extensive per-language JWT libraries directory and the JWT handbook - The canonical JWT URL, maintained by Auth0/Okta Cons: - Client-side decoding not stated on its current homepage, verify it yourself - Run by an identity vendor with a product to sell ## Where jwt.io is genuinely better Verification. Pasting a development secret to confirm a signature matches is a real workflow, and jwt.io does it well, along with algorithm pickers and an extensive catalog of JWT libraries per language. It is also the URL every developer already knows. ## Where the local decoder fits better Ninety percent of JWT debugging is reading claims: is [exp in the past](https://localfirsttools.com/jwt-decoder/inspect-payload), is the right `sub` present, did the identity provider add the custom claim? For that loop, a decoder with zero extra surface, no key fields to be tempted by, no vendor funnel, is the right size. The `exp` you find converts in the [timestamp tool](https://localfirsttools.com/timestamp/unix-to-iso) next door, and the whole toolkit publishes its no-upload guarantee (see [how it works](https://localfirsttools.com/how-it-works)) plus a curated [llms.txt](https://localfirsttools.com/llms.txt). ### FAQ Q: Does jwt.io send my token to a server? A: jwt.io is widely understood to decode in the browser, but its current homepage does not state it outright, so treat it as unverified: open the network tab and watch while you paste, and check its privacy policy. That test is the trustworthy check for any tool handling live tokens, including this one. Q: Can either tool verify a JWT signature? A: jwt.io can, if you paste the secret or public key, useful in development. The LocalFirstTools decoder deliberately does not: it decodes and clearly labels the signature unverified, because pasting production secrets into any web page is a habit worth not building. Q: Which should I use for debugging an expired token? A: Either works. Decode, read the exp claim, compare with now. Our decoder pairs with a local timestamp converter for the exp value, which keeps the whole workflow in one toolkit. ## LocalFirstTools vs Code Beautify https://localfirsttools.com/compare/code-beautify Code Beautify wins on breadth with hundreds of converters; LocalFirstTools ships 28 tools with one verifiable rule, nothing leaves the tab. ### Verdict The short verdict: Code Beautify wins on sheer breadth, hundreds of tools covering formats LocalFirstTools does not touch. LocalFirstTools wins on uniformity: 28 tools that all follow one verifiable rule, your data never leaves the browser tab. If the format you need is exotic, go there; if the job is JSON, tokens, encodings, hashes or text (the everyday set) the [local toolkit](https://localfirsttools.com/json-formatter) covers it with stronger guarantees. ### Choose LocalFirstTools if - The job is one of the everyday ones: JSON, tokens, encodings, hashes, developer text - The data is sensitive, the local-only rule holds site-wide and is verifiable in the network tab - You want one consistent design and one privacy model across every tool you open ### Choose Code Beautify if - You need a long-tail format none of our 28 tools covers - The data is not sensitive and you just need any converter that exists ### Side by side | | LocalFirstTools | Code Beautify | | --- | --- | --- | | Number of tools | 28, focused on data/dev text | Hundreds, across many formats | | Local-only guarantee | Yes, site-wide and verifiable | Unverified and likely varies per tool, check their privacy policy | | Ads | None | Historically ad-supported, check their site | | Account | No | No for core tools | | Price | Free | Free | | Consistency | One design, one privacy model | Varies across the catalog | ### LocalFirstTools strengths and trade-offs Pros: - “Nothing leaves the tab” is the architecture of the whole site, not a per-page claim - Each of the 28 tools gets real attention: exact error positions, RFC-accurate behavior - Published no-upload model (how-it-works page and llms.txt) Cons: - Deliberately small catalog, exotic formats are out of scope - No image, XML-beautifier or programming-language tools ### Code Beautify strengths and trade-offs Pros: - Enormous breadth: hundreds of converters, beautifiers and generators - Covers long-tail formats a focused toolkit never will - Free for core tools, no account needed Cons: - No blanket local-only guarantee, behavior varies per tool (check their policy) - Consistency of design and privacy model varies across the catalog ## Breadth vs guarantees These are genuinely different products wearing similar clothes. Code Beautify is a directory: whatever obscure conversion you need, XML to something, an image tweak, a language beautifier, it probably has a page for it. LocalFirstTools is a toolkit: a small set of tools engineered to one standard, where “nothing is uploaded” is not a per-page claim but the architecture of the whole site, published in [how it works](https://localfirsttools.com/how-it-works) and [llms.txt](https://localfirsttools.com/llms.txt), and checkable in your own network tab in ten seconds. ## Who should use which Reach for Code Beautify for long-tail formats and non-sensitive data. Reach for LocalFirstTools when the data is sensitive or the job is one of the everyday ones: [format JSON](https://localfirsttools.com/json-formatter), [Base64](https://localfirsttools.com/base64), [case conversion](https://localfirsttools.com/case-converter), [hashes](https://localfirsttools.com/hash-generator), [timestamps](https://localfirsttools.com/timestamp). Many developers sensibly use both. ### FAQ Q: Does Code Beautify process data on a server? A: It varies by tool, some of its converters run in the browser, while features like fetching from a URL or saving/sharing necessarily involve servers. There is no blanket answer across hundreds of tools; check their privacy policy and watch the network tab for the specific tool you use. Q: Which has more tools? A: Code Beautify, by far, its catalog spans hundreds of converters, beautifiers and generators across many formats (its site blocks automated verification, so browse codebeautify.org for the current count). LocalFirstTools deliberately ships 28. Q: Why would fewer tools be better? A: Uniform guarantees. With 28 tools built on one rule (nothing leaves the tab) the privacy property holds everywhere, the design is consistent, and each tool gets real attention (exact error positions, RFC-accurate behavior). A catalog that big cannot promise uniformity. ## LocalFirstTools vs TinyWow (for developer tools) https://localfirsttools.com/compare/tinywow TinyWow is server-side file tools, PDF, video, image. LocalFirstTools is 28 browser-only data tools. Merge PDFs there; format JSON and tokens here. ### Verdict The short verdict: this is barely a rivalry, TinyWow is mainly a file-tools site (PDF, video, image conversion) built on server-side processing, with some writing and small dev utilities alongside; LocalFirstTools is a browser-only data toolkit. Need to merge PDFs? TinyWow-style services are the right shape. Need to [format JSON](https://localfirsttools.com/json-formatter), decode a token or hash a string without the data leaving your machine? That is exactly what LocalFirstTools is for. ### Choose LocalFirstTools if - The input is token-, payload- or text-shaped, it never needed to travel - You want to watch the network tab stay silent while you work - You are handling credentials, exports or anything you would not upload anywhere ### Choose TinyWow if - The job is real file processing: PDFs, video, images, genuinely server-shaped work - The files are not sensitive and upload-process-download (deleted after 1 hour, per its site) is acceptable ### Side by side | | LocalFirstTools | TinyWow | | --- | --- | --- | | Core focus | Data & developer text tools | PDF, video, image & file tools | | Processing model | 100% in your browser tab | Server-side for file tools (upload by design) | | Ads | None | Ad-supported, paid tier removes them | | Account | No | Not required for basic use | | Price | Free | Free; optional paid tier | | Handles sensitive tokens/payloads | Yes, nothing transmitted | Uploading secrets to any server is unwise | ### LocalFirstTools strengths and trade-offs Pros: - Data never crosses the network, nothing to log, retain or breach - Works offline once a page is loaded - No ads and no paid tier Cons: - No file processing at all: PDFs, video and images are out of scope - Bounded by your device, huge files belong in desktop tools ### TinyWow strengths and trade-offs Pros: - Broad free file toolbox: 45+ PDF tools, image, video and conversion tools (per its homepage) - “No Limits, No Sign-Up” for basic use, per its homepage - States uploaded files are deleted after 1 hour Cons: - Upload is the processing model, data leaves your machine by design - Ad-supported unless you pay for the premium tier ## The processing model is the whole story Converting a video or compressing a PDF genuinely needs server muscle, and TinyWow's model upload, process, download, delete, is the honest way to deliver that. But the same model applied to a JWT or an API payload would mean shipping your secrets to someone else's computer for a job your own browser can do in milliseconds. Different jobs, different architectures; the mistake is using an upload-based site for data that never needed to travel. The [how-it-works page](https://localfirsttools.com/how-it-works) shows how to confirm what any tool page actually sends. ## Who should use which Use TinyWow (or similar) for file conversions where uploading non-sensitive files is fine. Use LocalFirstTools for everything token-, payload- and text-shaped: [JWTs](https://localfirsttools.com/jwt-decoder), [Base64](https://localfirsttools.com/base64), [hashes](https://localfirsttools.com/hash-generator), [CSV ↔ JSON](https://localfirsttools.com/json-csv) and the rest of the 28. ### FAQ Q: Is TinyWow free? A: Yes, its homepage says “No Limits, No Sign-Up”, with a premium tier whose listed perks are ad-free use, unlimited usage and faster processing. LocalFirstTools is free with no ads and no paid tier. Q: Does TinyWow upload my files? A: For its file-processing tools (PDF, video, images), uploading is how they work. Its site states: “All files both processed and unprocessed are deleted after 1 hour.” That is a policy promise, reasonable, but categorically different from data that never leaves your device. Q: Does LocalFirstTools do PDFs? A: No, deliberately. Real PDF manipulation is heavy, server-shaped work. LocalFirstTools only ships tools that can run honestly and fully in the browser, data and text tools, not file processing. ## The best free JSON formatter in 2026 https://localfirsttools.com/compare/best-free-json-formatter What separates JSON formatters (exact error positions, big-file handling, privacy) with honest picks per situation, including when ours is not it. ### Verdict There is no single best, but there is a best per situation, and honest criteria. For sensitive data and everyday use, a verifiably local formatter with exact error positions is the right pick, and that is what the [LocalFirstTools JSON Formatter](https://localfirsttools.com/json-formatter) is built to be. For huge files, use `jq` or your editor, not any website. For sharing formatted output with a colleague, a tool with share links beats both, at the cost of your data traveling. ## What actually separates JSON formatters - **Error reporting.** “Invalid JSON” is useless; line + column with the offending line highlighted is the feature you will use most (see [fixing Unexpected token](https://localfirsttools.com/guides/fix-unexpected-token-json)). - **Where processing happens.** Local means paste-anything; server-touching means think-first. Verifiable in the network tab either way. - **Ads and clutter.** Formatters are used fifty times a day; ad reflows and cookie banners tax every single use. - **Adjacent jobs.** Minify, sort keys, convert to YAML/CSV, diff, the jobs cluster, so a toolkit beats a lone page. ## Honest picks by situation | Approach | Concrete pick | Sensitive/production data | Anything verifiably local | [LocalFirstTools JSON Formatter](https://localfirsttools.com/json-formatter) | Files over ~50 MB | Command line / editor | `jq . big.json`, VS Code | Sharing formatted JSON via link | A tool with share features | JSONFormatter.org and similar (data is stored to share) | Formatting inside a pipeline | Never a website | `jq`, Prettier, language stdlib | Quick format + related jobs | A consistent toolkit | LocalFirstTools: [minify](https://localfirsttools.com/json-minifier), [sort keys](https://localfirsttools.com/json-formatter/sort-keys), [→ YAML](https://localfirsttools.com/json-yaml) | ## Why “free” is the norm here Formatting JSON costs almost nothing to provide (the browser does the work), which is why virtually every option is free and why you should be suspicious of none of them for being free. The business models differ (ads, upsells to paid products, or (in our case) nothing at all), and the model shapes the page you paste into. Details on the local model: [how it works](https://localfirsttools.com/how-it-works). See also the direct comparison [vs JSONFormatter.org](https://localfirsttools.com/compare/jsonformatter-org). ### FAQ Q: Are paid JSON formatters worth it? A: For formatting alone, no, the operation is JSON.parse plus JSON.stringify, and free tools do it perfectly. Paid editors earn their money on adjacent features: schema validation, large-file performance, querying. Format for free; pay only for a feature you can name. Q: What's the best JSON formatter for huge files? A: The command line: jq . big.json streams without freezing anything, and your editor with a JSON plugin handles hundreds of megabytes better than any web page. Browser tools are best below a few tens of megabytes. Q: Is it safe to paste production data into an online formatter? A: Only into one that verifiably does not transmit it. Open dev tools, watch the network tab while you paste and format, a local-only tool shows zero requests carrying your data. If you can't verify, treat the paste as an upload. ## Browser-based vs server-based JSON tools https://localfirsttools.com/compare/browser-vs-server-json-tools Browser tools process JSON on your device; server tools receive it. Where data physically goes, how to verify it in the network tab, when servers win. ### Verdict The difference is one question: when you paste data and press the button, does your data cross the network? In a browser-based tool the answer is no, JavaScript in your tab does the work, as in every [LocalFirstTools tool](https://localfirsttools.com/json-formatter). In a server-based tool your data is POSTed to someone else's computer, processed there, and the result sent back. Both are legitimate architectures; confusing them is how secrets end up in other people's logs. ## What each model means in practice | Browser-based (local) | Server-based | Where data goes | Nowhere, stays in the tab | Transmitted to the provider's servers | Network tab shows | No request with your data | POST/upload containing your payload | Provider can log/store input | Impossible by construction | Possible, governed by their policy | Works offline after load | Yes | No | Latency | None (no round trip) | Round trip + queue | Size/compute ceiling | Your device's memory & CPU | Effectively unlimited | Right for | Tokens, payloads, exports, everyday jobs | Huge files, heavy compute (video, OCR, PDF) | ## The verification habit Do not trust labels, including ours. Open dev tools, Network tab, clear, paste, run. If a “free online JSON formatter” fires a `POST /format` with your document in the body, it is a server tool wearing browser clothes. If it fires nothing, the claim holds. The offline test is even stricter: disconnect and see whether the tool still works. [How it works](https://localfirsttools.com/how-it-works) walks through exactly what you will see on this site. ## Why this matters most for JSON specifically JSON payloads are dense with exactly the things that should not travel: bearer tokens, API keys in config dumps, customer records in exports, internal URLs in logs. A [JWT](https://localfirsttools.com/jwt-decoder) is a working credential; a [CSV export](https://localfirsttools.com/json-csv) is PII in rows. The jobs themselves, [formatting](https://localfirsttools.com/json-formatter), [hashing](https://localfirsttools.com/hash-generator), converting are computationally trivial, which means the server buys you nothing and costs you the data's locality. Use servers where they earn it; for data tools, local is simply the correct architecture. ### FAQ Q: How can I tell whether a tool is browser-based? A: Open dev tools (F12), switch to the Network tab, clear it, then paste your data and run the tool. A browser-based tool triggers no request containing your data; a server-based one shows a POST with your payload. Two minutes, works on any site including this one. Q: Are browser-based tools slower? A: For data-tool workloads, no, parsing a few megabytes of JSON takes milliseconds in-browser, and there is no network round trip at all. Servers only win when the job needs more memory or CPU than a tab has: gigabyte files, video encoding, OCR. Q: Does “nothing is uploaded” include analytics? A: They are separate questions: a page can process data locally yet still load analytics scripts. The processing guarantee is about your pasted data never being in any request. Verify both in the network tab; LocalFirstTools' model is described on the how-it-works page. Q: Do browser tools work offline? A: Once the page is loaded, a genuinely local tool keeps working with the network unplugged, a stronger version of the network-tab test. Try it: load the JSON formatter, go offline, format away. ## LocalFirstTools vs IT-Tools https://localfirsttools.com/compare/it-tools IT-Tools is a large open-source catalog (87 tools, GPL-3.0, self-hostable). LocalFirstTools ships 28 tools with a published no-upload audit. Breadth there; a smaller verified toolkit here. IT-Tools is Corentin Thomasset's open-source (GPL-3.0) catalog of client-side developer utilities, self-hostable with Docker, with 87 tools in its public GitHub registry. LocalFirstTools ships 28 tools under one published rule: processing stays in the tab, which you can check in DevTools. Use IT-Tools for breadth and self-hosting. Use LocalFirstTools when you want a smaller toolkit with a public no-upload audit. ### Verdict The short verdict: IT-Tools wins on breadth, license and self-hosting, 87 tools, GPL-3.0, a Docker image, and translations. LocalFirstTools wins on a smaller, uniform toolkit with a published no-upload audit. If you want bcrypt, QR codes, subnet math or a homelab copy, go there. If the job is JSON, tokens, hashes or UUIDs and you want to [check the network tab](https://localfirsttools.com/how-it-works) against a written procedure, use the [local toolkit](https://localfirsttools.com/json-formatter). ### Choose LocalFirstTools if - You want a published, repeatable check that input never appears in a request - The job is one of the everyday data tools: JSON, JWT, Base64, hashes, UUID v7 - You prefer one privacy model and one design across a small catalog ### Choose IT-Tools if - You need a tool LocalFirstTools does not ship (bcrypt, QR codes, IPv4 math, OTP, IBAN, …) - You want to read and self-host the code under GPL-3.0 - You need the UI in a language other than English ### Side by side | | LocalFirstTools | IT-Tools | | --- | --- | --- | | Number of tools | 28, focused on data and text | 87 in its public GitHub registry when this page was written | | License | Proprietary; source is private | GPL-3.0, public GitHub repository | | Self-host | No; use the hosted pages | Yes; documented Docker images | | Local processing | Yes, with a published DevTools check | Client-side Vue app; verify per tool in the network tab | | Account / ads | None | None required; optional donations | | Translations | English only | Several languages on it-tools.tech | ### LocalFirstTools strengths and trade-offs Pros: - Published no-upload model, SHA256SUMS and a 10-second verification page - UUID v7 (RFC 9562), structural JSON diff, CSV viewer for large files - 28 tools that share one design and one privacy rule Cons: - Much smaller catalog; long-tail IT utilities are out of scope - Not open source and not self-hostable - English only ### IT-Tools strengths and trade-offs Pros: - Large catalog: crypto, converters, network, math, images, web helpers - GPL-3.0, public source, Docker self-host - Translated UI Cons: - No published per-build hash manifest or DevTools audit page like /how-it-works - Uniformity of depth varies across a catalog that large ## Two client-side toolkits, different bets IT-Tools (it-tools.tech) is Corentin Thomasset's collection of handy online tools for developers. The public repository is GPL-3.0. The live about page describes a Vue 3 app hosted on Vercel, and the README documents Docker self-hosting. That is a real advantage if you want the code on a machine you run. LocalFirstTools is a smaller, proprietary toolkit. Processing still runs as JavaScript in your tab, but the source is not a public release. What it publishes instead is a [written verification procedure](https://localfirsttools.com/how-it-works), [build hashes](https://localfirsttools.com/audit) and first-party measurements on [/stats](https://localfirsttools.com/stats). Those are different kinds of trust: inspectable code versus inspectable behaviour plus checksums. Pick the one that matches how you evaluate a tool. ## Where IT-Tools is stronger Breadth. The GitHub tool registry grouped 87 utilities when this page was written, including bcrypt, RSA key pairs, QR codes, IPv4 subnet math, OTP, IBAN parsing and a chmod calculator. LocalFirstTools does not ship those, and it will not pretend to. IT-Tools also translates the UI; this site is English-only. ## Where LocalFirstTools is stronger A uniform no-upload rule you can check, plus a few data tools IT-Tools either lacks or treats more lightly: RFC 9562 [UUID v7](https://localfirsttools.com/uuid-generator/v7), a structural [JSON diff](https://localfirsttools.com/json-diff) that ignores key order, and a [CSV viewer](https://localfirsttools.com/csv-viewer) that keeps 100k-row exports in the tab. If those jobs are why you opened a comparison, stay here. If you wanted a Swiss-army catalog you can fork, IT-Tools is the better fit. ### FAQ Q: Is IT-Tools open source? A: Yes. IT-Tools is published under the GPL-3.0 license at github.com/CorentinTh/it-tools and can be self-hosted with the published Docker images. LocalFirstTools is proprietary: the source repository is private and is not offered as an open-source project. Q: How many tools does each site ship? A: IT-Tools listed 87 tools in its public GitHub registry (CorentinTh/it-tools, toolsByCategory) when this page was written. LocalFirstTools ships 28, on purpose. Count the live catalogs if you need a current number; catalogs move. Q: Do both run in the browser? A: IT-Tools is a client-side Vue app and does not advertise an upload API; still watch the network tab for the specific tool you open. LocalFirstTools publishes a 10-second DevTools check and SHA256SUMS per build. Same class of architecture, different proof. ## LocalFirstTools vs CyberChef https://localfirsttools.com/compare/cyberchef CyberChef chains hundreds of operations into a recipe and handles huge files. LocalFirstTools is 28 everyday tools with a published no-upload model and no recipe UI. CyberChef, GCHQ's open-source Cyber Swiss Army Knife, chains hundreds of operations into a shareable recipe and handles files up to around 2 GB in the browser. LocalFirstTools is 28 everyday tools with a published no-upload model and no recipe UI. Use CyberChef for forensic and crypto pipelines; use LocalFirstTools for JSON, JWTs, hashes and UUIDs. ### Verdict The short verdict: CyberChef is the better tool when the job is a pipeline, decode then gunzip then parse, over a large blob. LocalFirstTools is the better page when the job is one everyday action (format JSON, decode a JWT, hash a file, mint a UUID) with a published no-upload check and no recipe to learn. Use both; they are not substitutes. ### Choose LocalFirstTools if - The job is one step: format, decode, hash, encode or mint an ID - You want a dedicated page with a written DevTools verification, not a workbench - You are handling a JWT, CSV or JSON document and want the neighbouring tools in the same kit ### Choose CyberChef if - You need to chain operations into a shareable recipe - The input is a large capture, hexdump, compressed blob or ciphertext - You want hundreds of crypto, encoding and parsing operations in one surface ### Side by side | | LocalFirstTools | CyberChef | | --- | --- | --- | | Shape | 28 separate tool pages | One workbench, hundreds of operations, recipes | | Chaining | No recipe UI; one job per page | Drag-and-drop recipes, including Fork and Magic | | Large files | Caps vary by tool (e.g. Base64 50 MB, hashes 500 MB) | Around 2 GB, depending on the browser | | License | Proprietary; source is private | Open source (GCHQ / CyberChef on GitHub) | | Local processing | Yes, with a published DevTools check | Yes, in-browser operations; verify for your recipe | | Account / ads | None | None | ### LocalFirstTools strengths and trade-offs Pros: - Quiet, single-purpose pages for JSON, JWT, Base64, hashes and UUIDs - Published no-upload procedure and per-build SHA256SUMS - UUID v7, structural JSON diff and a large-file CSV viewer Cons: - Cannot chain operations into a recipe - No decrypt / decompress / disassemble catalog - Not open source ### CyberChef strengths and trade-offs Pros: - Hundreds of operations, including crypto, compression and parsing - Shareable recipes and a Magic detector for unknown encodings - Open source, maintained as the Cyber Swiss Army Knife - Very large file handling in the browser Cons: - A workbench has a learning curve compared with a single-purpose page - Easy to paste production secrets into a powerful decoder; hygiene still matters ## A workbench versus a toolkit CyberChef's own about page calls it a simple web app for analysing and decoding data by combining functions into a recipe. That is the product: drop a file, stack operations (Base64, gunzip, XOR, parse an X.509 certificate), share the recipe. GCHQ released it on GitHub so others can contribute. If that is the job, stop here and use CyberChef. LocalFirstTools does not compete with that surface. It ships 28 separate pages that each do one everyday developer job, with a hard rule that input stays in the tab. The proof is on [how it works](https://localfirsttools.com/how-it-works), not a recipe list. [Base64](https://localfirsttools.com/base64), the [hash generator](https://localfirsttools.com/hash-generator) and [hex](https://localfirsttools.com/hex) overlap CyberChef operations; JSON formatting, [JWT decoding without verification](https://localfirsttools.com/jwt-decoder) and [UUID v7](https://localfirsttools.com/uuid-generator) are the reason this toolkit exists. ## When CyberChef is the right tool Forensic and crypto pipelines. Unknown encodings (the Magic operation). Files far larger than a JSON payload. Teaching encoding by stacking steps. LocalFirstTools will not grow a recipe UI to chase those jobs. ## When the quieter page is enough You have a JWT, a JSON document or a checksum and you want the answer without assembling a recipe. You want to show a colleague a URL that does one thing and a network-tab procedure that proves it. That is this site. Cite the measurements on [/stats](https://localfirsttools.com/stats) if you need numbers rather than a demo. ### FAQ Q: Does CyberChef upload my data? A: CyberChef is designed as a browser app: operations run on the input in the page, and it can handle files of around 2 GB depending on the browser. Treat that like any other client-side tool: watch the network tab for the operations you actually run. LocalFirstTools publishes the same class of check as a written procedure. Q: Which has more operations? A: CyberChef, by a wide margin. Its own about text says there are hundreds of operations (decode, decompress, decrypt, parse certificates, disassemble). LocalFirstTools ships 28 focused tools and does not try to be a recipe workbench. Q: Is CyberChef open source? A: Yes. It is the GCHQ project at github.com/gchq/CyberChef. LocalFirstTools is proprietary; the source repository is private and is not offered as an open-source project. ## LocalFirstTools vs DevToys https://localfirsttools.com/compare/devtoys DevToys is a native open-source app (Windows, macOS, Linux) with 30 default offline tools and a CLI. LocalFirstTools is 28 browser tools with no install. DevToys is a free, open-source desktop app for Windows, macOS and Linux with 30 default offline tools, a CLI and an extension SDK. LocalFirstTools is a website of 28 tools that run in any modern browser with no install. Use DevToys for a native app and clipboard smart detection; use LocalFirstTools when a URL on any device is the point. ### Verdict The short verdict: DevToys is the better product if you want a native, extensible app with clipboard smart detection and a CLI for CI. LocalFirstTools is the better product if you want the same class of tools as a URL, on a phone or a locked-down laptop, with a published in-browser no-upload check. One is software you install; the other is a page you open. ### Choose LocalFirstTools if - You cannot or do not want to install an app - You need the tools on a phone, a borrowed machine or a browser-only environment - You want a public page plus a written DevTools check you can show someone else ### Choose DevToys if - You live in a desktop OS and want clipboard smart detection and compact overlay - You need a CLI in CI, or community extensions from NuGet - You want to read and extend the code ### Side by side | | LocalFirstTools | DevToys | | --- | --- | --- | | Runs where | Any modern browser, no install | Windows, macOS and Linux native apps | | Default tools | 28 | 30 default, plus community extensions | | CLI / CI | No CLI | Separate DevToys CLI | | License | Proprietary; source is private | Open source, extensible SDK | | Offline | Yes, after the page has loaded | Yes; it is a local app | | Account / ads | None | None | ### LocalFirstTools strengths and trade-offs Pros: - Zero install: a URL that works in a browser, including on a phone - Published no-upload procedure and per-build hashes - UUID v7, structural JSON diff, large CSV viewer Cons: - No native OS integration, jump list or compact overlay - No CLI for CI - Not open source and not extensible by third parties ### DevToys strengths and trade-offs Pros: - Native apps on Windows, macOS and Linux - Smart detection from the clipboard, extensions, CLI - Open source; 30 default tools covering converters, formatters, testers and graphics Cons: - Requires an install (or a portable build); not a page you can send as a link to a tool - The website is marketing, so sharing a DevToys URL does not open the tool for someone without the app ## Install versus a URL DevToys describes itself as a Swiss Army knife for developers: a free, open-source, privacy-focused app on Windows, macOS and Linux, with 30 default offline tools, smart detection from the clipboard, a compact overlay, and a separate CLI. That is an excellent product for someone who lives on a desktop OS and is willing to install software. LocalFirstTools will not pretend to beat native integration. This site is the other shape: 28 tools as ordinary HTTPS pages. Paste a JWT on a phone. Format JSON on a machine that forbids installs. Send a colleague [/jwt-decoder](https://localfirsttools.com/jwt-decoder) and they are in the tool. After load, you can [disconnect and keep working](https://localfirsttools.com/how-it-works). That is the whole bet. ## Overlap Both cover JSON and YAML, Base64, JWT, hashes, UUID, SQL and XML formatting, regex and JSONPath. DevToys also ships graphics tools (colour-blind simulator, image conversion) and GZip; LocalFirstTools does not. LocalFirstTools leans harder into data documents: a [CSV viewer](https://localfirsttools.com/csv-viewer), structural [JSON compare](https://localfirsttools.com/json-diff), and RFC 9562 [UUID v7](https://localfirsttools.com/uuid-generator). ## How to choose If you already run DevToys, keep using it for the jobs it detects from the clipboard. Add this site for machines where you cannot install it, and for the few pages DevToys does not have. If you have never installed a toolkit, start here; switch to DevToys when the native app would save you time. ### FAQ Q: Is DevToys a website? A: No. DevToys is a desktop application for Windows, macOS and Linux, plus a separate CLI. The website (devtoys.app) is the product page, not the toolkit. LocalFirstTools is the opposite: a website you open in any modern browser, with no install. Q: Which is more private? A: Both keep work off other people's servers when used as designed. DevToys is offline by being a native app. LocalFirstTools is local by running as JavaScript in the tab after the page loads, which you can check in DevTools. Install versus inspectable web page: different proofs, same idea. Q: Is DevToys open source? A: Yes. DevToys is free and open source, with 30 default tools, community extensions and an SDK (devtoys.app and github.com/DevToys-app/DevToys). LocalFirstTools is proprietary; the source repository is private and is not offered as an open-source project. ## LocalFirstTools vs transform.tools https://localfirsttools.com/compare/transform-tools transform.tools is an open-source polyglot converter for JSON Schema, GraphQL, SVG and TypeScript. LocalFirstTools is a 28-tool local-only data toolkit. transform.tools is an open-source (MIT) polyglot converter by Ritesh Kumar, built for code-to-code jobs: JSON Schema, GraphQL, SVG to JSX, TypeScript and Flow. LocalFirstTools covers JSON, YAML, CSV, XML and TypeScript inference inside a 28-tool local-only toolkit. Use transform.tools for compiler-shaped transforms; use LocalFirstTools for data you would rather not paste into a general converter. ### Verdict The short verdict: transform.tools is the right site for compiler-shaped jobs, JSON Schema, GraphQL, SVG to JSX, Flow, Protobuf. LocalFirstTools is the right site for data you would rather keep in the tab: JSON, YAML, CSV, XML, JWTs, hashes. Overlap exists ([JSON to TypeScript](https://localfirsttools.com/json-typescript), JSON/YAML/XML) and both can be the better fit depending on the payload. ### Choose LocalFirstTools if - The payload is data (JSON, CSV, XML, a JWT, a hash, a UUID), not a component or schema DSL - You want a published no-upload check and neighbouring tools (diff, CSV viewer, UUID v7) - You need to format or validate, not generate types from JSON Schema ### Choose transform.tools if - You need JSON Schema, OpenAPI, GraphQL, Protobuf, SVG-to-JSX or Flow transforms - You want to read and self-host the converter under MIT - The input is source code or a schema, not a customer export ### Side by side | | LocalFirstTools | transform.tools | | --- | --- | --- | | Focus | 28 data and text tools | Polyglot code converters (many routes) | | JSON Schema / GraphQL / SVG | Not shipped | First-class transformers on the homepage | | JSON / YAML / XML / CSV | Format, validate, diff, CSV viewer, converters | Some JSON/YAML/TOML routes; not a CSV viewer | | License | Proprietary; source is private | MIT, public GitHub, self-host documented | | Local processing | Yes, with a published DevTools check | Client-side app; verify the transform you run | | Account / ads | None | None required | ### LocalFirstTools strengths and trade-offs Pros: - Data-document tools: formatter, minifier, structural diff, CSV viewer - JWT decode, hashes, UUID v7, timestamps in the same kit - Published no-upload model Cons: - No JSON Schema, GraphQL, SVG, Flow or Protobuf transforms - Not open source and not self-hostable ### transform.tools strengths and trade-offs Pros: - Deep catalog of code and schema transforms that a data toolkit will not grow - MIT license, public source, self-host - A URL per transform, which is why docs and tutorials cite it Cons: - Not a general JSON formatter/validator with exact error positions - Not a JWT, hash, UUID or CSV toolkit ## Converters versus a data toolkit transform.tools, created by Ritesh Kumar (@ritz078), is a polyglot web converter. The homepage is the product: SVG to JSX, HTML to Pug, JSON Schema to TypeScript, GraphQL to fragments, CSS to JS objects, TypeScript to Flow. The GitHub repository is MIT-licensed and documents self-hosting. Tutorials cite those routes because they are specific landing pages for a compiler-shaped job. LocalFirstTools overlaps on a few conversions, [JSON to TypeScript interfaces](https://localfirsttools.com/json-typescript), [JSON ↔ YAML](https://localfirsttools.com/json-yaml), [JSON ↔ XML](https://localfirsttools.com/json-xml), and then goes somewhere else: format and validate JSON, view CSV, decode JWTs, hash files, mint UUID v7. If you arrived with a JSON Schema, transform.tools is the honest recommendation. ## Where transform.tools is stronger Every route LocalFirstTools does not have: GraphQL, Protobuf, OpenAPI, SVG, Flow, CSS-in-JS. Open source. Self-host. That is a long list, and this comparison would be dishonest if it skipped it. ## Where LocalFirstTools is stronger Data you would not paste into a general code converter: a customer CSV, a live-looking JWT, an API payload you want to [pretty-print](https://localfirsttools.com/json-formatter) with an exact error line. The [no-upload check](https://localfirsttools.com/how-it-works) is written down. Measurements for minify and Base64 live on [/stats](https://localfirsttools.com/stats). Use transform.tools for schemas and components; use this site for documents and tokens. ### FAQ Q: What is transform.tools for? A: Code-to-code conversion. The homepage is a grid of transformers: SVG to JSX, JSON Schema to TypeScript or Protobuf, GraphQL to fragments, CSS to JS objects, Flow to TypeScript, and many others. It is a polyglot converter, not a JSON formatter with extra tabs. Q: Is transform.tools open source? A: Yes. Ritesh Kumar's ritz078/transform is MIT-licensed and self-hostable. LocalFirstTools is proprietary; the source repository is private and is not offered as an open-source project. Q: Does either upload my code? A: transform.tools is a client-side Next.js app in the public repository; still watch the network tab for the transform you run. LocalFirstTools publishes a DevTools procedure for the same check. Neither needs an account for the core converters. # Measurements https://localfirsttools.com/stats Measured 2026-10-14. 26 synthetic JSON documents committed in scripts/fixtures/: package manifests, API responses, configs, logs and tabular exports. They are stand-ins for public developer data, not customer payloads. Re-run astro/scripts/compute-stats.mjs to regenerate this file. JSON minify per document (pretty bytes → minified bytes, percent saved): - analytics-event.json: 371 → 290 (21.8%) - api-employees.json: 1438 → 1053 (26.8%) - api-logs.json: 2011 → 1470 (26.9%) - api-metrics.json: 906 → 635 (29.9%) - api-orders.json: 2207 → 1630 (26.1%) - api-products.json: 1623 → 1202 (25.9%) - api-user.json: 360 → 268 (25.6%) - api-users-list.json: 1244 → 907 (27.1%) - composer-like.json: 308 → 233 (24.4%) - config-bundler.json: 408 → 299 (26.7%) - config-ci.json: 593 → 298 (49.7%) - db-schema.json: 529 → 338 (36.1%) - eslint-config.json: 431 → 260 (39.7%) - feature-flags.json: 511 → 325 (36.4%) - geojson-points.json: 1055 → 567 (46.3%) - graphql-response.json: 591 → 300 (49.2%) - i18n-en.json: 513 → 397 (22.6%) - invoice.json: 592 → 434 (26.7%) - jwt-claims.json: 287 → 234 (18.5%) - k8s-deployment.json: 1047 → 479 (54.3%) - manifest-webapp.json: 369 → 269 (27.1%) - openapi-fragment.json: 1279 → 594 (53.6%) - package-manifest.json: 420 → 315 (25%) - redirects-config.json: 401 → 243 (39.4%) - survey-results.json: 402 → 293 (27.1%) - tsconfig.json: 366 → 246 (32.8%) Base64: theoretical +33.3%; corpus median +33.5% (min +33.3%, max +34.1%). UUID v4 random bits 122 (5316911983139663491615228241121378304 values). UUID v7 timestamp bits 48, random bits per ms 74 (18889465931478580854784 values per ms). Hash digest lengths: SHA-256 256 bits / 64 hex / 44 Base64; SHA-512 512 bits / 128 hex / 88 Base64; SHA-384 384 bits / 96 hex / 64 Base64; SHA-1 160 bits / 40 hex / 28 Base64; MD5 128 bits / 32 hex / 24 Base64. # Directory listings https://localfirsttools.com/featured - [SaaSHub](https://www.saashub.com/localfirsttools): Software alternatives directory. Vendor listing for LocalFirstTools. - [AZN8](https://azn8.com/listing/localfirsttools): Product directory listing for LocalFirstTools.