How to convert json metrics to a markdown status table
- Step 1Shape status entries as a JSON array of objects — One service or check per object:
[{ "service": "api", "status": "🟢 healthy", "latency_ms": 42, "uptime": "99.98%" }, ...]. Keys become columns in first-seen order. Put an emoji in the status string if you want a visual health column. - Step 2Extract the array if it's nested in a payload — Monitoring APIs often wrap the list (
{ "data": { "services": [...] } }). Pull the array out with json-path-extractor against$.data.servicesso the tool gets a top-level array of objects. - Step 3Open the tool and drop the file — Drop a
.json,.ndjson,.jsonl, or.txtfile, or an.xlsx/.xlssheet (first sheet read as rows → JSON). All parsing is local. - Step 4Pick alignment — Left (
:---) for service names and statuses, Center (:---:) for a short emoji status column, Right (---:) for numeric latency/uptime columns. Alignment applies to the whole table. - Step 5Click Convert to Markdown — The tool emits a header row, an alignment row, and one body row per entry. A single status object yields a one-row table.
- Step 6Copy into Slack, an Issue, or the wiki — Copy the Markdown or Download MD. Paste under a heading you write (Slack renders pipe tables in the new editor; GitHub Issues and most wikis render GFM tables natively). The preview truncates very long output at 5,000 characters on screen; the full table is still copied.
What this tool does — and what it does NOT
A direct correction of the earlier over-claims. Output is a single pipe table.
| Expected feature | Reality | Workaround |
|---|---|---|
Section headers (### Health, ### Deploys) | Not produced | Write the heading yourself above the table |
Key-value lists (- uptime: 99.9%) | Not produced — tabular only | Use a two-column key/value table |
| Status badges (shield-style) | Not produced | Put an emoji string in your data (🟢/🔴) |
| Conditional red/green coloring | Not produced — Markdown can't color cells | Emoji is the portable way to signal status |
| One row per service/check | Supported (the core feature) | Feed [{service, status, ...}, ...] |
Status report shapes
Common monitoring/deploy data and the JSON shape to feed it.
| Report type | JSON shape | Columns |
|---|---|---|
| Service health | [{service, status, latency_ms, uptime}, ...] | service, status, latency_ms, uptime |
| Deploy summary | [{env, version, result, duration_s}, ...] | env, version, result, duration_s |
| Test-suite metrics | [{suite, passed, failed, skipped}, ...] | suite, passed, failed, skipped |
| Single-metric snapshot (object) | {cpu: 42, mem: 70} (object, not array) | ONE row, a column per metric — reshape for a list |
Tier limits
Real configured numbers. A Pro tool; free is size-gated.
| Limit | Free | Pro |
|---|---|---|
| File size | 2 MB | 100 MB |
| Files per run | 1 | 10 (bulk) |
| Processing | Browser | Browser |
Cookbook
Real monitoring and deploy payloads turned into Markdown status tables, with the exact output emitted. Hostnames anonymized.
Service health → status table with emoji
ExamplePut the emoji in the status string in your data — the tool renders it verbatim, giving a visual health column without any badge feature.
Input (health.json):
[
{ "service": "api", "status": "🟢 healthy", "latency_ms": 42, "uptime": "99.98%" },
{ "service": "worker", "status": "🔴 down", "latency_ms": 0, "uptime": "97.10%" }
]
Output (alignment: left):
| service | status | latency_ms | uptime |
| :--- | :--- | :--- | :--- |
| api | 🟢 healthy | 42 | 99.98% |
| worker | 🔴 down | 0 | 97.10% |Extract the array from a wrapped monitoring payload
ExampleMost monitoring APIs nest the list. Pull it out first, then convert. The extracted array is what the tool turns into rows.
Raw API response:
{ "meta": { "ts": 1717200000 },
"data": { "services": [ {"service":"api","status":"ok"}, {"service":"db","status":"ok"} ] } }
Step 1 — json-path-extractor on $.data.services:
[ {"service":"api","status":"ok"}, {"service":"db","status":"ok"} ]
Step 2 — convert:
| service | status |
| :--- | :--- |
| api | ok |
| db | ok |Deploy summary across environments
ExampleOne row per environment makes a deploy report skimmable in a GitHub Issue. Numbers render exactly as stored.
Input:
[
{ "env": "staging", "version": "2.4.0", "result": "success", "duration_s": 84 },
{ "env": "prod", "version": "2.3.1", "result": "rolled back", "duration_s": 130 }
]
Output:
| env | version | result | duration_s |
| :--- | :--- | :--- | :--- |
| staging | 2.4.0 | success | 84 |
| prod | 2.3.1 | rolled back | 130 |Single-object snapshot is one row — reshape for a list
ExampleA snapshot object like { cpu, mem, disk } becomes ONE row with a column per metric. For a metric-per-row report, reshape into an array of {metric, value} objects first.
Object in:
{ "cpu": 42, "mem": 70, "disk": 55 }
→ | cpu | mem | disk |
| :--- | :--- | :--- |
| 42 | 70 | 55 | ← one row
Reshaped to array:
[{ "metric": "cpu", "value": 42 }, { "metric": "mem", "value": 70 }]
→ | metric | value |
| :--- | :--- |
| cpu | 42 |
| mem | 70 |Nested check details inlined as JSON
ExampleIf a service entry holds a nested checks object, the whole thing is JSON-stringified into one cell. Filter it out first for a clean summary table.
Input:
[{ "service": "api", "status": "ok", "checks": { "db": true, "cache": false } }]
Output:
| service | status | checks |
| :--- | :--- | :--- |
| api | ok | {"db":true,"cache":false} |
For a clean table, drop 'checks' with json-key-filter first.Errors and edge cases
Real errors and silent failures sourced from each platform's own documentation. Match the wording to the row, fix what the row says to fix.
Expecting headers, key-value lists, or badges
Not supportedThe tool emits a single Markdown table — no ### Section headings, no - key: value lists, no shield-style badges, and no conditional cell coloring (Markdown can't color cells). Add headings yourself, and signal status with an emoji string in your data (🟢/🟡/🔴). The earlier description claimed badges and section headers; that was inaccurate.
Status array is nested in the payload
Needs extractionMonitoring APIs usually wrap the list under data/results. A wrapped object becomes one row of stringified nested values, not a per-service table. Extract the array with json-path-extractor (e.g. $.data.services) before converting.
Single snapshot object instead of an array
One-row tableA { cpu, mem, disk } snapshot is one object, so it becomes one row with a column per metric. For metric-per-row, reshape into [{metric, value}, ...] first.
Nested check/sub-status objects
Inlined JSONA nested checks or details object is JSON-stringified into one cell, which is hard to read. Drop those fields with json-key-filter for a clean summary, or flatten them with json-flattener to get one column per sub-check.
Status text contains a pipe
PreservedA status like "degraded | partial" has its pipe backslash-escaped so the row keeps its column count. It renders with the pipe visible. Automatic, no toggle in the UI.
Invalid JSON from a logged payload
Parse errorJSON.parse is strict — a log line with trailing data, comments, or single quotes throws. Clean it with json-format-fixer or find the error with json-validator, then convert.
Timestamp stored as epoch
StringifiedAn epoch like 1717200000 renders as the raw number — it is NOT turned into a readable date. Convert timestamps to ISO strings in your data first if you want human-readable times.
Empty array (all checks filtered out)
Header-onlyNo entries means no keys and no columns. Make sure at least one status object reaches the tool — if you filtered aggressively upstream, you may have removed everything.
File over 2 MB on free
Blocked on freeFree caps input at 2 MB. A full monitoring dump with thousands of checks and nested details can exceed it. Filter to the fields and services you need, or upgrade to Pro (100 MB).
Frequently asked questions
Does this produce status badges or section headers?
No. It produces a single Markdown table — no shield-style badges, no ### Section headings, no key-value lists. The earlier description was inaccurate. For a visual status column, put an emoji (🟢/🟡/🔴) in the status string in your data; the tool renders it verbatim.
Can it color cells red for failures?
No — Markdown pipe tables can't color cells, so no tool can do that in portable Markdown. The standard, renderer-independent approach is an emoji in the status text. Slack, GitHub, and most wikis all show the emoji.
My monitoring data is nested under `data` — what do I do?
Extract the array first. Use json-path-extractor against the path to the list (e.g. $.data.services) so the tool receives a top-level array of objects, then convert.
How are columns chosen?
As the union of all keys across the status objects, in first-seen order. Add an owner or last_checked field to some entries and the column appears for all rows, blank where absent.
Can I align columns separately?
No — one alignment applies to the whole table. Left works for mixed status tables; Right is handy when the table is mostly numeric (latency, uptime, counts).
Are timestamps and numbers reformatted?
No. Numbers and date strings render exactly as stored; an epoch number stays a number. Convert timestamps to readable strings in your data before converting if you want them human-readable.
What about nested check details?
A nested object is JSON-stringified into one cell, which clutters the table. Drop it with json-key-filter for a summary, or flatten it with json-flattener to get one column per sub-check.
Will the table render in Slack and GitHub Issues?
GitHub Issues, PRs, and most wikis render GFM pipe tables natively. Slack's newer message editor renders Markdown tables when you paste them; older Slack or plain-text fields may show the raw pipes — in that case post it in a code block or an Issue and link it.
What inputs are accepted?
.json, .ndjson, .jsonl, .txt, and .xlsx/.xls. A spreadsheet's first sheet is read into a JSON array of objects before the table is built.
Are internal hostnames and IPs uploaded?
No. Conversion is fully client-side — hostnames, IPs, and incident details never leave your browser. Only an anonymous run count is recorded if you're signed in.
How large can the payload be?
2 MB on free, 100 MB on Pro. A full monitoring dump with nested details is the most likely thing to approach the free cap — filter first.
Can I post a status table automatically from CI?
Yes — it's a Pro tool with API access. Run it via the local runner in your pipeline to convert a status JSON into a Markdown table, then post that to Slack or a GitHub Issue. Conversion runs locally, so monitoring data stays on your machine.
Privacy first
Conversion runs locally in your browser. No file is uploaded — only metadata counters are saved for signed-in dashboard stats.