A DatoCMS plugin that gives a field a single-line text input where selected text can be made bold — and nothing else. Editors get a clean one-line field. It works on Structured text fields (recommended) and, as a legacy option, on JSON fields.

The route is chosen by the field type, which is fixed when the field is created — there is no switch.
| Structured text (recommended) | JSON (legacy) | |
|---|---|---|
| Field type | Structured text | JSON |
| How you enable it | Enable the Single-line strong addon in Presentation | Pick Single-line strong as the editor in Presentation |
| Field identifies as | structured_text, so translation and other format-aware tools treat it as native | the plugin’s own editor, so tools like ai-translations skip it |
| Length limit | The field’s native length validator, enforced at save | The plugin’s own setting, enforced in the editor only |
For a new field, use Structured text. The JSON route is legacy: supported, not recommended for new fields. Existing installs keep working and nothing is being removed.
Moving an existing JSON field to Structured text means creating a new field, migrating the content and updating your frontend. No migration is required: if the JSON route works for you, leave it as it is.
<strong> or
raw JSON.The plugin is on the DatoCMS Marketplace:
Then set up a field: a Structured text field for anything new, or the legacy JSON route for an existing JSON field.
The plugin can also take over a Structured text field. The field stays an ordinary structured text field as far as the rest of DatoCMS is concerned; only the editor you see changes.
Set the field up so it allows bold and nothing else:
If the field allows more than that, the addon’s settings panel says what — for example “This field also allows emphasis and underline, which the single-line editor cannot show.” It is only a heads-up: you can still save the field, and the editor simply drops what it cannot show the next time the field is edited.
Existing JSON fields keep working, and the route stays supported. It is not recommended for new fields.
json fields).Structured text route. The field holds an ordinary structured text value: a single
paragraph, with bold text carrying the strong mark. An empty field is null. Through
the CMA and GraphQL you get it back as standard
dast, like any other structured
text field.
JSON route (legacy). An empty field stores null. A non-empty field stores a
JSON string holding the normalized segment array plus a plain-text mirror:
{ "segments": [ { "value": "This is my ", "mark": false }, { "value": "highlighted", "mark": true }, { "value": " text", "mark": false } ], "raw": "This is my highlighted text"}Each segment is a run of text that is either bold (mark: true) or not. The array
is normalized — adjacent runs with the same mark are merged and there are no empty
runs — so a given rendered text always serializes to exactly one array. See
SPEC.md for the full data contract and normalization rules.
Structured text route. Nothing plugin-specific is needed. Render the field with
whatever you already use for structured text. In Astro, with
@datocms/astro:
---import { StructuredText } from '@datocms/astro/StructuredText';import { executeQuery } from '@datocms/cda-client';
const { page } = await executeQuery( `query { page { headline { value } } }`, { token: import.meta.env.DATOCMS_API_TOKEN },);---
<StructuredText data={page.headline} />Bold text comes out as <strong>. The value is always a single paragraph, so the
output is one <p>. If you need it without that wrapper — inside a heading, say — the
component lets you override how the paragraph node renders; see its
docs.
JSON route (legacy). The JSON→HTML transform is owned by the consuming app (intentionally out of scope for the plugin). A minimal renderer:
type Segment = { value: string; mark: boolean };
const escapeHtml = (s: string) => s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
export function renderStrong(fieldValue: string | null): string { if (!fieldValue) return ''; const { segments } = JSON.parse(fieldValue) as { segments: Segment[] }; return segments .map((s) => (s.mark ? `<strong>${escapeHtml(s.value)}</strong>` : escapeHtml(s.value))) .join('');}If you only need the unformatted text (slugs, meta tags), use the raw mirror
directly.
# use the Node version in .nvmrcnpm installnpm run dev # serve the plugin at http://localhost:5173npm run build # typecheck (tsc -b) + production build into dist/npm test # run the test suites (vitest)To try local changes against a real project, run npm run dev, then in DatoCMS go
to Settings → Plugins → Add new plugin → Advanced and register
http://localhost:5173 as the entry-point URL. Dato loads the plugin in an iframe
from that URL, so changes hot-reload as you edit.
Built with React + Vite + TypeScript on datocms-plugin-sdk / datocms-react-ui,
using Lexical as the editor engine.
MIT © 9elements