Skip to main content

Render Conditions

Render Conditions control whether a Granite UI element (dialog field, tab, toolbar button, or any other UI widget) is shown or hidden in the AEM Touch UI. They are evaluated server-side before the UI is rendered, so hidden elements never reach the browser. They are not an authorization boundary: protect content and write operations with ACLs, servlets, Sling Models, and workflow checks as appropriate.

This is the primary mechanism for:

  • Showing/hiding dialog fields based on user group membership
  • Conditionally displaying tabs or fields based on content path or depth
  • Hiding author-only controls on Publish instances
  • Restricting UI elements by run mode, permissions, or custom business logic

How Render Conditions Work​

  1. Any Granite UI element can contain a granite:rendercondition child node
  2. The child node's sling:resourceType points to a servlet (or JSP) that evaluates the condition
  3. The servlet sets a SimpleRenderCondition(true/false) as a request attribute
  4. The Granite UI framework reads the attribute and renders or skips the element

The key API classes are:

ClassPurpose
com.adobe.granite.ui.components.rendercondition.RenderConditionInterface with a single check() method
com.adobe.granite.ui.components.rendercondition.SimpleRenderConditionConvenience implementation wrapping a boolean
com.adobe.granite.ui.components.ConfigReads configuration properties from the render condition's XML node

Built-in Render Conditions​

AEM provides several out-of-the-box render conditions you can use without writing Java code:

User permissions​

Show an element only if the current user has specific permissions on a resource:

<granite:rendercondition
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/renderconditions/privilege"
path="${requestPathInfo.suffix}"
privileges="[rep:write]"/>

Feature flags​

Show an element only when a Granite feature flag is enabled:

<granite:rendercondition
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/renderconditions/feature"
feature="com.example.authoring.feature"/>
PropertyTypeDescription
featureStringFeature flag name to check

Simple expression​

Evaluate a simple EL-like expression:

<granite:rendercondition
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/renderconditions/simple"
expression="${requestPathInfo.suffix != null}"/>

AND / OR combinations​

Combine multiple render conditions with boolean logic:

AND - all conditions must be true:

<granite:rendercondition
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/renderconditions/and">
<condition1
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/renderconditions/privilege"
path="${requestPathInfo.suffix}"
privileges="[rep:write]"/>
<condition2
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/renderconditions/simple"
expression="${requestPathInfo.suffix != null}"/>
</granite:rendercondition>

OR - at least one condition must be true:

<granite:rendercondition
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/renderconditions/or">
<condition1
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/renderconditions/feature"
feature="com.example.authoring.featureA"/>
<condition2
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/renderconditions/feature"
feature="com.example.authoring.featureB"/>
</granite:rendercondition>

Built-in render conditions reference​

Resource TypePurpose
.../renderconditions/privilegeCheck JCR permissions on a path
.../renderconditions/simpleEvaluate a simple expression
.../renderconditions/andBoolean AND of child conditions
.../renderconditions/orBoolean OR of child conditions
.../renderconditions/notBoolean NOT (invert a child condition)
.../renderconditions/featureCheck a Granite feature flag

All live under granite/ui/components/coral/foundation/renderconditions/. The simple condition uses Granite's expression resolver, so expressions can refer to values such as ${requestPathInfo.suffix}, ${param.item}, and Granite functions such as ${granite:relativeParent(requestPathInfo.suffix, 1)}.


Custom Render Conditions​

When the built-in conditions don't cover your requirements, write a custom render condition servlet.

The cleanest approach - a proper OSGi servlet with access to all services:

core/.../servlets/GroupRenderConditionServlet.java
package com.myproject.core.servlets;

import com.adobe.granite.ui.components.Config;
import com.adobe.granite.ui.components.rendercondition.RenderCondition;
import com.adobe.granite.ui.components.rendercondition.SimpleRenderCondition;
import org.apache.jackrabbit.api.JackrabbitSession;
import org.apache.jackrabbit.api.security.user.Authorizable;
import org.apache.jackrabbit.api.security.user.Group;
import org.apache.jackrabbit.api.security.user.UserManager;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.servlets.SlingSafeMethodsServlet;
import org.osgi.service.component.annotations.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.servlet.Servlet;
import javax.servlet.ServletException;
import java.util.Iterator;

