AEM Project Archetype and Repository Structure for AEMaaCS

Most AEM as a Cloud Service (AEMaaCS) projects start with the same question: where does this file go? The answer determines whether your code deploys to the author and publish tiers, whether it survives an upgrade, and whether Cloud Manager accepts the build. Get it wrong and you spend a day debugging a package that installed into the wrong tree or was silently skipped.

In this lesson you generate a project with the official AEM project archetype, walk through every module it creates, and learn which package owns which part of the repository. By the end you will know why /apps is immutable, why configuration belongs in ui.config and not in ui.apps, and how the all package ties the deployment together.

Table of contents

Why this matters

AEMaaCS is a managed service. Adobe runs the author and publish tiers, and you do not get shell access to the repository the way you did on AEM 6.5. Everything that changes the repository has to arrive as a content package built by your Maven build, and Cloud Manager deploys those packages through its pipelines. The project structure is the contract between your code and that deployment. Each Maven module produces a package with a fixed target path and a fixed package type, and the platform enforces the rules that come with them.

Two rules do most of the work. First, /apps and /libs are immutable on AEMaaCS, so code that writes there at runtime fails. Second, /content, /conf, /var and /etc are mutable, so content authors can change them. Your packages have to respect that split.

Who should read this

This lesson is for developers starting a new AEMaaCS project, and for anyone inheriting a project and trying to map its modules to the deployment. If you have built AEM 6.5 projects with the older multi-module archetype, the module names will look familiar, but several packages now have different roles and the all package works differently.

Prerequisites

  • A JDK and Maven that match the requirements in the generated root pom.xml. Check the enforce-maven profile in the root POM before you install anything.
  • Access to an AEMaaCS program and environment in Cloud Manager, if you want to deploy the result. The build itself runs without one.
  • Lesson 04, Set Up AEMaaCS Local Development, so the AEM SDK, Maven and Java are already installed.

What the archetype generates

The AEM project archetype is published as com.adobe.aem:aem-project-archetype. At the time of writing, the latest version is 58, and that is the version used in this lesson. The archetype produces a root project with one parent POM and a set of child modules. The modules you will see most often are shown below.

ModuleMaven artifact roleWhat it containsDeploys to
allContainer packageEmbeds the other packages. This is the only package you usually deploy by hand.Cloud Manager deploys it. It installs its embedded packages from /apps/mysite-packages.
coreOSGi bundleJava code: Sling Models, services, servlets, schedulers, filters and tests.Embedded in all at /apps/mysite-packages/application/install
ui.appsApplication packageImmutable code: HTL components, dialogs, client libraries, i18n./apps/mysite
ui.apps.structureRepository structure packageDeclares the root paths that the code packages are allowed to write to. The build uses it to validate ui.apps.Nothing. It is used at build time and is not embedded in all.
ui.configContainer package (package type container)OSGi configurations per run mode and repoinit scripts./apps/mysite/osgiconfig
ui.contentContent packageSample and site content, /conf settings, DAM folders, experience fragments./content, /conf
dispatcherDispatcher configurationDispatcher rules for the Cloud Service, including the vhost and filter files.Dispatcher, not AEM

The archetype also adds front-end and test modules, such as ui.frontend, ui.tests and it.tests, depending on the options you choose. The all package also embeds the AEM Core Components under /apps/mysite-vendor-packages. Check the root pom.xml after generation to see which modules you have.

Mutable and immutable repository paths

Before you write any file, know which paths the runtime can change. On AEMaaCS, /apps and /libs are immutable. Code packages write to /apps during deployment, and the platform blocks runtime writes there. Content, configuration instances and the other mutable trees can change at runtime through authors, workflows and services.

Repository pathMutable?Who writes itWhere your code puts it
/appsImmutableDeployment onlyui.apps, ui.config
/libsImmutableAdobeNothing. Extend components, do not change them.
/contentMutableAuthors, servicesui.content (sample pages, experience fragments, DAM)
/confMutableAuthors, servicesui.content (site settings), ui.config (repoinit)
/varMutableThe runtimeNothing
/oak:indexMutable, but managed by the deploymentCloud Manager deploymentIndex definitions, deployed as code in a code package such as ui.apps

The practical consequence is simple. If a value is part of the application (a component, a service configuration, a repoinit script), it belongs in a code or configuration package. If a value is content or site settings that an author should edit, it belongs in ui.content.

Hands-on: generate and build the project

In this section you generate a project named mysite with the group ID com.mysite. The archetype derives the Java package com.mysite.core from the group ID, so the names line up across the project.

Step 1: Generate the project

Run the archetype with Maven. The property names below are the archetype parameters. Set the values for your own project.

