typescript-best-practices
cursor/plugins
TypeScript best practices for type safety, discriminated unions, and constructive modeling.
What is typescript-best-practices?
A style guide for writing type-safe TypeScript that prevents invalid states at compile time. Apply this when reading or editing .ts or .tsx files to enforce discipline around the type system, boundary validation, and runtime safety.
- Use discriminated unions with literal discriminants to model variants and prevent impossible states
- Brand primitives to prevent type confusion and enforce validation at boundaries
- Construct types so illegal values cannot be represented (e.g., non-empty arrays, even-length tuples)
- Prefer `unknown` over `any` for external data and validate at system boundaries
- Use schema-derived types and runtime validation libraries instead of hand-written type guards
- Avoid `as` casts; cast only after validation and use `satisfies` to preserve literal types
How to install typescript-best-practices
npx skills add https://github.com/cursor/plugins --skill typescript-best-practicesHow to use typescript-best-practices
- 1.Apply the type-system-discipline principle skill first
- 2.Review the rules table for the specific pattern you're implementing
- 3.Check `references/patterns.md` for concrete examples
- 4.When modeling variants, use a `kind` literal discriminant instead of optional fields
- 5.When accepting external data, parse it into a named domain type at the boundary
- 6.Use schema libraries (e.g., Zod) and infer types rather than hand-writing guards
Use cases
- Reviewing TypeScript code to catch type safety violations and impossible-state bugs
- Designing domain types that make illegal states unrepresentable
- Setting up boundary validation when parsing external data (JSON, API responses)
- Refactoring optional-field bags into discriminated unions
- Establishing type guard patterns that verify claims rather than lying about safety
- TypeScript developers writing type-safe applications
- Teams enforcing strict type discipline and preventing runtime crashes
- Code reviewers checking for type system violations
- Developers designing domain models and APIs
typescript-best-practices FAQ
Brand primitives with `& { readonly __brand: "X" }` when you need to prevent accidental mixing of similar types (e.g., UserId vs ProductId). Validate once at the boundary, then trust the type inside.
`satisfies` validates the value against a type without widening literal types, preserving specificity. `as` widens and bypasses checks. Prefer `satisfies`.
Use constructive modeling: declare the type as `[T, ...T[]]` so the shape itself guarantees at least one element. No runtime guards needed.
Prefer schema libraries (like Zod) and infer the type with `z.infer`. If hand-writing a guard, name it `isX` or `hasX` and ensure it actually verifies the claim.
Object arguments are self-documenting, but skip them on hot paths like per-frame renders, tokenizers, or parsers where performance matters.
Full instructions (SKILL.md)
Source of truth, from cursor/plugins.
name: typescript-best-practices description: TypeScript best practices. Use when reading or editing any .ts or .tsx file. paths: ["/*.ts", "/*.tsx"] disable-model-invocation: true
TypeScript best practices
Apply the type-system-discipline principle skill first.
| Rule | Summary |
|---|---|
| Discriminated unions | Model variants with a kind literal discriminant so impossible states can't be represented. No optional-field bags. |
| Branded types | Brand primitives with & { readonly __brand: "X" } so they can't be mixed up. Validate once at the boundary. |
| Constructive modeling | Build the shape so the illegal value can't be constructed. [T, ...T[]] for non-empty, [T, T][] for even length, start plus duration for a range. Not a runtime guard, not a wish for refinement types. |
| Simplest total type | Keep T[] while every operation on it stays total. Strengthen to NonEmpty<T> only where the loose type forces !, a cast, or a "should never happen" throw. |
unknown over any | External data is unknown. |
| Schemas before guards | Before hand-writing a property-by-property type guard, use the repository's runtime schema library and infer the type from the schema, such as z.infer. |
No as casts | Every as is a runtime crash waiting. Cast only after validation. |
| Narrowing hierarchy | Discriminant switch > in operator > typeof/instanceof > user-defined type guard > as. |
| Type guards | Must verify the claim. A lying guard is worse than as because the bug hides behind a name that says it's safe. Name them isX or hasX. |
| Exhaustiveness | Inline const _exhaustive: never = x; in default arms so the compiler errors when a new variant is added. |
satisfies over as | Validates the value without widening literal types. |
| Boundary validation | Parse where data crosses in, into a named domain type. Record<string, unknown> (however spelled) stops at that parse. Trust types inside. See the boundary-discipline principle skill. |
| Schema-derived types | Reach for Pick/Omit/Parameters/ReturnType/Awaited/typeof before declaring a new interface. |
| Object args | Pass objects, not positional, so argument order is self-documenting. Skip on hot paths (per-frame render, tokenizers, parsers). |
| Real tests | Don't mock what you can run. Prefer the framework's real test primitives with leak/disposable checks, and verify UI in a running build. Mock only what you can't run locally. |
| Structured telemetry | Prefer structured logger diagnostics with enough context to debug from an id. No console.log in shipped code. |
Examples: references/patterns.md.
Related skills
More from cursor/plugins and the wider catalog.

unslop
Remove AI writing patterns from any text—detect and fix superficial phrases, vague language, and filler.

verify-this
Verify claims with repeatable local evidence: baseline vs. treatment comparison with VERIFIED, NOT VERIFIED, or INCONCLUSIVE verdict.

weekly-review
Generate a weekly work summary from your commits, categorized by bugfix, tech debt, and new features.

what-did-i-get-done
Summarize your git commits into concise status updates for any time period.

why
Investigate design rationale, tradeoffs, and decision history behind code by querying multiple evidence sources in parallel.

workflow-from-chats
Extract reusable workflow preferences from Cursor chats and convert them into skills, rules, or docs.