The symptom in the Designer is undramatic: your custom Perspective component exposes a datasource property and it renders as a plain text box. The view author types , the gateway has MesDb, and nothing renders. No red overlay, no fault banner — just an empty table and a stack trace buried in the gateway log. Then the project gets promoted from dev to prod, where the datasource is named something else entirely, and every instance of your component breaks at once.
You reached for an enum in componentName.props.json to stop that. It will not work, and the reason points at a bigger design decision you should settle first.
Check 1: Can props.json Enumerate Datasources? No — Stop Here
props.json is a JSON Schema document that ships inside your module JAR. It is parsed once, statically, to build the property tree and drive the Designer's property editor. An enum array in that schema is frozen at module build time.
The list of configured datasources is gateway runtime state. It changes when someone adds a connection, and it differs between your dev gateway, the customer's staging gateway, and production. There is no expression, no $ref, and no schema keyword that resolves against a live gateway. Anything that pretends to do this is fetching the list somewhere else and populating a widget — which is exactly the mechanism described below.
Do not waste an afternoon trying to generate props.json at build time from a datasource list either. It will be correct on exactly one gateway.
Check 2: Does the Component Need to Query at All?
Take this branch first, before you write any Java. Ask what the component actually consumes.
-
It consumes rows. Then take a
datasetproperty, or an array of objects (list of dictionaries) if the shape is irregular. Publish the required column names and types in your documentation. The Designer binds it. You are done — no datasource property exists, so there is nothing to enumerate. - It genuinely owns the query — a paging widget that must issue a new SQL statement per scroll event, for example — then you need a datasource selector and you continue to Check 3.
Components that run their own queries are the ones integrators hate. They hide the SQL, they cannot be re-pointed, they cannot be filtered, and they cannot be fed test data. The Sepasoft-style "drag it on and it fills itself" model looks fast in a demo and turns into a wall the first time a customer wants an extra column, a different date filter, or data from a tag historian instead of SQL.
The customizable version is the one that accepts data. Let the author bind props.data to a Named Query, a script transform, a tag, or a custom property with an expression on it. A Named Query binding already carries its own datasource selection, parameter typing, caching, and security — you inherit all of it for free and delete your datasource property entirely.
Ship expression functions alongside your scripting functions where the work is cheap and repetitive. Expression bindings run without the script interpreter overhead, and view authors will use them for the aggregation and formatting glue you cannot anticipate.
Check 3: If It Must Query, Use PropertySuggestionSourceRegistry
The supported mechanism for populating a property editor with live gateway values is PropertySuggestionSourceRegistry in com.inductiveautomation.perspective.designer.api. You register a suggestion source against your component's property path; the Designer calls it when the author edits that property and offers the returned strings as suggestions.
Two things about the mechanism that decide how you build it:
- It is Designer scope, not gateway scope. Register it from your Designer hook, using the Perspective designer interface obtained from the module context. The exact registration method signature is in the Ignition SDK javadocs for that package — read it there rather than guessing.
- The Designer does not know your datasource names. The suggestion source has to ask the gateway. Add a module RPC method or a gateway message handler that returns the configured datasource names, call it from the suggestion source, and cache the result for the Designer session with a manual refresh path. Do not call it synchronously on every keystroke.
Understand the limit: suggestions are hints in the editor, not validation. The property stays a free-form string. An author can still type garbage, and a project imported into a gateway missing that connection will still hold a stale name.
Check 4: Where Does the Bad Name Actually Fail?
Because the schema cannot validate the name, validation moves to runtime. Decide by symptom:
| Symptom | Cause | Next action |
|---|---|---|
| Property editor is a bare text field | No suggestion source registered for that property path | Register via PropertySuggestionSourceRegistry in the Designer hook |
| Suggestions appear in Designer, empty in a fresh Designer session | RPC to gateway failed or cache never populated | Check the Designer console and gateway RPC handler wiring |
| Component blank, gateway log shows a datasource lookup exception | Name does not exist on this gateway | Emit a component quality/error state instead of swallowing the exception |
| Works in dev, blank after project export to prod | Datasource name hard-coded per environment | Move the query to a Named Query binding, or make the name a project/session property |
| Author cannot add a column or change the filter | Query is owned by the component | Refactor to a dataset input property |
An empty component with a silent log entry is the failure mode that costs the most field time. Make the component render its own error state — a short message with the offending datasource name — so the person in front of the Designer sees the cause without opening the gateway logs.
Procedure: Convert to a Data-Accepting Component
- Define the input contract. Pick
datasetfor fixed columns, an object array for irregular records. Write down the required column names, types, and null behaviour; this is your public API and you will not be able to change it quietly later. - Add the property to
componentName.props.jsonwith that type and a small, valid default so the component renders something the moment it is dropped on a view. - Move the SQL out of the component and into a Named Query in the project. The Named Query carries the datasource, the parameters, and the caching.
- Delete the
datasourceproperty, the gateway query code, and the delegate that returned rows to the component. If a legacy install depends on it, keep the property, mark it deprecated in the docs, and have it populateprops.datainternally so both paths converge on the same render code. - Expose the remaining behaviour as component methods, events, and — where the operation is cheap and side-effect free — expression functions.
- If you kept a datasource property for a genuinely query-owning component, register the suggestion source now and add the runtime name check from Check 4.
Verify Before You Ship
- Drop the component on a blank view with no binding. It must render its default state, not throw.
- Bind
props.datato a Named Query and confirm rows appear. Then bind the same property to a static dataset literal and confirm identical rendering — that proves the component has no hidden dependency on the query path. - Export the project and import it into a second gateway whose datasource names differ. A properly refactored component renders as soon as the Named Query's datasource is valid there.
- If you kept the suggestion source: restart the Designer, open the property, and confirm the list matches the gateway's configured connections. Add a datasource on the gateway and confirm your refresh path picks it up.
- Watch the gateway log during a full view load. No query should originate from your component unless it is the query-owning variant.
Pitfalls That Repeat on Custom Perspective Modules
- Treating suggestions as validation. They are autocomplete. Keep the runtime check.
- Querying from component scope on every prop write. Property writes fire more often than you expect during editing. Debounce, or drive the query from an explicit refresh method.
- Returning raw JDBC types. Serialize to the dataset/JSON types Perspective already understands, or the property tree will not round-trip.
- Baking business SQL into the module. Every schema change becomes a module rebuild, a gateway restart, and a change-control ticket. Bindings change in the Designer.
- Registering the suggestion source in the gateway hook. Wrong scope; it belongs in the Designer hook and will silently do nothing.
FAQ
Why does an enum in props.json not list my gateway datasources?
Because props.json is a static JSON Schema parsed from your module JAR at load time, while the datasource list is gateway runtime configuration. The schema has no mechanism to resolve values against a live gateway, so any enum you write is frozen at build time and wrong on every other gateway.
Why does my datasource property show suggestions in one Designer session and not another?
The suggestion source runs in Designer scope and must fetch the names from the gateway over module RPC. If that call fails or the cache was never populated for the new session, the editor falls back to a plain text field with no list.
Why does my component work in dev and render blank in production?
The datasource name stored in the property does not exist on the production gateway. Move the query to a Named Query binding so the datasource is resolved by project configuration, or surface a visible error state naming the missing datasource instead of failing silently.
Why should a Perspective component accept a dataset instead of querying itself?
A dataset property lets the view author bind a Named Query, a script transform, a tag, or a custom property with an expression, and lets them add columns and filters you never anticipated. A self-querying component locks all of that inside the module and forces a rebuild for every change.
Why add expression functions when scripting functions already work?
Expression bindings evaluate without the script interpreter, so per-row formatting and aggregation glue costs far less on views with many instances. Provide both: scripting for complex logic, expressions for the cheap repetitive work authors will bind everywhere.
Stop and escalate when the registration API itself misbehaves — the suggestion source is registered in the Designer hook, the RPC returns names when called directly, and the property editor still shows nothing. That is module SDK behaviour, not your code; check the current javadocs for com.inductiveautomation.perspective.designer.api against your Ignition version, then open a ticket with Inductive Automation support quoting the version and your registration snippet.