N
Naveenr.dev
Chapter 02
20 min read2026-06-30

EDS Project Setup

Setup guide for an AEM Edge Delivery Services project. Covers the GitHub repository, aem-code-sync, fstab.yaml, local development, and the first page render.

Content Objective

This chapter covers:

  • The exact files that make up an EDS project and what each one does
  • How to connect GitHub, AEM Cloud, and the EDS Edge Network
  • What fstab.yaml does and why getting it wrong breaks everything
  • How the local development server works and what it actually proxies
  • How content flows from AEM Cloud to your browser during development
  • Common setup errors and how to diagnose them

Before writing a single block, you need to understand the wiring that connects your GitHub code to your AEM content and the EDS edge network.

Most developers skip this step. They clone the boilerplate, run aem up, see something in the browser, and start building blocks without understanding what is actually happening.

That approach works until something breaks. Then debugging becomes guesswork.

If you understand the setup wiring, you can diagnose almost any EDS environment issue in minutes rather than hours.

The Three Things That Must Connect

An EDS project has three independent systems that must be connected correctly for anything to work.

GitHub Repository
(your code: blocks, scripts, styles)
         ↕ aem-code-sync app
AEM Cloud Instance
(your content: pages, assets, component models)
         ↕ fstab.yaml
EDS Edge Network
(delivers the page to users)

If any connection breaks, the site does not work. Understanding which connection is broken is the key to fast debugging.

The Boilerplate Repository

Adobe provides a starting point called aem-boilerplate. For Universal Editor (xwalk) projects, the correct starting point is aem-boilerplate-xwalk.

https://github.com/adobe/aem-boilerplate-xwalk

This repository contains:

  • The foundational scripts.js and aem.js that power all EDS sites
  • Example blocks: hero, cards, columns, footer, fragment
  • The component-definition.json, component-models.json, and component-filters.json files needed for Universal Editor
  • The fstab.yaml configuration file you must update for your project

Do not build from scratch. The boilerplate contains carefully tuned performance optimizations in scripts.js that are difficult to replicate correctly.

Understanding fstab.yaml

The fstab.yaml file is the most important configuration file in your project.

It tells the EDS Edge Network where to fetch your content from when a request comes in.

A typical xwalk fstab.yaml looks like this:

mountpoints:
  /:
    url: "https://author-p{program}-e{environment}.adobeaemcloud.com/bin/franklin.delivery/{owner}/{repo}/main"
    type: "markup"
    suffix: ".html"

Let's break this down:

FieldMeaning
mountpointsMaps URL paths to content sources
/The root path — all pages are fetched from this source
urlThe AEM Cloud delivery endpoint
type: markupEDS expects HTML content (not JSON or plain text)
suffix: .htmlAppended to page paths when fetching from AEM

How a Page Request Uses fstab

When a user visits https://main--eds-poc--yourname.aem.live/adc-test:

1. EDS Edge Network receives request for /adc-test
2. Reads fstab.yaml: root is mapped to AEM Cloud endpoint
3. Fetches:
   https://author-pXXX-eYYY.adobeaemcloud.com/bin/franklin.delivery/owner/repo/main/adc-test.html
4. AEM returns the page HTML
5. EDS caches and delivers it

If the fstab.yaml URL is wrong, no page on your site will load. The error will look like a 404 or a blank page depending on what AEM returns.

Common fstab Mistakes

Mistake 1: Wrong program/environment IDs

# Wrong
url: "https://author-p999999-e888888.adobeaemcloud.com/..."

# The program and environment IDs must match your actual AEM Cloud instance

Check Cloud Manager for your correct program and environment IDs.

Mistake 2: Wrong owner or repo name

The {owner} and {repo} values in the URL must match your GitHub username and repository name exactly. Case sensitive.

Mistake 3: Using the publish URL instead of the author URL

For xwalk projects, the fstab.yaml points to the author instance, not publish. This is intentional. The bin/franklin.delivery servlet on the author instance handles preview and live delivery separately.

The aem-code-sync GitHub App

The aem-code-sync app is a GitHub App installed on your repository. It connects your GitHub repository to the AEM Edge Network.

When you push code to the main branch of your repository, aem-code-sync detects the change and updates the edge network so your new blocks, styles, and scripts are available immediately.

EDS Code Sync Flow
EDS Code Sync Flow

Installing aem-code-sync

  1. Navigate to https://github.com/apps/aem-code-sync
  2. Click Install
  3. Select your GitHub account
  4. Choose Only select repositories and select your EDS repo
  5. Click Install

After installation, every push to main is automatically synchronized.