@Component(
service = Servlet.class,
property = {
"sling.servlet.resourceTypes=" + GroupRenderConditionServlet.RESOURCE_TYPE,
"sling.servlet.methods=GET"
}
)
public class GroupRenderConditionServlet extends SlingSafeMethodsServlet {

private static final Logger LOG = LoggerFactory.getLogger(GroupRenderConditionServlet.class);

// Virtual resource type - no file on disk, only referenced in dialog XML
static final String RESOURCE_TYPE = "myproject/renderconditions/group-check";

@Override
protected void doGet(SlingHttpServletRequest request, SlingHttpServletResponse response)
throws ServletException {

boolean shouldRender = false;

// Read configuration from the dialog XML node
Resource conditionResource = request.getResource();
Config config = new Config(conditionResource);
String requiredGroup = config.get("group", String.class);

if (requiredGroup != null) {
shouldRender = isUserInGroup(request, requiredGroup);
}

request.setAttribute(
RenderCondition.class.getName(),
new SimpleRenderCondition(shouldRender)
);
}

private boolean isUserInGroup(SlingHttpServletRequest request, String groupName) {
try {
Session session = request.getResourceResolver().adaptTo(Session.class);
if (session instanceof JackrabbitSession) {
UserManager userManager = ((JackrabbitSession) session).getUserManager();
Authorizable currentUser = userManager.getAuthorizable(session.getUserID());

if (currentUser != null) {
Iterator<Group> groups = currentUser.memberOf();
while (groups.hasNext()) {
if (groups.next().getID().equals(groupName)) {
return true;
}
}
}
}
} catch (RepositoryException e) {
LOG.error("Error checking group membership", e);
}
return false;
}
}

Usage in dialog XML:

<myField
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/textfield"
fieldLabel="Admin-only field"
name="./adminNote">
<granite:rendercondition
jcr:primaryType="nt:unstructured"
sling:resourceType="myproject/renderconditions/group-check"
group="administrators"/>
</myField>

Approach 2: JSP-based (legacy)​

For simpler cases or when you need quick scripting. The JSP calls a Java class that implements RenderCondition:

/apps/myproject/renderconditions/path-depth/path-depth.jsp
<%@include file="/libs/granite/ui/global.jsp" %>
<%@page session="false"
import="com.adobe.granite.ui.components.rendercondition.RenderCondition,
com.adobe.granite.ui.components.rendercondition.SimpleRenderCondition,
com.adobe.granite.ui.components.Config"
%>
<%
Config cfg = new Config(resource);
int requiredDepth = cfg.get("minDepth", 0);

String contentPath = request.getRequestPathInfo().getSuffix();
boolean shouldRender = false;

if (contentPath != null) {
int depth = contentPath.split("/").length - 1;
shouldRender = depth >= requiredDepth;
}

request.setAttribute(RenderCondition.class.getName(), new SimpleRenderCondition(shouldRender));
%>

Usage:

<granite:rendercondition
jcr:primaryType="nt:unstructured"
sling:resourceType="myproject/renderconditions/path-depth"
minDepth="{Long}4"/>
tip

Prefer the servlet approach over JSP. Servlets are testable, support dependency injection, and follow modern AEM development patterns. JSPs are harder to debug and maintain.


Practical Examples​

Show a field only for content authors (not on templates)​

core/.../servlets/ContentPageRenderCondition.java
@Component(
service = Servlet.class,
property = {
"sling.servlet.resourceTypes=myproject/renderconditions/content-page-only",
"sling.servlet.methods=GET"
}
)
public class ContentPageRenderCondition extends SlingSafeMethodsServlet {

@Override
protected void doGet(SlingHttpServletRequest request, SlingHttpServletResponse response)
throws ServletException {

// The suffix contains the path of the content being edited
String contentPath = request.getRequestPathInfo().getSuffix();

// Show only on content pages, not on template structure pages
boolean isContentPage = contentPath != null
&& contentPath.startsWith("/content/")
&& !contentPath.contains("/conf/");

request.setAttribute(
RenderCondition.class.getName(),
new SimpleRenderCondition(isContentPage)
);
}
}

Show a tab only on specific resource types​

