Library / SDK
gustavoguichard/string-ts avatar
gustavoguichard/string-ts

string-ts keeps the literal type through the string operation

Strongly typed string functions

1,253 stars20 forksTypeScriptMIT

At a glance

What is it?
string-ts is a MIT licensed TypeScript library of string functions that preserve literal types and unions of literals at both the type level and the runtime level, so a constant stays a constant after a transformation. It also ships object key transforms, casing type utilities, and an optional import that overrides the native String.prototype declarations, inspired by ts-reset.
Who is it for?
string-ts suits a TypeScript codebase that leans on literal types for configuration keys, discriminated string unions and API routes, and that wants those types to survive a transformation instead of collapsing to string. It does not suit a project that treats strings as plain strings, where the added types buy nothing and the extra dependency is pure cost, and it will not work on TypeScript 4, since the library states it only works on TypeScript v5 or newer.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 8 days ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on November 7, 2026, and from our analysis. They are not legal advice.

From daily snapshots

Star history

  • gustavoguichard/string-ts1,253 ★ · +0
Star history
Repository2026-10-192026-11-07
gustavoguichard/string-ts1.3k1.3k

0 Stars in the last 19 days, measured from the daily GitHub snapshot. Compare with other projects

Editorial analysis

The type of a constant dies at the first replace

The problem the library describes is specific and worth restating in the author's terms. When you are working with literal strings, the string manipulation functions only work at the runtime level and the types do not follow those transformations. You end up losing type information and possibly having to cast the result.

The example is a single line:

ts
const str = 'hello-world'
const result = str.replace('-', ' ') // you should use: as 'hello world'
//    ^? string

The type of the result is string, even though the value is known to be 'hello world'. The information was available at compile time and was thrown away by the standard library method's signature.

The stated reason this matters is that TypeScript yields the best static analysis when types are highly specific, and that literals are more specific than the type string. The library's claim is that it preserves literals, and unions of literals, after transformations, unlike most existing utility libraries and unlike the built-in string methods.

That last clause is the one to test. A library that returns string where a literal was possible is no better than the built-in method, so the value of the whole thing rests on the return type being narrower than string, and on that narrow type being correct rather than merely plausible.

The native override is one import and it is global

Extracting functions is the recommended usage, but the library also offers a project-wide option that borrows an idea from ts-reset: a single import that overrides the native String.prototype type declarations.

ts
import 'string-ts/native'

const str = 'hello-world' as const

str.replace('-', '_')
//  ^? 'hello_world'

str.charAt(6)
//  ^? 'w'

The method is described as giving you strong typing for supported methods like charAt, startsWith, endsWith, replace, split, and more. The result is that the whole codebase benefits without changing a single call site, which is a large ergonomic win over converting every call to a function call.

The cost is that the change is global and type level, which is exactly why the README frames it as the second choice rather than the first. It also documents the type checker overhead honestly, with a measurement: in the library's tests, tsc went from 17.21 seconds to 17.41 seconds. That is a small number, and it is the kind of number worth reading precisely because it is specific.

There is one limit stated in the same place. Only methods already supported by the library are typed, and the length property cannot be strongly typed because it is a read-only property rather than a method. The scope of the override is therefore the library's own function list, projected onto the prototype.

Key transforms, shallow and deep, are where the type earns its keep

The object functions come in two flavours, and the deep one is the more interesting.

The shallow family is camelKeys, constantKeys, delimiterKeys, kebabKeys, pascalKeys, replaceKeys and snakeKeys, which rename the keys of an object one level. The deep family is deepCamelKeys, deepConstantKeys, deepDelimiterKeys, deepKebabKeys, deepPascalKeys and deepSnakeKeys, which walk the structure. There is also a runtime-only utility, deepTransformKeys, which the API reference lists separately because it has no type level counterpart.

The README's longer example is the case for the deep variant, and it is worth reading closely because it shows what a precise type buys. The setup parses process.env with a zod schema and then maps the keys to camel case, in one version with lodash-es mapKeys and camelCase, and in the other with deepCamelKeys.

The lodash version produces Dictionary<string>, which the README calls too loose, and the comment explains the consequence: TypeScript is okay with it, 'abc' is expected to be of type string, and this will have unexpected behavior at runtime. The precise version produces { nodeEnv: string }, so the error is that the property 'abc' does not exist on the type, and the mistake is caught at compile time.

This is the clearest statement of the library's value proposition, because the two versions do the same thing at runtime and differ only in what the compiler knows afterwards. A library that only produced the loose type would be a runtime convenience; the type is the feature.

The catalogue is wide, and the type utilities are the quiet half

The API section of the README is a long list, and reading it tells you how much of the standard library this project shadows.

There are runtime counterparts of the native type utilities, capitalize and uncapitalize, which mirror the Capitalize<T> and Uncapitalize<T> helpers from src/types.d.ts. There are strongly-typed alternatives to the native runtime utilities, covering charAt, concat, endsWith, includes, join, length, padEnd, padStart, repeat, replace, replaceAll, slice, split, startsWith, toLowerCase, toUpperCase, trim, trimEnd and trimStart.

Then there is a set of strongly-typed alternatives to common loosely-typed functions, which is where the library stops mirroring the language and starts covering the utility library ecosystem: camelCase, constantCase, delimiterCase, kebabCase, pascalCase, reverse, snakeCase, titleCase, truncate and words. These are the functions people usually reach for in a general purpose utility library, and the claim is that their return types stay narrow.

The last part of the API list is the type utilities, split into native TypeScript type utilities, general type utilities from the library, casing type utilities, and other exported type utilities. The casing type utilities are the ones that let you write a type-level transformation without a runtime function, which is the cheapest possible use of the library, since it adds no runtime code at all.

