# Using modifyClass to change core behavior

**URL:** https://meta.discourse.org/t/using-modifyclass-to-change-core-behavior/262064
**Category:** Developer Guides
**Created:** [April 18, 2023, 10:24am UTC](https://meta.discourse.org/t/using-modifyclass-to-change-core-behavior/262064 "2023-04-18T10:24:36Z")
**Posts on this page:** 7
**Page:** 1

<div class="post-metadata">

### Author: ![Discourse](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/discourse/32/148734_2.png) [@Discourse](https://meta.discourse.org/u/Discourse)
#### Post date: [April 18, 2023, 10:24am UTC](https://meta.discourse.org/t/using-modifyclass-to-change-core-behavior/262064/1 "2023-04-18T10:24:37Z")

</div>

For advanced themes and plugins, Discourse offers the `modifyClass` system. This allows you to extend and override functionality in many of core’s javascript classes.

## When to use `modifyClass`

`modifyClass` should be a last resort, when your customization cannot be made via Discourse’s more stable customization APIs (e.g. plugin-api methods, plugin outlets, transformers).

Core’s code can change at any time. And therefore, customizations made via `modifyClass` could break at any time. When using this API, you should ensure that you have controls in place to catch those issues before they reach a production site. For example, you could add automated tests to the theme/plugin, or you could use a staging site to test incoming Discourse updates against your theme/plugin.

## Basic Usage

`api.modifyClass` can be used to modify the functions and properties of any class which is accessible via the Ember resolver. That includes Discourse’s routes, controllers, services and components.

`modifyClass` takes two arguments:

- `resolverName` (string) - construct this by using the type (e.g. component/controller/etc.), followed by a colon, followed by the (dasherized) filename name of the class. For example: `component:d-button`, `component:modal/login`, `controller:user`, `route:application`, etc.

- `callback` (function) - a function which receives the existing class definition, and then returns an extended version.

For example, to modify the `click()` action on d-button:

```js
api.modifyClass(
  "component:d-button",
  (Superclass) =>
    class extends Superclass {
      @action
      click() {
        console.log("button was clicked");
        super.click();
      }
    }
);

```

The `class extends ...` syntax mimics that of [JS child classes](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes#inheritance). In general, any syntax/features supported by child classes can be applied here. That includes `super`, static properties/functions, and more.

However, there are some limitations. The `modifyClass` system only detects changes to the class’s JS `prototype`. Practically, that means:

- introducing or modifying a `constructor()` is not supported

- introducing or modifying class fields is not supported (although some decorated class fields, like `@tracked` can be used)

- simple class fields on the original implementation cannot be overridden in any way (although, as above, `@tracked` fields can be overridden by another `@tracked` field)

If you find yourself wanting to do these things, then your use-case may be better satisfied by making a PR to introduce new APIs in core (e.g. plugin outlets, transformers, or bespoke APIs).

## Upgrading Legacy Syntax

In the past, modifyClass was called using an object-literal syntax like this:

```js
// Outdated syntax - do not use
api.modifyClass("component:some-component", {
  someFunction() {
    const original = this._super();
    return original + " some change";
  }
  pluginId: "some-unique-id"
});

```

This syntax is no longer recommended, and has known bugs (e.g. overriding getters or `@actions`). Any code using this syntax should be updated to use the native-class syntax described above. In general, conversion can be done by:

1. removing `pluginId` - this is no longer required
2. Update to the modern native-class syntax described above
3. Test your changes

## Troubleshooting

### Class already initialized

When using modifyClass in an initializer, you may see this warning in the console:

> `Attempted to modify "{name}", but it was already initialized earlier in the boot process`

In theme/plugin development, there are two ways this error is normally introduced:

- **Adding a `lookup()` caused the error**

- **Adding a new `modifyClass` caused the error**

* * *

This document is version controlled - suggest changes [on github](https://github.com/discourse/discourse/blob/main/docs/developer-guides/docs/03-code-internals/15-modifyclass.md).

---

<div class="post-metadata">

### Author: ![merefield](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/merefield/32/176214_2.png) [@merefield](https://meta.discourse.org/u/merefield)
#### Post date: [March 10, 2025, 12:41pm UTC](https://meta.discourse.org/t/using-modifyclass-to-change-core-behavior/262064/20 "2025-03-10T12:41:10Z")

</div>

I presume it is impossible or at least unreliable to attempt use modifyClass (within the legal use cases mentioned above) within a plugin on another plugin’s Component in the same install?

Even if it is a plugin included in core (e.g. Chat or Poll)?

---

<div class="post-metadata">

### Author: ![david](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/david/32/157490_2.png) [@david](https://meta.discourse.org/u/david)
#### Post date: [March 10, 2025, 12:47pm UTC](https://meta.discourse.org/t/using-modifyclass-to-change-core-behavior/262064/21 "2025-03-10T12:47:10Z")

</div>

If both plugins are installed and enabled, it should work with no problems. If the target isn’t installed/enabled, you’ll get a warning in the console. But you can use the [`ignoreMissing` parameter](https://github.com/discourse/discourse/blob/99038372804bc01d277fe3557727aa59ce38d475/app/assets/javascripts/discourse/app/lib/plugin-api.gjs#L273C5-L283C6) to silence that.

```plaintext
api.modifyClass(
  "component:some-component",
  (Superclass) => ...,
  { ignoreMissing: true }
);

```

Of course, standard `modifyClass` advice still applies: it should be a last resort, and it can break at any time so you should ensure your testing is good enough to identify issues quickly. Using [transformers](https://meta.discourse.org/t/using-transformers-to-customize-client-side-values-and-behavior/349954) would be a much safer strategy.

---

<div class="post-metadata">

### Author: ![merefield](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/merefield/32/176214_2.png) [@merefield](https://meta.discourse.org/u/merefield)
#### Post date: [March 10, 2025, 1:21pm UTC](https://meta.discourse.org/t/using-modifyclass-to-change-core-behavior/262064/22 "2025-03-10T13:21:44Z")

</div>

So how does that work, it defers the application until all components from all plugins have been registered and loaded?

I think I have a case which doesn’t appear to work.

---

<div class="post-metadata">

### Author: ![david](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/david/32/157490_2.png) [@david](https://meta.discourse.org/u/david)
#### Post date: [March 10, 2025, 1:23pm UTC](https://meta.discourse.org/t/using-modifyclass-to-change-core-behavior/262064/23 "2025-03-10T13:23:46Z")

</div>

All ES6 modules (including components) are defined first, then we run pre-initializers, then we run regular initializers. So yeah, by the time any initializer runs, all components are resolvable.

> [@merefield](#):
>
> I think I have a case which doesn’t appear to work.

Happy to take a look if you can share a snippet or branch 👀

---

<div class="post-metadata">

### Author: ![merefield](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/merefield/32/176214_2.png) [@merefield](https://meta.discourse.org/u/merefield)
#### Post date: [March 10, 2025, 4:39pm UTC](https://meta.discourse.org/t/using-modifyclass-to-change-core-behavior/262064/25 "2025-03-10T16:39:54Z")

</div>

My bad, you need to be careful you provide the full path!

e.g.:

`api.modifyClass("component:chat/modal/create-channel",` ✅

neither:

`api.modifyClass("component:create-channel",` ❌

nor even

`api.modifyClass("component:modal/create-channel",` ❌

are enough!

---

<div class="post-metadata">

### Author: ![pangbo](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/pangbo/32/538562_2.png) [@pangbo](https://meta.discourse.org/u/pangbo)
#### Post date: [March 4, 2026, 7:13am UTC](https://meta.discourse.org/t/using-modifyclass-to-change-core-behavior/262064/26 "2026-03-04T07:13:43Z")

</div>

The example for `api.modifyClass` in plugin-api.gjs still uses legacy syntax. Perhaps it requires updating?

> <https://github.com/discourse/discourse/blob/9738c429c8603d40f8a5a1b2548bb27f33191cbe/frontend/discourse/app/lib/plugin-api.gjs#L258-L275>
