
TypeScript/JavaScript Development
FreeEnforce Metabase coding standards in TypeScript and JavaScript.
Free · Opens the source repo
What TypeScript/JavaScript Development does
The TypeScript/JavaScript Development Skill is designed for developers working within the Metabase codebase, ensuring adherence to specific coding standards and best practices. This skill emphasizes the importance of strict type usage, avoiding the use of any in new code, and mandates thorough type verification before changes are finalized. By following these guidelines, developers can maintain a robust and type-safe codebase, which is crucial for long-term maintainability and collaboration.
The skill outlines a series of rules and practices aimed at tightening type definitions, promoting the use of generics, and ensuring that types are modeled accurately according to the actual data contracts. Developers are encouraged to leverage existing types and avoid unnecessary type casts, which can lead to potential runtime errors. This focus on type safety not only improves code quality but also enhances the developer experience by reducing ambiguity and increasing confidence in the code.
Additionally, the skill provides guidance on managing null and undefined values effectively, promoting sensible defaults and rigorous checks to prevent unexpected behavior. Naming conventions are also emphasized, ensuring that variable names accurately reflect their purpose and align with the overall code structure. By adhering to these principles, developers can create clean, understandable, and reusable code that aligns with the Metabase philosophy.
This skill is particularly useful for teams working on TypeScript or JavaScript projects within the Metabase ecosystem, whether they are developing new features or refactoring existing code. It serves as a comprehensive reference for best practices, helping to onboard new developers and maintain consistency across the codebase.
When to use it
Use this skill when developing or refactoring TypeScript or JavaScript code in the Metabase environment to ensure compliance with established coding standards.
When not to use it
This skill may not be suitable for projects outside the Metabase ecosystem or for developers who prefer a more flexible approach to type definitions.
What you can build with it
Refactoring Existing Code
When refactoring TypeScript or JavaScript code, use this skill to ensure that all changes comply with Metabase's strict type safety standards.
Onboarding New Developers
New team members can refer to this skill to quickly understand the coding standards and best practices expected in the Metabase codebase.
Developing New Features
When adding new features, utilize this skill to maintain consistency and quality in type definitions and overall code structure.
How to install TypeScript/JavaScript Development
View source1. Install with the skills CLI
npx skills add metabase/metabase/typescript-write --agent claude-code2. Or install it manually
Download the skill folder and drop it into ~/.claude/skills/ for all projects, or .claude/skills/ to scope it to one repo. Restart Claude Code so it picks up the new skill.
Anthropic's agentic coding CLI, and the reference implementation of Agent Skills. Drop a skill folder into ~/.claude/skills and Claude Code loads it automatically whenever a task matches the skill's description. Claude Code docs
Inside SKILL.md
Written by metabaseTypeScript/JavaScript Development Skill
@./../_shared/development-workflow.md @./../_shared/typescript-commands.md @./../_shared/react-redux-patterns.md
No any — hard rule
- New code must not introduce
any, explicit or implicit. Noanyannotations, noas any/as unknown as, no untyped parameters or returns that inferany, no implicitly-anydestructures or array/object literals. - Untyped third-party / boundary values must be typed at the boundary (a declared type,
unknown+ type guard, or a small typed wrapper) — never letanypropagate inward. - Mandatory type verification. Before finishing a TS/TSX change, run
bun run type-check-pure. If TypeScript LSP tools are available, also inspect changed symbols with hover and go-to-definition; otherwise skip LSP check.
Type tightening
- Avoid type casts and loose
unknown— fix the signature instead. - If a function only needs one field of a wide object, accept that field — not the wide object. The cast often disappears once the signature is right.
- Reach for
Partial<T>,Pick<T, K>,Record<K, V>, and generics before reaching for a cast. - Prefer making props/components generic (
<T>) when a value flows through unchanged and the caller knows the type. - Prefer
unknownover loose typing and narrow before use — anunknownvalue forces a guard at the point of use. satisfiesfor object literals that must conform without widening (config objects, lookup maps, discriminated literals) — better than: T(widens) oras T(unsafe).- Avoid non-null assertions (
!). Prefer a guard, early return, or?.. Use!only when non-nullness is provably true and localized, with a comment. - No redundant runtime coercion — don't wrap already-typed values in
Number()/String()/Boolean(). - Type guards belong in
frontend/src/metabase-types/guards/. Do not redefine them locally. - A cast you can't avoid needs a real justification comment. The
metabase/no-unjustified-type-castsrule accepts any preceding comment — state the actual reason the cast is safe. NEVER write the legacy// Unjustified type cast. FIXMEplaceholder; it exists only on casts that predated the rule, and copying it sneaks an unjustified cast past the linter. If you can't articulate why the cast is correct, the cast is wrong — fix the types.
Type modeling
- Reuse existing types; don't re-declare them. Use canonical IDs and domain entity types from
metabase-types/api(FieldId,TableId,ConcreteTableId,SchemaName, …) and key data structures by them (new Map<ConcreteTableId, …>()). Don't duplicate generated/API types — compose or derive (Pick,Omit, indexed accessSomeType["field"],ReturnType). - Use generics to allow TypeScript to infer correct types when creating functions and components that need to be reusable and type-safe. Don't hesitate to introduce complex generics if they allow to derive types automatically instead of manual narrowing.
- Model the actual data contract; keep types narrow. Optional
field?: Tfor a key that may be absent,field: T | undefinedonly when the key is always present but the value may be undefined,| nullfor explicit API nulls. Prefer domain unions over broadstring/number/ looseRecord. - Refer to API implementation when defining or refining types to ensure they match the actual data structure. When considering a type cast, first consider if the type should be refined to match the actual data structure.
- Discriminated unions for variant state, with exhaustive checks. Model "one of N shapes" as a union with a literal discriminant rather than a bag of optional fields, and exhaust it with ts-pattern's
.exhaustive()so adding a variant becomes a compile error:import { match } from "ts-pattern"; const result = match(status) .with({ type: "loading" }, () => <Spinner />) .with({ type: "error", error: P.select() }, (error) => <Error message={error.message} />) .with({ type: "success", data: P.select() }, (data) => <Content data={data} />) .exhaustive(); // Compile-time guarantee all cases handled - Derive union types from constants (
as const+typeof/keyof) so the type and the values can't drift. readonly/ immutability where mutation isn't intended — component props, shared constants, exported config. Preferreadonly T[]/ReadonlyArray<T>for inputs you don't mutate.- Type async and error states explicitly (a discriminated union or the data-layer's typed result) — never leave loading/error/empty implicit.
Null and undefined
- Narrow at the source. If a value is optional only in a corner case, don't thread
undefinedthrough every layer — guard at the producer. - Sensible defaults for optional values. Use
?.and??at the consumer. - Lists should be filtered before being used in a map or other iteration.
- Avoid non-strict null comparisons (
X != null) whenXcan never benull— use a strict check or narrow the type. UsecheckNotNullwhere necessary. - Check actual nullability against API implementation. Find the API endpoint implementation and check if the field can actually be null.
Naming
- Names describe the entity, not the mechanism. A name must reflect what the value holds.
- Align sibling concepts: keep verb conventions consistent across a related API.
- No names that encode implementation history rather than current meaning. Suffixes like
Base,New,Old,Initialneed a real semantic distinction, otherwise drop them. - Avoid cryptic identifiers (
v,n,$n) for domain values; short names are fine only in tiny conventional contexts (loop indexi, coordinatesx/y, generic paramsT/K/V).
Code structure and organization
- Prioritize reusability over duplication. The codebase already has many utility functions — leverage them. If you introduce duplicated logic, extract it to a shared utility.
- Generic helpers do not belong in feature folders. Promote to a shared utility.
- Keep functions small and single-purpose. A 100+ line function is hard to review — split into focused named helpers, each with one responsibility and a minimal dependency surface. When necessary, cover with unit tests.
- Extract distinct complex JSX into named components. Choose same-file vs separate-file by reuse, coupling, testability, and readability.
Comments
- No comments by default. Well-named identifiers carry the
what. - Comments should be concise. Add a short, concise comment only when the
whyis non-obvious: a workaround, a hidden invariant, a subtle ordering constraint, a clever reduction. Never document the actual implementation, focus on the intent and the why.
TypeScript Migration
When touching existing JavaScript files, propose to convert them to TypeScript first. Create a separate PR for the conversion, then implement the changes.
Verify before done
- Run the project type-check when finished (see the shared TypeScript commands above).
Frequently asked questions about TypeScript/JavaScript Development
Similar skills
React Composition Patterns
Streamline your React component architecture with proven patterns.
Pester Should Migration
Easily convert Pester v5 assertions to v6 syntax.
Radix to Base UI Migration
Seamlessly migrate React components from Radix UI to Base UI.
Migrate Next.js to Vinext
Seamlessly transition your Next.js projects to Vinext.
WinUI 3 Migration Guide
Streamline your UWP to WinUI 3 migration process.
Refactor
Enhance code maintainability without altering behavior.