A library that shadows this much of the standard library is making a bet that the cost of losing literal types is high enough to justify a large API surface. The bet is not obviously right for every codebase, and the README even carries a link to an interview section titled as a question about the purpose of the library.

Tree shaking is a build configuration, not a promise

The library states that it has been designed with tree shaking in mind, and that it has been tested with build tools like Webpack, Vite and Rollup. The package metadata backs that up in a concrete way: sideEffects is set to false, which tells a bundler that importing the package has no side effects and that unused exports can be dropped.

The build is tsup, with the script running tsup against src/index.ts for both esm and cjs formats with declaration output and the treeshake flag, followed by a script that generates the entry points. The package exports two subpaths, the root with the import, require and default conditions plus types, and ./native with its own default and types, and a typesVersions entry so that TypeScript resolves the native types on older resolution modes.

The tooling around it is modern and small: Biome for linting, Vitest for the tests with v8 coverage, a tsconfig.dist.json for the distribution type check, and pnpm as the package manager at a pinned version, with an override that keeps Vite on the 6 line for the test tooling.

Tree shaking matters more here than in an ordinary utility library. A project that imports one function from this package should not end up shipping the whole catalogue, and sideEffects false plus a per-export module structure is what makes that possible with the bundlers the project says it tested against.

Install, then decide between the function and the import

Installation is one command:

bash
npm install string-ts

The first decision after that is whether you want the functions or the native override, and the README is clear that extracting functions is the recommended usage. The override is for the case where you want strongly-typed native string methods across the whole project.

The second decision is where you apply it. A sensible start is a boundary rather than a blanket change: use the functions in the few places where a literal type carries meaning, such as a configuration key set or a route table, and leave the rest of the codebase on plain strings. The native override is the opposite bet, and it is the one that changes global type declarations.

The third decision is whether you need the object key transforms at all. The camelKeys and deepCamelKeys families are for the case where data arrives with one key convention and your code expects another, and the deep version in particular is a bigger commitment than a string function because it changes the type of an entire object shape.

The constraint to check first is the TypeScript version. The README states that the library currently only works on TypeScript v5 and newer, and points to a known limitations section for the rest. On a project still on TypeScript 4, the library is not an option as it stands.

A steady release line, and an open question about purpose

The maintenance picture is healthy. The published versions are 2.3.1 from 2025-11-28, 2.3.0 from 2025-11-27 whose release note is Native string method overrides, and 2.2.1 from February 2025, and the repository is not archived with the last push on 2026-06-01.

That 2.3.0 release is the significant one, because the native String.prototype override arrived there and is the feature most likely to change how people use the library. Shipping a global type override and the underlying functions in the same release is a coherent choice, since the override is generated from the same function list.

The licence is MIT, with the LICENSE file at the root, which is the least frictional option for a library that most consumers will add to a TypeScript project without publishing anything of their own.

The one thing to weigh is stated in the README itself, in the link to an interview section framed as still not getting the purpose of the library. That is an unusual thing to publish and an honest one, and it points at a real limitation of the approach rather than of the implementation. A literal type is a compile time fact about a value the program already knows, and TypeScript's own inference will keep it alive as long as the operations applied to it are typed. The operations TypeScript does not type precisely are the ones this library replaces, which means the value of the library is proportional to how much of your code does string manipulation on values that were literal to begin with. In a codebase that mostly parses input into strings and passes them around, the investment does not pay back.

The repository also carries a SOW.md, a statement of work, alongside a.claude/ directory and a.wispbit/ directory, and the project documents its coverage with a codecov badge and lists its contributors, which is the profile of a small library maintained with care.

Editorial conclusion

string-ts suits a TypeScript codebase that leans on literal types for configuration keys, discriminated string unions and API routes, and that wants those types to survive a transformation instead of collapsing to string. It does not suit a project that treats strings as plain strings, where the added types buy nothing and the extra dependency is pure cost, and it will not work on TypeScript 4, since the library states it only works on TypeScript v5 or newer. Verify first that your compiler is on TypeScript 5, and that the type-level cost is acceptable for your build, since the native override import is global and the library's own measurement of its effect on tsc is in the README.

Frequently asked questions

What does string-ts do differently from the built-in string methods?

It preserves literal types and unions of literals after a transformation, so a value known at compile time to be 'hello-world' stays known after a replacement. The built-in methods return string, and the README notes that most existing utility libraries do the same.

How do I install string-ts?

With npm install string-ts. The library only works on TypeScript v5 or newer, and the README points to a known limitations section for the rest of the constraints.

What does importing string-ts/native do?

It overrides the native String.prototype type declarations, in the style of ts-reset, so supported methods such as charAt, startsWith, endsWith, replace and split keep their literal types across a whole project without changing call sites.

What is the difference between camelKeys and deepCamelKeys?

camelKeys renames the keys of an object one level, while deepCamelKeys walks the nested structure. The README's example shows the shallow lodash-es mapKeys approach producing a loose Dictionary<string> and deepCamelKeys producing a precise { nodeEnv: string } that catches a missing property at compile time.

Is string-ts tree shakable?

The project states that it has been designed with tree shaking in mind and tested with build tools like Webpack, Vite and Rollup. The package metadata sets sideEffects to false, and the build runs tsup with the treeshake flag.

Official sources

  1. gustavoguichard/string-ts on GitHub
  2. Issues
  3. License: MIT
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/gustavoguichard-string-ts.svg)](https://hysenlabs.com/projects/gustavoguichard-string-ts)

Star history chart for your README

markdown
[![Star history](https://hysenlabs.com/chart/gustavoguichard-string-ts.svg)](https://hysenlabs.com/projects/gustavoguichard-string-ts)

Was this useful?