Skip to main content

Command Palette

Search for a command to run...

AEM Project Structure Explained

Updated
4 min readView as Markdown
M
AEM Technical Lead experienced with building and architecting solutions on AEM Sites and Assets. Sharing practical tutorials, real-world implementation lessons, and best practices to help fellow developers and leads navigate AEM.

Series: AEM Foundations — Part 5

When you generated a project from Adobe's Maven archetype earlier in this series, it created several modules with cryptic-looking names — core, ui.apps, ui.content, ui.config, and more. This post breaks down what each module is for and why AEM projects are structured this way.

Why AEM Projects Are Multi-Module

Unlike a typical web app where everything might live in one deployable unit, AEM separates code, content, and configuration into distinct Maven modules. This separation exists because these three things have very different lifecycles:

  • Code changes with every feature or bug fix

  • Content (sample pages, component defaults) is set up once and rarely redeployed

  • Configuration (OSGi settings) often differs between environments (dev, stage, prod)

Keeping them separate means you can deploy code changes without accidentally overwriting content, and manage environment-specific configuration independently.

The Core Modules

core

This is where your Java code lives — Sling Models, Servlets, OSGi services, schedulers, and any custom business logic. If you're writing Java for AEM, this is almost always where it goes. It compiles into an OSGi bundle that gets deployed into AEM's runtime.

ui.apps

This holds everything that goes under /apps in the JCR repository:

  • Component definitions (dialogs, HTL scripts, .content.xml files)

  • Client libraries (CSS/JS, bundled as clientlibs)

  • Templates and template policies

  • Any other application-level structure

Recall from the previous post in this series: /apps is where you place your customizations, which Sling looks up before falling back to Adobe's defaults under /libs. This module is effectively "your app's definition" in the repository.

ui.content

This contains sample or default content — example pages, folder structures under /content, and sometimes reference content used for demos or initial setup. In many real projects, this module is either minimal or excluded from production deployments entirely, since live content is authored directly in the Author environment rather than deployed via code.

ui.config

This holds OSGi configuration — settings for things like PDF processing, workflow launchers, or any configurable service, often split by run mode (author vs. publish, dev vs. prod) so the same codebase behaves correctly across environments.

ui.frontend (in newer archetype versions)

Newer AEM projects often include a dedicated frontend module using modern tooling (webpack, npm) to build client-side assets, which then get packaged into ui.apps clientlibs during the build. This lets front-end developers work with familiar JS/CSS tooling rather than hand-writing clientlib folder structures.

it.tests (optional)

Some projects include an integration test module here, used for automated tests that run against a deployed AEM instance as part of CI/CD pipelines.

all

This is a "wrapper" package that bundles core, ui.apps, ui.content, and ui.config together into a single deployable content package. When you ran mvn clean install -PautoInstallSinglePackage in the previous post, this is typically the package that actually got installed onto your local instance.

A Simplified Visual

my-project/
├── core/            → Java code (Sling Models, Servlets, OSGi services)
├── ui.apps/         → /apps content: components, clientlibs, templates
├── ui.content/      → /content sample or reference content
├── ui.config/       → OSGi configurations per run mode
├── ui.frontend/     → Modern frontend build tooling (if present)
├── it.tests/        → Integration tests (optional)
└── all/             → Combined deployable package

Why This Structure Pays Off Long-Term

This separation might feel like overhead on a small project, but it becomes essential as a project grows:

  • Multiple developers can work on core (Java logic) and ui.apps (components/front-end) simultaneously with minimal conflict

  • Environment-specific behavior lives cleanly in ui.config, instead of scattered if (environment == "prod") checks in code

  • CI/CD pipelines can build and deploy the all package as a single unit, while still keeping the underlying modules independently testable

Series Wrap-Up

That completes the AEM Foundations series. At this point you should understand:

  • What AEM actually is, and how Sites, Assets, and Forms differ

  • The Author/Publish/Dispatcher architecture and how requests flow through it

  • How to set up a local AEM SDK environment

  • How Sling resolves resources and processes requests

  • How a real AEM project is organized on disk

From here, the AEM Core Development series picks up with hands-on component building — starting with your first HTL component and Sling Model.