core/.../servlets/ResourceTypeRenderCondition.java
@Component(
service = Servlet.class,
property = {
"sling.servlet.resourceTypes=myproject/renderconditions/resource-type-check",
"sling.servlet.methods=GET"
}
)
public class ResourceTypeRenderCondition extends SlingSafeMethodsServlet {

@Override
protected void doGet(SlingHttpServletRequest request, SlingHttpServletResponse response)
throws ServletException {

Config config = new Config(request.getResource());
String[] allowedTypes = config.get("allowedResourceTypes", new String[0]);

String contentPath = request.getRequestPathInfo().getSuffix();
boolean shouldRender = false;

if (contentPath != null && allowedTypes.length > 0) {
Resource contentResource = request.getResourceResolver().getResource(contentPath);
if (contentResource != null) {
String resourceType = contentResource.getResourceType();
for (String allowed : allowedTypes) {
if (allowed.equals(resourceType)) {
shouldRender = true;
break;
}
}
}
}

request.setAttribute(
RenderCondition.class.getName(),
new SimpleRenderCondition(shouldRender)
);
}
}

Usage:

<advancedTab
jcr:primaryType="nt:unstructured"
jcr:title="Advanced"
sling:resourceType="granite/ui/components/coral/foundation/container">
<granite:rendercondition
jcr:primaryType="nt:unstructured"
sling:resourceType="myproject/renderconditions/resource-type-check"
allowedResourceTypes="[myproject/components/hero,myproject/components/teaser]"/>
<items jcr:primaryType="nt:unstructured">
<!-- fields shown only for hero and teaser components -->
</items>
</advancedTab>

Show a field only in specific WCM modes​

core/.../servlets/WcmModeRenderCondition.java
@Component(
service = Servlet.class,
property = {
"sling.servlet.resourceTypes=myproject/renderconditions/wcm-mode",
"sling.servlet.methods=GET"
}
)
public class WcmModeRenderCondition extends SlingSafeMethodsServlet {

@Override
protected void doGet(SlingHttpServletRequest request, SlingHttpServletResponse response)
throws ServletException {

Config config = new Config(request.getResource());
String[] allowedModes = config.get("modes", new String[]{"EDIT"});

WCMMode currentMode = WCMMode.fromRequest(request);
boolean shouldRender = false;

for (String mode : allowedModes) {
if (currentMode.name().equalsIgnoreCase(mode)) {
shouldRender = true;
break;
}
}

request.setAttribute(
RenderCondition.class.getName(),
new SimpleRenderCondition(shouldRender)
);
}
}

Show a field based on OSGi configuration​

Combine render conditions with OSGi configs for feature flags:

core/.../servlets/FeatureFlagRenderCondition.java
@Component(
service = Servlet.class,
property = {
"sling.servlet.resourceTypes=myproject/renderconditions/feature-flag",
"sling.servlet.methods=GET"
}
)
public class FeatureFlagRenderCondition extends SlingSafeMethodsServlet {

@Reference
private FeatureFlagService featureFlagService;

@Override
protected void doGet(SlingHttpServletRequest request, SlingHttpServletResponse response)
throws ServletException {

Config config = new Config(request.getResource());
String featureName = config.get("feature", String.class);

boolean shouldRender = featureName != null
&& featureFlagService.isEnabled(featureName);

request.setAttribute(
RenderCondition.class.getName(),
new SimpleRenderCondition(shouldRender)
);
}
}

Usage:

<experimentalField
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/textfield"
fieldLabel="Experimental Feature"
name="./experimentalValue">
<granite:rendercondition
jcr:primaryType="nt:unstructured"
sling:resourceType="myproject/renderconditions/feature-flag"
feature="enable-experimental-dialog"/>
</experimentalField>

Where Render Conditions Can Be Applied​

Render conditions work on any Granite UI element, not just form fields:

ElementUse case
Dialog fieldsShow/hide individual fields based on permissions
Dialog tabsHide entire tabs for non-admin users
Toolbar buttonsShow custom actions only for specific roles
Inline edit toolbarConditionally show inline editing options
Page properties tabsHide advanced tabs from content authors
Asset metadata fieldsShow DAM metadata fields by asset type
Column view itemsConditionally show column items

Example: Hiding an entire dialog tab​

_cq_dialog/.content.xml (excerpt)
<items jcr:primaryType="nt:unstructured">
<tabs jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/tabs">
<items jcr:primaryType="nt:unstructured">
<!-- Always visible -->
<general jcr:primaryType="nt:unstructured"
jcr:title="General"
sling:resourceType="granite/ui/components/coral/foundation/container">
<items jcr:primaryType="nt:unstructured">
<!-- ... general fields ... -->
</items>
</general>
<!-- Only visible to administrators -->
<advanced jcr:primaryType="nt:unstructured"
jcr:title="Advanced"
sling:resourceType="granite/ui/components/coral/foundation/container">
<granite:rendercondition
jcr:primaryType="nt:unstructured"
sling:resourceType="myproject/renderconditions/group-check"
group="administrators"/>
<items jcr:primaryType="nt:unstructured">
<!-- ... admin-only fields ... -->
</items>
</advanced>
</items>
</tabs>
</items>

