Skip to content

Instantly share code, notes, and snippets.

@ggorlen
Last active April 10, 2026 21:44
Show Gist options
  • Select an option

  • Save ggorlen/81a50ac3625e80a544a3a651cb8d60aa to your computer and use it in GitHub Desktop.

Select an option

Save ggorlen/81a50ac3625e80a544a3a651cb8d60aa to your computer and use it in GitHub Desktop.
HAR minimizer.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>HAR Cleaner</title>
<style>
:root { color-scheme: light dark; }
body { margin:0; font-family: system-ui, sans-serif; }
#overlay {
position: fixed; inset:0; display:flex; align-items:center; justify-content:center;
z-index: 10;
}
#dropZone {
border:1px dashed currentColor;
padding:20px;
max-width: 420px;
background: Canvas;
}
.opts { font-size:12px; margin-top:12px; }
textarea {
position: fixed; inset:0; width:100%; height:100%;
border:none; outline:none; resize:none;
font-family: monospace; font-size:12px;
padding:16px; box-sizing:border-box;
display: none;
}
#copyBtn, #stats {
position: fixed;
top:12px;
padding:6px 10px;
border:1px solid currentColor;
background: Canvas;
font-size:12px;
z-index: 20;
}
#copyBtn { right:12px; cursor:pointer; }
#stats { left:12px; }
#copyBtn.hidden, #stats.hidden { display:none; }
</style>
</head>
<body>
<div id="overlay">
<div id="dropZone">
<div><strong>Drop HAR file to extract API calls</strong></div>
<div class="opts">
<div><strong>Preset</strong></div>
<select id="preset">
<option value="api">API Only</option>
<option value="text">All Text</option>
<option value="all">Everything</option>
</select>
</div>
<div class="opts">
<div><strong>Content Types</strong></div>
<label><input type="checkbox" value="json" checked> application/json</label><br>
<label><input type="checkbox" value="js"> application/javascript</label><br>
<label><input type="checkbox" value="html" checked> text/html</label><br>
<label><input type="checkbox" value="css"> text/css</label><br>
<label><input type="checkbox" value="image"> images</label><br>
<label><input type="checkbox" value="font"> fonts</label><br>
</div>
<div class="opts">
<label><input type="checkbox" id="trim" checked> Trim large responses</label><br>
<label><input type="checkbox" id="headers" checked> Remove tracking headers</label><br>
<label><input type="checkbox" id="cookies" checked> Remove cookies</label>
</div>
</div>
</div>
<textarea id="output"></textarea>
<div id="stats" class="hidden"></div>
<button id="copyBtn" class="hidden">Copy</button>
<script>
// similar to: https://tweaksuite.com/tools/har-editor
const output = document.getElementById('output');
const overlay = document.getElementById('overlay');
const copyBtn = document.getElementById('copyBtn');
const stats = document.getElementById('stats');
const preset = document.getElementById('preset');
const typeChecks = Array.from(document.querySelectorAll('input[type="checkbox"][value]'));
const opts = {
trim: document.getElementById('trim'),
headers: document.getElementById('headers'),
cookies: document.getElementById('cookies')
};
let lastMinified = '';
const applyPreset = (value) => {
const map = {
api: { json: true, html: false, js: false, css: false, image: false, font: false },
text: { json: true, html: true, js: true, css: true, image: false, font: false },
all: { json: true, html: true, js: true, css: true, image: true, font: true }
};
const config = map[value];
typeChecks.forEach(cb => cb.checked = !!config[cb.value]);
};
preset.addEventListener('change', (e) => applyPreset(e.target.value));
applyPreset('api');
const isAllowedType = (mime) => {
const selected = new Set(typeChecks.filter(c => c.checked).map(c => c.value));
if (mime.includes('json')) return selected.has('json');
if (mime.includes('javascript')) return selected.has('js');
if (mime.includes('html')) return selected.has('html');
if (mime.includes('css')) return selected.has('css');
if (mime.startsWith('image/')) return selected.has('image');
if (mime.includes('font')) return selected.has('font');
return false;
};
const isUseful = (entry) => {
const mime = entry.response?.content?.mimeType || '';
const method = entry.request?.method || '';
if (method !== 'GET') return true;
return isAllowedType(mime);
};
const cleanHeaders = (headers) => {
return headers.filter(h => {
const name = h.name.toLowerCase();
if (opts.cookies.checked && (name === 'cookie' || name === 'set-cookie')) return false;
if (opts.headers.checked && (name.startsWith('x-') || name.startsWith('cf-') || name.includes('cloudflare'))) return false;
return true;
});
};
const trimText = (text) => {
if (!opts.trim.checked || !text) return text;
if (text.length <= 500) return text;
return text.slice(0, 500) + '\n[...excluded for brevity]';
};
const minimizeEntry = (entry) => ({
url: entry.request.url,
method: entry.request.method,
status: entry.response.status,
mime: entry.response?.content?.mimeType,
requestHeaders: cleanHeaders(entry.request.headers || []),
responseHeaders: cleanHeaders(entry.response.headers || []),
postData: trimText(entry.request.postData?.text),
response: trimText(entry.response.content?.text)
});
const formatBytes = (bytes) => {
if (bytes < 1024) return bytes + ' B';
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
};
const handleFile = (file) => {
const reader = new FileReader();
reader.onload = (e) => {
try {
const har = JSON.parse(e.target.result);
const cleaned = har.log.entries.filter(isUseful).map(minimizeEntry);
const pretty = JSON.stringify(cleaned, null, 2);
const minified = JSON.stringify(cleaned);
lastMinified = minified;
output.value = pretty;
output.style.display = 'block';
stats.textContent = `${cleaned.length} entries + ${formatBytes(minified.length)}`;
stats.classList.remove('hidden');
overlay.style.display = 'none';
copyBtn.classList.remove('hidden');
} catch {
alert('Invalid HAR file');
}
};
reader.readAsText(file);
};
window.addEventListener('dragover', (e) => e.preventDefault());
window.addEventListener('drop', (e) => {
e.preventDefault();
const file = e.dataTransfer.files[0];
if (file?.name.endsWith('.har')) handleFile(file);
});
copyBtn.addEventListener('click', async () => {
await navigator.clipboard.writeText(lastMinified);
copyBtn.textContent = 'Copied';
setTimeout(() => copyBtn.textContent = 'Copy', 1000);
});
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment