# Vertical menu tree in perspective

**URL:** <https://forum.inductiveautomation.com/t/vertical-menu-tree-in-perspective/75685>\
**Category:** Ignition\
**Tags:** perspective, ignition81\
**Created:** [June 29, 2023, 6:55am UTC](https://forum.inductiveautomation.com/t/vertical-menu-tree-in-perspective/75685 "2023-06-29T06:55:28Z")\
**Posts on this page:** 14\
**Page:** 1

<div class="post-metadata">

**Author:** ![Priyanka.Khandge](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/priyanka.khandge/32/35989_2.png) [@Priyanka.Khandge](https://forum.inductiveautomation.com/u/Priyanka.Khandge)\
**Post date:** [June 29, 2023, 6:55am UTC](https://forum.inductiveautomation.com/t/vertical-menu-tree-in-perspective/75685/1 "2023-06-29T06:55:28Z")

</div>

Hi guys,

Any idea how to set a vertical menu tree like when I click Menu Item then display the sub-menu in the same view when I click this Menu Item again and then collapse this sub-menu in perspective?

Same as that dropdown.

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

Thanks,  
Priyanka

---

<div class="post-metadata">

**Author:** ![pascal.fragnoud](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/pascal.fragnoud/32/24728_2.png) [@pascal.fragnoud](https://forum.inductiveautomation.com/u/pascal.fragnoud)\
**Post date:** [June 29, 2023, 7:39am UTC](https://forum.inductiveautomation.com/t/vertical-menu-tree-in-perspective/75685/2 "2023-06-29T07:39:42Z")

</div>

I don't think this component can do what you want, but it's easy enough to make yourself.  
The simplest way would be to use flex containers to host your navigation buttons, and make those containers basis depend on a custom property on the menu button.  
You can even add a `transition: flex-basis 500ms` style prop to the container to make it animate nicely.

Here's the json of an example based on what I use:

> ****
>
> ```json
> {
> "custom": {
> "current_category": "debug"
> },
> "params": {},
> "propConfig": {
> "custom.current_category": {
> "binding": {
> "config": {
> "path": "page.props.primaryView"
> },
> "transforms": [
> {
> "code": "\ttry:\n\t\treturn value.split(\u0027/\u0027)[-2]\n\texcept:\n\t\treturn \"debug\"",
> "type": "script"
> }
> ],
> "type": "property"
> },
> "persistent": true
> }
> },
> "props": {},
> "root": {
> "children": [
> {
> "children": [
> {
> "custom": {
> "view": "some_category/mainpage"
> },
> "events": {
> "component": {
> "onActionPerformed": {
> "config": {
> "params": {},
> "view": "{this.custom.view}"
> },
> "scope": "C",
> "type": "nav"
> }
> }
> },
> "meta": {
> "name": "main_button"
> },
> "position": {
> "basis": "34px",
> "shrink": 0
> },
> "propConfig": {
> "props.style.classes": {
> "binding": {
> "config": {
> "expression": "if ({view.custom.current_category} \u003d {parent.custom.category},\r\n\t\"button_selected\",\r\n\t\"button\"\r\n)"
> },
> "type": "expr"
> }
> }
> },
> "props": {
> "justify": "start",
> "style": {
> "borderColor": "#808080",
> "fontWeight": "normal"
> },
> "text": "some category"
> },
> "type": "ia.input.button"
> },
> {
> "custom": {
> "view": "some_category/page_1"
> },
> "events": {
> "component": {
> "onActionPerformed": {
> "config": {
> "params": {},
> "view": "{this.custom.view}"
> },
> "scope": "C",
> "type": "nav"
> }
> }
> },
> "meta": {
> "name": "page1"
> },
> "position": {
> "basis": "34px",
> "shrink": 0
> },
> "propConfig": {
> "props.style.classes": {
> "binding": {
> "config": {
> "expression": "if ({this.custom.view} \u003d {page.props.primaryView},\r\n\t\"button_selected\",\r\n\t\"button\"\r\n)"
> },
> "type": "expr"
> }
> }
> },
> "props": {
> "justify": "start",
> "style": {
> "borderColor": "#808080",
> "fontWeight": "normal"
> },
> "text": "\u003e page 1"
> },
> "type": "ia.input.button"
> },
> {
> "custom": {
> "view": "some_category/page_2"
> },
> "events": {
> "component": {
> "onActionPerformed": {
> "config": {
> "params": {},
> "view": "{this.custom.view}"
> },
> "scope": "C",
> "type": "nav"
> }
> }
> },
> "meta": {
> "name": "page2"
> },
> "position": {
> "basis": "34px",
> "shrink": 0
> },
> "propConfig": {
> "props.style.classes": {
> "binding": {
> "config": {
> "expression": "if ({this.custom.view} \u003d {page.props.primaryView},\r\n\t\"button_selected\",\r\n\t\"button\"\r\n)"
> },
> "type": "expr"
> }
> }
> },
> "props": {
> "justify": "start",
> "style": {
> "borderColor": "#808080",
> "fontWeight": "normal"
> },
> "text": "\u003e page 2"
> },
> "type": "ia.input.button"
> },
> {
> "custom": {
> "view": "some_category/page_3"
> },
> "events": {
> "component": {
> "onActionPerformed": {
> "config": {
> "params": {},
> "view": "{this.custom.view}"
> },
> "scope": "C",
> "type": "nav"
> }
> }
> },
> "meta": {
> "name": "page3"
> },
> "position": {
> "basis": "34px",
> "shrink": 0
> },
> "propConfig": {
> "props.style.classes": {
> "binding": {
> "config": {
> "expression": "if ({this.custom.view} \u003d {page.props.primaryView},\r\n\t\"button_selected\",\r\n\t\"button\"\r\n)"
> },
> "type": "expr"
> }
> }
> },
> "props": {
> "justify": "start",
> "style": {
> "borderColor": "#808080",
> "fontWeight": "normal"
> },
> "text": "\u003e page 3"
> },
> "type": "ia.input.button"
> }
> ],
> "custom": {
> "category": "some_category"
> },
> "meta": {
> "name": "some_category"
> },
> "position": {
> "shrink": 0
> },
> "propConfig": {
> "position.basis": {
> "binding": {
> "config": {
> "expression": "if ({view.custom.current_category} \u003d {this.custom.category},\r\n\t\u0027148px\u0027,\r\n\t\u002734px\u0027\r\n)"
> },
> "type": "expr"
> }
> }
> },
> "props": {
> "direction": "column",
> "style": {
> "gap": "4px",
> "margin": "6px",
> "overflow": "hidden",
> "transition": "flex-basis 500ms"
> }
> },
> "type": "ia.container.flex"
> },
> {
> "children": [
> {
> "custom": {
> "view": "other_category/mainpage"
> },
> "events": {
> "component": {
> "onActionPerformed": {
> "config": {
> "params": {},
> "view": "{this.custom.view}"
> },
> "scope": "C",
> "type": "nav"
> }
> }
> },
> "meta": {
> "name": "main_button"
> },
> "position": {
> "basis": "34px",
> "shrink": 0
> },
> "propConfig": {
> "props.style.classes": {
> "binding": {
> "config": {
> "expression": "if ({view.custom.current_category} \u003d {parent.custom.category},\r\n\t\"button_selected\",\r\n\t\"button\"\r\n)"
> },
> "type": "expr"
> }
> }
> },
> "props": {
> "justify": "start",
> "style": {
> "borderColor": "#808080",
> "fontWeight": "normal"
> },
> "text": "other category"
> },
> "type": "ia.input.button"
> },
> {
> "custom": {
> "view": "other_category/page_1"
> },
> "events": {
> "component": {
> "onActionPerformed": {
> "config": {
> "params": {},
> "view": "{this.custom.view}"
> },
> "scope": "C",
> "type": "nav"
> }
> }
> },
> "meta": {
> "name": "page1"
> },
> "position": {
> "basis": "34px",
> "shrink": 0
> },
> "propConfig": {
> "props.style.classes": {
> "binding": {
> "config": {
> "expression": "if ({this.custom.view} \u003d {page.props.primaryView},\r\n\t\"button_selected\",\r\n\t\"button\"\r\n)"
> },
> "type": "expr"
> }
> }
> },
> "props": {
> "justify": "start",
> "style": {
> "borderColor": "#808080",
> "fontWeight": "normal"
> },
> "text": "\u003e page 1"
> },
> "type": "ia.input.button"
> },
> {
> "custom": {
> "view": "other_category/page_2"
> },
> "events": {
> "component": {
> "onActionPerformed": {
> "config": {
> "params": {},
> "view": "{this.custom.view}"
> },
> "scope": "C",
> "type": "nav"
> }
> }
> },
> "meta": {
> "name": "page2"
> },
> "position": {
> "basis": "34px",
> "shrink": 0
> },
> "propConfig": {
> "props.style.classes": {
> "binding": {
> "config": {
> "expression": "if ({this.custom.view} \u003d {page.props.primaryView},\r\n\t\"button_selected\",\r\n\t\"button\"\r\n)"
> },
> "type": "expr"
> }
> }
> },
> "props": {
> "justify": "start",
> "style": {
> "borderColor": "#808080",
> "fontWeight": "normal"
> },
> "text": "\u003e page 2"
> },
> "type": "ia.input.button"
> },
> {
> "custom": {
> "view": "other_category/page_3"
> },
> "events": {
> "component": {
> "onActionPerformed": {
> "config": {
> "params": {},
> "view": "{this.custom.view}"
> },
> "scope": "C",
> "type": "nav"
> }
> }
> },
> "meta": {
> "name": "page3"
> },
> "position": {
> "basis": "34px",
> "shrink": 0
> },
> "propConfig": {
> "props.style.classes": {
> "binding": {
> "config": {
> "expression": "if ({this.custom.view} \u003d {page.props.primaryView},\r\n\t\"button_selected\",\r\n\t\"button\"\r\n)"
> },
> "type": "expr"
> }
> }
> },
> "props": {
> "justify": "start",
> "style": {
> "borderColor": "#808080",
> "fontWeight": "normal"
> },
> "text": "\u003e page 3"
> },
> "type": "ia.input.button"
> }
> ],
> "custom": {
> "category": "other_category"
> },
> "meta": {
> "name": "other_category"
> },
> "position": {
> "shrink": 0
> },
> "propConfig": {
> "position.basis": {
> "binding": {
> "config": {
> "expression": "if ({view.custom.current_category} \u003d {this.custom.category},\r\n\t\u0027148px\u0027,\r\n\t\u002734px\u0027\r\n)"
> },
> "type": "expr"
> }
> }
> },
> "props": {
> "direction": "column",
> "style": {
> "gap": "4px",
> "margin": "6px",
> "overflow": "hidden",
> "transition": "flex-basis 500ms"
> }
> },
> "type": "ia.container.flex"
> }
> ],
> "meta": {
> "name": "root"
> },
> "props": {
> "direction": "column"
> },
> "type": "ia.container.flex"
> }
> }
> 
> ```

This assumes folders `some_category` and `other_category`, each containing the views `mainpage`, `page_1`, `page_2` and `page_3`.  
And also the styles `button` and `button_selected`, because I like highlighting the button that corresponds to the category and the page I'm in.

How it works:  
The view itself has a custom property `current_category` that uses the `page.primaryView` property to determine what category the user is currently in.  
Each category container has a `category` custom prop.  
Those 2 properties are compared in a binding on the `mainpage` button's style classes, to selected the proper class.  
The category containers have a binding on the `basis` property to make it grow or shrink based on whether the currently displayed page matches the category. And as a bonus there's a `transition` style prop to make it look nice.  
Each button has a `view` custom property that is the path it navigates to when clicked.  
That property is also used in a binding to select a style class.  
And finally each button has an `onActionPerformed` navigation event to navigate to the view specified in the custom property.

This will need customization to match your needs but it's a start.

edit: forgot the json ;p

---

<div class="post-metadata">

**Author:** ![Priyanka.Khandge](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/priyanka.khandge/32/35989_2.png) [@Priyanka.Khandge](https://forum.inductiveautomation.com/u/Priyanka.Khandge)\
**Post date:** [June 29, 2023, 11:11am UTC](https://forum.inductiveautomation.com/t/vertical-menu-tree-in-perspective/75685/3 "2023-06-29T11:11:59Z")

</div>

![image](https://us1.discourse-cdn.com/inductiveautomation/original/3X/6/7/67ce80e525126db03013366946d7841c4e6f83db.png)

Can use this tree component for navigation in perspective and any idea how to set view?

---

<div class="post-metadata">

**Author:** ![ToMakPo](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/tomakpo/32/23875_2.png) [@ToMakPo](https://forum.inductiveautomation.com/u/ToMakPo)\
**Post date:** [June 29, 2023, 11:14am UTC](https://forum.inductiveautomation.com/t/vertical-menu-tree-in-perspective/75685/4 "2023-06-29T11:14:30Z")

</div>

There are a LOT of issues with the built in menu component. My team started using the tree component for navigation a few months back and it has never given us any issues.

---

<div class="post-metadata">

**Author:** ![Priyanka.Khandge](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/priyanka.khandge/32/35989_2.png) [@Priyanka.Khandge](https://forum.inductiveautomation.com/u/Priyanka.Khandge)\
**Post date:** [June 29, 2023, 11:19am UTC](https://forum.inductiveautomation.com/t/vertical-menu-tree-in-perspective/75685/5 "2023-06-29T11:19:18Z")

</div>

Any idea how to use this?

---

<div class="post-metadata">

**Author:** ![ToMakPo](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/tomakpo/32/23875_2.png) [@ToMakPo](https://forum.inductiveautomation.com/u/ToMakPo)\
**Post date:** [June 29, 2023, 11:28am UTC](https://forum.inductiveautomation.com/t/vertical-menu-tree-in-perspective/75685/6 "2023-06-29T11:28:53Z")

</div>

[Menu\_2023-06-29\_1126.zip](https://forum.inductiveautomation.com/uploads/short-url/d9K8P7iH1ajITYtisUryLzVmrwu.zip) (11.3 KB)

This is what we are using. There might be some stuff in there that you don't need.

---

<div class="post-metadata">

**Author:** ![Priyanka.Khandge](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/priyanka.khandge/32/35989_2.png) [@Priyanka.Khandge](https://forum.inductiveautomation.com/u/Priyanka.Khandge)\
**Post date:** [June 29, 2023, 11:38am UTC](https://forum.inductiveautomation.com/t/vertical-menu-tree-in-perspective/75685/7 "2023-06-29T11:38:09Z")

</div>

@ToMakPo Thank you so much I will try.

---

<div class="post-metadata">

**Author:** ![Transistor](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/transistor/32/3380_2.png) [@Transistor](https://forum.inductiveautomation.com/u/Transistor)\
**Post date:** [June 29, 2023, 4:29pm UTC](https://forum.inductiveautomation.com/t/vertical-menu-tree-in-perspective/75685/8 "2023-06-29T16:29:49Z")

</div>

Here's how I did it.  
 ![Page configuration](https://us1.discourse-cdn.com/inductiveautomation/original/3X/e/c/ec99daea60bb4e5d39717ddec1ace55bca391352.png)  
_Figure 1. Do the Page Configuration._

 ![Menu structure editor](https://us1.discourse-cdn.com/inductiveautomation/original/3X/3/5/3503a7d3fe2e00829dfbd00976aacac6b3955f99.png)  
_Figure 2. Create the menu structure in text. I do this in the actual application. Note the use of the '|' pipe character to separate the menu and URL._

Bind the tree's `items` property to the text and apply a script transform on it:

> **The 'items' binding script transform.**
>
> ```auto
> def transform(self, value, quality, timestamp):
> # Update the test tree with the structure.
> #	Anchors and path URLs to be provided in form shown below using '|' (vertical bar) as separator.
> #	A001 |/Home
> #	A001/B001 |/Area/1/Bath/1
> #	A001/B001/C001 |/Area/1/Bath/1/Clamp1
> # 2023-06-29 Transistor
> 	
> items = []
> treeExpand = self.view.params.treeExpand
> 	
> for line in value.splitlines():
> if line.strip(): # Ignore empty lines.
> if line[0] != "#": # Lines can be commented out with #.
> current = items
> branch, url = line.split('|')
> 	
> for part in branch.strip().split("/"): # Strip off leading and trailing spaces.
> folderExists = False # Check if the current folder exists in our items list.
> for itemsPointerItem in current:
> if part == itemsPointerItem['label']:
> folderExists = True
> current = itemsPointerItem['items']
> 			
> if not folderExists:
> item = {
> "label": part,
> "expanded": treeExpand,
> "items": [],
> "data": {"url": url.strip()}
> }
> current.append(item)
> current = item['items']
> 	
> return items
> 
> ```
> 
> The selected item is available in `selectionData.0.value.url`
> 
> ```auto
> [
> {
> "itemPath": "3/0",
> "value": {
> "url": "/machine/Sealer03"
> }
> }
> ]
> 
> ```

On the menu tree add an `onItemClicked` event, Script:

```auto
def runAction(self, event):
	pagePath = self.props.selectionData[0].value.url
	system.perspective.navigate(pagePath)

```

Note that I've used a script to do the navigation. If found that if I used a navigation event that the navigation happened before `selectionData[0].value.url` had been updated and so a second click was required each time.

Have fun!

---

<div class="post-metadata">

**Author:** ![Dale\_Wheatley](https://avatars.discourse-cdn.com/v4/letter/d/ba8739/32.png) [@Dale\_Wheatley](https://forum.inductiveautomation.com/u/Dale_Wheatley)\
**Post date:** [July 24, 2024, 11:20am UTC](https://forum.inductiveautomation.com/t/vertical-menu-tree-in-perspective/75685/9 "2024-07-24T11:20:55Z")

</div>

@ToMakPo in Your example there is a property pageId within the page, im assuming this should be an integer. I am encountering the error.....  
Error running kanoa/core/docks/navigation/menu (1)@D/root.onMessageReceived(self, payload): Traceback (most recent call last): File "function:onMessageReceived", line 14, in onMessageReceived TypeError: com.inductiveautomation.perspective.gateway.script.PropertyTreeScriptWrapper$ArrayWrapper indices must be integers  
Do you have any idea what might be affecting this?

---

<div class="post-metadata">

**Author:** ![pascal.fragnoud](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/pascal.fragnoud/32/24728_2.png) [@pascal.fragnoud](https://forum.inductiveautomation.com/u/pascal.fragnoud)\
**Post date:** [July 24, 2024, 2:13pm UTC](https://forum.inductiveautomation.com/t/vertical-menu-tree-in-perspective/75685/10 "2024-07-24T14:13:00Z")

</div>

Show us the script. Except for "You're trying to index an array with something that's not an int", there's not much we can say without the script.

---

<div class="post-metadata">

**Author:** ![Dale\_Wheatley](https://avatars.discourse-cdn.com/v4/letter/d/ba8739/32.png) [@Dale\_Wheatley](https://forum.inductiveautomation.com/u/Dale_Wheatley)\
**Post date:** [July 24, 2024, 2:31pm UTC](https://forum.inductiveautomation.com/t/vertical-menu-tree-in-perspective/75685/12 "2024-07-24T14:31:55Z")

</div>

@pascal.fragnoud It is the script that @ToMakPo uploaded in his attached zip. It is in update navigation event handler(within the root container). Which passes in a string- pageId, Into the array, which is causing the exception.

 ![image](https://us1.discourse-cdn.com/inductiveautomation/original/3X/f/5/f5993479dcbed5325a463e73ece97b1b6862174b.png)

---

<div class="post-metadata">

**Author:** ![pascal.fragnoud](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/pascal.fragnoud/32/24728_2.png) [@pascal.fragnoud](https://forum.inductiveautomation.com/u/pascal.fragnoud)\
**Post date:** [July 24, 2024, 2:45pm UTC](https://forum.inductiveautomation.com/t/vertical-menu-tree-in-perspective/75685/13 "2024-07-24T14:45:29Z")

</div>

`self.page.props.pageId` is not an int.  
I suspect you created `self.session.custom.pages` as an array, when it should really be a dict/object.

also, take a look at this:

> [@Wiki - how to post code on this forum](https://forum.inductiveautomation.com/t/wiki-how-to-post-code-on-this-forum/77831):
>
> This thread is intended as a short help file for all those struggling to post formatted code on the forum. Code block formatting Posting formatted code on the forum (and most others) is as simple as selecting the code and pressing the \</\> button. It works the same as applying Bold or Italic styles to your document. It preserves indentation and applies syntax highlighting. [How to post code] Note that all the \</\> button does is add three backticks ``` on the line before the code block and anot…

---

<div class="post-metadata">

**Author:** ![Dale\_Wheatley](https://avatars.discourse-cdn.com/v4/letter/d/ba8739/32.png) [@Dale\_Wheatley](https://forum.inductiveautomation.com/u/Dale_Wheatley)\
**Post date:** [July 24, 2024, 3:51pm UTC](https://forum.inductiveautomation.com/t/vertical-menu-tree-in-perspective/75685/14 "2024-07-24T15:51:00Z")

</div>

@pascal.fragnoud

````python
def onMessageReceived(self, payload):
	```
	system.perspective.print("updateNavigation() msg rcvd with " + system.util.jsonEncode(payload))
	# - implement your handler here
	# - any navigation action will call this, ex: menu tree, home button, any links
	# - this function adds new history items
	# - if this function is called when currentIndex is not pointing to the end of history,
	# delete every entry after the currentIndex, then add the new history item
	# - afterwards, navigate to the newly added history item/view
	

	
	
	if payload['type']	!= 'url':	#urls will be opended in a new tab so we don't need to store history
		logger = system.util.getLogger('log1')
    		logger.info("this is page iD " + self.page.props.pageId)
		pageId = self.page.props.pageId
		if '/' in pageId: pageId = 'designer' #For debug in designer
		pageNavigation = self.session.custom.pages[pageId].navigation
		history = history = pageNavigation.history
		currentIndex = pageNavigation.currentIndex
		
		if currentIndex < len(history) - 1:
			self.session.custom.pages[pageId].navigation.history = history[:currentIndex + 1]
			
		if history[currentIndex]['path'] != payload['path']:
			newHistoryItem = {
				"path": payload['path'],
				"name": payload['name'],
				"type": payload['type'],
				"params": system.util.jsonEncode(payload['params'])
			}
			self.session.custom.pages[pageId].navigation['history'].append(newHistoryItem)
			
			if len(self.session.custom.pages[pageId].navigation["history"]) > 30:
				self.session.custom.pages[pageId].navigation["history"] = self.session.custom.pages[pageId].navigation["history"][1:]
			else:
				self.session.custom.pages[pageId].navigation["currentIndex"] += 1
		else:
			return
	
	self.navigateTo(payload['type'], payload['path'], payload['params'])

	returnPreformatted text

````

I changed 'pages' from an array to a object and the error has gone(I had done this earlier today but had reverted it as again on line 14 I received this error, which I thought was a result of sending the wrong data . )

```python
com.inductiveautomation.ignition.common.script.JythonExecException
Traceback (most recent call last):
  File "<function:onMessageReceived>", line 18, in onMessageReceived
KeyError: 'designer'

	at org.python.core.Py.KeyError(Py.java:220)
	at com.inductiveautomation.perspective.gateway.script.PropertyTreeScriptWrapper$ObjectWrapper. __finditem__ (PropertyTreeScriptWrapper.java:464)
	at com.inductiveautomation.ignition.common.script.abc.AbstractJythonMap. __finditem__ (AbstractJythonMap.java:54)
	at org.python.core.PyObject. __getitem__ (PyObject.java:717)
	at org.python.pycode._pyx237.onMessageReceived$1(<function:onMessageReceived>:43)
	at org.python.pycode._pyx237.call_function(<function:onMessageReceived>)
	at org.python.core.PyTableCode.call(PyTableCode.java:173)
	at org.python.core.PyBaseCode.call(PyBaseCode.java:306)
	at org.python.core.PyFunction.function ___call__ (PyFunction.java:474)
	at org.python.core.PyFunction. __call__ (PyFunction.java:469)
	at org.python.core.PyFunction. __call__ (PyFunction.java:464)
	at com.inductiveautomation.ignition.common.script.ScriptManager.runFunction(ScriptManager.java:847)
	at com.inductiveautomation.ignition.common.script.ScriptManager.runFunction(ScriptManager.java:829)
	at com.inductiveautomation.ignition.gateway.project.ProjectScriptLifecycle$TrackingProjectScriptManager.runFunction(ProjectScriptLifecycle.java:868)
	at com.inductiveautomation.ignition.common.script.ScriptManager$ScriptFunctionImpl.invoke(ScriptManager.java:1010)
	at com.inductiveautomation.ignition.gateway.project.ProjectScriptLifecycle$AutoRecompilingScriptFunction.invoke(ProjectScriptLifecycle.java:950)
	at com.inductiveautomation.perspective.gateway.script.ScriptFunctionHelper.invoke(ScriptFunctionHelper.java:161)
	at com.inductiveautomation.perspective.gateway.script.ScriptFunctionHelper.invoke(ScriptFunctionHelper.java:98)
	at com.inductiveautomation.perspective.gateway.model.MessageHandlerCollection$MessageHandlerImpl$1.lambda$invoke$0(MessageHandlerCollection.java:86)
	at java.base/java.util.concurrent.FutureTask.run(Unknown Source)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
	at com.inductiveautomation.perspective.gateway.threading.BlockingWork$BlockingWorkRunnable.run(BlockingWork.java:58)
	at java.base/java.lang.Thread.run(Unknown Source)
Caused by: org.python.core.PyException
Traceback (most recent call last):
  File "<function:onMessageReceived>", line 18, in onMessageReceived
KeyError: 'designer'

	... 24 more

Ignition v8.1.39 (b2024040909)
Java: Azul Systems, Inc. 17.0.10

```

Do you have any idea why this would be thrown?

---

<div class="post-metadata">

**Author:** ![pascal.fragnoud](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/pascal.fragnoud/32/24728_2.png) [@pascal.fragnoud](https://forum.inductiveautomation.com/u/pascal.fragnoud)\
**Post date:** [July 24, 2024, 4:24pm UTC](https://forum.inductiveautomation.com/t/vertical-menu-tree-in-perspective/75685/15 "2024-07-24T16:24:00Z")

</div>

Well, clearly you don't have any entry under the `'designer'` key in the `session.custom.pages` prop.

I couldn't tell you why, I have no idea what should add those entries.
