Using system.config to create themes across gateways

I am trying out the new system.config functions to send a custom theme to remote gateways. Here is my project library script and my corresponding message script.

def sendHpHmiTheme(gateways = None):
	if not gateways:
		gateways = getOnlineGateways()
		if not gateways:
			# No gateways are online
			return
	
	# Get resource definition and package it into payload
	res = system.config.getResource(moduleId="com.inductiveautomation.perspective", typeId="themes", name="hp-hmi")
	configData = {
		'name': res.name,
		'collection': res.collection,
		'config': res.config,
		'files': res.files,
		'enabled': res.enabled,
		'attributes': res.attributes
	}
	
	system.util.sendMessage('Datavan', "addTheme", payload = {'configData': configData}, scope="G", remoteServers=gateways)
def handleMessage(payload):
	moduleId="com.inductiveautomation.perspective"
	typeId="themes"
	configData = payload['configData']
	
	try:
		# Check if resource already exists
		res = system.config.getResource(moduleId=moduleId, typeId=typeId, name=configData['name'])
	except ValueError:
		# It does not exist, create it
		system.config.create(moduleId=moduleId, typeId=typeId, **configData)
	else:
		# It does exist, replace it with same signature
		signature = res.signature
		system.config.replace(moduleId=moduleId, typeId=typeId, signature=signature, **configData)

When sending this as a remote message to a gateway without the theme, I get the exception line 11, in handleMessage ValueError: Provide resource config via the config parameter

Strangely, when I try setting the remote gateway as the gateway it is being called from (so a gateway with the theme already existing), I get a different exception: ValueError: java.lang.IllegalStateException: Resource not found: com.inductiveautomation.perspective/themes/hp-hmi

Is this some artifact of trying to do this over a gateway message, or is creating themes through system.config just not fully supported at this time?

Creating themes should definitely be possible via system.config.

Out of curiosity, if you change this line in the broadcast:

Into:
'config': dict(res.config),
Does it behave any differently?

And have you checked via a debug log the exact keys/values/types you're receiving in your message handler?

I added the line and here's the value types for each key

Also here's a printout of each value:
config = {'isPrivate': False, 'entrypoint': u'index.css'}

