A grammar checker feels simple when you use one: type a sentence, see an underline, accept a correction. Building that experience is more demanding. The system has to understand context, preserve the writer's intent, return precise edits, and stay out of the user's way as the document changes.
This guide explains the architecture of a real-time grammar checker and shows how a proofreading API can handle the language layer while your application remains in control of the editing experience.
What a real-time grammar checker needs
A production writing assistant needs more than a corrected string. It should:
- find grammar, spelling, punctuation, and style issues in context;
- preserve formatting, meaning, tone, and domain-specific terminology;
- return the exact range and replacement for each correction;
- respond quickly enough to feel native to the editor;
- handle documents that change between requests; and
- keep customer text private.
The most useful API response is therefore a list of stable text edits, not only a rewritten paragraph. Each edit identifies the original range and its replacement. Your interface can underline that range, explain the suggestion, and apply or reject it without replacing the entire document.
A simple grammar checker architecture
The client should debounce user input, send the current text to your backend, and render the returned edits. A typical flow looks like this:
- The user changes the document.
- The editor waits briefly so it does not send a request after every keystroke.
- Your backend calls the proofreading API.
- The API returns sorted, non-overlapping edits.
- The editor maps those edits to highlights and suggestion cards.
- The user accepts or dismisses each suggestion.
Keep the API key on your server. A browser or mobile client should call an endpoint you control so credentials never ship to users.
Request proofreading edits
Send text to AmberPen's /proofread endpoint with a server-side API key:
curl https://api.amberpen.dev/proofread \
-H "authorization: Bearer $AMBER_PEN_API_KEY" \
-H "content-type: application/json" \
-d '{
"text": "The new settings works across every workspace.",
"mode": "correct"
}'The response contains the source range, original text, and replacement:
{
"mode": "correct",
"edits": [
{
"id": 0,
"start": 17,
"end": 22,
"original": "works",
"replacement": "work"
}
]
}Offsets are measured in UTF-16 code units, which means they work directly with JavaScript's slice method. Apply edits from the end of the document toward the beginning so an earlier replacement does not move the ranges that follow it.
type TextEdit = {
start: number;
end: number;
replacement: string;
};
function applyEdits(text: string, edits: TextEdit[]): string {
return [...edits]
.sort((a, b) => b.start - a.start)
.reduce(
(result, edit) =>
result.slice(0, edit.start) + edit.replacement + result.slice(edit.end),
text,
);
}For a complete typed implementation, use the AmberPen TypeScript SDK, which applies edits and validates API responses for you.
Avoid stale suggestions
Text can change while a proofreading request is in flight. If you display the response against a newer document, the ranges may point to the wrong characters.
Store the document version or source text with every request. When a response arrives, compare it with the current version. Discard the response when the text has changed, then schedule a new check. You can also cancel the previous HTTP request with AbortController when the user resumes typing.
For longer documents, assign a stable textId to the document. Incremental proofreading lets AmberPen identify the parts that changed since the last request and proofread only those regions. This reduces repeated work while keeping edit offsets relative to the complete document.
Stream corrections into the interface
Waiting for an entire document to finish can make a capable grammar checker feel slow. With streaming mode, complete batches of edits arrive as newline-delimited JSON. Your interface can show the first suggestions while the rest of the document is still being processed.
Streaming works best when the UI treats each batch as an addition to a request-scoped suggestion set. Do not mix batches from different document versions, and wait for the final empty batch before marking the check as complete.
Preserve product names and specialist vocabulary
Generic grammar tools often “correct” names, technical terms, or industry language that they do not recognize. Pass known vocabulary with each request:
{
"text": "AmberPen supports Astro, ProseMirror, and TipTap editors.",
"properNouns": ["AmberPen", "Astro", "ProseMirror", "TipTap"],
"dictionary": ["prerender", "codebase"]
}Use properNouns for names whose spelling and capitalization should be preserved. Use dictionary for accepted words that may not appear in a general dictionary. See the custom dictionaries guide for request examples and limits.
Design suggestions users can trust
Correction quality is only part of the experience. A good suggestion interface should show the original phrase, the replacement, and enough surrounding context to make the change understandable. It should also offer clear accept and dismiss actions and preserve the user's cursor and selection when a change is applied.
Consider these interaction details:
- Use subtle underlines instead of interrupting the writer with pop-ups.
- Keep suggestions anchored to text as the document changes.
- Let users undo an accepted correction with the editor's normal history.
- Do not silently apply style changes.
- Distinguish spelling, grammar, style, and repetition when the response includes categories.
If your product needs an explanation for every suggestion, use evaluation mode. It adds a second review pass that drops the edits it rejects and returns a category, a one-sentence explanation, and relevance-ranked replacements for the rest.
Measure the complete experience
Track the latency users feel, not only the time spent in the API. Useful measurements include time from the last keystroke to the first visible suggestion, time to the complete result, acceptance rate, dismissal rate, and the percentage of responses discarded because the document changed.
Test with realistic documents from your product. Include short messages, long-form content, pasted text, Unicode characters, code-adjacent writing, and your customers' specialist vocabulary. Automated examples catch regressions, while anonymized aggregate interaction metrics help reveal suggestions that are technically valid but not useful.
Start with a small, reliable loop
The first version does not need every feature. Begin with debounced requests, version checks, and a clear accept-or-dismiss interaction. Add incremental requests, streaming, and custom vocabulary as document size and usage grow.
The central design choice is to keep your editor in control. A proofreading API should return precise, inspectable edits; your product decides when and how those edits reach the writer. That separation makes the grammar checker easier to test, safer to integrate, and more natural to use.
For the framework-specific version of this loop, see the TipTap and ProseMirror integration, the Lexical plugin, or the CodeMirror 6 extension. To choose the API behind it, the grammar checker API comparison puts the seven realistic options side by side.