CodeMirror JSON Editor with Linting — Free JS Snippet

CodeMirror JSON Editor with Validation Gutter · Forms · Plain HTML, CSS & JS · Live preview

CategoryForms

What's included

Features

Live JSON validation with debounced linting
Gutter markers, underlined ranges and hover tooltips from the lint add-on
Built-in JSON checker returning the exact error offset, converted to a line and column
Clickable problems list that moves the cursor to the error
Status pill showing Valid JSON or the problem count
Format button that re-serialises with two-space indentation when valid
JSON mode via { name: "javascript", json: true } with bracket matching
Lint helper registered by name with registerHelper

About this UI Snippet

CodeMirror JSON Editor with Validation Gutter — HTML, CSS & JavaScript

Screenshot of the CodeMirror JSON Editor with Validation Gutter snippet rendered live

Editing JSON by hand is a fragile activity. A missing comma, a stray trailing comma, an unquoted key or a mismatched brace turns the whole document invalid, and a plain textarea reports nothing until something downstream fails. A good JSON editor tells you the moment it goes wrong and shows exactly where. CodeMirror's lint add-on provides the machinery — markers in a gutter column, tooltips on hover, underlined ranges in the text — and leaves the actual checking to a function you supply.

The lint add-on is worth understanding because it separates two concerns. CodeMirror handles the presentation: given a list of annotations, each with a from position, a to position, a message and a severity, it draws the gutter marker, the wavy underline and the hover tooltip, debounced by the delay option so it does not run on every keystroke. Your job is only to produce those annotations. Here that is lintJson(), which calls JSON.parse and converts its failure into a single annotation. Two setup details are easy to miss: the CodeMirror-lint-markers gutter must be listed in the gutters array or there is nowhere for the markers to appear, and the function can be registered with registerHelper('lint', 'json', fn) or passed directly through lint: { getAnnotations }.

The subtle part is turning an error into a location. It is tempting to call JSON.parse and read its exception, but browsers disagree about what that exception contains: older V8 reports a character position, current Chrome quotes a fragment of the text with no position at all, Firefox reports a line and column, and Safari reports something different again. Code that scrapes the message works in one browser and silently points at the end of the document in another. So this snippet includes a small recursive-descent checker, findJsonError(), that walks the text itself and returns the exact character offset of the first mistake along with a plain-English message — "Trailing comma is not allowed in JSON", "Property names must be in double quotes", "Expected ',' or '}'". A short locate() function then converts the offset into a line and column by counting newlines. JSON.parse stays as a final safety net for anything the checker accepts but the engine rejects. The checker stops at the first error, so only one problem is reported at a time; fix one and the next appears, and a genuinely multi-error linter needs a tolerant parser such as jsonc-parser.

The surrounding UI turns diagnostics into workflow. A status pill shows Valid JSON or the problem count, a problems list underneath is clickable and moves the cursor to the error, and the Format button re-serialises with two-space indentation — but only when the document is valid, since formatting invalid JSON is impossible. The editor opens on a document with a trailing comma, the most common real-world JSON mistake, so the gutter marker is visible immediately.

Build with AI

Build, Understand, Optimize, and Extend It With AI

Ask an AI assistant like Claude to add JSON Schema validation with Ajv, an error position tolerant of multiple problems using jsonc-parser, or auto-fix for trailing commas.

Prompt to recreate it

Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:

text
Build a JSON editor with live validation using CodeMirror 5.65 from cdnjs (core, javascript mode, matchbrackets, closebrackets and the lint add-on with its CSS).

Requirements:
- Use mode { name: 'javascript', json: true } and gutters ['CodeMirror-lint-markers', 'CodeMirror-linenumbers'] with lint: { getAnnotations, delay: 250 }.
- Implement lintJson(text) with a small recursive-descent JSON checker that returns the exact character offset and a plain-English message for the first error (trailing commas, unquoted keys, missing commas), convert the offset to a line and column, and return a from/to/message/severity annotation; keep JSON.parse as a final safety net.
- Show a status pill (Valid JSON / n problems) and a clickable problems list that moves the cursor to the error.
- Add a Format button using JSON.stringify(JSON.parse(text), null, 2) that does nothing on invalid input, and a button toggling a known-bad sample with a trailing comma.

Want to tighten it up first? Run this prompt through the AI Prompt Studio to score it across 8 quality dimensions, catch anti-patterns, and tune the wording for Claude, ChatGPT, or Gemini before you paste it in.

Source Code

<div class="jl-app">
  <div class="jl-bar">
    <div class="jl-title">config.json</div>
    <span class="jl-pill" id="jlPill" role="status" aria-live="polite">Checking...</span>
    <div class="jl-btns">
      <button type="button" id="jlFormat">Format</button>
      <button type="button" id="jlBreak">Break it</button>
    </div>
  </div>
  <div id="jlHost"></div>
  <ul class="jl-problems" id="jlProblems" aria-label="Problems"></ul>
</div>

Step by step

How to Use

  1. 1
    See the errorThe editor opens on JSON with a trailing comma. A red marker sits in the gutter and the status pill shows one problem.
  2. 2
    Hover the markerHover the gutter marker or the underlined text for the parser's message.
  3. 3
    Jump to the problemClick the entry in the problems list. The cursor moves to the exact position.
  4. 4
    Fix it and formatDelete the trailing comma. The pill turns green; press Format to re-indent the whole document.
  5. 5
    Break it againPress Break it to toggle the error back and forth and watch the diagnostics update.

Real-world uses

Common Use Cases

Config and settings editors
Let users edit JSON configuration safely. For a general-purpose editor with folding and themes see the CodeMirror editor snippet.
ADMIN
API request and payload builders
Validate request bodies before sending them, and show exactly what is wrong.
Feature-flag and schema editing
Edit flag definitions or form schemas with immediate feedback.
Learning error location
A worked example of turning a parser's character offset into an editor position.

Got questions?

Frequently Asked Questions

The checker stops at the first syntax error, like JSON.parse. To report several at once, use a tolerant parser such as jsonc-parser or a JSON-schema validator.

Browsers format those messages differently and some no longer include a position. A small custom checker gives the same exact offset in every browser.

The gutters array must include "CodeMirror-lint-markers". Without that gutter column there is nowhere to draw the markers.

Take the text before the offset, count its newlines for the line, and subtract the last newline index for the column, as locate() does.

Register a helper for the mode or pass getAnnotations returning from/to positions, messages and severities from any checker you have.

It parses first. Invalid JSON cannot be re-serialised, so the button leaves the text alone and the gutter explains why.

Run a validator such as Ajv on the parsed value and map each schema error's path back to a position to create additional annotations.

Yes. Use the JSX, Vue, Angular or Tailwind export buttons on this page to convert the markup and styles. The behaviour comes from CodeMirror, so in a framework project install it with npm install codemirror (v5) or use the modular CodeMirror 6 packages instead of the CDN tag, create it in useEffect / onMounted / ngAfterViewInit on a host element, and release it with toTextArea() or removing the wrapper element when the component unmounts.