# 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:** 20\
**Page:** 1

<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:** [March 27, 2026, 6:43am UTC](https://forum.inductiveautomation.com/t/js-injection-hack-usefuls-javascript/114714/1 "2026-03-27T06:43:40Z")

</div>

I thought I’d create a wiki topic to post useful JS “hacks”. It’d be good to include methods using the Markdown component as well as Ben Musson’s Periscope module’s `system.perspective.runJavaScript*` methods, but either or is ok initially and others can update your _wiki_ reply later.

What I had in mind was to have posts include the JSON for the Markdown component that includes the JS script within it, so that all you need to do is copy the JSON and paste it into your View.

E.g.

> **Example Markdown JSON**
>
> ```json
> [
> {
> "type": "ia.display.markdown",
> "version": 0,
> "props": {
> "style": {
> "position": "absolute"
> },
> "markdown": {
> "escapeHtml": false
> }
> },
> "meta": {
> "name": "JSInject_PowerChart_XTraceTagColours"
> },
> "position": {
> "shrink": 0,
> "basis": 0
> },
> "custom": {
> "inlineJavascript": "(function() {\n function getPenColours() {\n const map = {};\n document.querySelectorAll('svg.pen-visibility-checkbox[data-pen-name]').forEach(svg => {\n const name = svg.getAttribute('data-pen-name');\n const fill = svg.style.fill;\n if (name && fill) map[name] = fill;\n });\n return map;\n }\n\n function colourXTraceLabels() {\n const penColours = getPenColours();\n document.querySelectorAll('.ia_powerChartComponent __xTrace__ box__label').forEach(textEl => {\n const boldSpan = textEl.querySelector('tspan[style*=&quot;font-weight&quot;]');\n if (!boldSpan) return;\n const penName = boldSpan.textContent.replace(/:\\s*$/, '').trim();\n if (penColours[penName]) {\n textEl.style.fill = penColours[penName];\n }\n });\n }\n\n if (window._xTraceObserver) window._xTraceObserver.disconnect();\n\n window._xTraceObserver = new MutationObserver((mutations) => {\n for (const mutation of mutations) {\n const hasXTrace = [...mutation.addedNodes].some(node =>\n node.nodeType === 1 && (\n node.classList?.contains('ia_timeMarker') ||\n node.querySelector?.('.ia_timeMarker')\n )\n );\n const isInsideXTrace = mutation.target.closest?.(\n '.ia_timeMarker, .ia_powerChartComponent__xTrace__box'\n );\n if (hasXTrace || isInsideXTrace) {\n colourXTraceLabels();\n break;\n }\n }\n });\n\n window._xTraceObserver.observe(document.body, {\n childList: true,\n subtree: true,\n characterData: true,\n });\n\n colourXTraceLabels();\n })();"
> },
> "propConfig": {
> "props.source": {
> "binding": {
> "config": {
> "struct": {
> "script": "{this.custom.inlineJavascript}"
> },
> "waitOnAll": true
> },
> "transforms": [
> {
> "code": "#\tcode = \"<img style='display:none' src='/favicon.ico' onload=\\\"\" + value.script.replace('\"', '&quot;') + '\\\"></img>'\n#\t\n#\tcode = code.replace(\"\\n\", \"\").replace(\"\\t\", \"\").replace(\"\\n\", \"\").replace(\"\\r\", \"\").replace(\"\\r\", \"\").replace(\" \", \" \").replace(\" \", \" \").replace(\" \", \" \").replace(\" \", \" \")\n#\t\n#\treturn code\n\tscript = value.script\n\tscript = script.replace('\"', '&quot;')\n\tscript = script.replace('\\n', '').replace('\\t', '').replace('\\r', '')\n\t\n\t# Collapse multiple spaces - repeat until stable\n\twhile ' ' in script:\n\t script = script.replace(' ', ' ')\n\t\n\tcode = '<img style=\"display:none\" src=\"/favicon.ico\" onload=\"{}\"></img>'.format(script)\n\treturn code",
> "type": "script"
> }
> ],
> "type": "expr-struct"
> }
> }
> }
> }
> ]
> 
> ```

This is the template that I use myself for Markdown JS hacking, and I just edit the `custom.inlineJavaScript` prop value with the JS script.  
_You need to make sure that the JS script is valid for inserting into a dummy DOM element’s `onLoad` script._

Please include a screenshot and a brief description of what the code does.

Please make sure to change your post to a wiki after posting so that others can edit if needed.

## Contents

#### Accordian

- [Scroll to Item Index](https://forum.inductiveautomation.com/t/js-injection-hack-usefuls-javascript/114714/4)

#### Power Chart

- [Set X-Trace tag labels to pen colour](https://forum.inductiveautomation.com/t/js-hack-usefuls/114714/3)

---

<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:** [March 27, 2026, 6:46am UTC](https://forum.inductiveautomation.com/t/js-injection-hack-usefuls-javascript/114714/2 "2026-03-27T06:46:59Z")

</div>

This is a template you can copy for new posts. Feel free to improve it…

## Feature Title

This is a description of the feature.

This is a screenshot of what the feature does.

### Instructions

Copy and paste into a View...

### Resources

> **Markdown Version**
>
> ```json
> 
> ```

> **Periscope Version (N/A yet)**
>

---

<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:** [March 27, 2026, 6:54am UTC](https://forum.inductiveautomation.com/t/js-injection-hack-usefuls-javascript/114714/3 "2026-03-27T06:54:56Z")

</div>

## Power Chart - Set X-Trace tag labels to their pen colour

This will set the tag labels and values shown in all x-trace panels to the colour of their chart pens. The script (dodgilly?) grabs the colours from the legend key at the bottom.

_I haven’t tested if this works when the legend is collapsed into the smaller version_

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

### Instructions

Copy and paste into a View with a Power Chart and your operators will no longer become frustrated trying to eyeball x-trace tags with their corresponding pens on the chart.

### Resources

> **Markdown Version**
>
> ```json
> [
> {
> "type": "ia.display.markdown",
> "version": 0,
> "props": {
> "style": {
> "position": "absolute"
> },
> "markdown": {
> "escapeHtml": false
> }
> },
> "meta": {
> "name": "JSInject_PowerChart_XTraceTagColours"
> },
> "position": {
> "shrink": 0,
> "basis": 0
> },
> "custom": {
> "inlineJavascript": "(function() {\n function getPenColours() {\n const map = {};\n document.querySelectorAll('svg.pen-visibility-checkbox[data-pen-name]').forEach(svg => {\n const name = svg.getAttribute('data-pen-name');\n const fill = svg.style.fill;\n if (name && fill) map[name] = fill;\n });\n return map;\n }\n\n function colourXTraceLabels() {\n const penColours = getPenColours();\n document.querySelectorAll('.ia_powerChartComponent __xTrace__ box__label').forEach(textEl => {\n const boldSpan = textEl.querySelector('tspan[style*=&quot;font-weight&quot;]');\n if (!boldSpan) return;\n const penName = boldSpan.textContent.replace(/:\\s*$/, '').trim();\n if (penColours[penName]) {\n textEl.style.fill = penColours[penName];\n }\n });\n }\n\n if (window._xTraceObserver) window._xTraceObserver.disconnect();\n\n window._xTraceObserver = new MutationObserver((mutations) => {\n for (const mutation of mutations) {\n const hasXTrace = [...mutation.addedNodes].some(node =>\n node.nodeType === 1 && (\n node.classList?.contains('ia_timeMarker') ||\n node.querySelector?.('.ia_timeMarker')\n )\n );\n const isInsideXTrace = mutation.target.closest?.(\n '.ia_timeMarker, .ia_powerChartComponent__xTrace__box'\n );\n if (hasXTrace || isInsideXTrace) {\n colourXTraceLabels();\n break;\n }\n }\n });\n\n window._xTraceObserver.observe(document.body, {\n childList: true,\n subtree: true,\n characterData: true,\n });\n\n colourXTraceLabels();\n })();"
> },
> "propConfig": {
> "props.source": {
> "binding": {
> "config": {
> "struct": {
> "script": "{this.custom.inlineJavascript}"
> },
> "waitOnAll": true
> },
> "transforms": [
> {
> "code": "#\tcode = \"<img style='display:none' src='/favicon.ico' onload=\\\"\" + value.script.replace('\"', '&quot;') + '\\\"></img>'\n#\t\n#\tcode = code.replace(\"\\n\", \"\").replace(\"\\t\", \"\").replace(\"\\n\", \"\").replace(\"\\r\", \"\").replace(\"\\r\", \"\").replace(\" \", \" \").replace(\" \", \" \").replace(\" \", \" \").replace(\" \", \" \")\n#\t\n#\treturn code\n\tscript = value.script\n\tscript = script.replace('\"', '&quot;')\n\tscript = script.replace('\\n', '').replace('\\t', '').replace('\\r', '')\n\t\n\t# Collapse multiple spaces - repeat until stable\n\twhile ' ' in script:\n\t script = script.replace(' ', ' ')\n\t\n\tcode = '<img style=\"display:none\" src=\"/favicon.ico\" onload=\"{}\"></img>'.format(script)\n\treturn code",
> "type": "script"
> }
> ],
> "type": "expr-struct"
> }
> }
> }
> }
> ]
> 
> ```

> **Periscope Version (N/A yet)**
>
> 🫪

---

<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:** [March 27, 2026, 9:21am UTC](https://forum.inductiveautomation.com/t/js-injection-hack-usefuls-javascript/114714/4 "2026-03-27T09:21:59Z")

</div>

## Accordian - Allow smooth scrolling to items based on item index

This allows you to scroll an Accordian component to show a specific item, given an item index and the Accordian’s `domId`.

### Instructions

1. Copy the JSON below and paste it into your View with the Accordian.

2. To your accordian component, add the `meta.domId` key and set it to something unique like “Accordian”

3. Inside the “JSInject\_Accordian\_ScrollToIndex” component, set the “accordianDomId” to what you set it to. Then you can scroll to a specific item index by setting the `custom.index` to the index number. You can bind to this or set its value via script

### Resources

> **Markdown Version (Includes 2x Markdown components)**
>
> This contains 2x Markdown components:
> 
> - “JSInject\_Accordian\_ScrollToIndexBase” injects the JS function `scrollToAccordionItem`
> - “JSInject\_Accordian\_ScrollToIndex” calls the function with the domID of the accordian to scroll, and the index to scroll to, based on custom props on the component.
> 
> ```json
> [
> {
> "type": "ia.display.markdown",
> "version": 0,
> "props": {
> "style": {
> "position": "absolute"
> },
> "markdown": {
> "escapeHtml": false
> }
> },
> "meta": {
> "name": "JSInject_Accordian_ScrollToIndexBase"
> },
> "position": {
> "shrink": 0,
> "basis": 0
> },
> "custom": {
> "inlineJavascript": "window.scrollToAccordionItem = function(id, index) {\n const accordion = document.querySelector('#' + id + '.ia_accordionComponent');\n if (!accordion) {\n console.warn('Accordion component not found for id: ' + id);\n return;\n }\n\n const wrapper = accordion.querySelector('.ia_accordionComponent__wrapper');\n const header = wrapper.querySelector('.ia_accordionComponent__header[data-index=&quot;' + index + '&quot;]');\n\n if (!header) {\n console.warn('Accordion item with index ' + index + ' not found.');\n return;\n }\n\n const accordionTop = accordion.getBoundingClientRect().top;\n const headerTop = header.getBoundingClientRect().top;\n const offset = headerTop - accordionTop;\n\n accordion.scrollTo({\n top: accordion.scrollTop + offset,\n behavior: 'smooth'\n });\n };"
> },
> "propConfig": {
> "custom.inlineJavascript": {
> "access": "PRIVATE"
> },
> "props.source": {
> "binding": {
> "config": {
> "struct": {
> "script": "{this.custom.inlineJavascript}"
> },
> "waitOnAll": true
> },
> "transforms": [
> {
> "code": "#\tcode = \"<img style='display:none' src='/favicon.ico' onload=\\\"\" + value.script.replace('\"', '&quot;') + '\\\"></img>'\n#\t\n#\tcode = code.replace(\"\\n\", \"\").replace(\"\\t\", \"\").replace(\"\\n\", \"\").replace(\"\\r\", \"\").replace(\"\\r\", \"\").replace(\" \", \" \").replace(\" \", \" \").replace(\" \", \" \").replace(\" \", \" \")\n#\t\n#\treturn code\n\tscript = value.script\n\tscript = script.replace('\"', '&quot;')\n\tscript = script.replace('\\n', '').replace('\\t', '').replace('\\r', '')\n\t\n\t# Collapse multiple spaces - repeat until stable\n\twhile ' ' in script:\n\t script = script.replace(' ', ' ')\n\t\n\tcode = '<img style=\"display:none\" src=\"/favicon.ico\" onload=\"{}\"></img>'.format(script)\n\treturn code",
> "type": "script"
> }
> ],
> "type": "expr-struct"
> }
> }
> }
> },
> {
> "type": "ia.display.markdown",
> "version": 0,
> "props": {
> "style": {
> "position": "absolute"
> },
> "markdown": {
> "escapeHtml": false
> }
> },
> "meta": {
> "name": "JSInject_Accordian_ScrollToIndex"
> },
> "position": {
> "shrink": 0,
> "basis": 0
> },
> "custom": {
> "accordianDomId": "ME",
> "index": 3
> },
> "propConfig": {
> "custom.accordianDomId": {
> "access": "PRIVATE"
> },
> "custom.index": {
> "access": "PRIVATE"
> },
> "custom.inlineJavascript": {
> "binding": {
> "config": {
> "expression": "if({this.custom.index} != \"\"\r\n\t,\"(function() {scrollToAccordionItem('\" + {this.custom.accordianDomId} + \"', \" + {this.custom.index} + \");})();\"\r\n \t,\"\"\r\n)"
> },
> "type": "expr"
> },
> "access": "PRIVATE"
> },
> "props.source": {
> "binding": {
> "config": {
> "struct": {
> "script": "{this.custom.inlineJavascript}"
> },
> "waitOnAll": true
> },
> "transforms": [
> {
> "code": "#\tcode = \"<img style='display:none' src='/favicon.ico' onload=\\\"\" + value.script.replace('\"', '&quot;') + '\\\"></img>'\n#\t\n#\tcode = code.replace(\"\\n\", \"\").replace(\"\\t\", \"\").replace(\"\\n\", \"\").replace(\"\\r\", \"\").replace(\"\\r\", \"\").replace(\" \", \" \").replace(\" \", \" \").replace(\" \", \" \").replace(\" \", \" \")\n#\t\n#\treturn code\n\tscript = value.script\n\tscript = script.replace('\"', '&quot;')\n\tscript = script.replace('\\n', '').replace('\\t', '').replace('\\r', '')\n\t\n\t# Collapse multiple spaces - repeat until stable\n\twhile ' ' in script:\n\t script = script.replace(' ', ' ')\n\t\n\tcode = '<img style=\"display:none\" src=\"/favicon.ico\" onload=\"{}\"></img>'.format(script)\n\treturn code",
> "type": "script"
> }
> ],
> "type": "expr-struct"
> }
> }
> }
> }
> ]
> 
> ```

> **Periscope Version (N/A yet)**
>

---

<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:** [April 6, 2026, 8:04pm UTC](https://forum.inductiveautomation.com/t/js-injection-hack-usefuls-javascript/114714/5 "2026-04-06T20:04:28Z")

</div>

I thought I would add this despite it not being an injection since it can be helpful even when trying to inspect data through `window.__client.page.views._data` (although you can change this path in the code if needed).

This utility attempts to find the **shortest path** , within a fixed time limit (currently hard‑coded to 40 seconds), to a target **key** , **value** , or **prototype property** , depending on how it is called.

Because the search space can be large and complex, the function may return **multiple possible paths**. Some of these paths may be React-related internals or may not actually point to the value you’re interested in, so you’ll need to manually choose the correct one from the results.

* * *

**How It Works**

- The script walks through objects starting from `window.__client.page.views._data`.

- It searches breadth‑first to find the shortest reachable paths.

- Depending on the selected search type, it checks:

- The search stops when:

* * *

**How to Use It**

1. Open **Inspect Element** in your browser.

2. Go to the **Sources** tab.

3. Create a new **JavaScript Snippet**.

4. Paste the script into the snippet and save it.

5. Run the snippet so the function becomes available.

After that, you can call it directly from the browser console using findPath(arg1,arg2):

- The **first argument** : the key, value, or prototype property you’re searching for.

- The **second argument** : the search type (`key`, `value`, `proto`, or `all`).

- an example would be something like findPath('virtualized','key').

* * *

**Notes**

- The function returns several candidate paths; not all of them will necessarily be useful.

- Some results may point to React internals or unrelated references.

- This was written quickly, so while it should work, there may be minor edge cases or errors that were missed.

```javascript
function findPath(target, searchType = 'all')
{ // 'key', 'value', 'proto', 'all'
const results = [];
const visited = new WeakSet();
const queue = [];
const startTime = Date.now();
const MAX_TIME = 40000;
const dataMap = window.__client?.page?.views?._data;
if (!dataMap) return console.error("Perspective client not found.");

dataMap.forEach((view, id) => {
    queue.push({ obj: view, path: `window.__client.page.views._data.get('${id}')` });
});

console.log(`Searching for ${searchType}: "${target}"...`);

while (queue.length > 0 && results.length < 10) {
    if (Date.now() - startTime > MAX_TIME) break;
    const { obj, path } = queue.shift();

    if (!obj || typeof obj !== 'object' || visited.has(obj)) continue;
    visited.add(obj);

    try {
        // 1. CHECK THE PROTOTYPE (The "Class" level)
        if (searchType === 'proto' || searchType === 'all') {
            const proto = Object.getPrototypeOf(obj);
            if (proto && proto.hasOwnProperty(target)) {
                console.log(`%c PROTO MATCH: "${target}" exists on ${path} prototype`, "color: #9C27B0; font-weight: bold;");
                results.push({ path: `${path}. __proto__ ['${target}']`, type: 'proto' });
            }
        }

        // 2. CHECK KEYS AND VALUES (The "Instance" level)
        for (const key in obj) {
            const val = obj[key];
            const currentPath = `${path}['${key}']`;

            if ((searchType === 'key' || searchType === 'all') && key === target) {
                console.log(`%c KEY MATCH: ${currentPath}`, "color: #2196F3; font-weight: bold;");
                results.push({ path: currentPath, type: 'key' });
            }
            
            if ((searchType === 'value' || searchType === 'all') && val === target) {
                console.log(`%c VAL MATCH: ${currentPath}`, "color: #4CAF50; font-weight: bold;");
                results.push({ path: currentPath, type: 'value' });
            }

            if (val && typeof val === 'object') {
                queue.push({ obj: val, path: currentPath });
            }
        }
    } catch (e) {}
}
return results;
}

```

if any edits need to be made, please let me know.

---

<div class="post-metadata">

**Author:** ![Kyle.Chase](https://avatars.discourse-cdn.com/v4/letter/k/3bc359/32.png) [@Kyle.Chase](https://forum.inductiveautomation.com/u/Kyle.Chase)\
**Post date:** [April 17, 2026, 7:31pm UTC](https://forum.inductiveautomation.com/t/js-injection-hack-usefuls-javascript/114714/6 "2026-04-17T19:31:42Z")

</div>

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.

---

<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:** [April 17, 2026, 8:11pm UTC](https://forum.inductiveautomation.com/t/js-injection-hack-usefuls-javascript/114714/7 "2026-04-17T20:11:02Z")

</div>

The tooltip when you hover escapeHTML does say use at your own risk, but I suppose a warning here probably is a good idea as I doubt many people read the tooltip.

So, does that mean that there are plans to patch/remove the escapeHTML feature? If this is getting patched, is there an official feature in the works that will let us run JavaScript on perspective pages? I’d love to see a way to load Web Dev files directly into a Perspective page. Most of the time I use Markdown injections, I’m just pulling in a Web Dev file.

I feel as if a way to add JavaScript is almost a requirement for some tasks. For instance, clicking an element and getting its data back is incredibly easy in JS, but as far as I know, doing that natively in Perspective requires an event script on every single element, which is a massive chore.

---

<div class="post-metadata">

**Author:** ![lrose](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/lrose/32/62724_2.png) [@lrose](https://forum.inductiveautomation.com/u/lrose)\
**Post date:** [April 17, 2026, 8:19pm UTC](https://forum.inductiveautomation.com/t/js-injection-hack-usefuls-javascript/114714/8 "2026-04-17T20:19:43Z")

</div>

> [@travisaggiebrown](#):
>
> is there an official feature in the works that will let us run JavaScript on perspective pages

Not an answer to your question, but, there is this:

> [@Musson Industrial's Embr-Periscope Module](https://forum.inductiveautomation.com/t/musson-industrials-embr-periscope-module/99047):
>
> Embr-Periscope An Ignition module that includes design extensions and enhancements for Perspective. A periscope provides a new perspective by allowing users to see over, around, or through obstacles, offering a unique view otherwise obstructed. NOTE: This modules is still in pre-release. All versions before v1.0.0 should be considered unstable. Components [Embedded View +](https://docs.mussonindustrial.com/ignition/embr-periscope/components/embedded-view-plus/) This component is a enhancement of the default Embedded View component with server-side property mapping. In testing, t…

---

<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:** [April 17, 2026, 8:39pm UTC](https://forum.inductiveautomation.com/t/js-injection-hack-usefuls-javascript/114714/9 "2026-04-17T20:39:15Z")

</div>

yeah, modules are great, but my company doesn't like opensource stuff due to there not being a LTSA. it just seems crazy to me that the JS injections have been talked about for at least three years on this forum, yet we are no closer to an official solution.

---

<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:** [April 17, 2026, 8:49pm UTC](https://forum.inductiveautomation.com/t/js-injection-hack-usefuls-javascript/114714/10 "2026-04-17T20:49:48Z")

</div>

> [@travisaggiebrown](#):
>
> LTSA

Hit us up? We'll sell you a reasonable support agreement for the right price.

> **[Musson Industrial](https://mussonindustrial.com/)**
>
> Musson Industrial is an instrumentation and process controls company that has been serving the greater midwest for over 30 years.

> [@travisaggiebrown](#):
>
> I’d love to see a way to load Web Dev files directly into a Perspective page. Most of the time I use Markdown injections, I’m just pulling in a Web Dev file.

I have a branch of Periscope that introduces a dedicated JavaScript resource.

It has Typescript/TSX support, lets you run code with side effects on page load, and allows you to create your own React components.

DM me for a local build/example project, I'm looking for testers before I merge it.

 ![image](https://us1.discourse-cdn.com/inductiveautomation/original/3X/2/7/2782d2feec0a2a7dd48e479c453ccb93e2933c27.png)  
 ![image](https://us1.discourse-cdn.com/inductiveautomation/original/3X/a/1/a19cbb615cdb3319c5bba179b3a3a7b20037441c.png)

[https://github.com/mussonindustrial/embr/tree/8.1/feat/persicope-react-component/modules/periscope](https://github.com/mussonindustrial/embr/tree/8.1/feat/persicope-react-component/modules/periscope)

---

<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:** [April 17, 2026, 8:58pm UTC](https://forum.inductiveautomation.com/t/js-injection-hack-usefuls-javascript/114714/11 "2026-04-17T20:58:42Z")

</div>

if they remove the ability to do JavaScript injection, then I'll probably reach out but for now I will be an "exploiter" for as long as I'm allowed.

---

<div class="post-metadata">

**Author:** ![Kyle.Chase](https://avatars.discourse-cdn.com/v4/letter/k/3bc359/32.png) [@Kyle.Chase](https://forum.inductiveautomation.com/u/Kyle.Chase)\
**Post date:** [April 17, 2026, 9:01pm UTC](https://forum.inductiveautomation.com/t/js-injection-hack-usefuls-javascript/114714/12 "2026-04-17T21:01:14Z")

</div>

Adding official support for running arbitrary Javascript is not on our road map. While I understand the flexibility this offers, it also opens up security and stability issues depending on the environment.

Sorry I do not have a solution for you at this time. We will re-evaluate this position over time.

---

<div class="post-metadata">

**Author:** ![a.alghannam](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/a.alghannam/32/75158_2.png) [@a.alghannam](https://forum.inductiveautomation.com/u/a.alghannam)\
**Post date:** [April 17, 2026, 9:18pm UTC](https://forum.inductiveautomation.com/t/js-injection-hack-usefuls-javascript/114714/13 "2026-04-17T21:18:21Z")

</div>

I have this question in my head. Why to use hacks of injecting JS while i can create a proper module to handle my custom needs? is there an advantage to use JS injection or markdown trick rather than building a custom module?

---

<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:** [April 17, 2026, 9:57pm UTC](https://forum.inductiveautomation.com/t/js-injection-hack-usefuls-javascript/114714/14 "2026-04-17T21:57:51Z")

</div>

No need for knowledge of Java, or learning the module framework built with it. Other than that, probably not.

---

<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:** [April 17, 2026, 10:37pm UTC](https://forum.inductiveautomation.com/t/js-injection-hack-usefuls-javascript/114714/15 "2026-04-17T22:37:53Z")

</div>

If js hacking is going away, then we need to have some of the basic things we use it for available in the components, like scrolling to an item in a table or ability to click on svg elements and run a script. Some things are definitely more hacky than others, but unless you know how to or have the time to invest into writing a module, some basic things that are offered by the underlying react componentsthat just aren't exposed just aren't possible without it.

---

<div class="post-metadata">

**Author:** ![paul-griffith](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/paul-griffith/32/37249_2.png) [@paul-griffith](https://forum.inductiveautomation.com/u/paul-griffith)\
**Post date:** [April 17, 2026, 11:05pm UTC](https://forum.inductiveautomation.com/t/js-injection-hack-usefuls-javascript/114714/16 "2026-04-17T23:05:28Z")

</div>

> [@Exchange Resource to use Javascript with Perspective](https://forum.inductiveautomation.com/t/exchange-resource-to-use-javascript-with-perspective/81937/19):
>
> You'll have none of those in 8.3, guaranteed. Offering alternatives to Jython is something we want to do, but as I mentioned in the dev panel at ICC, there isn't a solution out there right now that's compatible with our technology stack (and without significant non-technical drawbacks). I already said I was on board with this capability! But the fact remains - for at least 90% of our users, Perspective as it is works well enough and offers enough functionality for them to accomplish what they…

---

<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:** [April 17, 2026, 11:43pm UTC](https://forum.inductiveautomation.com/t/js-injection-hack-usefuls-javascript/114714/17 "2026-04-17T23:43:01Z")

</div>

It'd be trivial for Embr-Periscope to offer a simple `InlineJavaScript` component that runs JavaScript when it loads, but I'd need more details from the community.

I don't use any Markdown component hacks, so I am totally unaware of how people use it/what they use it for.

---

<div class="post-metadata">

**Author:** ![justinedwards.jle](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/justinedwards.jle/32/37854_2.png) [@justinedwards.jle](https://forum.inductiveautomation.com/u/justinedwards.jle)\
**Post date:** [April 25, 2026, 4:20am UTC](https://forum.inductiveautomation.com/t/js-injection-hack-usefuls-javascript/114714/18 "2026-04-25T04:20:35Z")

</div>

> [@Kyle.Chase](#):
>
> and we're treating this as a security concern.

That stinks. Everybody loves this feature

> [@nminchin](#):
>
> If js hacking is going away, then we need to have some of the basic things we use it for available in the components

Seconded

---

<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:** [April 25, 2026, 7:20am UTC](https://forum.inductiveautomation.com/t/js-injection-hack-usefuls-javascript/114714/19 "2026-04-25T07:20:33Z")

</div>

I'll get a list together of what I've used it for this week some time

---

<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 5, 2026, 10:31pm UTC](https://forum.inductiveautomation.com/t/js-injection-hack-usefuls-javascript/114714/20 "2026-05-05T22:31:24Z")

</div>

I'm late to the game - figured this out before searching the forums since my initial searches for "Perspective color picker" turned up nothing that I wanted.

MobX observables, fiber walks, derived component caches vs. source-of-truth view params

An easy markdown-color-picker is awesome!

Has this been patched in v8.3+?

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