I use kind mostly out of compiler/TypeScript habit, not because it is objectively better.
In TypeScript, both of these are normal discriminants:
type Route =
| { type: 'mount'; path: string }
| { type: 'app'; name: string };type Route =
| { kind: 'mount'; path: string }
| { kind: 'app'; name: string };They work the same for narrowing. The choice is mostly API taste, ecosystem convention, and collision avoidance.
Why People Use kind
kind is common in compiler-ish code, ASTs, IRs, and internal state machines.
Reasons:
-
Avoids overloading
typetypecan already mean many things:{ type: 'json', contentType: 'application/json', runtimeType: 'browser', schemaType: 'object' }
If a domain already has a meaningful “type,”
kindcan be a cleaner discriminant. -
Reads like “variant kind” In a discriminated union, the field is not always a real-world type. It is often “which shape of object is this?”
{ kind: 'text-node', value: 'hello' } { kind: 'element-node', tag: 'div' }
-
Internal compiler convention A lot of internal code uses concepts like:
node.kind token.kind event.kind instruction.kind
It feels natural when building parsers, compilers, runtime graphs, state machines, or intermediate representations.
-
Less likely to clash with external schemas If user data has a
typeproperty, internal wrappers can still usekind:{ kind: 'resource', resource: { type: 'user' } }
-
Signals “internal representation”
kindcan make an object feel less like public JSON and more like a typed internal object.
Why People Use type
type is more common in public JavaScript/JSON APIs.
Examples:
{ type: 'click' }
{ type: 'module' }
{ type: 'button' }
{ type: 'object' }
{ type: 'user.created' }Reasons:
-
More familiar to JS users Public config often uses
typebecause it is obvious. Users do not stop and ask why the field is calledkind. -
Matches event/action conventions Redux, DOM-ish events, message buses, JSON descriptors, and config objects often use
type.{ type: 'user.created', payload: user }
-
Better for serialized JSON If something is meant to be inspected, emitted, stored, sent across process boundaries, or documented as JSON,
typetends to feel natural. -
Schema tooling expects it JSON Schema, OpenAPI-ish thinking, and many config validators already use
typelanguage. -
Less compiler-y
kindcan feel like implementation jargon.typefeels like ordinary app config.
When kind Is Better
Use kind when:
- The object is mostly internal.
- You are building an AST, compiler IR, parser, runtime graph, or state machine.
- The domain already needs a separate
typefield. - You want to distinguish “variant discriminant” from “domain type.”
- Users rarely write this object by hand.
Example:
type InternalNode =
| { kind: 'route'; routeId: string }
| { kind: 'cache'; cacheId: string }
| { kind: 'origin'; originUrl: string };When type Is Better
Use type when:
- Users write the object directly.
- The object is public API/config.
- The value is serialized, logged, inspected, or sent over a wire.
- You want to align with normal JS/JSON conventions.
- There is no competing domain meaning for
type.
Example:
{
type: 'redirect',
from: '/old',
to: '/new'
}When Neither Is Best Sometimes a domain-specific word is clearer than both.
Use runtime instead of type:
{
runtime: 'browser'
}Use mode:
{
mode: 'development'
}Use role:
{
role: 'frontend'
}Use target:
{
target: 'cloudflare'
}Use format:
{
format: 'json'
}This is usually the best rule: if a more specific noun exists, use it. Use type or kind only when the field is genuinely a generic discriminant.
Practical Rule For public JS/TS config:
Prefer domain-specific names first.
If generic discriminant is needed, prefer type.
Use kind for internal/compiler-ish structures or when type would collide.So my earlier kind instinct came from internal TypeScript/IR habits. For a public config API, type is usually the better default unless there is a concrete collision.