You do not need to run any build step. There is no npm run build for production. The code in your GitHub repository IS the production code. EDS loads your JavaScript and CSS files directly by URL.

This is a significant architectural choice. It means:

  • No compilation step required
  • No bundling required
  • No deployment pipeline for code changes (just git push)
  • Developers can see changes in preview within seconds of pushing

The Local Development Server

During development you do not want to push every change to GitHub to see the result. The aem CLI provides a local development server that proxies requests to your live preview site.

npm install
aem up

This starts a local server at http://localhost:3000.

What aem up Actually Does

Many developers assume aem up serves files from their local disk only. This is incorrect.

aem up proxies HTML content from your preview URL (.aem.page) and serves your local code files (blocks, scripts, styles) from disk.

Your Browser (localhost:3000)
  ↓
aem up (local server)
  ↓ for HTML content
  Preview Site (.aem.page)
  ↓ for JS/CSS files
  Your local disk (blocks/, scripts/, styles/)

This means:

  • Content changes (page edits in UE or DA) require a preview/publish action before they appear locally
  • Code changes (JS, CSS, JSON) appear immediately on save — no restart needed
  • You need an active internet connection — the HTML comes from AEM Cloud, not from disk
EDS Local Dev Flow
EDS Local Dev Flow

Practical Implication

If you edit a block's JavaScript file and reload localhost:3000, your change appears immediately.

If an author edits a page in Universal Editor and you reload localhost:3000, you will not see the change yet. The author must first click Preview in the Sidekick to push the content to .aem.page, and then your local server will pick it up.

This surprises many developers the first time they encounter it.

The Configuration Files for Universal Editor

If you are using Universal Editor (xwalk), your project has three additional JSON files that define what authors can edit and how the editor presents it.

component-definition.json

This file is the registry of all blocks available in the Universal Editor sidebar.

{
  "groups": [
    {
      "title": "Blocks",
      "id": "blocks",
      "components": [
        {
          "title": "Hero",
          "id": "hero-banner",
          "plugins": {
            "xwalk": {
              "page": {
                "resourceType": "core/franklin/components/block/v1/block",
                "template": {
                  "name": "hero-banner",
                  "model": "hero-banner"
                }
              }
            }
          }
        }
      ]
    }
  ]
}

If a block is missing from this file, authors cannot add it from the sidebar. The block may already exist as a folder in your repository, but Universal Editor does not know about it until you register it here.

component-models.json

This file tells Universal Editor which fields appear in the properties panel when an author clicks on a block.

{
  "definitions": [
    {
      "title": "Hero Banner",
      "id": "hero-banner",
      "fields": [
        {
          "component": "select",
          "name": "size",
          "label": "Size",
          "options": [
            { "name": "Tall", "value": "tall" },
            { "name": "Medium", "value": "medium" },
            { "name": "Short", "value": "short" }
          ]
        },
        {
          "component": "aem-content",
          "name": "desktopImage",
          "label": "Desktop Image"
        }
      ]
    }
  ]
}

If this file has a JSON syntax error (trailing comma, missing bracket), the entire UE properties panel will crash with "Something went wrong" — not just for the broken block, but for every block on the page.

Always validate your JSON before pushing.

component-filters.json

This file controls which blocks are allowed to be added inside which containers.

{
  "definitions": [
    {
      "id": "section",
      "components": [
        "hero-banner",
        "cards",
        "columns",
        "adc-button"
      ]
    }
  ]
}

If a block is in component-definition.json but not in component-filters.json under section, authors will see it in the sidebar but will not be able to add it to a page. It will appear greyed out.

The models/ Folder

Individual block models can be stored inside each block's folder as _block.json, or they can be stored in the top-level models/ folder as reusable fragments.

The models/ folder contains shared field definitions that multiple blocks can reference:

models/
├── _button.json        ← Reusable button fields
├── _image.json         ← Reusable image fields
├── _text.json          ← Reusable rich text field
├── _page.json          ← Page-level metadata
└── _section.json       ← Section-level fields

When multiple blocks need the same field type (for example, all blocks that have a CTA button), you define the field once in models/ and reference it from each block's model.

helix-query.yaml

This file configures the query API for your EDS site. It allows you to create structured queries against your content — for example, fetching all pages tagged with a certain category for a blog listing.

For most projects in the beginning, you do not need to modify this file. The default configuration supports the most common use cases.

head.html

This file is injected into the <head> of every page on your site.

Common uses:

  • Adding custom fonts via <link rel="preconnect">
  • Adding a <meta> tag for a third-party verification
  • Loading a global analytics script

Be careful what you add here. Anything in head.html runs on every page. Render-blocking resources added here will hurt your Core Web Vitals scores across the entire site.

