---
title: "Classes, when the platform demands one"
image: "https://hem.run/social/hem.png"
---

> Documentation Index
> Fetch the complete documentation index at: https://hem.run/llms.txt
> Use this file to discover all available pages before exploring further.

# Classes, when the platform demands one

Hem's domain code is records, functions, and variants. But Durable Objects, custom elements, and `Error` subclasses _require_ class declarations, so a deliberately minimal form exists:

```hem
export class Counter extends DurableObject<Env> {
  mut count = 0
  static kind = "counter"

  fetch(req: Request): Response {
this.count += 1
return Response.json({ count: this.count })
  }
}
```

This is a bounded interop form, not Hem's domain model. Generic class headers and an `extends` clause pass through to TypeScript. An omitted constructor uses JavaScript's implicit constructor; an explicit constructor, prototype method, or `static` member is preserved for platform protocols that require those shapes. The `override` modifier is preserved on fields and methods so TypeScript can verify framework base-class contracts. Instance and static fields are readonly by default, and `mut` opts into a writable field. Arrow-valued fields remain available when per-instance function identity is intentional. Class-local `this` and `super` work, and Hem's `==` / `!=` still emit strict equality inside class members.

Decorators, getters/setters, declaration-only fields, `abstract`/`declare`/`accessor`/explicit `readonly` members, and other class-member forms produce HEM3003. Use the supported class subset or a separately compiled TypeScript module, subject to the same trust rules as other imports. Access modifiers, `mut` methods, `async` field modifiers, and constructor parameter properties produce HEM3007 instead. Parameter properties are excluded by the erasable-output contract that lets output run under Node type stripping; use explicit fields and constructor assignments. Write an asynchronous field value with an `async` arrow initializer. Member bodies retain native declarations and control flow. Hem expressions work in returns, initializers, and call statements (including arguments and pipes). Statement-producing expressions can hoist in returns/calls or before a standalone local declaration; fields, parameter defaults, loop heads, and grouped initializers require an ordinary Hem helper. Other structural Hem/native statement mixtures receive an authored HEM3003 refusal. The formatter preserves member body text. Plain `let` remains immutable in these regions; use `let mut` for writable local storage. Immutable `const` and native `switch`, loops, labels, comma expressions, function declarations, and updates on writable locations remain supported. Boolean conditions, numeric `+`, foreign `any` demotion, assertion refusal, readonly access, refinement invalidation, and must-use still apply inside nested callbacks and initializers. Write `let _ = work()` for an intentional discard. `var` and definite-assignment `!` are refused. Behavioral object literals and dynamic `this` contracts outside a class remain TypeScript boundaries.

Source: https://hem.run/tour/classes-when-the-platform-demands-one/index.mdx
