🏛️

Classes (.flsh)
Reusable BrightScript-only logic: extends, override, super.

A .flsh file is BrightScript-only reusable logic, entirely separate from a .thr component — no <script> wrapper, no template, no XML output at all, just a class compiled straight to a plain .brs function. The file's own base name must match the declared class name.

Fields, constructor, methods

Three visibility levels — public/private/protected — on both fields and methods. A field can be declared top-level or, more commonly, straight inside the constructor as the "assign a parameter to a field" shorthand:

Counter.flsh
class Counter {
  constructor(start: integer) {
    private count: integer = start
  }

  public function get(): integer {
    return m.count
  }

  public function isZero(): boolean {
    return m.count == 0
  }

  public function describe(): string {
    return str(m.count)
  }
}

extends / override / super

An overriding constructor's first statement must be exactly one super(...) call. An overriding method must be marked override — an unmarked shadow of a base member is a compile error, on the theory that it's far more likely a missing keyword than an intentional new member.

LabeledCounter.flsh
import Counter from "./Counter.flsh"

class LabeledCounter extends Counter {
  override constructor(start: integer, label: string) {
    super(start)
    private label: string = label
  }

  override public function describe(): string {
    if (m.get() == 0) {
      return m.label + ": none yet"
    }
    return m.label + ": " + str(m.get())
  }
}

Using a class from a .thr component

FavoriteCounter.thr (excerpt)
<script>
import LabeledCounter from "components/Classes/LabeledCounter.flsh"

derived milestoneLabel: string = LabeledCounter(favoriteCount, "Favorites so far").describe()
</script>

Reaching global singletons from a class method

theme/router/taskManager all work from inside a class method exactly like they do from a .thr component's own script — no special syntax needed on the author's side.

' Inside any class method — router/theme/taskManager all work exactly like in a .thr component:
public function goHome() {
  router.navigate("/home")
}

Reference implementation — apps/classes-demo

Every mechanism on this page has a router-mounted, scaled chapter in apps/classes-demo — 3 chapters (/fields-and-methods through /global-singletons-from-class), reachable with REWIND/ FAST-FORWARD once compiled and sideloaded. Each chapter shows a default, no-customization example alongside a deliberately different, customized one — e.g. FieldsAndMethodsDemo.thr's BankAccount (top-level AND constructor-shorthand fields, all three visibility levels) contrasts a successful withdrawal against one deliberately exceeding the private-field-backed overdraft limit, and ExtendsOverrideSuperDemo.thr constructs the base and derived class side by side so override's actual effect is visible by direct comparison. This is the live, compiling reference for the whole page — see findings/classes-demo-app.md for what each chapter covers and findings/demo-app-conventions.md for the app-structure convention it follows.

⚠️ Not (yet) supported

  • ○ store/state/focus(...) — entirely unreachable from a class body; a class has no reactive lifecycle at all.
  • ○ taskManager.onAlertChanged/onResult/onRequestSent/onResponseReceived — not supported from a class body (needs a real node identity these don't have).
  • ○ Lint-enforced visibility — protected parses but compiles identically to public; BrightScript has no real access boundary to enforce.
  • ○ Multiple constructors, or a field declared more than once (or as both a field and a method).
  • ○ No .flsh class-body animation form — same root cause as the reactive-lifecycle gap above: animations are tied to a template's element ids, which a class has none of.

Exact grammar: GRAMMAR.md. Full feature status: docs/features.md.