mvn -B org.apache.maven.plugins:maven-archetype-plugin:3.3.1:generate \
  -DarchetypeGroupId=com.adobe.aem \
  -DarchetypeArtifactId=aem-project-archetype \
  -DarchetypeVersion=58 \
  -DappId=mysite \
  -DgroupId=com.mysite \
  -DartifactId=mysite \
  -Dversion=1.0.0-SNAPSHOT \
  -DappTitle="MySite" \
  -DaemVersion=cloud \
  -DincludeDispatcherConfig=y

Maven creates a mysite folder. The appId sets the repository paths, such as /apps/mysite and /content/mysite. The groupId sets the Java package, and the aemVersion value cloud selects the AEMaaCS build.

Terminal running maven-archetype-plugin generate with aem-project-archetype 58 and aemVersion=cloud, ending with BUILD SUCCESS and the list of generated module folders
Generate the project with archetype 58. The new mysite folder has one folder per module plus the root pom.xml.

Step 2: Build the project

Change into the project folder and run a full build. The first build downloads a lot of dependencies, and the front-end module may run npm, so allow a few minutes.

cd mysite
mvn clean install

Check the output for the reactor summary. Each module should show SUCCESS. The packages are written to the target folder of each module, for example ui.apps/target/mysite.ui.apps-1.0.0-SNAPSHOT.zip. Because the names come from the artifact ID, your file names will be different if you changed artifactId.

Maven reactor summary for MySite with every module SUCCESS, core first and All near the end, plus the generated zip files in target
mvn clean install builds every module. The all package builds after the packages it embeds.

Step 3: Read the module layout

Open the generated tree and confirm the module folders. You should see the following layout. Pay attention to where the code and content sources live, because you will edit them in the next sections.

mysite/
├── pom.xml                      (root reactor POM, profiles)
├── all/                         (container package)
├── core/                        (OSGi bundle, Java)
│   └── src/main/java/com/mysite/core/
├── ui.apps/                     (immutable code and components)
│   └── src/main/content/jcr_root/apps/mysite/
├── ui.apps.structure/           (repository structure, build-time only)
├── ui.config/                   (OSGi configs and repoinit)
│   └── src/main/content/jcr_root/apps/mysite/osgiconfig/
├── ui.content/                  (site content and /conf)
│   └── src/main/content/jcr_root/content/mysite/
├── ui.frontend/                 (front-end build: webpack, npm)
├── ui.tests/ and it.tests/      (UI and integration tests)
└── dispatcher/                  (Dispatcher configuration)
VS Code explorer with ui.apps expanded to apps/mysite and ui.config expanded to osgiconfig run mode folders, with an OSGi config JSON using secret and env placeholders open
ui.apps holds code under /apps/mysite. ui.config holds one osgiconfig folder per run mode; the file name is the PID.

The ui.apps package

ui.apps holds the code that the runtime needs, and nothing that an author should edit. The archetype places everything under /apps/mysite. Because /apps is immutable, every change to this package needs a new deployment, not an edit in the author UI.

ui.apps/src/main/content/jcr_root/apps/mysite/
├── clientlibs/          (CSS and JS client libraries)
├── components/          (HTL components, dialogs, component definitions)
│   └── hero/            (your custom component)
│       ├── .content.xml
│       ├── _cq_dialog/
│       └── hero.html
├── i18n/                (dictionaries)
└── .content.xml

A component in ui.apps has three important parts: the component definition in .content.xml, the HTL markup, and the dialog. The dialog is a Granite UI definition, and it belongs in the same package as the component because the author UI reads it from /apps.

CRXDE Lite on the local SDK showing /apps/mysite/components/hero with cq:dialog and hero.html, and its cq:Component properties
CRXDE Lite on the local SDK. The hero component sits under /apps/mysite/components. Treat it as read-only; change it in ui.apps and redeploy.

The ui.config package

ui.config holds the OSGi configuration files and the repoinit scripts. Keeping configuration in its own package means you can change an endpoint or a credential for one run mode without rebuilding the component code. Run-mode folders under osgiconfig follow the pattern config.<author|publish>.<dev|stage|prod>. The archetype creates config, config.author, config.publish, config.stage and config.prod. Add other folders, such as config.author.dev, when you need them.

ui.config/src/main/content/jcr_root/apps/mysite/osgiconfig/
├── config/                       (all environments)
├── config.author/                (author tier only)
├── config.author.dev/            (author tier, dev environments; you add this one)
├── config.publish/               (publish tier only)
├── config.stage/                 (stage environment)
└── config.prod/                  (production environment)

OSGi configuration files use the .cfg.json format. The file name is the PID, and the most specific run-mode folder wins for the whole PID. Values can reference an environment variable or a secret, so the same file works across environments without hard-coded credentials.

