Baja API

The Baja API is where Niagara stops being configuration and becomes software engineering. It is Tridium’s Java framework underneath every station – components, points, histories, alarms, and drivers are all Baja objects. Software Pile develops against it directly: this is the practice’s core competency, led by a Tridium-certified Niagara Full Stack Developer.

What Baja Development Unlocks

  • Custom components – typed objects with properties, actions, and topics that behave like native palette entries, slot sheets and all
  • Custom drivers – full driver stacks: network, device, and proxy-point tiers for equipment Niagara has never heard of
  • Services – station-level logic that runs continuously: schedulers, watchdogs, data processors, integration daemons
  • Program logic beyond kitControl – when wire sheets hit their limits, compiled Baja logic takes over cleanly

Engineering Standards

Modules are developed against your exact Niagara version line, respect station lifecycle (steady-state starts, clean stops, license checks), handle fault conditions without wedging the station, and ship signed with third-party-certificate module signing. We document slot semantics so the next engineer – yours or ours – can maintain what we built.

Typical Baja Engagements

A driver for a proprietary chiller protocol. A component set an OEM ships with its hardware. A service that reconciles station data against an external system of record every night. If it lives inside the station and the palette cannot do it, it is probably a Baja project. Describe yours – include the Niagara version and target hardware.

What Is the Baja API?

Baja is the Java API that the Niagara Framework is built on. It defines the component model — components, properties, actions, topics — along with the type system, ORD resolution, subscription mechanics, permissions and the module structure that everything in a station conforms to. Writing a Niagara module means writing against Baja.

Understanding it is what separates a module that behaves like a native part of Niagara from one that technically runs but fights the framework: components that do not appear correctly in Workbench, properties that do not persist, actions that ignore permissions, or logic that misbehaves when a station restarts.

What Do You Build with the Baja API?

  • Custom drivers for equipment with no supported Niagara driver.
  • Custom components that engineers place on a wiresheet like any stock component.
  • Server-side logic that must run continuously regardless of whether anyone has a browser open.
  • Integrations that push or pull data between the station and an external database, API or business system.
  • Workbench extensions — custom views, tools and wizards — that automate engineering work.
  • Custom alarm, history and reporting behaviour beyond what stock components provide.

How Is Baja Different from BajaScript?

Baja is server-side Java, running inside the station or Workbench. BajaScript is the browser-side JavaScript library that talks to a running station over the network. They model the same component space from opposite ends of the connection.

The dividing line in practice: anything that must happen whether or not a person is watching belongs in Baja. Anything that exists to present or interact belongs in BajaScript. Control logic implemented in the browser stops when the tab closes — a mistake that is easy to make and unpleasant to discover in production.

What Does Baja Development Actually Require?

Java, plus the framework-specific parts that no general Java experience covers: module structure and the build, correct component and property definitions, subscription and lifecycle handling, permission enforcement, threading rules inside a station, and module signing so the result will load on a modern station.

There is also a version dimension. A module is built against a target Niagara version, and it needs retesting — sometimes rework — when the station is upgraded. Any Baja development should be commissioned with that lifecycle understood and owned, not discovered at the next upgrade.

Related services: custom Niagara module and driver development · Workbench engineering tools.

What a Niagara Module Actually Contains

A Niagara module is not one jar. It is a set of jars named module-part.jar, and the part suffix is not cosmetic: it is a runtime profile that declares where the code may run and what it is allowed to depend on. A part can depend only on parts with an equal or more restricted profile, so a class placed in the wrong jar either fails to resolve at build time or produces a module that installs fine on a workstation and is simply not there on the controller.

module.xml carries the module name, vendor, vendor version and the dependency list, each dependency with a minimum vendor version. Those minimums are a real decision rather than boilerplate: set too high and the module refuses to install on stations that would have run it, set too low and it installs onto one missing an API it calls. module.palette is separate and easy to forget. It defines what Workbench shows when the palette is opened, and if it is missing or misnamed the module works normally while the palette looks empty.

  • module-rt.jar: station-side code, the part that runs on a controller as well as on a Supervisor. The most restricted profile, and the right default for anything that has to run unattended.
  • module-ux.jar: browser-facing code, HTML and JavaScript served to a client. Available from a station with no Workbench present.
  • module-wb.jar: Workbench views, tools and wizards. Present only where Workbench is installed, so nothing station-side may depend on it.
  • module-se.jar: code that needs a full Java SE runtime rather than the reduced one on a controller.
  • module-doc.jar: documentation built for bajadoc. Nothing executable ships in it.

Slots, Flags and What Breaks at the Next Module Version

Slots declared in source are frozen: compiled into the type, present on every instance, addressed by name. Slots added at runtime are dynamic, stored per instance and written into the saved station database along with the component. Which kind a property is decides what happens to an existing site when a new version of the module loads.

Rename or remove a frozen property and the values already saved under the old name no longer match a declared slot. There is no exception to catch. The component comes back up on its declared defaults, so a station restarts running the module's defaults instead of the values an engineer set two years ago. Adding frozen slots is safe, since the saved config simply has nothing for them; changing a slot's type is the same problem as removing it. When a name has to change, keep the old slot, hide it, and migrate its value once the component reaches steady state.

A property that does not persist at all is usually either flagged transient, which excludes it from the saved config by design, or is being mutated in place instead of through its setter, so nothing marks the component dirty and the station saves the old value over the new one.

Flags are part of a slot's contract too, and permission flags are the ones most often misread. An action's permission requirement is enforced against the context that invokes it. Server-side code invoking with no context is not a user, so it passes checks a Workbench operator would fail. Logic that genuinely has to be restricted needs to check deliberately rather than assume the flag covers every caller.

