Part 1: Building AEM Admin Consoles that will not break with new AEM releases


So, you built this new and shiny Admin Console in AEM Author to enable your authors to do something cool, like ACS Commons Generic List for example. And you’ve probably built said Admin Console UI using whatever is shipped with your AEM version: Coral UI2, Coral UI3 and/or jQuery and others.

This is part 1 of a 2 part series, you can find part 2 here 

You’ve probably used Granite UI Shell to build your console. Which gives you the AEM header which includes: navigation, profile, search, notifications and so on:

Default Aem Admin Console Shell

And you’ve probably used CoralUI 2/3, jQuery and other UI frameworks and libraries to build your app.

Fast forward a few months later

A few months later, a new Service Pack is released, or you need to migrate to a new AEM version. Lo and behold, your console is broken! So you spend hours fixing the UI and patching it up to work around whatever breaking changes adobe introduced. Or maybe you’ve used an AEM internal undocumented javascript API that has now changed.

Problems with this approach:

  1. You depend on JS/CSS libraries that are outside of your control and can change with AEM updates (major issue.)
  2. You cannot “just” update or fix broken things in said JS libraries.
  3. Adobe’s CSS/JS can “leak” into yours and break your UI.
  4. You end up writing a lot of work-arounds and nonsense code to get around several issues in Coral UI and other dependencies. Trust me, I had to do that a LOT.

This all means uncertainty. And uncertainty is a no-no if you plan on building a maintainable application.

I’m here to tell you, there is a better way: iFrames!

iFrames get a bad rep. But when used properly, iFrames can be awesome! heck, AEM uses iFrames for its page editor and in many other locations.
The idea is simple, build your full HTML application, on your own terms, with your own JS/CSS libraries, and then include that as an iframe in a shell page.

Here is the idea, visualized:

Aem Admin Console Visual

But why is that better?

A few things:

  1. You control every piece of your application HTML; your application being a full HTML document.
  2. You bring your own JS/CSS, use any framework or UI library you want, no strings and no dependencies on AEM!
  3. Adobe’s CSS/JS will never leak into your application; it being included in an iframe. So from a CSS/JS standpoint, your application is AEM agnostic.
  4. You get to keep the full AEM header functionality: search, profile, nav and so on.

All of this means that no matter what Adobe changes, your application will still work as expected, from a UI standpoint.
You get to keep everything, build your app however you want and most importantly: save time and money!

Let’s build an extremely simple example that illustrates this idea

You can also download this example from github and follow along.

I’ve already created the following structure for  /apps/my-console:

My Console Structure

Here is a JSON representation for that:

  "jcr:primaryType": "nt:unstructured",
  "jcr:title": "My Console",
  "consoleId": "my-console",
  "sling:resourceSuperType": "granite/ui/components/shell/page",
  "sling:resourceType": "/apps/my-console",
  "content": {
    "jcr:primaryType": "nt:unstructured",
    "sling:resourceType": "granite/ui/components/coral/foundation/container",
    "items": {
      "jcr:primaryType": "nt:unstructured",
      "content": {
        "jcr:primaryType": "nt:unstructured",
        "sling:resourceType": "/apps/my-console/components/iframe"
  "components": {
    "jcr:primaryType": "sling:Folder",
    "iframe": {
      "jcr:primaryType": "nt:unstructured",
      "iframe.html": {
        "jcr:primaryType": "nt:file"
    "page": {
      "jcr:primaryType": "nt:unstructured",
      "page.html": {
        "jcr:primaryType": "nt:file"
  "frame": {
    "jcr:primaryType": "nt:unstructured",
    "jcr:title": "My App",
    "sling:resourceSuperType": "/apps/my-console/components/page",
    "sling:resourceType": "/apps/my-console/frame"

Lets take a deeper look:

/apps/my-console is a simple Granite UI Shell page of type:granite/ui/components/shell/page
It only includes the component: /apps/my-console/components/iframe

The iframe component HTML looks like this:
As you can see, the iframe element points to the frame page: /apps/my-console/frame.htm
Also, the height is set to calc(100vh - 60px); that is, full browser height minus 60px (to account for the AEM header.)

<!--/* /apps/my-console/components/iframe/iframe.html */-->

<iframe src="/apps/my-console/frame.html" style="width:100vw; height:calc(100vh - 60px);"></iframe>

The frame node is a simple page of type:/apps/my-console/components/page

The page component looks like this:

<!--/* /apps/my-console/components/page.hml */-->

<!DOCTYPE html>
<html style="height:100%">
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>My App</title>
<body >
  <h1>Welcome to my app!</h1>

And the result is the following:

When navigating to the console page: http://localhost:4502/apps/my-console.html

My App

and when navigating to the frame page: http://localhost:4502/apps/my-console/frame.html

My App Frame


Now you can add clientlibs with your favorite JS/CSS include that in page.html and build UI to your hearts desire.

Part 2

If you want your Admin Console to match Adobe’s Design Specification (AKA Spectrum), you can include coral-spectrum JS and CSS. coral-spectrum is a web component implementation of the Spectrum Design Spec. In part 2 of this blog I show you how to include coral-spectrum.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Ahmed Musallam, Adobe Technical Lead

Ahmed is an Adobe Technical Lead and expert in the Adobe Experience Cloud.

More from this Author

Subscribe to the Weekly Blog Digest:

Sign Up
Follow Us