Passing Configuration to Render Conditions​

Use the Config class to read properties from the render condition's XML node. Any property set on the granite:rendercondition node is available:

<granite:rendercondition
jcr:primaryType="nt:unstructured"
sling:resourceType="myproject/renderconditions/custom-check"
group="content-authors"
minDepth="{Long}3"
allowedPaths="[/content/myproject,/content/dam/myproject]"
enabled="{Boolean}true"/>
Reading configuration in the servlet
Config config = new Config(request.getResource());

String group = config.get("group", String.class); // "content-authors"
Long minDepth = config.get("minDepth", Long.class); // 3
String[] paths = config.get("allowedPaths", new String[0]); // ["/content/myproject", ...]
Boolean enabled = config.get("enabled", Boolean.class); // true

Type hints in XML:

  • {Long}3 - Long integer
  • {Boolean}true - Boolean
  • [value1,value2] - String array
  • Plain string values need no hint

Render Conditions vs data-sly-test​

MechanismWhen evaluatedScopeSecurity
Render ConditionServer-side, before dialog rendersGranite UI (dialogs, admin consoles)UI hiding only; enforce access separately
data-sly-testServer-side, during HTL renderingHTL templates (component rendering)UI/output hiding only; enforce access separately
sling:hideResource / sling:hideChildrenRequest-time, during Sling Resource Merger resolutionDialog inheritance and overlaysRemoves inherited UI nodes; not a permission check
Client-side JS (showhide)Browser, after dialog loadsCoral UI show/hide listenersNot secure - DOM elements exist but are hidden
warning

Never use client-side show/hide for security-sensitive fields. DOM elements hidden via JavaScript can be revealed with browser DevTools. If a field should not be visible to a user, use a server-side render condition.


Best Practices​

Use built-in conditions first​

Before writing a custom servlet, check if AEM's built-in render conditions cover your need (privilege, group, simple expression, and/or/not combinators).

Keep conditions fast​

Render conditions execute during dialog rendering. Slow conditions (e.g., external API calls) delay the dialog opening. Cache results or precompute values in OSGi services.

Use virtual resource types​

The sling:resourceType of a render condition servlet doesn't need a physical file. The value is a virtual identifier resolved by Sling's servlet registration. This keeps your /apps tree clean.

Test render conditions​

Test your render condition servlets with AEM Mocks:

core/.../servlets/GroupRenderConditionServletTest.java
@ExtendWith(AemContextExtension.class)
class GroupRenderConditionServletTest {

private final AemContext context = new AemContext();

@Test
void shouldRenderForAdministrators() throws Exception {
// Set up the render condition resource with config
context.create().resource("/apps/test/rendercondition",
"sling:resourceType", "myproject/renderconditions/group-check",
"group", "administrators");

context.currentResource("/apps/test/rendercondition");

GroupRenderConditionServlet servlet = new GroupRenderConditionServlet();
servlet.doGet(context.request(), context.response());

RenderCondition condition = (RenderCondition) context.request()
.getAttribute(RenderCondition.class.getName());

// Assert based on the mock user's group membership
assertNotNull(condition);
}
}

Document your render conditions​

Render conditions are invisible in the UI - if a field doesn't appear, authors may not know why. Add fieldDescription hints or documentation explaining which conditions control visibility.


Common Pitfalls​

PitfallSolution
Field disappears and nobody knows whyAdd documentation or fieldDescription explaining the render condition
Condition works in edit mode but not in template editorCheck requestPathInfo.suffix - template paths differ from content paths
Condition not evaluated (field always shows)Verify sling:resourceType matches the servlet's registered resource type exactly
Condition blocks entire tab when only one field should hidePlace the render condition on the field, not the tab container
Servlet has no access to OSGi servicesUse @Reference on the servlet class (servlets are OSGi components); don't put @Reference inside inner classes
Render condition evaluated multiple times per dialogNormal - Granite UI evaluates conditions for each element individually; keep conditions lightweight
Client-side showhide conflictsRender conditions and showhide listeners can conflict; pick one approach per field

See also​