strip-outer
strip-outer is a dependency-free JavaScript function that removes one exact substring from the start of a string and then one exact copy from the end. If only one side matches, it removes that side; if neither matches, it returns the original string. Matching is case-sensitive and uses startsWith and endsWith, so the argument is a literal substring rather than a regular expression or set of trim characters. Version 2 is an ESM-only package for Node 12.20 or newer lines.
Do not add strip-outer to a new project for a handful of calls; the local implementation is clearer, typeable, and lets you fix the empty-substring case. Keep it only where its exact one-per-side ESM behavior is already part of the contract.
Use it if
- You repeatedly remove one known multi-character wrapper from both ends and want that intent named
- Your project already uses ESM and supports the Node versions declared by version 2
- You specifically want at most one removal per side, with literal case-sensitive matching
- You inherited the package transitively or directly and want to preserve its exact behavior during a focused refactor
- You only have one or two call sites: two startsWith or endsWith checks and slice calls are easier than adding a dependency
- The substring can be empty: the implementation's second slice uses a negative zero length, causing a non-empty input to collapse to an empty string
- You need to remove repeated wrappers: the function strips at most one prefix and one suffix, not every matching layer
- You need whitespace trimming or character-set trimming: native trim handles whitespace and Lodash trim treats its second argument as characters, which is different from one exact substring
- You use CommonJS or need bundled TypeScript declarations: version 2 is ESM-only and publishes no type declaration, while the last release was in August 2021
Setup reality
npm install strip-outer adds no transitive dependencies, but version 2.0.0 is an ESM-only package with type: module and an exports entry pointing to index.js. Use import stripOuter from 'strip-outer'; require('strip-outer') in a CommonJS file will not work without an asynchronous import bridge or a build step. The declared engines are Node 12.20, Node 14.13.1 or newer 14.x, or Node 16 and newer. The package includes no TypeScript declarations, so strict TypeScript projects may need a local one-line module declaration or should simply inline a typed helper. Both arguments must be primitive strings; any other type throws TypeError with the generic message Expected a string. Matching is literal, case-sensitive, and performed in sequence: first startsWith, then endsWith on the already shortened value. It removes one copy from each side, not all copies. That order matters for short overlapping strings. The most important edge case is an empty substring. Every string starts and ends with an empty string; the prefix slice changes nothing, then slice(0, -substring.length) receives negative zero, which behaves as zero, and returns an empty string. Guard substring.length before calling if empty input is possible. A substring equal to the full input returns an empty string normally. There is no Unicode normalization or locale-aware comparison, though slicing by substring.length is correct when startsWith or endsWith matched the same UTF-16 sequence. The package's last release was August 2021 and last repository push was July 2022. Its implementation is seven executable lines, so vendoring or writing a typed local function is usually a better maintenance trade for new projects.
Patterns
Remove one matching wrapper from both sidesstrip-both-sides
import stripOuter from 'strip-outer'
const value = stripOuter('foobarfoo', 'foo')
// 'bar'Matching is literal and case-sensitive, and only one copy is removed from each side.
Remove the wrapper when only the start matchesstrip-prefix-only
const value = stripOuter('unicorncake', 'unicorn')
// 'cake'The same function also checks the shortened result's end, but cake does not end with unicorn.
Remove the wrapper when only the end matchesstrip-suffix-only
const value = stripOuter('unicorncake', 'cake')
// 'unicorn'This is exact substring removal, not trimming any of the characters c, a, k, and e.
Remove one pair of matching quote tokensunwrap-quoted-token
const unquoted = stripOuter('\"draft\"', '\"')
// 'draft'It does not parse escapes or require both sides to match; a lone opening quote is removed too.
Remove one leading and trailing separatorstrip-path-separators
const segment = stripOuter('/api/users/', '/')
// 'api/users'Repeated slashes remain because only one slash per side is removed. Do not treat this as path normalization.
Reject an empty wrapper before callingguard-empty-substring
function safeStripOuter(value, wrapper) {
if (wrapper.length === 0) return value
return stripOuter(value, wrapper)
}
safeStripOuter('keep me', '')
// 'keep me'Without this guard, version 2.0.0 returns an empty string for a non-empty value and empty wrapper.
Repeat explicitly when every layer must goremove-repeated-wrappers
function stripAllOuter(value, wrapper) {
if (wrapper.length === 0) return value
let next = stripOuter(value, wrapper)
while (next !== value) {
value = next
next = stripOuter(value, wrapper)
}
return value
}
stripAllOuter('foofoobarfoofoo', 'foo')
// 'bar'The package itself removes one layer only; the empty-wrapper guard also prevents a bad or non-terminating helper.
Leave a non-matching string unchangedpreserve-nonmatching-input
const input = 'Hello'
const output = stripOuter(input, 'hello')
console.log(output === input)
// trueMatching uses startsWith and endsWith without case folding or Unicode normalization.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| lodash | npm | You already depend on Lodash and want trim, trimStart, or trimEnd by a set of characters rather than one exact substring |
| trim | npm | You support old JavaScript environments and only need whitespace trimming rather than wrapper removal |
| escape-string-regexp | npm | You need to build a safe anchored regular expression for repeated or more flexible literal-wrapper removal |