Using AEM's JavaScript Use-API for Controllers
A JavaScript controller changes where a Sightly component obtains its data. Instead of asking a browser script to find elements, update markup and retain state after load, a Use-API module runs while AEM renders the component. It reads approved request or resource information and returns a small model to the template. That boundary makes content logic easier to test and gives Java developers a consistent extension point.
CIRCUIT suited this topic because its audience crossed Java, front-end, architecture and systems engineering. Controllers sit between those disciplines: component authors define the model, template developers control presentation, and platform engineers protect the runtime. A useful design therefore depends as much on contracts, caching and observability as on JavaScript syntax.
The strongest implementations keep the controller narrow. They transform component data, apply explicit rules and call approved services when necessary; they do not become a browser application, an undeclared database layer or a container for secrets. This discipline matters because the same content component may run in author, preview and published environments, under concurrent requests and with different authorisation rules.
Australian teams add another practical dimension. Components may serve government, banking, health, retail and mining audiences across Sydney, Melbourne, Brisbane and regional centres, often under strict privacy and accessibility expectations. The goal is selective placement: keep each piece of logic where it is visible, testable and safest.
Why controller boundaries matter
In the older HTL pattern, component JavaScript was often shipped through a client library and executed in the browser. That remains suitable for genuine interaction, but it is a weak home for content lookup, presentation decisions or business rules. A controller executes within AEM’s rendering lifecycle, so its result becomes part of the server-rendered contract.
The practical split is straightforward:
| Concern | Typical imperative component script | JavaScript Use-API controller |
|---|---|---|
| Execution | Browser after load | AEM render lifecycle |
| Output | DOM mutations and client state | Explicit values consumed by HTL |
| Coupling | Selectors and event flow | Resource binding and returned model |
| Testing | Browser harness and DOM assertions | Model tests and component tests |
| Failure surface | Console, network and timing failures | Render errors, logs and cache behaviour |
Use imperative code for events that depend on the browser: menu state, validation feedback and local interaction. Use a controller when a template needs a value that must be ready in the first response. Keeping these responsibilities separate reduces hydration races, improves cacheability and makes failures easier to trace.
Designing the Use-API layer
A controller is stored in a component client-library folder for discovery, but it is evaluated by AEM rather than sent to the browser as page logic. A typical module wraps its work in use(function () { ... }) and returns only the values needed by the template. Globals such as resource, request, properties and sling provide approved access, while exposed Java bindings depend on the AEM version and configuration.
Keep transformation close to the component, but keep integration behind a clear boundary. Read the current resource and request parameters, normalise them, and return display-ready values. If an external API is required, use an allow-listed operation, store credentials outside source, apply strict timeouts and define a safe fallback. Never assume access to Node.js packages or unrestricted Java classes.
A compact template and controller might look like this:
<div data-sly-use.card="${'card.js'}">
<h2>${heading}</h2>
<p>${summary}</p>
</div>
use(function () {
var values = properties;
return {
heading: values.get('heading', 'Latest update'),
summary: values.get('summary', '')
};
});
The file name is bound to the element with data-sly-use, and HTL reads only heading and summary. Returning a narrow object limits accidental exposure and makes the model straightforward to unit test. It also leaves genuine client behaviour in a separate, purpose-built component.
A controller design checklist
Controller code can be short while its contract still spans content, security and operations. Implementation and verification should therefore be reviewed as separate concerns.
Implementation choices should remain deliberately small:
- Return an explicit, minimal model instead of whole resource or request objects.
- Read component values through documented APIs and normalise them once.
- Keep credentials in AEM service configuration, never in controller source.
- Treat browser globals, mutable modules and shared state as unavailable.
Verification should cover the publishing path:
- Test normal, empty and malformed values with representative content.
- Exercise author, preview and published modes with caching on and off.
- Review render errors and timeouts whenever an external service participates.
- Confirm that paths, parameters and properties cannot expose unauthorised data.
These reviews connect code quality with operational quality. The WCSQ community guidance can offer an external perspective on standards, while ownership of tests, logs and publishing stays with the delivery team.
Data access, resilience and testing
Controllers should not turn AEM into a general-purpose database client. Where requirements involve variable document shapes, the CIRCUIT material on AEM and MongoDB offers a useful architectural counterpoint: MongoDB can hold documents outside the repository while AEM retains responsibility for structured content, templates and delivery.
That separation keeps rendering deterministic. Resolve external content through a service with a narrow contract, return only fields required by the component, and decide deliberately whether stale content is preferable to an empty response. Cache keys must include every input that changes the output, while logs should record correlation identifiers without exposing personal data.
Failure handling is part of the model, not an afterthought. Set connection and response timeouts, bound retries and avoid long synchronous work on publish instances. Controller logic must also tolerate AEM convergence: the same request may run again, so returned values should be stable and updates should remain idempotent wherever possible.
Australian deployment realities
Australian audiences face sharp morning peaks in Sydney and Melbourne, while Brisbane and Perth customers often operate far from central headquarters. Publish layers and a properly configured CDN can absorb read bursts, but cache invalidation needs clear ownership. People commonly check shopping, banking and news apps while commuting, so fast HTML, compressed images and resilient interactions matter more than a benchmark collected on a quiet office network.
Privacy is a design constraint. For covered organisations, the Privacy Act 1988 and Australian Privacy Principles govern collection, use, disclosure and security; commercial messaging may also engage the Spam Act 2003. A controller should receive only necessary parameters, avoid logging identifiers and make consent-dependent content easy to change without editing unrelated components.
The local market includes government, health, finance, retail and mining, each with different risk profiles. Public-facing services must meet Australian digital service and accessibility obligations, commonly targeting WCAG 2.2 AA, while regulated sectors may impose additional audit and retention needs. Procurement contracts priced in Australian dollars also make cloud, support and egress assumptions worth reviewing before launch.
The CIRCUIT conference archive preserves the 2015 and 2016 agendas, speakers and session recordings from the period when this pattern was emerging. It remains valuable context for teams comparing a browser-dependent script with a server-confined model, especially where managed cloud deployments restrict custom publish-side bundles and favour portable component code.
Put the pattern into practice
The pattern remains relevant because it gives AEM components an explicit, reviewable boundary. It works well for authored content, personalised modules and integrations with bounded operations, while leaving animation, widgets and other browser-dependent behaviour in client code. Managed AEM environments also benefit when routine component logic does not require a custom bundle.
Beyond the original meeting, technical programmes such as the CIEI 2024 programme provide contemporary context for continuing that exchange. Adoption works best as an incremental comparison: record the current markup and response time, move one rule into a controller, test each AEM mode, then inspect the rendered output, logs and cache behaviour before expanding.
Pick one low-risk, non-critical component this sprint, implement its model with a peer review, and measure the result against the existing implementation. Publish the findings inside the team, standardise the pattern after the evidence is clear, and carry the next component through the same review.