Prettier ConfigPrettier Config

cursorOffset β€” the option that stops format-on-save losing your place

The Prettier Cursor Offset option

cursorOffset tells Prettier where your caret is, and Prettier reports back where that same position ended up after formatting. It is the small piece of machinery that makes format-on-save feel invisible instead of infuriating.

Print (to stderr) where a cursor at the given position would move to after formatting.
Prettier's own description, from the official options documentation
Default
-1
Type
int
CLI flag
--cursor-offset

The problem it solves

Formatting rewrites the text of a file, so every character offset after the first change moves. If an editor simply replaced the buffer with Prettier's output, your caret would stay at the same numeric offset and therefore end up somewhere else entirely β€” a few lines off, mid-token, in a different function.

On a file that reformats heavily the drift is dramatic, and it is the difference between format-on-save being pleasant and being unusable.

How it works

You pass the caret's current offset; Prettier tracks that point through the transformation and returns its new position. Through the API you use formatWithCursor, which returns both the formatted text and the moved cursor:

import * as prettier from 'prettier';

const { formatted, cursorOffset } = await prettier.formatWithCursor(source, {
  parser: 'typescript',
  cursorOffset: 142,
});
What an editor integration actually calls.

The returned offset is the position in the new text corresponding to where you were in the old one. The editor writes formatted into the buffer and moves the caret to cursorOffset, and from the user's point of view nothing moved at all.

On the command line the option exists too, but the result is printed to standard error rather than returned, since there is no caret for the CLI to restore.

Why you will probably never set it

This is an integration option. Every editor plugin worth using already passes it, so the behaviour you actually want is on by default and invisible.

  • Do not put it in .prettierrc. A fixed offset in a config file is meaningless β€” it would claim your caret is always at the same position in every file.
  • Do reach for it if you are writing a formatter integration, a code-mod tool, or anything that reformats a buffer a human is editing.
  • Combine it with rangeStart and rangeEnd for a Format Selection command that both narrows the work and preserves the caret.

Common mistakes

  • Calling format instead of formatWithCursor and wondering why no cursor is returned.
  • Setting it in a configuration file, where it has no sensible meaning.
  • Assuming the returned offset equals the one you passed. Its whole purpose is that it usually differs.
  • Passing a byte offset rather than a character offset on non-ASCII source.

Why the offset moves so much

It is tempting to assume the caret drifts only by the number of characters added or removed before it. In a reformat that is rarely true, because indentation changes on every line between the start of the file and the caret, and each of those changes shifts the offset.

Reformatting a file that was indented with four spaces to use two moves a caret on line 400 by hundreds of characters. Tracking that by arithmetic is not feasible from outside, which is why Prettier does it internally and hands back the answer.

The tracked position is a point in the text rather than a token, so it survives the caret sitting in whitespace or in the middle of an identifier. What it cannot do is preserve a selection β€” only a single offset is tracked, so an integration that wants to restore a selection has to make its own decision about the anchor.

Using it together with the range options

A complete Format Selection implementation passes all three. The ranges say what to format; the cursor offset says what to preserve:

const { formatted, cursorOffset } = await prettier.formatWithCursor(text, {
  parser: 'typescript',
  rangeStart: startOffset,
  rangeEnd: endOffset,
  cursorOffset: caretOffset,
});

applyEdit(formatted);
moveCaretTo(cursorOffset);
All three options in one call.

If the caret falls outside the formatted range the returned offset simply accounts for however much the range grew or shrank, so the call is safe regardless of where the user was pointing.

Use it in .prettierrc

Drop cursorOffset into your Prettier config file:

{
  "cursorOffset": -1
}
Try it in the generator

Common questions

Why does my cursor jump when I save?
The integration is probably not passing cursorOffset, or is discarding the value Prettier returns. A plugin using formatWithCursor and applying the returned offset keeps the caret in place.
Should I set cursorOffset in .prettierrc?
No. It describes a caret position at one moment in one file, which a project-wide config cannot sensibly express.
What does the CLI do with it?
It prints the resulting offset to standard error. There is no caret to move, so the value is informational.

Other Special options

Generated from Prettier 3.9.6.