Diagnosing a Module That Is Not Behaving

Symptoms are misleading here because several unrelated failures look identical from a wiresheet. Work from the outside in: did the module load, did the type get created, is the value in front of you current, and is the station keeping up. Answering in that order is what stops an afternoon spent rewriting code that turns out to have a trust store problem.

The failure that gets misread most often is a value that is plausible but old. A proxy point updates while something is subscribed to it. With nothing subscribed there is nothing polling, and a view that someone closed leaves the last reading sitting in place. Server-side code that reads a point without subscribing or leasing it first gets the same stale number, and from the outside it is indistinguishable from a driver that quietly stopped working.

  • Application Director output, which names the module when a load, a start or a signature check fails.
  • The spy pages, reached with a spy: ORD, for engine cycle timing, subscription counts and thread state.
  • The log setup page under spy, which raises one module's logger level at runtime with no restart, instead of turning logging up everywhere and then reading the flood.
  • The device's poll rate and tuning policy alongside the point itself, which separates "this point is not being polled" from "this value genuinely is not changing".

Why a Module Fails to Load

A module that never loads fails before any of its own code runs. The loader reads the manifest, resolves every declared dependency against what is installed on that host, and verifies the module signature; a class inside the jar is initialized only after all of that passes. That is why a dependency or trust problem presents as a module that is simply absent, with no stack trace pointing anywhere near your logic, rather than as an exception at the point of use.

Signing is a trust question, not a syntax one. A module signed with a certificate that chains to a CA already in the platform's system trust store installs without comment. A self-signed certificate has to be approved into the user trust store on each host that will run the module, which is a per-controller, per-Supervisor step somebody has to perform, and perform again when a controller is replaced.

The other common cause is a platform difference a workstation build cannot see. Controllers run a reduced Java runtime, so desktop-only APIs such as AWT and Swing, and third-party libraries that pull them in transitively, compile cleanly against a workstation JDK and then throw NoClassDefFoundError on the controller. The same shape of failure comes from station code referencing a class that lives in a wb part: it resolves in Workbench and does not exist on the controller at all. Testing on the target hardware rather than only against a Supervisor is what catches both.

When a Module Is More Than the Job Needs

A Baja module is a build, a signing step and a version commitment for the life of the station. Several requests that arrive described as module work are already covered by something that ships.

If the requirement is a nightly push of history data into a database, the RDBMS drivers already do scheduled history export and import, with the mapping done in Workbench and no jar to maintain. If it is a few hundred lines of glue that will keep changing as the site changes, a Program object compiles Java inside the station and can be edited by whoever maintains the site, with no rebuild and no signing, where the license includes it. If the equipment speaks BACnet/IP or Modbus TCP and only the register map is unusual, the stock driver plus a point map is quicker to build and far easier to hand over than a driver stack.

What pushes the work back to Baja is specific: state that has to survive a restart and be visible on a slot sheet, behavior that other components bind to as typed slots, and failure paths that have to be handled in code rather than logged and forgotten. Program objects also give you no palette entry, and they are edited in the station, which is fine until the same logic is needed on twelve of them.

Frequently Asked Questions

Is the Baja API something an outside system can call?

No. There is no endpoint to call. The module is loaded by the framework and its objects live in the station's own component space. Reaching a station from another system means using an interface built for that purpose: oBIX, a BACnet or Modbus server exposure, or a servlet the module registers under the station's web service. Choosing which one is a separate decision from whether a module is needed at all, and it is often the only part of the work the outside system ever sees.

What has to be nailed down before a custom driver can be specified?

Protocol documentation at the level of a register or object map: function codes or object types, encodings, scaling, and what the device does with a write it does not like. Access to a device or a simulator matters as much as the document, because published maps and shipped firmware disagree often enough that a driver written from paper alone gets rewritten. Counts shape the design more than features do, so device count per network, point count per device, and how current each value actually has to be all belong in the specification; a point that must be right within a second and one read hourly do not belong on the same poll rate. List the firmware revisions already installed in the field as well, since one network is rarely all one revision.

Where does a module have to be installed?

In the modules directory of the target host's Niagara installation. For a remote controller that is done through the Software Manager in Workbench, which copies the jar to the host, and the station restarts before the new types exist, so this is not a hot swap and it needs a window. The engineering workstation needs the same module installed locally too, otherwise its views and palette entries are missing in Workbench even though the station itself is running the code correctly.

How does station-side code talk to an external API or database without stalling the station?

By keeping the blocking part off the engine thread. A slow HTTP call or database query made inside a property change callback holds up execution for every component in the station, which is why that work belongs in a service component with its own worker, or on a scheduled ticket that writes results back into slots when it returns. Timeouts have to be set explicitly, because many client libraries default to waiting indefinitely. On a controller, outbound TLS is validated against the host's trust store, so an endpoint using a private or internal CA needs that certificate imported on the host before anything connects.

Can an existing Niagara AX module be moved to Niagara 4?

It is a port, not a recompile. Component and driver logic usually survives with mechanical changes, since the component model, slots and ORDs are recognizably the same on both sides. AX-era user interface code generally does not, because the Workbench view layer and the browser side were both replaced. The useful thing an old module gives you is a specification already proven against real equipment, not a jar to rebuild.

What has to be decided about source and signing?

Who holds the source, and whose certificate signs the jar. Signing happens per build, so any later rebuild, including one done years afterward against a newer Niagara version, has to be signed again with a certificate the target hosts will accept. A module whose source cannot be reached is frozen at whatever Niagara version it was last built against, which turns a routine station upgrade into a replacement project.