The Complete Request Flow During Development

Now that you understand all the pieces, here is how a complete development request flows:

You type localhost:3000/adc-test in browser
  ↓
aem up receives request
  ↓
Fetches HTML from: main--eds-poc--yourname.aem.page/adc-test
  ↓
HTML arrives with block tables like:
  <div class="hero block" data-block-name="hero">...</div>
  ↓
Browser loads scripts.js from localhost:3000/scripts/scripts.js (your local file)
  ↓
scripts.js discovers .hero.block element
  ↓
Loads localhost:3000/blocks/hero/hero.js (your local file)
  ↓
decorate(block) runs
  ↓
Loads localhost:3000/blocks/hero/hero.css (your local file)
  ↓
Page renders with your local code + AEM content

This is why you can iterate on block code instantly without touching the content.

Common Setup Errors and How to Diagnose Them

Error: Blank page or 404 on localhost

Most likely cause: fstab.yaml URL is wrong or the preview site is not set up.

Diagnosis:

  1. Open fstab.yaml and copy the URL
  2. Append your page path: .../main/your-page.html
  3. Open that URL directly in the browser
  4. If you get a login prompt or 404, the fstab URL is wrong or your AEM instance is not accessible

Error: Block exists but doesn't run

Most likely cause: File naming mismatch, or export default missing.

Diagnosis:

  1. Open browser DevTools → Network tab
  2. Filter by JS
  3. Look for blocks/your-block/your-block.js
  4. If the file loaded but block didn't decorate: check the console for errors
  5. If the file did not load: the block name in the HTML does not match the folder name
// This will NOT work (named export)
export function decorate(block) { ... }

// This WILL work (default export)
export default function decorate(block) { ... }

Error: UE properties panel shows "Something went wrong"

Most likely cause: JSON syntax error in a model file.

Diagnosis:

  1. Open browser DevTools → Console tab
  2. Look for a JSON parse error with a file path
  3. Open that file and look for:
    • Trailing commas after the last item in an array or object
    • Missing closing brackets or braces
    • Single quotes instead of double quotes

Even one character error in any JSON file connected to the UE will crash the entire properties panel.

Error: Code changes don't appear on preview

Most likely cause: You changed a JS or CSS file but the edge network is serving a cached version.

Diagnosis:

Add ?nocache=1 to the preview URL, or use the Sidekick to explicitly preview the page again.

For local development, hard refresh with Cmd+Shift+R (Mac) or Ctrl+Shift+R (Windows).

Error: Author edits not visible locally

Most likely cause: The page has not been previewed after the edit.

Diagnosis:

In Universal Editor or DA, the author must click Preview (or the equivalent action) to push content to .aem.page. Until that happens, localhost:3000 will show the previous version.

The Sidekick Extension

The AEM Sidekick is a browser extension that authors and developers install to manage the content lifecycle.

Key actions available in the Sidekick:

ActionWhat it Does
PreviewPushes content from author to .aem.page
PublishPushes content from preview to .aem.live
DeleteRemoves a page from preview and live
UnpublishRemoves a page from live but keeps it in preview

As a developer, you will use Preview frequently during development to pull the latest authored content to your local server.

Key Takeaways

  • Three systems must connect: GitHub (code), AEM Cloud (content), EDS Edge Network (delivery)
  • fstab.yaml is the most critical config file — a wrong URL breaks every page
  • aem-code-sync links GitHub to the edge — no build step, git push is deployment
  • aem up is a proxy server, not a static file server — HTML comes from .aem.page, code comes from disk
  • Content changes require a Preview action before they appear locally
  • Three JSON files power Universal Editor: component-definition.json (registry), component-models.json (fields), component-filters.json (placement rules)
  • A JSON syntax error in any model file crashes the entire UE properties panel
  • Block JS must use export default function decorate(block) — named exports do not work
  • Block folder name must exactly match the CSS class in the HTML — case-insensitive but hyphen-sensitive

Next Steps

In the next chapter, we will cover the most important concept in EDS development: Blocks.

We will cover:

  • The anatomy of a block (JS, CSS, JSON model)
  • How scripts.js discovers and loads blocks
  • The decorate(block) function and how to use it correctly
  • Reading key-value content from the block DOM
  • Building your first block from scratch
  • The difference between key-value: true blocks and regular multi-cell blocks
  • Common block mistakes that cause silent failures

Enjoyed this chapter?

Get an email when I publish the next chapter. No spam — just new technical deep-dives.

Comments

Share feedback or questions about this blog post.

No comments yet. Be the first to share your thoughts.