The Anatomy of a Project

This document describes what a typical project should look like from a code standpoint. Before reading this document, please familiarize yourself with the document Getting Started - Developer Tutorial.

Git and GitHub

One of our defining philosophies is that it is easiest to allow users to work with the tools that they are familiar with. The overwhelming majority of developers manage their code in systems based on Git, so it only makes sense to allow developers to work with Git to manage and deploy their code.

We are using a buildless approach that runs directly from your GitHub repo. After installing the AEM GitHub bot on your repo, websites are automatically created for each of your branches for content preview on https://<branch>--<repo>--<owner> and the production site on https://<branch>--<repo>--<owner> for published content.

Every resource that you put into your GitHub repo is available on your website, so a file in your GitHub repo on the main branch in /scripts/scripts.js will be available on https://main--<repo>--<owner>
This should be very intuitive. There are few “special” files that Adobe Experience Manager uses to connect the content into your website.

We strongly recommend that repos are kept public on GitHub, to foster the community. For a public facing website there really is no need to keep the code hidden, as it is being served to the browsers of your website.

Important notes:

Configuration Files

There are some files in your GitHub repo that have a special meaning to AEM and are processed in a special fashion. These files are in the root directory of your repo.

The Content Connection: fstab.yaml

As you may have seen in the getting started guide the fstab.yaml file serves as the connection to your Google Drive or SharePoint folders that contain your content. This file is used as an indicator for how the code in your GitHub repo gets combined with the content in your content source.

Beyond providing the connection to the content, the fstab.yaml also provides a folder mapping facility that allows you map extensionless “routes” to a given piece of content or static HTML.

The Entry Point: head.html

The head.html file is the most important extension point to influence the markup of the content. The easiest way to think of it is that this file is injected on the server side as part of the <head> HTML tag and is combined with the metadata coming from the content.

The head.html should remain largely unchanged from the boilerplate and there are only very few legitimate reasons in a regular project to make changes. Those include remapping your project to a different base URL for the purposes of exposing your project in a different folder than the root folder of your domain on your CDN or to support legacy browsers which usually require scripts that are not loaded as modules.

Adding marketing technology like Adobe Web SDK, Google Tag Manager or other 3rd party scripts to your head.html file is strongly advised against due to performance impacts. Adding inline scripts or styles to head.html is also not advisable for performance and code management reasons, see the section Scripts and Styles below for more information about handling scripts and styles.

Please see the following examples.

Not Found: 404.html

To create a custom 404 response, place a 404.html file into the root of your github repository. This will be served on any URL that doesn’t map to an existing resource in either content or code, and replaces the body of the out of the box minimalist 404 response.

The 404 can mimic the markup of an existing page including your code for the site, with navigation footers etc., or it can have a completely different appearance.

Please see the following examples.

Don’t Serve: .hlxignore

There are some files in your repo that should not be served from your website, either because you would like to keep them private or they are not relevant to the delivery of the website (e.g. tests, build tools, build artifacts, etc.) and don’t need to be observed by the AEM bot. You can add those to a .hlxignore file in the same format as the well-known .gitignore file.

Please see the following example.

Tame the Bots: robots.txt

A robots.txt file is generally a regular file that is served as you would expect on your production website on your own domain. To protect your preview and origin site from being indexed, your .page and .live sites will serve a robots.txt file that disallows all robots instead of the robots.txt file from your repo.

Please see the following examples.

Query and Indexing: helix-query.yaml

There is a flexible indexing facility that lets you keep track of all of your content pages conveniently as a spreadsheet. This facility is often used to show lists or feeds of pages as well as to filter and sort content on a website. Please see the document Indexing for more information.

Please see the following examples.

Automate Your Sitemap: helix-sitemap.yaml

Complex sitemaps can automatically be created for you whenever authors publish new content, including flexible hreflang mappings where needed. This functionality is usually based on the indexing facility.
See this GitHub issue for sitemap configuration options.

Please see the following examples.

Commonly Used File and Folder Structure

Beyond the files treats as special or configuration files, there is a commonly-used structure that is expressed in the Boilerplate repo.

The common folders below are usually in the root directory of a project repo, but in cases where only a portion of a website is handled by AEM, they are often moved to a subfolder to reflect the mapping of the route of the origin in a CDN.

This means that in a case where for example only /en/blog/ is initially mapped to AEM from the CDN, all the folder structures below (eg. /scripts, /styles, /blocks etc.) are moved into a the /en/blog/ folder in GitHub to keep the CDN mapping as simple as possible.

With a simple adjustment of the reference to scripts.js and styles.css in head.html (see above) it is possible to indicate that all the necessary files are loaded from the respective code base directory. To avoid url rewriting the folder structure is also created the content source (eg. sharepoint or google drive) by having a directory structure of /en/blog/.
In many cases as the AEM footprint grows on a site there is a point in time when the code gets moved back to the root folder and the head.html references are adjusted accordingly.

Scripts and Styles

By convention in a AEM project, the head.html references styles.css, scripts.js, and lib-aem.js located in /scripts and /styles, as the entry points for the project code.

scripts.js is where your global custom javascript code lives and is where the block loading code is triggered. styles.css hosts the global styling information for your site, and minimally contains the global layout information that is needed to display the Largest Contentful Paint (LCP).

As all three files are loaded before the page can be displayed, it is important that they are kept relatively small and executed efficiently.

Beyond styles.css, a lazy-styles.css file is commonly used, which is loaded after the LCP event, and therefore can contain slower/more CSS information. This could be a good place for fonts or global CSS that is below the fold.

In addition to scripts.js, there is the commonly-used delayed.js. This is a catch-all for libraries that need to be loaded on a page but should be kept from interfering with the delivery of the page. This is a good place for code that is outside of the control of your project and usually includes the martech stack and other libraries.

Please see the document Keeping it 100, Web Performance for more information about optimizing your site performance.


Most of the project-specific CSS and JavaScript code lives in blocks. Authors create blocks in their documents. Developers then write the corresponding code that styles the blocks with CSS and/or decorates the DOM to take the markup of a block and transform it to the structure that’s needed or convenient for desired styling and functionality.

The block name is used as both the folder name of a block as well as the filename for the .css and .js files that are loaded by the block loader when a block is used on a page.

The block name is also used as the CSS class name on the block to allow for intuitive styling. The javascript is loaded as a module (ESM) and exports a default function that is executed as part of the block loading.

A simple example is the Columns Block. It adds additional classes in JavaScript based on how many columns are in the respective instance created by the author. This allows it to be able to use flexible styling of content that is in two columns vs. three columns.


Most projects have SVG files that are usually added to the /icons folder, and can be referenced with a :<iconname>: notation by authors. By default, icons are inlined into the DOM so they can be styled with CSS, without having to create SVG symbols.