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.yamldoes 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.jsandaem.jsthat power all EDS sites - Example blocks: hero, cards, columns, footer, fragment
- The
component-definition.json,component-models.json, andcomponent-filters.jsonfiles needed for Universal Editor - The
fstab.yamlconfiguration 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:
| Field | Meaning |
|---|---|
mountpoints | Maps URL paths to content sources |
/ | The root path — all pages are fetched from this source |
url | The AEM Cloud delivery endpoint |
type: markup | EDS expects HTML content (not JSON or plain text) |
suffix: .html | Appended 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.

Installing aem-code-sync
- Navigate to
https://github.com/apps/aem-code-sync - Click Install
- Select your GitHub account
- Choose Only select repositories and select your EDS repo
- 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

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:
- Open
fstab.yamland copy the URL - Append your page path:
.../main/your-page.html - Open that URL directly in the browser
- 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:
- Open browser DevTools → Network tab
- Filter by JS
- Look for
blocks/your-block/your-block.js - If the file loaded but block didn't decorate: check the console for errors
- 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:
- Open browser DevTools → Console tab
- Look for a JSON parse error with a file path
- 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:
| Action | What it Does |
|---|---|
| Preview | Pushes content from author to .aem.page |
| Publish | Pushes content from preview to .aem.live |
| Delete | Removes a page from preview and live |
| Unpublish | Removes 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.yamlis the most critical config file — a wrong URL breaks every pageaem-code-synclinks GitHub to the edge — no build step,git pushis deploymentaem upis 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.jsdiscovers 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: trueblocks 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.