2739 words
14 minutes
Typed Solution Model: JSForm.NAMES, JSValueList.NAMES, and the End of Magic Strings

Typed Solution Model tutorial hero image

This is a Servoy Tutorial on the typed solution model references that shipped in Servoy 2025.12. Magic strings have been the quiet tax on every Servoy codebase for as long as I can remember, and the 2025.12 release finally hands us a way out: typed references for forms, value lists, and component properties that the editor actually understands.

This article builds on a couple of earlier ones in the series. The Modern JavaScript tutorial (#17) is where I covered the 2025.09 parser shift that makes the editor smart enough to follow these typed references in the first place. The typed Query Builder tutorial (#19) is where the same generic-annotation pattern shows up on the data side. Reading those first helps, but it is not required. If you have ever passed a form name as a string and discovered six months later that you typed it three different ways across a single solution, this tutorial is going to be useful even on its own.

The Magic String Problem#

Let’s imagine for a moment that you are six months into a Servoy project. You have somewhere north of forty forms, a handful of value lists, and a navigation scope that opens forms by name. Every navigation call looks like this:

application.showForm('frm_customer_detail');

That string 'frm_customer_detail' is a magic string. The Servoy editor has no idea what it refers to. It is just a sequence of characters that the runtime hopes will match a form name when the call fires. If you typo it, you get a runtime error. If you rename the form, every reference scattered across your .js files keeps the old name and silently fails until somebody clicks the broken navigation. If you want to find every place in your solution that opens this form, you do a project-wide text search and pray you spelled the form name consistently the first time.

Are you really going to grep for 'frm_customer_detail' across two hundred .js files and trust the result? No, of course not. There is always one place where somebody concatenated the name from two variables, or one place where the spelling drifted by a single character, or one place where a dynamic dispatcher built the form name at runtime from a config record. Grep finds none of them.

The same problem applies to value list names passed to application.setValueListItems(), and to component property names passed to setJSONProperty() and getJSONProperty(), and to permission names passed to security.hasPermission(). Every one of those calls used to be a string literal. Every one of those strings was an unrecorded coupling between two pieces of your code, invisible to the editor and unprotected by the compiler. Make sense?

The Approach#

The 2025.12 release introduced typed namespaces that mirror the names in your solution model. Form names live under JSForm.NAMES. Value list names live under JSValueList.NAMES. Component property paths live under JSWebComponent.<package>.<component>.<property>. Permission names live under JSPermission (which actually shipped earlier, in 2025.06). Each of those namespaces autocompletes from your solution. Each one produces a compiler warning if you reach for something that does not exist. And as of Servoy 2026.03, the editor does F3 navigation and find-references on JSForm.NAMES.xxx so refactoring is finally a first-class operation rather than a grep-and-pray exercise.

The walkthrough below covers each typed surface in turn, with a before-and-after on each one so you can see the migration shape. Then we close with the migration story, which is the part most people will actually care about: this is a free upgrade. Existing string-literal code keeps working. The editor warnings just start showing up on the lines that were always wrong.

JSForm.NAMES: Form Names That The Editor Understands#

The most common magic-string offender is application.showForm(). Here is the before:

/**
* Open the customer detail form.
* @author Gary Dotzlaw
* @since 2026-05-03
* @public
*/
function openCustomerDetail() {
application.showForm('frm_customer_detail');
}

And here is the after, with the typed reference:

/**
* Open the customer detail form.
* @author Gary Dotzlaw
* @since 2026-05-03
* @public
*/
function openCustomerDetail() {
application.showForm(JSForm.NAMES.frm_customer_detail);
}

The runtime behaviour is identical. Both calls resolve to the same string at runtime, because that is what JSForm.NAMES.frm_customer_detail evaluates to. The difference is everything that happens before runtime. With the typed reference, the editor autocompletes every form in your solution as soon as you type JSForm.NAMES.. If you typo the form name, the editor underlines it with a compiler warning. If you rename the form via the IDE refactor, the typed reference updates with it. If you need to find every place in the solution that opens this form, you put the cursor on frm_customer_detail in the typed reference and use find-references. As of 2026.03, that find-references call returns every line in the codebase that mentions JSForm.NAMES.frm_customer_detail.

I want to be specific about the 2026.03 enhancement, because the release notes are scoped tightly. The F3 + find-references treatment is documented for JSForm.NAMES.xxx. The release-note text does not extend that promise to JSValueList.NAMES or JSWebComponent.<...>.<...> even though the IDE almost certainly picks them up via the same mechanism. If you depend on F3 navigation working on the value list or component paths, verify it in the IDE on your version before promising it in code review.

JSForm.INSTANCES: The Solution Model Shortcut#

There is a second namespace on JSForm that is easy to miss: JSForm.INSTANCES. Where JSForm.NAMES.xxx returns the form name as a string, JSForm.INSTANCES.xxx returns the actual JSForm object. The release notes describe it as a shortcut for solutionModel.getForm(JSForm.NAMES.myform), which is exactly what it is. Use NAMES when an API wants the form name (application.showForm, forms[name], persisted config). Use INSTANCES when you want the JSForm object itself, typically for solution-model inspection or modification.

/**
* Read the title of the customer detail form for a navigation breadcrumb.
* @author Gary Dotzlaw
* @since 2026-05-03
* @public
*
* @return {String} the title text of the customer detail form
*/
function getCustomerDetailTitle() {
/**@type {JSForm}*/
const jsForm = JSForm.INSTANCES.frm_customer_detail;
/**@type {String}*/
const sTitle = jsForm.titleText;
return sTitle;
}

Before 2025.12, you would have written solutionModel.getForm('frm_customer_detail').titleText and crossed your fingers that the form name was spelled right. The INSTANCES shortcut gives you the same result with one less function call and full type safety on the form name. The returned object is a real JSForm, so the editor knows about every property on it: titleText, dataSource, extendsForm, onShow, all of them.

A quick aside on the docs. As of this writing, the JSForm reference page on docs.servoy.com does not yet list NAMES and INSTANCES as documented properties, even though they shipped in 2025.12. The release notes are the authoritative source. The reference pages are likely to catch up in a future docs pass; until then, do not be alarmed if you click the link and see only the old constant list. The typed surface is real, it works, and it is in production use.

JSValueList.NAMES: Same Pattern, Value Lists Edition#

Value list names suffer from the same magic-string problem as form names, and the 2025.12 release applies the same fix. The typed namespace is JSValueList.NAMES:

/**
* Refresh the priority value list with the current org-specific options.
* @author Gary Dotzlaw
* @since 2026-05-03
* @public
*
* @param {Array<String>} aPriorityLabels the priority labels to populate
*/
function refreshPriorityValueList(aPriorityLabels) {
application.setValueListItems(
JSValueList.NAMES.vl_priority,
aPriorityLabels
);
}

The 2025.12 release notes describe the surface as code completion for every design-time value list. There is a small inconsistency in the release-note text worth knowing about: one bullet shows the namespace as JSValueList.myvaluelist (no NAMES segment), and another references JSValuelist.NAMES.xxx. Both shapes appear to coexist, but the canonical form per the related bug entry is JSValueList.NAMES.<valuelist>. Use that one. It is the form that mirrors JSForm.NAMES and the form most likely to be supported uniformly by future tooling.

Same doc-gap caveat applies as with JSForm: the JSValueList reference page on docs.servoy.com does not yet list NAMES as a documented property. The release note is the authoritative source until the reference pages catch up.

JSWebComponent: Typed Property Paths For Components#

Component property names have always been one of the worst magic-string surfaces in Servoy. When you call wc.setJSONProperty('mytext', 'Hello World!'), that 'mytext' is a string that has to match a property name defined in the component’s .spec file, in a package you may not have authored, and which the Servoy editor cannot validate against the component definition. It is the worst kind of coupling: invisible, fragile, and three layers removed from where the bug actually appears.

The 2025.12 release introduces typed property paths under JSWebComponent:

/**
* Update the dashboard greeting text and badge count.
* @author Gary Dotzlaw
* @since 2026-05-03
* @public
*
* @param {String} sGreeting the greeting text to display
* @param {Number} nUnreadCount the unread message count for the badge
*/
function updateDashboardGreeting(sGreeting, nUnreadCount) {
/**@type {RuntimeWebComponent}*/
const oGreetingComp = elements.dashboard_greeting;
oGreetingComp.setJSONProperty(
JSWebComponent.mypackage.greetingbox.mytext,
sGreeting
);
oGreetingComp.setJSONProperty(
JSWebComponent.mypackage.greetingbox.mynumber,
nUnreadCount
);
}

The shape is JSWebComponent.<packagename>.<componentname>.<propertyname>. Three layers, all autocompleted from the actual component spec files in your solution. The editor knows which packages are installed, which components live in each package, and which properties live on each component. If you reach for a property that does not exist on the component, the editor flags it. If you reach for a component that does not exist in the package, the editor flags it. The release note frames the change as “more strict typing so you know what can be set or get from a component,” which is a polite way of saying “we are tired of debugging typos in property names too.”

Handler references work the same way. If your component declares an onActionMethodID handler, the typed path is JSWebComponent.mypackage.greetingbox.onActionMethodID. As of this writing the JSWebComponent reference page on docs.servoy.com still uses string-literal examples for setJSONProperty() and getJSONProperty(), but the typed surface is in fact in the IDE: autocomplete walks JSWebComponent<package><component><property> end to end on Servoy 2025.12+. Verified in the script editor on 2026-05-04. The reference docs are mid-transition here; the editor is already there.

JSPermission: Typed Permissions Since 2025.06#

Permissions were the first solution-model object to get a typed enum, back in Servoy 2025.06. The release notes are blunt:

Permissions are now defined as enums (JSPermission.XXX), providing developers with immediate feedback through compiler warnings if invalid or non-existent permissions are referenced.

Translation: typo a permission name and the editor warns you at edit time, not at runtime when a security check fails to match and the guarded code runs anyway.

/**
* Show the admin tools menu only to administrators.
* @author Gary Dotzlaw
* @since 2026-05-03
* @public
*/
function maybeShowAdminMenu() {
try {
if (security.hasPermission(JSPermission.Administrators)) {
application.showForm(JSForm.NAMES.frm_admin_tools);
}
} catch (oError) {
application.output(
'maybeShowAdminMenu: ' + oError.message,
LOGGINGLEVEL.ERROR
);
plugins.dialogs.showErrorDialog(
'Error',
'Could not check permission: ' + oError.message
);
}
}

A note on the example: Administrators is the only JSPermission constant publicly documented as of this writing. The 2025.06 release note confirms that all permissions become typed enums, but the reference page is sparse on the full enum membership. In your own solution, the available constants are whatever permission names you have defined in the security UI. If you write JSPermission.<YourPermissionName> for a real permission you have configured, the editor will autocomplete it from the same source the security UI reads from. If you typo the name, the editor warns you. The names come from your security configuration, not from this article.

The legacy security.hasPermission('Administrators') still works in 2025.06 and later. The string form is not deprecated, just superseded for new code. New tutorials should use the typed enum throughout, since the compile-time warnings are exactly the kind of guard you want around security-sensitive branches.

A Realistic Scenario: A Typed Navigation Scope#

Let’s pull all of this together with a small but realistic example. A navigation scope that opens forms, sets value lists, and gates an admin form behind a permission check, all without a single string literal:

/**
* Navigate to the customer detail form and refresh the priority value list.
* @author Gary Dotzlaw
* @since 2026-05-03
* @public
*
* @param {Array<String>} aPriorityLabels the org-specific priority options
*/
function navigateToCustomerDetail(aPriorityLabels) {
try {
application.setValueListItems(
JSValueList.NAMES.vl_priority,
aPriorityLabels
);
application.showForm(JSForm.NAMES.frm_customer_detail);
} catch (oError) {
application.output(
'navigateToCustomerDetail: ' + oError.message,
LOGGINGLEVEL.ERROR
);
plugins.dialogs.showErrorDialog(
'Navigation Error',
'Could not open customer detail: ' + oError.message
);
}
}
/**
* Navigate to the admin tools form, but only for administrators.
* @author Gary Dotzlaw
* @since 2026-05-03
* @public
*/
function navigateToAdminTools() {
try {
if (!security.hasPermission(JSPermission.Administrators)) {
plugins.dialogs.showWarningDialog(
'Access Denied',
'Administrator permission required.'
);
return;
}
application.showForm(JSForm.NAMES.frm_admin_tools);
} catch (oError) {
application.output(
'navigateToAdminTools: ' + oError.message,
LOGGINGLEVEL.ERROR
);
plugins.dialogs.showErrorDialog(
'Navigation Error',
'Could not open admin tools: ' + oError.message
);
}
}

Three string-literal couplings eliminated in twenty lines of code: two form names, one value list name, one permission name. Every one of those references is now visible to the editor. Every one of them autocompletes from the actual solution. Every one of them produces a compiler warning if it stops being valid. And on Servoy 2026.03+, the form name reference participates in find-references, so the next time somebody renames frm_customer_detail they will see this navigation function in the rename refactor preview before they hit OK.

Migration: It Is Free#

Here is the part most readers will actually want to know. None of this requires a migration. Existing string-literal code keeps working. application.showForm('frm_customer_detail') continues to do what it has always done. security.hasPermission('Administrators') continues to work. The typed namespaces are additive, not breaking.

What does change after upgrading to 2025.12 (or 2026.03 for the F3 + find-references support) is the editor experience on new code. The warnings appear, the autocomplete works, the typed paths show up in the reference search. You get the benefit on the first new function you write after the upgrade. You get the benefit on every function you touch during your next maintenance pass. You do not have to schedule a migration sprint or write a codemod. You just stop reaching for string literals.

If you do want to run a one-time cleanup pass on your existing code, the pragmatic approach is:

  • Use find-and-replace on the most-called form names. Start with the navigation scope and the menu handlers, since those are the highest-leverage references.
  • Replace application.setValueListItems('vl_xxx', ...) calls with application.setValueListItems(JSValueList.NAMES.vl_xxx, ...). The compiler warnings will surface every misspelling in your existing list configuration.
  • Replace security.hasPermission('xxx') calls with security.hasPermission(JSPermission.xxx). Same compiler-warning effect on permission names.
  • Leave the component property paths alone unless you are already in that file for another reason. The migration is mechanical but high-volume, and the per-file payoff is smaller than the form and value list cases.

Make sense? You do not have to do any of this on day one. The free editor warnings will tell you, file by file, where the typos are. You fix them as you encounter them.

House Style And Why It Matters For AI#

The dotzlaw.com coding conventions now require typed references over string literals for forms, value lists, components, and permissions in any code targeting Servoy 2025.06+ (for permissions) or 2025.12+ (for the rest). The reasoning is straightforward: the typed references are autocompleted, refactor-safe, and warning-checked. The string literals are none of those things.

There is also an angle that matters for anybody using AI agents to generate Servoy code. When you teach an AI agent the house style, the typed references are exactly the kind of pattern the agent will pick up on the first round. The agent does not have to remember which form names exist in your solution; it just emits JSForm.NAMES.<whatever> and the editor takes care of validation. If the agent guesses wrong, the editor flags it and the next round of agent feedback corrects it. The string-literal pattern, by contrast, gives the agent no signal at all. It writes whatever the prompt suggested, the editor stays silent, and the bug shows up at runtime in a code review you have to do by hand. Typed references close the feedback loop with the editor, which closes the feedback loop with the agent.

Closing#

That concludes this Servoy tutorial on the typed solution model references that landed in 2025.12 (with JSPermission going back to 2025.06, and the F3 + find-references enhancement on JSForm.NAMES.xxx arriving in 2026.03). It is one of those upgrades that does not look dramatic in a release note bullet, but compounds quietly across every navigation scope, every menu handler, and every dynamic dispatcher you have ever written. Magic strings are the kind of bug that does not announce itself until Monday morning when somebody clicks the broken nav, and this is the upgrade that makes most of them go away. I hope you enjoyed it, and I look forward to bringing you more Servoy tutorials in the future.

Typed Solution Model: JSForm.NAMES, JSValueList.NAMES, and the End of Magic Strings
https://dotzlaw.com/insights/servoy-tutorial-20-typed-solution-model/
Author
Gary Dotzlaw
Published at
2026-06-05
License
CC BY-NC-SA 4.0

Building production AI, or modernizing a legacy system?

That is the kind of work we do at Dotzlaw Consulting. Book a free 20-minute intro call and tell us what you are trying to build, or what is slowing you down.

← Back to Insights