// ui.config/.../osgiconfig/config.author.dev/com.mysite.core.services.ExternalApiService.cfg.json
{
  "endpoint": "https://api.dev.example.com/v1",
  "apiKey": "$[secret:MYSITE_API_KEY]",
  "timeoutMs": "$[env:MYSITE_API_TIMEOUT;default=5000]"
}

The $[secret:NAME] form reads a secret that you set in Cloud Manager. The $[env:NAME;default=value] form reads an environment variable with a fallback. Use $[env:] for non-secret values that differ between environments, and $[secret:] for passwords and API keys. You set both per environment in Cloud Manager, under Environments, Configuration. Values that are the same for every environment of one type can go straight into the run-mode folder. The variable naming rules and the reserved prefixes are covered in Lesson 06, AEMaaCS OSGi Configurations and Environment Variables.

Cloud Manager Developer Console Status tab with the Configurations dump filtered to the ExternalApiService PID on the dev author
Developer Console, Status, Configurations. Filter by PID to confirm which configuration the dev author is running.

ui.config also holds repoinit scripts, which create service users, paths and ACLs at deployment time. Repoinit scripts are the scripts property of a org.apache.sling.jcr.repoinit.RepositoryInitializer factory configuration, for example org.apache.sling.jcr.repoinit.RepositoryInitializer~mysite.config. Adobe recommends the .config format here because it supports multi-line scripts. Keep the scripts small. The lesson on service users covers the syntax in depth: Create a service user in AEM as a Cloud Service.

The ui.content package

ui.content is a content package. It holds the pages, experience fragments, DAM folders and the /conf settings that the site needs to function. Its package type is content, and it depends on ui.apps because the content refers to components defined there.

ui.content/src/main/content/jcr_root/
├── content/mysite/                 (site root and sample pages)
├── content/experience-fragments/mysite/
├── content/dam/mysite/             (sample assets)
└── conf/mysite/
    ├── settings/                   (site settings and templates)
    └── _sling_configs/             (context-aware configurations, sling:configs in the JCR)

The conf folder is the most common surprise. Because it is mutable, authors can change site settings at runtime. Context-aware configurations, covered in Sling Context-Aware Configuration in AEM, live under /conf for this reason.

The core bundle

core is an OSGi bundle built with Maven. Its Java package is com.mysite.core, which the archetype derives from the group ID. The module contains Sling Models, services, servlets, schedulers, filters and the unit tests. Because it is a bundle rather than a content package, the all package embeds the JAR directly at /apps/mysite-packages/application/install, and AEM installs it from there.

core/src/main/java/com/mysite/core/
├── models/          (Sling Models, adaptable from Resource or SlingHttpServletRequest)
├── servlets/        (Sling servlets)
├── services/        (OSGi services; add this package yourself)
├── schedulers/      (Sling scheduled jobs)
└── filters/         (Sling filters)
core/src/test/java/com/mysite/core/
└── ...              (JUnit 5 tests, covered in the unit testing lesson)

The all container package

The all module is the one you deploy. It is a container package that embeds ui.apps, ui.config, ui.content and the core bundle in one archive, plus the Core Components as vendor packages. It must not contain code or content of its own. Cloud Manager and the local autoInstallSinglePackage profile both deploy this package.

Infographic: archetype Maven modules build into one all container package with application and content install folders, deployed to immutable /apps and mutable /content and /conf; dispatcher deployed separately
How the archetype reaches AEM. Code packages land in immutable /apps; ui.content lands in mutable /content and /conf. The dispatcher module is deployed separately.

Its POM declares the container type and embeds the other packages, so you do not add them to a deployment list by hand.

<!-- all/pom.xml (excerpt) -->
<packaging>content-package</packaging>
...
<plugin>
  <groupId>org.apache.jackrabbit</groupId>
  <artifactId>filevault-package-maven-plugin</artifactId>
  <configuration>
    <packageType>container</packageType>
    <embeddeds>
      <embedded>
        <groupId>com.mysite</groupId>
        <artifactId>mysite.ui.apps</artifactId>
        <type>zip</type>
        <target>/apps/mysite-packages/application/install</target>
      </embedded>
      <embedded>
        <groupId>com.mysite</groupId>
        <artifactId>mysite.ui.content</artifactId>
        <type>zip</type>
        <target>/apps/mysite-packages/content/install</target>
      </embedded>
      <!-- core and ui.config also go to /apps/mysite-packages/application/install -->
    </embeddeds>
  </configuration>
</plugin>

The other packages set cloudManagerTarget to none in their POMs, so Cloud Manager deploys them only as part of the all container. The filter.xml of the container must include a root for /apps/mysite-packages, or the embedded packages are not installed. The folder name decides where a package installs: application holds code packages, content holds content packages, and an install.author or install.publish folder limits a package to one tier. A dispatcher module does not produce a content package, so it sits outside this chain.

