# Theme Developer Tutorial: 6. Using the JS API

**URL:** https://meta.discourse.org/t/theme-developer-tutorial-6-using-the-js-api/357801
**Category:** Developer Guides
**Created:** [March 18, 2025, 3:59pm UTC](https://meta.discourse.org/t/theme-developer-tutorial-6-using-the-js-api/357801 "2025-03-18T15:59:11Z")
**Posts on this page:** 4
**Page:** 1

<div class="post-metadata">

### Author: ![system](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/system/32/443519_2.png) [@system](https://meta.discourse.org/u/system)
#### Post date: [March 18, 2025, 3:59pm UTC](https://meta.discourse.org/t/theme-developer-tutorial-6-using-the-js-api/357801/1 "2025-03-18T15:59:11Z")

</div>

In the last couple of chapters, we’ve explored how to use the JavaScript API to render content into outlets. `renderInOutlet` is the most commonly-used API, but there are a ton more! In this chapter we’ll try out a few of them, and show you how to discover more.

## Common API methods

### getCurrentUser()

`api.getCurrentUser()` will return information about the current user, or `null` if nobody is logged in. This can be used for all sorts of things, including per-group logic, or rendering a user’s username into the UI.

For group membership checks backed by theme settings, use the [`resolve_group_membership` option](https://meta.discourse.org/t/-/82557) in your theme’s `settings.yml` file instead of checking `currentUser.groups`. This avoids leaking or missing hidden group memberships.

```gjs
import { apiInitializer } from "discourse/lib/api";

export default apiInitializer((api) => {
  console.log("Current user is", api.getCurrentUser());
});

```

### headerIcons

`api.headerIcons` will allow you to add, remove and re-arrange icons in the header. For example, to add a new icon before the search icon, you’d do something like

```gjs
import DButton from "discourse/components/d-button";
import { apiInitializer } from "discourse/lib/api";

export default apiInitializer((api) => {
  const sayHello = () => {
    alert("Hello, world!");
  };
  api.headerIcons.add(
    "my-unique-icon-name",
    <template>
      <li>
        <DButton
          @action={{sayHello}}
          @icon="wand-magic"
          class="icon btn-flat"
        />
      </li>
    </template>,
    { before: "search" }
  );
});

```

### replaceIcon()

```js
api.replaceIcon(source, destination);

```

With this method, you can easily replace any Discourse icon with another. For example, we have [a theme component](https://meta.discourse.org/t/change-the-like-icon/87748) that replaces the heart icon for like with a thumbs-up icon

### decorateCookedElement()

`api.decorateCookedElement()` allows you to customize the rendered content of Discourse posts. This can be used for anything from simple formatting changes, all the way up to advanced integrated UIs like the built-in ‘poll’ plugin.

The API should be passed a callback function which will be run for every post when it’s rendered to the screen. The first argument to the callback will be the post’s root HTML element, and the second will be a helper.

A simple example which appends content to every post would look like:

```js
api.decorateCookedElement((element, helper) => {
  const myNewParagraph = document.createElement("p");
  myNewParagraph.textContent = "Hello, this is appended to every post!";
  element.appendChild(myNewParagraph);
});

```

Or for a more advanced UI, you can render a glimmer component into a post. For example, to render the counter component we authored earlier into every post, you could do something like this:

```js
import { apiInitializer } from "discourse/lib/api";
import CustomWelcomeBanner from "../components/custom-welcome-banner";

export default apiInitializer((api) => {
  api.decorateCookedElement((element, helper) => {
    const counterWrapper = helper.renderGlimmer(
      "div.my-counter",
      CustomWelcomeBanner
    );
    element.appendChild(counterWrapper);
  });
});

```

`helper.getPost()` will return the current post, and can be used to build conditional logic into these `decorateCookedElement` callbacks. `console.log` the post to see what’s available.

### registerValueTransformer()

`api.registerValueTransformer` allows you to inject logic into predefined parts of the Discourse JavaScript application. For example, you can add a `"home-logo-href"` transformer to link the logo to `example.com`:

```gjs
api.registerValueTransformer("home-logo-href", () => "https://example.com");

```

For more information on Transformers, check out the [dedicated guide](https://meta.discourse.org/t/349954)

## Finding more JS API methods

All the available APIs are listed in the [`plugin-api.gjs` source code](https://github.com/discourse/discourse/blob/main/frontend/discourse/app/lib/plugin-api.gjs) in Discourse core, along with a short description and examples.

That’s it for this chapter, and almost the end of the tutorial. Let’s wrap things up [in the conclusion](https://meta.discourse.org/t/357802).

* * *

This document is version controlled - suggest changes [on github](https://github.com/discourse/discourse/blob/main/docs/developer-guides/docs/07-theme-developer-tutorial/06-js-api.md).

---

<div class="post-metadata">

### Author: ![pfaffman](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/pfaffman/32/120154_2.png) [@pfaffman](https://meta.discourse.org/u/pfaffman)
#### Post date: [April 14, 2025, 8:45pm UTC](https://meta.discourse.org/t/theme-developer-tutorial-6-using-the-js-api/357801/2 "2025-04-14T20:45:00Z")

</div>

> [@system](#):
>
> A simple example which appends content to every post would look like:

I want to affect posts only in a particular category. I can’t figure out a way to tell if an element is in a given category. I thought I might use `element.parentElement` and work my way up to the `body`, but that doesn’t seem to work. Before I’ve solved this problem when I realized that I could tell if I wanted to change things if the **user** was in a particular group, so I could use `currentUser`, but right now, I’m doing this:

```plaintext
if (!cooked.innerHTML.includes(newFormText)) {
      console.log("fixSubmittedForm no new form text");
      return;
    }

```

Searching all of every post to decide if I want to change it seems bad, right? It’s a hosted customer or I’d have solved this problem a bunch of ways with a plugin.

---

<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: [April 14, 2025, 8:52pm UTC](https://meta.discourse.org/t/theme-developer-tutorial-6-using-the-js-api/357801/3 "2025-04-14T20:52:35Z")

</div>

In the decorateCookedElement examples above, you’ll see that the callback has two arguments: element, and helper.

If you do `helper.getModel()`, then you’ll get access to the post model, which has all the post/topic/category info.

For some examples, I’d recommend searching all-the-\* for “getModel()”

---

<div class="post-metadata">

### Author: ![pfaffman](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/pfaffman/32/120154_2.png) [@pfaffman](https://meta.discourse.org/u/pfaffman)
#### Post date: [April 14, 2025, 11:16pm UTC](https://meta.discourse.org/t/theme-developer-tutorial-6-using-the-js-api/357801/4 "2025-04-14T23:16:42Z")

</div>

Oh. Sigh. I’m afraid this isn’t the first time you’ve told me this very thing.

That should do the trick, though! Thanks so very much.

I’ll try not to ask again!

EDIT: Maybe this time I’ve learned!

```plaintext
async function fixSubmittedForm(element, helper, category_id) {
  if (helper.getModel().topic.category_id !== category_id) {
    return;
  }
 .....

```
