Repository navigation
Recipes
Each recipe below ran as shown against the published 2.0.0, and the output under it is what it printed.
RegExp.prototype.exec gives the match and its index, which is what the function needs. This is the README's example; it replaces the link the expression found and leaves the other one alone:
import replaceStringAtPosition from 'replace-string-at-position';
const text = 'See https://example.com and https://example.com/docs.';
const match = /https:\/\/example\.com\/docs/u.exec(text);
console.log(replaceStringAtPosition(text, match[0], '<link>', match.index));prints:
See https://example.com and <link>.
exec returns null when nothing matches; check that before reading match.index.
Replacing a match with text of another length moves everything after it, so positions found before the first edit are wrong for the later ones. Work from the last match to the first:
import replaceStringAtPosition from 'replace-string-at-position';
const text = 'Call @ana, then @bo.';
const matches = [...text.matchAll(/@[a-z]+/gu)];
let result = text;
for (const match of matches.toReversed()) {
result = replaceStringAtPosition(result, match[0], `<${match[0].slice(1)}>`, match.index);
}
console.log(result);
let wrong = text;
for (const match of matches) {
wrong = replaceStringAtPosition(wrong, match[0], `<${match[0].slice(1)}>`, match.index);
}
console.log(wrong);prints:
Call <ana>, then <bo>.
Call <ana>, then<bo>o.
The second line is what going left to right does: the first edit made the text one unit longer, and the second edit landed one unit early. Array.prototype.toReversed needs Node 20 or later, which the package needs anyway. For a plain "replace every match", String.prototype.replaceAll with a function is simpler; this recipe is for when each edit needs its own position.
An empty newString deletes. indexOf returns -1 when the text is missing, which the function refuses with a RangeError, so test for it:
import replaceStringAtPosition from 'replace-string-at-position';
const message = 'hi @bot please help';
const mention = '@bot ';
const at = message.indexOf(mention);
if (at !== -1) {
console.log(replaceStringAtPosition(message, mention, '', at));
}prints:
hi please help
The function trusts the position and never compares sourceString with the text there (How text is replaced). When a position might be stale, guard it with startsWith:
import replaceStringAtPosition from 'replace-string-at-position';
function replaceIfPresent(text, expected, replacement, position) {
if (!text.startsWith(expected, position)) {
return text;
}
return replaceStringAtPosition(text, expected, replacement, position);
}
console.log(replaceIfPresent('hello world', 'world', 'there', 6));
console.log(replaceIfPresent('hello world', 'earth', 'there', 6));prints:
hello there
hello world
When a position counts code points (characters as [...text] or Array.from sees them) instead of UTF-16 code units, convert it by measuring the code points before it:
import replaceStringAtPosition from 'replace-string-at-position';
const text = '😀😀ab';
const codePointIndex = 2;
const position = [...text].slice(0, codePointIndex).join('').length;
console.log(position);
console.log(replaceStringAtPosition(text, 'a', 'A', position));prints:
4
😀😀Ab
Grapheme clusters (a flag, an emoji with a skin tone, a letter with combining marks) need Intl.Segmenter for the same conversion; that was not tested here.
Positions measured in the original text stay valid if the edits are applied from the highest position down:
import replaceStringAtPosition from 'replace-string-at-position';
const text = 'The quick fox';
const edits = [
{position: 0, source: 'The', replacement: 'A'},
{position: 10, source: 'fox', replacement: 'brown dog'},
];
let result = text;
for (const edit of edits.toSorted((a, b) => b.position - a.position)) {
result = replaceStringAtPosition(result, edit.source, edit.replacement, edit.position);
}
console.log(result);prints:
A quick brown dog
Edits that overlap give an answer that depends on their order; keep them apart.
This wiki describes replace-string-at-position 2.0.0 and was last updated on 2026-09-30. The library is MIT licensed. Report problems in the issues. On npm: replace-string-at-position.
Using it
The releases
Contributing
Elsewhere