Need help with your JSON?
Try our JSON Formatter tool to automatically identify and fix syntax errors in your JSON. JSON Formatter tool
Safari Extension Development for JSON Formatting
If you want Safari to pretty-print raw API responses, build a Safari Web Extension, not an older Safari App Extension. The job sounds simple, but the tricky part is doing it safely: detect real JSON documents, render them cleanly, and avoid breaking ordinary HTML pages that only happen to contain braces.
Apple's current Safari Web Extensions documentation describes this model as available on macOS with Safari 14 and later, on iOS/iPadOS with Safari 15 and later, and on visionOS 1 and later. If you already have a Chrome, Edge, or Firefox extension, Apple's converter is usually the fastest path into Safari.
Use the Current Safari Extension Model
For JSON formatting in 2026, the practical default is a Safari Web Extension built with HTML, CSS, and JavaScript and packaged through Xcode or App Store Connect. That keeps your Safari version close to the same extension architecture used in other browsers and makes it much easier to share code.
- New project: Start with Xcode's
Safari Extension Apptemplate. - Existing cross-browser extension: Use Apple's command-line converter to generate the Safari project wrapper.
- Simple JSON viewer: A content script plus a stylesheet is usually enough.
- Use a background context only when needed: Add one for messaging, toolbar actions, storage sync, native app communication, or more advanced request handling.
Apple also now supports packaging Safari Web Extensions through App Store Connect from a ZIP upload, which is useful if your extension already exists as a browser-ready codebase and you want a lighter packaging workflow.
Choose the Right Starting Point
The fastest route depends on whether you are starting fresh or porting an existing formatter.
Common setup commands
# Convert an existing WebExtension into a Safari project xcrun safari-web-extension-converter /path/to/extension # Add iOS/iPadOS support to an existing macOS Safari Web Extension project xcrun safari-web-extension-converter --rebuild-project /path/to/MyExtension.xcodeproj
- If you already have a JSON formatter extension for Chromium or Firefox, convert it first, then review any browser-specific APIs before polishing the Safari version.
- If Safari is your first target, start in Xcode and keep the first release small: detect raw JSON, pretty print it, and preserve a raw fallback for oversized responses.
- Apple also documents temporary installation of a web extension folder in macOS Safari for quick local testing without a full packaging flow.
A Manifest That Fits a JSON Formatter
One problem with many tutorials is that they mix up activeTab and automatic page formatting. If you want Safari to format raw JSON pages on load, the core tool is the content_scripts section, not a toolbar-only permission model.
`manifest.json` example
{
"manifest_version": 3,
"name": "JSON Formatter for Safari",
"version": "1.0.0",
"description": "Formats raw JSON documents in Safari.",
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["content.js"],
"css": ["content.css"],
"run_at": "document_idle"
}
]
}- No `activeTab` here: that permission is a better fit for an opt-in toolbar action, not an automatic formatter.
- `<all_urls>` is convenient but broad: use it only if your JSON detection logic is strict. If you know the hosts you care about, narrow the match patterns.
- `document_idle` is a safe default: it gives Safari time to finish rendering raw JSON into the document before your formatter decides whether to take over.
Detect Raw JSON Without Breaking Normal Pages
The biggest implementation mistake is replacing the page whenever the body text starts with { or [. A safer formatter combines MIME hints, DOM shape, successful parsing, and a size limit.
`content.ts` example
const JSON_MIME_HINTS = ["application/json", "text/json", "application/ld+json"];
const MAX_RENDER_BYTES = 2_000_000;
function describeValue(value: unknown): string {
if (Array.isArray(value)) return "Array";
if (value === null) return "null";
if (typeof value === "object") return "Object";
return typeof value;
}
function readRawJson(): string | null {
if (!document.body) return null;
const mime = (document.contentType || "").toLowerCase();
const lonePre =
document.body.childElementCount === 1 && document.body.firstElementChild?.tagName === "PRE"
? (document.body.firstElementChild as HTMLPreElement)
: null;
const candidate = (lonePre?.textContent ?? document.body.textContent ?? "").trim();
if (!candidate) return null;
const hasJsonMime = JSON_MIME_HINTS.some((hint) => mime.includes(hint));
const looksLikeJson = candidate.startsWith("{") || candidate.startsWith("[");
const byteLength = new TextEncoder().encode(candidate).byteLength;
if (!hasJsonMime && !looksLikeJson) return null;
if (byteLength > MAX_RENDER_BYTES) return null;
try {
JSON.parse(candidate);
return candidate;
} catch {
return null;
}
}
function mountViewer(rawJson: string) {
const parsed = JSON.parse(rawJson);
const pretty = JSON.stringify(parsed, null, 2);
const app = document.createElement("main");
app.className = "json-viewer";
const heading = document.createElement("h1");
heading.textContent = "Formatted JSON";
const meta = document.createElement("p");
meta.className = "json-viewer__meta";
meta.textContent =
`${describeValue(parsed)} • ${pretty.split("\n").length.toLocaleString()} lines`;
const pre = document.createElement("pre");
pre.textContent = pretty;
app.append(heading, meta, pre);
document.body.replaceChildren(app);
document.title = "Formatted JSON";
}
const rawJson = readRawJson();
if (rawJson) {
mountViewer(rawJson);
}This approach is much safer than dumping formatted text into innerHTML. It only proceeds when the response looks like a real JSON document, uses textContent for rendering, and refuses very large payloads that would make the tab sluggish.
Styling and UX Details That Actually Matter
A production JSON formatter is more than indentation. You want something that stays readable on dark and light pages, handles long lines, and gives the user a way back to raw content when needed.
`content.css` starter
body {
margin: 0;
background: #0f172a;
color: #e2e8f0;
}
.json-viewer {
max-width: 1100px;
margin: 0 auto;
padding: 24px 16px 40px;
font: 14px/1.6 ui-monospace, SFMono-Regular, Menlo, monospace;
}
.json-viewer__meta {
color: #94a3b8;
margin: 0 0 12px;
}
.json-viewer pre {
margin: 0;
overflow: auto;
padding: 16px;
border: 1px solid #243041;
border-radius: 14px;
background: #111827;
}- Add a raw-view toggle or copy button early. People debugging APIs often need the original payload.
- Keep a hard size threshold. Pretty-printing huge responses is one of the fastest ways to make the extension feel broken.
- If you add collapse and expand behavior, build it lazily so large objects do not render every nested node up front.
Testing and Packaging in Safari Today
Safari development still revolves around a containing app, even when your extension code is mostly web technology.
- Create the Safari Extension App project in Xcode, or convert your existing extension first.
- Keep your HTML, CSS, JS, and JSON files inside the extension resources and make sure the Safari web extension target includes them.
- For macOS testing, build the containing app from Xcode. Apple also documents temporary folder-based installation for quick local checks.
- For iPhone or iPad testing, deploy the containing app from Xcode to a simulator or device, then enable the extension in Safari.
- For unsigned local testing on Mac, enable Safari's Develop menu and then allow unsigned extensions before running from Xcode.
If you are shipping publicly, review the App Store extension guidelines and keep your permissions narrow. Safari is explicit about website access, so overreaching permissions create friction fast.
Common Failure Modes
- You format normal pages by mistake: broad URL matches are fine only if your detection logic is strict.
- You rely on `activeTab` for auto-formatting: that works for click-to-run tools, not for automatic page formatting at load time.
- You replace the DOM too early: wait until the document is idle or confirm the raw payload is present first.
- You assume every browser API is identical: review Apple's compatibility guidance before shipping features copied from a Chrome extension.
- You ignore huge responses: if a multi-megabyte payload times out or freezes the tab, show a fallback message and keep the raw view available.
When a Safari Extension Is the Right Tool
Build the extension when you need automatic formatting for live responses, team-wide distribution, or a browser-native debugging workflow. If you only need to pretty-print copied JSON once in a while, an offline formatter is simpler and avoids extension permissions entirely.
Conclusion
A good Safari JSON formatter is really a good JSON page detector. Use the current Safari Web Extension model, keep the manifest lean, verify that the page is truly raw JSON before replacing it, and test the Safari packaging flow early so Xcode or permissions do not surprise you late in the project.
Need help with your JSON?
Try our JSON Formatter tool to automatically identify and fix syntax errors in your JSON. JSON Formatter tool