# JS Injection Hack Usefuls (JavaScript)

**URL:** <https://forum.inductiveautomation.com/t/js-injection-hack-usefuls-javascript/114714>\
**Category:** Ignition\
**Tags:** perspective\
**Created:** [March 27, 2026, 6:43am UTC](https://forum.inductiveautomation.com/t/js-injection-hack-usefuls-javascript/114714 "2026-03-27T06:43:40Z")\
**Posts on this page:** 15\
**Page:** 3

<div class="post-metadata">

**Author:** ![Ryan\_Deardorff](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/ryan_deardorff/32/63815_2.png) [@Ryan\_Deardorff](https://forum.inductiveautomation.com/u/Ryan_Deardorff)\
**Post date:** [May 20, 2026, 5:09pm UTC](https://forum.inductiveautomation.com/t/js-injection-hack-usefuls-javascript/114714/41 "2026-05-20T17:09:19Z")

</div>

> [@matt5](#):
>
> no need to worry about something that hasn't been set in stone

True, and even if IA wants to squash it, I don't see why they couldn't/wouldn't still provide an administrative mechanism for enabling it, along with a strong caution disclaimer.

---

<div class="post-metadata">

**Author:** ![travisaggiebrown](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/travisaggiebrown/32/85824_2.png) [@travisaggiebrown](https://forum.inductiveautomation.com/u/travisaggiebrown)\
**Post date:** [May 20, 2026, 5:30pm UTC](https://forum.inductiveautomation.com/t/js-injection-hack-usefuls-javascript/114714/42 "2026-05-20T17:30:46Z")

</div>

> [@matt5](#):
>
> Was hoping for more of a beneficial engagement with the cookbook - exploring the possibilities of the markdown component... 🙃

if you are looking for more beneficial engagement then I suggest creating a module for those examples.

first of off like you have said

> [@matt5](#):
>
> Totally fair pattern recognition with eval+atob being the malware fingerprint.

ask yourself if you would feel comfortable downloading someone's code that contains code similar to malware? I mean even if the people on here knew and trusted you it's still sketchy.

my solution to the indention and escape characters is to just make a webdev mounted file and attach it to the perspective page using a markdown injection but, I understand not everyone has the webdev module.

secondly markdown injections are dying. ignition hasn't set it in stone, but they have said

> [@Kyle.Chase](#):
>
> Just wanted to give everyone a heads up. The JavaScript execution technique being shared here is considered an exploit. The Markdown component was not intended to execute arbitrary JavaScript, and we're treating this as a security concern.
> 
> We'll be addressing this in a future release. In the meantime, be aware of the risks this introduces, particularly in environments where external sources could influence the content of that property.

which makes me strongly believe they plan on just full on killing it at some point and I don't think there will be an alternative natively until much later.

I would suggest having a plan for if they do remove script parsing from markdown injections. Thus far it seems custom modules are the only guaranteed supported way to add JS

---

<div class="post-metadata">

**Author:** ![matt5](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/matt5/32/85428_2.png) [@matt5](https://forum.inductiveautomation.com/u/matt5)\
**Post date:** [May 20, 2026, 5:58pm UTC](https://forum.inductiveautomation.com/t/js-injection-hack-usefuls-javascript/114714/43 "2026-05-20T17:58:24Z")

</div>

👍 Already been working on a module as my time allows: [GitHub - EckmanTechLLC/ignite · GitHub](https://github.com/EckmanTechLLC/ignite)

The cookbook was just exploration, and this thread is titled "JS Injection Hack Usefuls (JavaScript)" so I thought my posts are on topic. I did consider the webdev module route - which 2 of the cookbook recipes do use it, but still defaulted to the base64 for ease and quick results/trials.

You are right - I did see that from Kyle Chase, which means I'll update the cookbook readme to be more accurate about the supportability of "exploration" ideas and techniques.

---

<div class="post-metadata">

**Author:** ![bmusson](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/bmusson/32/87530_2.png) [@bmusson](https://forum.inductiveautomation.com/u/bmusson)\
**Post date:** [May 21, 2026, 3:01pm UTC](https://forum.inductiveautomation.com/t/js-injection-hack-usefuls-javascript/114714/44 "2026-05-21T15:01:29Z")

</div>

> [@nminchin](#):
>
> I'm on mobile atm, can get the command when back at laptop, but I'm sure you're probably aware of it anyway. That's one thing I want to start using from now on, as you can animate the transition as well which looks significantly more professional. Im using your runJavascriptAsync function to call the toggle, I haven't got a current state of it coming back yet though

Do you have an example command? I think I understand, but I’ve never done this before.

---

<div class="post-metadata">

**Author:** ![MMaynard](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/mmaynard/32/3366_2.png) [@MMaynard](https://forum.inductiveautomation.com/u/MMaynard)\
**Post date:** [May 21, 2026, 3:54pm UTC](https://forum.inductiveautomation.com/t/js-injection-hack-usefuls-javascript/114714/45 "2026-05-21T15:54:08Z")

</div>

We need someone to JS Inject a rich text editor that is actually worth it. Like ToastUI

That is a module we would pay for....

---

<div class="post-metadata">

**Author:** ![bmusson](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/bmusson/32/87530_2.png) [@bmusson](https://forum.inductiveautomation.com/u/bmusson)\
**Post date:** [May 21, 2026, 4:00pm UTC](https://forum.inductiveautomation.com/t/js-injection-hack-usefuls-javascript/114714/46 "2026-05-21T16:00:18Z")

</div>

> [@MMaynard](#):
>
> ToastUI

Got a link? I can almost guarantee you can load this in Periscope.

---

<div class="post-metadata">

**Author:** ![Ryan\_Deardorff](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/ryan_deardorff/32/63815_2.png) [@Ryan\_Deardorff](https://forum.inductiveautomation.com/u/Ryan_Deardorff)\
**Post date:** [May 21, 2026, 4:07pm UTC](https://forum.inductiveautomation.com/t/js-injection-hack-usefuls-javascript/114714/47 "2026-05-21T16:07:01Z")

</div>

I started working on making my [Markdown+ Editor](https://inductiveautomation.com/exchange/2836/overview) a true WYSIWYG rich text editor when I discovered how awesome Claude is at JavaScript, but got sidetracked by using him to make games instead. I thought, wouldn't it be cool to include a little Tetris Easter egg in it, and soon found myself building something [a bit more substantial](https://digeratist.itch.io/)...

---

<div class="post-metadata">

**Author:** ![MMaynard](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/mmaynard/32/3366_2.png) [@MMaynard](https://forum.inductiveautomation.com/u/MMaynard)\
**Post date:** [May 21, 2026, 4:10pm UTC](https://forum.inductiveautomation.com/t/js-injection-hack-usefuls-javascript/114714/48 "2026-05-21T16:10:58Z")

</div>

> [@Ryan\_Deardorff](#):
>
> I started working on making my [Markdown+ Editor](https://inductiveautomation.com/exchange/2836/overview)

I really need to browse the exchange more often.

Will test it out!

---

<div class="post-metadata">

**Author:** ![bmusson](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/bmusson/32/87530_2.png) [@bmusson](https://forum.inductiveautomation.com/u/bmusson)\
**Post date:** [May 21, 2026, 6:41pm UTC](https://forum.inductiveautomation.com/t/js-injection-hack-usefuls-javascript/114714/49 "2026-05-21T18:41:25Z")

</div>

> [@MMaynard](#):
>
> ToastUI

Loaded from a CDN, I'm still mulling over how to handle external libraries within Periscope's module resolution system.

---

<div class="post-metadata">

**Author:** ![nminchin](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/nminchin/32/6911_2.png) [@nminchin](https://forum.inductiveautomation.com/u/nminchin)\
**Post date:** [May 22, 2026, 6:31am UTC](https://forum.inductiveautomation.com/t/js-injection-hack-usefuls-javascript/114714/50 "2026-05-22T06:31:00Z")

</div>

> [@bmusson](#):
>
> Do you have an example command?

```javascript
() => { document.documentElement.classList.toggle('dark'); }

```

Comparison shown below comparing switching theme via session prop (purple icon) and switching via the above.

Note: the purple switch is broken because I deleted the old dark theme, but it still demonstrates what was happening when it was there with the brief unstyled page.

Essentially, you need to define your default light-themed CSS variables and then override them within a `.dark`-scoped selector so the values change when dark mode is active with the js toggle

```python

/* Default (light theme) */
:root {
  --colour-pg-bg: hsl(214 30% 91%);
}

/* Dark theme override (triggered by class on <html>) */
html.dark {
  --colour-pg-bg: hsl(216 14% 27%);
}

```

What I had to do also however was to include the IA "dark" theme under a different name within my theme (import it), however I edited the selectors within the css files by adding `.dark ` to the front of each, e.g.

 ![image](https://us1.discourse-cdn.com/inductiveautomation/original/3X/2/b/2b71b6fbec02fdf0e7a2fed7d4624c3db7641e76.png)

---

<div class="post-metadata">

**Author:** ![bmusson](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/bmusson/32/87530_2.png) [@bmusson](https://forum.inductiveautomation.com/u/bmusson)\
**Post date:** [May 22, 2026, 7:59am UTC](https://forum.inductiveautomation.com/t/js-injection-hack-usefuls-javascript/114714/51 "2026-05-22T07:59:27Z")

</div>

> [@nminchin](#):
>
> `() => { document.documentElement.classList.toggle('dark'); }`

Got it.

Client Resource: `InjectionHacks/Theme`

```ts
import { context } from "system:perspective";

const THEME_KEY = "theme";
const DARK_CLASS = "dark";

const syncSessionTheme = () => {
  const isDark = document.documentElement.classList.contains(DARK_CLASS);
  const themeValue = isDark ? "dark" : "light";

  context.page.sessionCustom.write(THEME_KEY, themeValue);
};

export const setTheme = (isDark) => {
  document.documentElement.classList.toggle(DARK_CLASS, isDark);
  syncSessionTheme();
};

export const toggleTheme = () => {
  document.documentElement.classList.toggle(DARK_CLASS);
  syncSessionTheme();
};

export const initThemeListener = () => {
  syncSessionTheme();

  const observer = new MutationObserver((mutations) => {
    mutations.forEach((mutation) => {
      if (mutation.attributeName === "class") {
        syncSessionTheme();
      }
    });
  });

  observer.observe(document.documentElement, {
    attributes: true,
    attributeFilter: ["class"],
  });

  return () => observer.disconnect();
};

initThemeListener();

```

Button Script:

```python
def runAction(self, event):
	system.perspective.runJavaScriptAsync('''async () => {
		const theme = await periscope.resource('InjectionHacks/Theme')
		theme.toggleTheme()
	}''')

```

Notice that you can access client resources by name via `periscope.resource(name)`.

In this example, the label is bound to `session.custom.theme` and the background of the container is `--colour-pg-bg`.

Also, you don't really need to use an observer. You could easily just have `setTheme` write directly to the session custom property.

[client-resource-testing\_2026-05-22\_0759.zip](https://forum.inductiveautomation.com/uploads/short-url/5bXLl0qMziUkdTnSEu9kTdQeHfP.zip) (8.9 KB)

---

<div class="post-metadata">

**Author:** ![bmusson](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/bmusson/32/87530_2.png) [@bmusson](https://forum.inductiveautomation.com/u/bmusson)\
**Post date:** [May 23, 2026, 12:47am UTC](https://forum.inductiveautomation.com/t/js-injection-hack-usefuls-javascript/114714/52 "2026-05-23T00:47:51Z")

</div>

The Client Resource feature is merged for both 8.1 and 8.3, and will be contained in next week's releases.

Documentation is the next big hurdle 😆

 ![20260522204401](https://us1.discourse-cdn.com/inductiveautomation/original/3X/5/0/504ae4dc2bb85b3e1a1d054dc8c335942a62cff6.png)  
 ![20260522204414](https://us1.discourse-cdn.com/inductiveautomation/original/3X/b/e/be2f803c184267c0e71eee16dd266a6b48838216.png)

---

<div class="post-metadata">

**Author:** ![ryan.white](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/ryan.white/32/73193_2.png) [@ryan.white](https://forum.inductiveautomation.com/u/ryan.white)\
**Post date:** [May 23, 2026, 4:31pm UTC](https://forum.inductiveautomation.com/t/js-injection-hack-usefuls-javascript/114714/53 "2026-05-23T16:31:03Z")

</div>

Would I be able to use this to provide an external handler/plugin for the chartjs that's part of your embr charts module?

---

<div class="post-metadata">

**Author:** ![bmusson](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/bmusson/32/87530_2.png) [@bmusson](https://forum.inductiveautomation.com/u/bmusson)\
**Post date:** [May 23, 2026, 5:13pm UTC](https://forum.inductiveautomation.com/t/js-injection-hack-usefuls-javascript/114714/54 "2026-05-23T17:13:59Z")

</div>

Yup, that’s actually my primary use case.

The current plan is to introduce a `propsTransformers` property on all Embr components.

- This property will apply a list of functions to the component’s `props` before rendering.
- Using this, you can add your plugins (written as Client Resources) to the chart.

The exact implementation may change, but 100% there will be some method for using Client Resources as Chart.js plugins.  
It actually might even be possible now using `await periscope.resource(path)`?

The biggest hurdle is that even though Client Resources are cached hard on the client, getting a reference to them still _has_ to be asynchronous to ensure they are finished loading.

(Because I can’t provide a resource manifest in the initial Page HTML, I have to do an HTTP request to get it.)

Which basically means any components that use Client Resources need to be wrapped in React ‘lazy` to delay rendering until loading is finished. It’s the only way to do things, without heavily hacking into how Perspective starts a client.

---

<div class="post-metadata">

**Author:** ![bmusson](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/bmusson/32/87530_2.png) [@bmusson](https://forum.inductiveautomation.com/u/bmusson)\
**Post date:** [May 28, 2026, 3:44pm UTC](https://forum.inductiveautomation.com/t/js-injection-hack-usefuls-javascript/114714/55 "2026-05-28T15:44:08Z")

</div>

The Client Resource feature has been released.

> [@Musson Industrial's Embr-Periscope Module](https://forum.inductiveautomation.com/t/musson-industrials-embr-periscope-module/99047/89):
>
> Release Notice [Embr v8.1 - 2026.5.28](https://github.com/mussonindustrial/embr/releases/tag/releases%2F8.1%2F2026.5.28)[Embr v8.3 - 2026.5.28](https://github.com/mussonindustrial/embr/releases/tag/releases%2F8.3%2F2026.5.28)Client Resources + React Component Client Resources are files compiled to JavaScript that are loaded by the Perspective client on launch. There are currently two supported types: TypeScript (supports JavaScript, TypeScript, JSX, and TSX) CSS Modules All Client Resources loaded and run on page load/refresh, serving as a direct, robust alternative to Markdown Injection. They can also provide React components, which can be used via a …

[Previous page](https://forum.inductiveautomation.com/t/js-injection-hack-usefuls-javascript/114714.md?page=2)