attributes = {'lastModification': {'actor': u'external', 'timestamp': u'2026-08-12T14:16:33Z'}, 'lastModificationSignature': u'c62e8991dd539da2bfc56ec399c1d923f2ce9b9a40cf21581f11a699f402303f'

files =

{u'config.json': {'isPrivate': False, 'entrypoint': u'index.css'}, u'high-performance-hmi.css': u'/* Direct stylesheet authoring is an advanced feature. Knowledge of CSS required.*/\r\n\r\n/* ROOT STYLES\r\n------------------------------------------------------------------------------*/\r\n\r\n\r\n/* STATES */\r\n:root {\r\n\t--state-color-green: #008F00;\r\n\t--state-color-red: #FF0A0A;\r\n\t--state-color-darkred: #941212;\r\n\t--state-color-orange: #F78103;\r\n\t--state-color-yellow: #F5C800;\r\n\t--state-color-blue: #1E7DB8;\r\n\t--state-color-purple: #4F2091;\r\n\t--state-color-gray: #B0B4A7;\r\n\t--state-color-black: #000000;\r\n\t--state-color-white: #FFFFFF;\r\n\t--state-color-cyan: #47FFF5;\r\n\r\n\t--state-aborted: var(--state-color-yellow);\r\n\t--state-aborting: var(--state-color-yellow);\r\n\t--state-alarm: var(--state-color-darkred);\r\n\t--state-auto: var(--state-color-green);\r\n\t--state-blocked: var(--state-color-purple);\r\n\t--state-complete: var(--state-color-green);\r\n\t--state-error: var(--state-color-darkred);\r\n\t--state-faulted: var(--state-color-red);\r\n\t--state-homed: var(--state-color-blue);\r\n\t--state-homing: var(--state-color-blue);\r\n\t--state-manual: var(--state-color-yellow);\r\n\t--state-off: var(--state-color-gray);\r\n\t--state-on: var(--state-color-white);\r\n\t--state-paused: var(--state-color-blue);\r\n\t--state-pausing: var(--state-color-blue);\r\n\t--state-proceeding: var(--state-color-blue);\r\n\t--state-running: var(--state-color-green);\r\n\t--state-starved: var(--state-color-purple);\r\n\t--state-stopped: var(--state-color-yellow);\r\n\t--state-stopping: var(--state-color-yellow);\r\n\t--state-warning: var(--state-color-yellow);\r\n\t--state-interlock: var(--state-color-yellow);\r\n\t--state-change: #6da4b0;\r\n\t--state-none: #d9d9d9;\r\n\t--state-simulation: var(--state-color-cyan);\r\n}\r\n\r\n/* ALARM PRIORITIES */\r\n:root {\r\n\t/* Follows Rockwell Automation best practices for alarm distinction and visibility */\r\n\t/* https://literature.rockwellautomation.com/idc/groups/literature/documents/wp/proces-wp023\_-en-p.pdf */\r\n\t--alarm-background-diagnostic: #E0E0E0;\r\n\t--alarm-foreground-diagnostic: #000000;\r\n\t--alarm-background-low: #916AAD;\r\n\t--alarm-foreground-low: #FFFFFF;\r\n\t--alarm-background-medium: #F5E11B;\r\n\t--alarm-foreground-medium: #3F3F3F;\r\n\t--alarm-background-high: #EC8629;\r\n\t--alarm-foreground-high: #FFFFFF;\r\n\t--alarm-background-critical: #E22028;\r\n\t--alarm-foreground-critical: #FFFFFF;\r\n}\r\n\t\r\n/* THEME */\r\n:root {\r\n\t--text-base: #333333;\r\n\t--text-weak-accent: #000000;\r\n\t--text-strong-accent: #002878;\r\n\t--text-light: #f0f0f0;\r\n\t--text-error: var(--state-color-red);\r\n\t--text-input: #5e5e5e;\r\n\t--text-button: var(--state-color-white);\r\n\t--background-base: #d9d9d9;\r\n\t--background-weak-accent: #e6e6e6;\r\n\t--background-strong-accent: #FFFFFF;\r\n\t--background-content: #C8ECFA;\r\n\t--background-dock: #f0f0f0;\r\n\t--background-header: #004e9ecc;\r\n\t--background-button-base: #fafdff;\r\n\t--background-button-hover: #066391;\r\n\t--background-button-disable: #cf2626;\r\n --callToAction: var(--neutral-80);\r\n --callToAction--hover: var(--neutral-60);\r\n --borderRadiusInput: 4px;\r\n\r\n}\r\n\r\n/* Neutrals */\r\n:root {\r\n /* Neutrals */\r\n --neutral-0: #FFFFFF;\r\n --neutral-10: #F7F3F2; /* warm-10 */\r\n --neutral-20: #E5E0DF; /* warm-20 */\r\n --neutral-30: #CAC5C4; /* warm-30 */\r\n --neutral-40: #ADA8A8; /* warm-40 */\r\n --neutral-50: #8F8B8B; /* warm-50 */\r\n --neutral-60: #736F6F; /* warm-60 */\r\n --neutral-70: #565151; /* warm-70 */\r\n --neutral-80: #3C3838; /* warm-80 */\r\n --neutral-90: #272525; /* warm-90 */\r\n --neutral-100: #171414; /* warm-100 */\r\n\r\n --alarmDashboardBg: #FDB2B3;\r\n}\r\n\r\n/* ALARM BORDERS\r\n------------------------------------------------------------------------------*/\r\n\r\n\r\n/*Critical Alarm*/\r\n\r\n.psc-alarmCritical {\r\n position: relative;\r\n box-shadow: 0px 0px 0px 3px var(--alarm-background-critical);\r\n}\r\n\r\n.psc-alarmTableCritical {\r\n position: relative;\r\n box-shadow: 0px 0px 0px 2px var(--alarm-background-critical);\r\n}\r\n\r\n.psc-alarmCriticalIcon {\r\n position: relative;\r\n}\r\n\r\n\r\n.psc-alarmCriticalIcon::before,.psc-alarmCritical::before {\r\n content: "";\r\n position: absolute;\r\n top: -10px;/*0px;*/\r\n left: -10px;/*0px;*/\r\n width: 20px;\r\n height: 20px;\r\n background-color: var(--alarm-background-critical);\r\n /*border-style: solid;\r\n border-width: 1px;\r\n border-color: var(--state-color-black);*/\r\n}\r\n\r\n.psc-alarmCriticalIcon::before,.psc-alarmTableCritical::before {\r\n content: "";\r\n position: absolute;\r\n top: 0px;\r\n left: calc(100% - 26px);\r\n width: 20px;\r\n height: 20px;\r\n background-color: var(--alarm-background-critical);\r\n border-style: solid;\r\n border-width: 1px;\r\n border-color: #941212;\r\n}\r\n\r\n.psc-alarmCriticalIcon::after,.psc-alarmCritical::after {\r\n counter-reset: alarm-content var(--alarm-priority-number, 4);\r\n content: counter(alarm-content);\r\n position: absolute;\r\n top: -10px;/*0px;*/\r\n left: -10px;/*0px;*/\r\n width: 20px;\r\n height: 20px;\r\n color: var(--alarm-foreground-critical); \r\n font-size: 12px;\r\n font-weight: bold;\r\n text-align: center;\r\n line-height: 20px;\r\n}\r\n\r\n.psc-alarmCriticalIcon::after,.psc-alarmTableCritical::after {\r\n counter-reset: alarm-content var(--alarm-priority-number, 4);\r\n content: counter(alarm-content);\r\n position: absolute;\r\n top: 0px;\r\n left: calc(100% - 26px);\r\n width: 20px;\r\n height: 20px;\r\n color: var(--alarm-foreground-critical); \r\n font-size: 12px;\r\n font-weight: bold;\r\n text-align: center;\r\n line-height: 20px;\r\n}\r\n\r\n/*High Alarm*/\r\n\r\n.psc-alarmHigh {\r\n position: relative;\r\n box-shadow: 0px 0px 0px 3px var(--alarm-background-high);\r\n}\r\n\r\n.psc-alarmTableHigh {\r\n position: relative;\r\n box-shadow: 0px 0px 0px 2px var(--alarm-background-high);\r\n}\r\n\r\n.psc-alarmHighIcon {\r\n position: relative;\r\n}\r\n\r\n.psc-alarmHighIcon::before,.psc-alarmHigh::before {\r\n content: "";\r\n position: absolute;\r\n top: -13px;/*0px;*/\r\n left: -13px;/*0px;*/\r\n width: 0;\r\n height: 0;\r\n border-left: 12px solid transparent;\r\n border-right: 12px solid transparent;\r\n border-bottom: 22px solid var(--alarm-background-high);\r\n}\r\n\r\n.psc-alarmHighIcon::before,.psc-alarmTableHigh::before {\r\n content: "";\r\n position: absolute;\r\n top: 0px;\r\n left: calc(100% - 27px);\r\n width: 0;\r\n height: 0;\r\n border-left: 12px solid transparent;\r\n border-right: 12px solid transparent;\r\n border-bottom: 22px solid var(--alarm-background-high);\r\n}\r\n\r\n.psc-alarmHighIcon::after,.psc-alarmHigh::after {\r\n counter-reset: alarm-content var(--alarm-priority-number, 3);\r\n content: counter(alarm-content);\r\n position: absolute;\r\n top: -10px;/*3px;*/\r\n left: -10px;/*2px;*/\r\n width: 20px;\r\n height: 20px;\r\n color: var(--alarm-foreground-high); \r\n font-size: 12px;\r\n font-weight: bold;\r\n text-align: center;\r\n line-height: 20px;\r\n}\r\n\r\n.psc-alarmHighIcon::after,.psc-alarmTableHigh::after {\r\n counter-reset: alarm-content var(--alarm-priority-number, 3);\r\n content: counter(alarm-content);\r\n position: absolute;\r\n top: 3px;\r\n left: calc(100% - 25px);\r\n width: 20px;\r\n height: 20px;\r\n color: var(--alarm-foreground-high); \r\n font-size: 12px;\r\n font-weight: bold;\r\n text-align: center;\r\n line-height: 20px;\r\n}\r\n\r\n/*Medium Alarm*/\r\n\r\n.psc-alarmMedium {\r\n position: relative;\r\n box-shadow: 0px 0px 0px 3px var(--alarm-background-medium);\r\n}\r\n\r\n.psc-alarmTableMedium {\r\n position: relative;\r\n box-shadow: 0px 0px 0px 2px var(--alarm-background-medium);\r\n}\r\n\r\n.psc-alarmMediumIcon {\r\n position: relative;\r\n}\r\n\r\n.psc-alarmMediumIcon::before,.psc-alarmMedium::before {\r\n content: "";\r\n position: absolute;\r\n top: -10px;/*0px;*/\r\n left: -13px;/*0px;*/\r\n width: 0;\r\n height: 0;\r\n border-left: 12px solid transparent;\r\n border-right: 12px solid transparent;\r\n border-bottom: 22px solid var(--alarm-background-medium);\r\n transform: rotate(180deg); \r\n}\r\n\r\n.psc-alarmMediumIcon::before,.psc-alarmTableMedium::before {\r\n content: "";\r\n position: absolute;\r\n top: 0px;\r\n left: calc(100% - 27px);\r\n width: 0;\r\n height: 0;\r\n border-left: 12px solid transparent;\r\n border-right: 12px solid transparent;\r\n border-bottom: 22px solid var(--alarm-background-medium);\r\n transform: rotate(180deg); \r\n}\r\n\r\n.psc-alarmMediumIcon::after,.psc-alarmMedium::after {\r\n counter-reset: alarm-content var(--alarm-priority-number, 2);\r\n content: counter(alarm-content);\r\n position: absolute;\r\n top: -13px;/*-2px;*/\r\n left: -10px;/*2px;*/\r\n width: 20px;\r\n height: 20px;\r\n color: var(--alarm-foreground-medium); \r\n font-size: 12px;\r\n font-weight: bold;\r\n text-align: center;\r\n line-height: 20px;\r\n}\r\n\r\n.psc-alarmMediumIcon::after,.psc-alarmTableMedium::after {\r\n counter-reset: alarm-content var(--alarm-priority-number, 2);\r\n content: counter(alarm-content);\r\n position: absolute;\r\n top: -3px;\r\n left: calc(100% - 25px);\r\n width: 20px;\r\n height: 20px;\r\n color: var(--alarm-foreground-medium); \r\n font-size: 12px;\r\n font-weight: bold;\r\n text-align: center;\r\n line-height: 20px;\r\n}\r\n\r\n/*Low Alarm*/\r\n\r\n.psc-alarmLow {\r\n position: relative;\r\n box-shadow: 0px 0px 0px 3px var(--alarm-background-low);\r\n}\r\n\r\n.psc-alarmTableLow {\r\n position: relative;\r\n box-shadow: 0px 0px 0px 2px var(--alarm-background-low);\r\n}\r\n\r\n.psc-alarmLowIcon {\r\n position: relative;\r\n}\r\n\r\n.psc-alarmLowIcon::before,.psc-alarmLow::before {\r\n content: "";\r\n position: absolute;\r\n top: -10px;/*4px;*/\r\n left: -10px;/*4px;*/\r\n width: 20px;\r\n height: 20px;\r\n background-color: var(--alarm-background-low); \r\n transform: rotate(45deg); \r\n}\r\n\r\n.psc-alarmLowIcon::before,.psc-alarmTableLow::before {\r\n content: "";\r\n position: absolute;\r\n top: 4px;\r\n left: calc(100% - 24px);\r\n width: 20px;\r\n height: 20px;\r\n background-color: var(--alarm-background-low); \r\n transform: rotate(45deg); \r\n}\r\n\r\n.psc-alarmLowIcon::after,.psc-alarmLow::after {\r\n counter-reset: alarm-content var(--alarm-priority-number, 1);\r\n content: counter(alarm-content);\r\n position: absolute;\r\n top: -10px;/*4px;*/\r\n left: -10px;/*4px;*/\r\n width: 20px;\r\n height: 20px;\r\n color: var(--alarm-foreground-low); \r\n font-size: 12px;\r\n font-weight: bold;\r\n text-align: center;\r\n}\r\n\r\n.psc-alarmLowIcon::after,.psc-alarmTableLow::after {\r\n counter-reset: alarm-content var(--alarm-priority-number, 1);\r\n content: counter(alarm-content);\r\n position: absolute;\r\n top: 4px;\r\n left: calc(100% - 24px);\r\n width: 20px;\r\n height: 20px;\r\n color: var(--alarm-foreground-low); \r\n font-size: 12px;\r\n font-weight: bold;\r\n text-align: center;\r\n}\r\n\r\n/*Diagnostic Alarm*/\r\n\r\n.psc-alarmDiagnostic {\r\n position: relative;\r\n box-shadow: 0px 0px 0px 2px var(--state-color-black);\r\n}\r\n\r\n.psc-alarmTableDiagnostic {\r\n position: relative;\r\n box-shadow: 0px 0px 0px 2px var(--alarm-background-diagnostic);\r\n}\r\n\r\n.psc-alarmDiagnosticIcon {\r\n position: relative;\r\n}\r\n\r\n.psc-alarmDiagnosticIcon::before,.psc-alarmDiagnostic::before {\r\n content: "";\r\n position: absolute;\r\n top: -10px;/*4px;*/\r\n left: -10px;/*4px;*/\r\n width: 20px;\r\n height: 20px;\r\n background-color: var(--alarm-background-diagnostic);\r\n border: 1px solid var(--state-color-black);\r\n}\r\n\r\n.psc-alarmDiagnosticIcon::before,.psc-alarmTableDiagnostic::before {\r\n content: "";\r\n position: absolute;\r\n top: 4px;\r\n left: calc(100% - 24px);\r\n width: 20px;\r\n height: 20px;\r\n background-color: var(--alarm-background-diagnostic);\r\n border: 1px solid var(--state-color-black);\r\n}\r\n\r\n.psc-alarmDiagnosticIcon::after,.psc-alarmDiagnostic::after {\r\n counter-reset: alarm-content var(--alarm-priority-number, 0);\r\n content: counter(alarm-content);\r\n position: absolute;\r\n top: -10px;/*4px;*/\r\n left: -10px;/*4px;*/\r\n width: 20px;\r\n height: 20px;\r\n color: var(--alarm-foreground-diagnostic); \r\n font-size: 12px;\r\n font-weight: bold;\r\n text-align: center;\r\n}\r\n\r\n.psc-alarmDiagnosticIcon::after,.psc-alarmTableDiagnostic::after {\r\n counter-reset: alarm-content var(--alarm-priority-number, 0);\r\n content: counter(alarm-content);\r\n position: absolute;\r\n top: 4px;\r\n left: calc(100% - 24px);\r\n width: 20px;\r\n height: 20px;\r\n color: var(--alarm-foreground-diagnostic); \r\n font-size: 12px;\r\n font-weight: bold;\r\n text-align: center;\r\n}\r\n\r\n/* WARNING ICON */\r\n.psc-warning {\r\n position: relative;\r\n}\r\n\r\n.psc-warningIcon {\r\n position: relative;\r\n}\r\n\r\n.psc-warningIcon::before,.psc-warning::before {\r\n content: "";\r\n position: absolute;\r\n top: calc(50%);\r\n left: calc(50% - 15px);\r\n width: 0;\r\n height: 0;\r\n border-left: 15px solid transparent;\r\n border-right: 15px solid transparent;\r\n border-bottom: 25px solid var(--state-color-yellow); \r\n}\r\n\r\n.psc-warningIcon::after,.psc-warning::after {\r\n content: \'!\';\r\n position: absolute;\r\n top: calc(50% + 5px);\r\n left: calc(50% - 10px);\r\n width: 20px;\r\n height: 20px;\r\n color: var(--state-color-black); \r\n font-size: 12px;\r\n font-weight: bolder;\r\n text-align: center;\r\n line-height: 20px;\r\n}\r\n\r\n/* OPERATOR ICON */\r\n.psc-operatorIcon {\r\n position: relative;\r\n}\r\n\r\n.psc-operatorIcon::before {\r\n content: "";\r\n position: absolute;\r\n top: calc(50% - 7.5px);\r\n left: calc(50% - 7.5px);\r\n width: 13px;\r\n height: 15px;\r\n background: var(--state-none); \r\n border: 1px solid var(--state-color-black);\r\n}\r\n\r\n.psc-operatorIcon::after {\r\n content: \'O\';\r\n position: absolute;\r\n top: calc(50% - 7.5px);\r\n left: calc(50% - 7.5px);\r\n width: 15px;\r\n height: 15px;\r\n color: var(--state-color-black); \r\n font-size: 12px;\r\n font-weight: bolder;\r\n text-align: center;\r\n line-height: 15px;\r\n}\r\n\r\n/* LOCKED ICON */\r\n.psc-lockedIcon {\r\n position: relative;\r\n}\r\n\r\n.psc-lockedIcon::before {\r\n content: "";\r\n position: absolute;\r\n top: calc(50% - 7.5px);\r\n left: calc(50% - 7.5px);\r\n width: 13px;\r\n height: 15px;\r\n background: var(--state-none); \r\n border: 1px solid var(--state-color-black);\r\n}\r\n\r\n.psc-lockedIcon::after {\r\n content: \'L\';\r\n position: absolute;\r\n top: calc(50% - 7.5px);\r\n left: calc(50% - 7.5px);\r\n width: 15px;\r\n height: 15px;\r\n color: var(--state-color-black); \r\n font-size: 12px;\r\n font-weight: bolder;\r\n text-align: center;\r\n line-height: 15px;\r\n}\r\n\r\n/* INTERLOCK ICON */\r\n.psc-interlock {\r\n position: relative;\r\n box-shadow: 0px 0px 0px 3px var(--state-interlock);\r\n}\r\n\r\n.psc-interlockIcon {\r\n position: relative;\r\n}\r\n\r\n.psc-interlockIcon::before,.psc-interlock::before {\r\n content: "";\r\n position: absolute;\r\n top: -10px;/*0px;*/\r\n left: calc(100% - 10px);/*calc(100% - 20px);*/\r\n width: 20px;\r\n height: 20px;\r\n background-color: var(--state-interlock);\r\n}\r\n\r\n.psc-interlockIcon::after,.psc-interlock::after {\r\n content: "I";\r\n position: absolute;\r\n top: -10px;/*0px;*/\r\n left: calc(100% - 10px);/*calc(100% - 20px);*/\r\n width: 20px;\r\n height: 20px;\r\n color: var(--state-color-black); \r\n font-size: 12px;\r\n font-weight: bold;\r\n text-align: center;\r\n line-height: 20px;\r\n}', u'index.css': u'@import "./variables.css";\r\n@import "../light/fonts.css";\r\n@import "../light/globals.css";\r\n@import "../light/app/index.css";\r\n@import "../light/common/index.css";\r\n@import "../light/designer/index.css";\r\n@import "../light/palette/index.css";\r\n@import "./high-performance-hmi.css";\r\n', u'variables.css': u'@import "../light/variables.css";\r\n\r\n:root {\r\n /* Neutrals */\r\n --neutral-10: #F2F4F8; /* cool-10 */\r\n --neutral-20: #DDE1E6; /* cool-20 */\r\n --neutral-30: #C1C7CD; /* cool-30 */\r\n --neutral-40: #A2A9B0; /* cool-40 */\r\n --neutral-50: #878D96; /* cool-50 */\r\n --neutral-60: #697077; /* cool-60 */\r\n --neutral-70: #4D5358; /* cool-70 */\r\n --neutral-80: #343A3F; /* cool-80 */\r\n --neutral-90: #21272A; /* cool-90 */\r\n --neutral-100: #121619; /* cool-100 */\r\n}\r\n'}

name = hp-hmi

enabled = True

collection = local

And no change still? Same errors on both sides?

Maybe work the problem differently - start with trying to create a theme directly on the local gateway, in a gateway scoped script (e.g. from a button in Perspective so you're running on the gateway). Then take a step up and move to a local message (Designer sending to your message handler) - and only then move to a remote server message handler?

When running the script on a local gateway with no message handler, I am getting the exact same exceptions. When trying to create a new resource with a different name but same config its the exception ValueError: Provide resource config via the config parameter.

When I try replacing the theme with the exact same config, I get the exception ValueError: java.lang.IllegalStateException: Resource not found: com.inductiveautomation.perspective/themes/hp-hmi which makes no sense to me because it does not throw that error when I do system.config.get

You should probably contact support at this point. They can take a live look at your system/full setup and help troubleshoot further.

Was there a solution to this issue? I have a similar issue and it might have been solved here.

How to upload a Custom Theme from scratch using the Gateway API? - Ignition - Inductive Automation Forum