Terminal listing the all package: packageType container, ui.apps, core and ui.config under application/install, ui.content under content/install, Core Components under vendor packages
Inside mysite.all.zip: a container package with code under application/install and content under content/install.

Comparison table

The table below compares the four packages you edit most often. Use it when you decide where a new file belongs.

Questionui.appsui.configui.content
What does it hold?Components, clientlibs, i18nOSGi configs, repoinit scriptsPages, DAM, /conf settings
Target path/apps/mysite/apps/mysite/osgiconfig/content, /conf
Package typeapplicationcontainercontent
Changes at runtime?NoNo (OSGi configurations are deployed as code)Yes, by authors
Change needs redeploy?YesYesOnly when you change the sample or the seed content
Typical ownerFront-end and component developersPlatform and DevOpsContent and site setup

Common mistakes and troubleshooting

Most build and deployment problems with a new project fall into a handful of categories. The table lists the symptoms you are likely to see, the cause and the fix.

SymptomLikely causeFix
Components are missing on the author UI after deploymentComponent was put in ui.content instead of ui.appsMove the file to ui.apps/src/main/content/jcr_root/apps/mysite and redeploy the all package
Build fails with an enforcer errorJDK or Maven version does not match the enforce-maven profileInstall the version the root POM asks for, then run mvn clean install again
A request or job fails with an access error when it writes under /appsCode tried to create nodes under /apps during a requestMove the write into a deployed package or a repoinit script
OSGi config is ignored on publishFile placed in config.author but the service runs on publishMove the file to config.publish or to the shared config folder
Changes to a dialog do not appearOld package still installed, or the file was edited in CRXDE Lite onlyEdit the source in the repository, rebuild and redeploy. Never rely on edits in CRXDE Lite

Note:- Edits made in CRXDE Lite to /apps are not kept on AEMaaCS. Treat CRXDE Lite as a read-only inspection tool for code and configuration, and make the change in the project.

Hands-on challenge

Add a new component called teaser and place a new OSGi configuration for it, then prove each file lands in the right tree.

  1. Create ui.apps/src/main/content/jcr_root/apps/mysite/components/teaser/ with a .content.xml and a teaser.html file.
  2. Create a configuration file named com.mysite.core.services.TeaserConfig.cfg.json in config.author with one property. The local SDK runs as author without a dev run mode, so config.author.dev would not apply on your laptop.
  3. Add a sample page under ui.content/src/main/content/jcr_root/content/mysite/us/en/teaser-demo.
  4. Run mvn clean install from the root and open the all/target folder. Confirm that the zip contains all three additions.
  5. Deploy to your local instance with mvn clean install -PautoInstallSinglePackage and confirm the component appears in the page editor.

Success criteria: the component is under /apps/mysite/components, the sample page is under /content/mysite, and the configuration is listed in the Web Console at http://localhost:4502/system/console/configMgr. On a cloud environment, use Developer Console, Status, Configurations instead.

Key takeaways

  • The archetype creates a root project with one module per deployable concern, and all bundles them into one container package.
  • /apps and /libs are immutable. Code, components and configuration belong in code packages.
  • /content, /conf, /var and /etc are mutable. Site content and settings that authors edit belong in ui.content.
  • OSGi configuration lives in ui.config, one folder per run mode, and uses $[secret:] and $[env:] instead of hard-coded values.
  • Always edit the source in the project and redeploy. Changes made only in CRXDE Lite do not survive a deployment.

Knowledge check

1. Which two repository paths are immutable on AEMaaCS?

/apps and /libs. Code packages deploy to /apps, and the runtime cannot write there.

2. In which module does a new OSGi configuration file for a service belong?

ui.config, under osgiconfig/config or a run-mode folder such as config.publish.prod.

3. Why does ui.content depend on ui.apps?

Because the pages and experience fragments in ui.content reference components that are defined in ui.apps. Without those components, the content cannot render.

4. What does the all module do?

It is a container package that embeds the other packages, so one deployment installs the whole project. Its target and package type are set in its POM.

5. Where does the Java package com.mysite.core come from when you use the archetype?

From the groupId property. The archetype derives ${package} from the group ID, so com.mysite produces com.mysite.core for the core module.

Frequently asked questions

Can I put a component in ui.content to make it easier to edit?
No. Components are code, and code belongs in ui.apps under /apps. Content that uses the component belongs in ui.content.

Why does my build produce a zip for every module?
Each Maven module that uses the content-package packaging produces a package. The all package embeds them, so deploy all rather than each module.

Continue your learning

Spread the love

Leave a Reply

Your email address will not be published. Required fields are marked *

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