Tiptap code blocks: add Lowlight without registering the node twice

Use Tiptap's CodeBlockLowlight extension when authors need syntax highlighting inside editable code blocks. Supply a Lowlight instance and avoid also registering the ordinary codeBlock node. In a StarterKit editor, disable StarterKit's codeBlock before adding the replacement. CodeBlockLowlight, StarterKit configuration

An internal integration note contains a short JSON response. The author should be able to edit its literal keys and values with useful coloring. Executing that text as HTML, or coloring a separate reader page, solves a different problem.

Supply a highlighter with the languages you need

Current Lowlight documentation uses createLowlight. This configuration fragment registers JSON explicitly and supplies the instance to Tiptap:

import StarterKit from '@tiptap/starter-kit';
import CodeBlockLowlight from '@tiptap/extension-code-block-lowlight';
import { createLowlight } from 'lowlight';
import json from 'highlight.js/lib/languages/json';

const lowlight = createLowlight();
lowlight.register('json', json);

const integrationExtensions = [
  StarterKit.configure({ codeBlock: false }),
  CodeBlockLowlight.configure({
    lowlight,
    defaultLanguage: 'json',
    enableTabIndentation: true,
    tabSize: 2
  })
];

Use these entries in an existing compatible editor's extension list. Declare highlight.js as a project dependency when importing its grammar directly, alongside the Tiptap extension and Lowlight packages. Keep the rest of the application's editor setup and styles. Lowlight's current package documentation describes registering language grammars.

If your editor already configures StarterKit elsewhere, change that entry instead of registering a second StarterKit. The same principle applies to a separately registered CodeBlock extension: replace the ordinary node rather than keeping two features with the same node identity.

Make the language choice visible in the document

The default defaultLanguage is null, which allows Lowlight's automatic detection. Setting JSON above makes the intended default explicit for this particular integration-note editor. A default is a content policy; it does not validate JSON or convert arbitrary pasted code into JSON.

Use this original illustrative response as the block's text:

{
  "ticket": "R-208",
  "status": "awaiting_sample"
}

The editor should retain the braces, quotes, line breaks, and underscore. Tiptap documents fenced-code input with a language, such as three backticks followed by css, as another route to a language-tagged code block. Choose a grammar actually registered in your integration.

The extension exports pre and code markup with language classes. Its languageClassPrefix defaults to language-. Do not change that prefix casually if your reader renderer or stylesheet already relies on it.

Fix uncolored code at the right boundary

A working highlighter still needs CSS for its generated token classes. Check that the editor's stylesheet defines the classes used by the registered grammar, such as string and property-name token styling, with adequate contrast against the code background.

The highlight.js HTML guide covers highlighting reader-side markup. That renderer is a separate step from Tiptap's editable Lowlight decorations. Do not infer that editor colors will appear in a custom reader renderer merely because the saved block retains a language class.

If code remains plain, check the registered grammar and language identifier, the supplied Lowlight instance, and token styles. If initialization warns about duplicate extension names, inspect the ordinary CodeBlock and StarterKit entries before changing the code text.

Preserve code through editing and saving

The configured Tab behavior uses two spaces. Test it inside the code block and retain a clear route back to normal paragraphs. The extension also documents setCodeBlock() and toggleCodeBlock() for application controls; their result depends on the current selection.

Save the integration note through the application's normal storage route and reopen it. Check the literal response and its language in the stored document, then read the final renderer. The status must still be awaiting_sample; coloring is useful only if it leaves that copyable text intact.

Keep reading