Org Skills

aem-dispatcher

Writes, reviews and debugs AEM Dispatcher and CDN configuration — flexible-mode file layout, deny-by-default filters, cache rules, statfileslevel and invalidation, TTLs, rewrites and redirects, headers and CORS, and Cloud Service CDN rules for traffic filtering and WAF.

Download .zip Raw Source
When agents use itUse this whenever the user edits or reviews dispatcher .any, .farm, .vhost, .rules or .vars files (filters.any, rules.any, clientheaders.any, virtualhosts.any, rewrite.rules), runs the dispatcher SDK validator or docker_run.sh, asks why a page is not cached or not invalidated, sees a 404 from dispatcher, exposes .json or .model.json, configures redirects, vanity URLs, CORS or security headers, or edits cdn.yaml. Also apply it when a performance or security issue on AEM publish is really a cache or filter problem.

Install

Copilot (VS Code, Visual Studio, Copilot CLI and github.com) reads skills from the repository — commit them so the whole team gets them.

npx skills add AGCO-Global/org-skills --skill aem-dispatcher -a github-copilot
# or with the org installer (adds .github/skills/aem-dispatcher):
npx -y github:AGCO-Global/org-skills add skill aem-dispatcher

Uses the open skills CLI. Works with Claude Code, Codex, Cursor, Copilot, Gemini CLI, OpenCode, Windsurf and 60+ others — it asks which agent to install into.

npx skills add AGCO-Global/org-skills --skill aem-dispatcher
# user-level instead of project-level:
npx skills add AGCO-Global/org-skills --skill aem-dispatcher -g

Installs the aem-architecture-skills plugin, which bundles all AEM / Architecture skills and keeps them updated.

/plugin marketplace add AGCO-Global/org-skills
/plugin install aem-architecture-skills@org-skills
# or just this skill, in this repository:
npx skills add AGCO-Global/org-skills --skill aem-dispatcher -a claude-code

Use Download .zip above, then upload it under Customize → Skills → Upload skill. Team and Enterprise admins can sync this repo as a plugin marketplace instead.

Installs into .agents/skills/, which Codex reads.

npx skills add AGCO-Global/org-skills --skill aem-dispatcher -a codex

Installs into Cursor's skills folder.

npx skills add AGCO-Global/org-skills --skill aem-dispatcher -a cursor

Installs into Gemini CLI's skills folder.

npx skills add AGCO-Global/org-skills --skill aem-dispatcher -a gemini-cli

Any tool with rules, instructions or custom prompts: use Copy SKILL.md above and paste it in. It is plain Markdown.

Commands use your normal git sign-in to GitHub, so they work while the repository is private. Node.js 20+ required.

Skill contents

AEM Dispatcher and CDN

Dispatcher is both the security perimeter and the cache for AEM publish. Every rule must answer two questions: can an attacker reach something they shouldn't through this? and will this response be cached and correctly invalidated? Deny by default, cache by default, and prove both with the validator and response headers.

1. File layout (Cloud Service, flexible mode)

dispatcher/src/
├── opt-in/USE_SOURCES_DIRECTLY          ← enables flexible mode
├── conf.d/
│   ├── available_vhosts/<site>.vhost    ← your vhosts
│   ├── enabled_vhosts/<site>.vhost      ← symlink → ../available_vhosts/<site>.vhost
│   ├── rewrites/rewrite.rules           ← mod_rewrite rules (included from vhost)
│   └── variables/custom.vars            ← Define ${CONTENT_FOLDER_NAME} etc.
└── conf.dispatcher.d/
    ├── available_farms/<site>.farm       enabled_farms/<site>.farm → symlink
    ├── filters/filters.any              ← $include "./default_filters.any" + yours
    ├── cache/rules.any                  ← cacheable paths
    ├── clientheaders/clientheaders.any  ← headers passed to publish
    ├── virtualhosts/virtualhosts.any    ← hostnames for the farm
    └── renders/                          ← publish render (Adobe-managed default)
  • Immutable files (overwritten or rejected by the pipeline): the default_* files (default_filters.any, default_rules.any, default_invalidate.any, default_clientheaders.any, default_virtualhosts.any, default_renders.any, default.farm, default.vhost), dispatcher.any, dispatcher_vhost.conf, and the Apache base config. Extend them via $include and your own files; treat the validator's list as authoritative.
  • Symlinks must be real relative symlinks (Windows checkouts break them).
  • Environment differences via Apache variables (${ENVIRONMENT_TYPE} and Defines), not per-environment file copies.

Validate before every commit:

./bin/validate.sh ./src                                   # syntax, immutability, allowed directives
./bin/docker_run.sh ./src host.docker.internal:4503 8080  # run against local publish SDK

On 6.5 the same principles apply, but you own the whole dispatcher.any and the Apache build.

2. Filters: allow-list only

Filters are evaluated top to bottom; last match wins. Start from deny-all (the Cloud defaults already do) and add narrow allows.

/0100 { /type "allow" /method "GET" /path "/content/mysite/*" /extension "html" }
/0101 { /type "allow" /method "GET" /path "/content/mysite/*" /selectors "model" /extension "json" }
/0102 { /type "allow" /method "GET" /url "/graphql/execute.json/mysite/*" }   # persisted queries, exact prefix
/0110 { /type "deny"  /selectors "(feed|rss|pages|languages|blueprint|infinity|tidy|sysview|docview|query|[0-9-]+)" /extension "json" }
/0111 { /type "deny"  /path "/content/*" /query "debug=*" }
Rule Why
Match on /path, /selectors, /extension, /suffix, /method, /query — not a raw /url glob Glob URLs are bypassable with selectors/suffixes
Never allow /crx/*, /system/*, /bin/*, /libs/* broadly Consoles, Query Builder, servlets; allow one exact servlet path if needed
.json only with an explicit selector (model) and path Default JSON servlet leaks content structure; .infinity.json dumps trees
Deny numeric selectors (.1.json, .-1.json) Recursive JSON rendering
POST only to exact form-handler paths Everything else on publish is read-only
Test the negative cases A filter review isn't done until blocked URLs return 404

3. Cache rules

  • /rules: cache * under content paths; deny only what must never be cached (authenticated or per-user endpoints).
  • /ignoreUrlParams — ignore everything except parameters that change the response; otherwise every tracking parameter (utm_*, gclid) is a cache miss:
/ignoreUrlParams {
  /0001 { /glob "*"    /type "allow" }   # allow = ignore for caching
  /0002 { /glob "page" /type "deny"  }   # deny = response depends on it → not cached
}
  • /statfileslevel: set to the depth of your site roots (e.g. /content/brand/country/lang → 4 or 5) so publishing a page in one site doesn't invalidate every site. Too low → mass invalidation; too high → stale navigation/header fragments shared across the tree.
  • /invalidate: auto-invalidate *.html (and page .json) on activation; don't auto-invalidate clientlibs or DAM renditions — they use versioned/fingerprinted URLs.
  • /allowAuthorized "0" (default): requests with auth headers/cookies aren't cached. Turn it on only with permission-sensitive caching in place.
  • /enableTTL "1" makes dispatcher honour Cache-Control: max-age/Expires from publish — use it for API responses and content that should expire rather than be invalidated.
  • Headers: Cache-Control drives browsers and CDN; Surrogate-Control targets the CDN only. Set them in the vhost per path pattern (short TTL for HTML, long+immutable for fingerprinted clientlibs). Cache and serve only the headers you list in /headers.

4. Invalidation flow

AEM 6.5 Cloud Service
Dispatcher Flush replication agent(s) on publish call /dispatcher/invalidate.cache Invalidated automatically on publish/unpublish via content distribution
CDN Your purge integration or short TTLs Adobe CDN honours TTL headers; keep HTML TTLs short rather than rely on purges
Common bug Flush agent on author only, or pointing at the wrong dispatcher Stale header/footer XF because statfileslevel isolates it from the pages that include it

Content included into many pages (XFs, navigation, CF-driven lists) needs a deliberate strategy: shared statfile level, a TTL, or client-side/SDI inclusion.

5. Rewrites, redirects, vanity URLs

  • Short URLs: map /content/mysite/en/.../en/... with mod_rewrite [PT] rules plus Sling mappings/resource resolver config so links are rewritten on output.
  • Redirects: small, stable sets in rewrite.rules (RewriteRule ^/old$ /new [R=301,L]); large marketer-owned lists in a RewriteMap. Cloud Service also supports redirect maps maintained outside the code pipeline and CDN-level redirects — confirm the current mechanism in the product docs before designing around it.
  • Vanity URLs (sling:vanityPath): enable the dispatcher /vanity_urls feature and allow its endpoint in filters, or rewrite them at Apache; don't let every unknown URL fall through to publish.
  • Keep redirect chains to one hop; always L flag; test with curl -I.

6. Headers, CORS, security

  • Security headers in the vhost: Strict-Transport-Security, X-Content-Type-Options: nosniff, X-Frame-Options/CSP frame-ancestors, Referrer-Policy, a CSP owned by whoever owns the tag manager.
  • CORS for headless: configure the AEM CORS policy OSGi config (com.adobe.granite.cors.impl.CORSPolicyImpl~<name>) and pass Origin (plus Access-Control-Request-*) in clientheaders.any; cache per origin or restrict to one origin list so a cached response doesn't carry the wrong Access-Control-Allow-Origin.
  • Pass only needed client headers; never forward cookies to cacheable paths unless required.

7. CDN (Cloud Service) and BYO CDN

  • The Adobe-managed CDN (Fastly-based) sits in front of dispatcher. CDN rules live in config/cdn.yaml deployed through a Cloud Manager config pipeline: traffic filter rules (rate limits, geo/IP blocks), WAF rules (licence-dependent), request/response transformations, origin selectors and redirects. Check current syntax and entitlements in the docs rather than copying old examples.
  • Start WAF and rate-limit rules in log/alert mode, tune, then block.
  • BYO CDN: point it at the Adobe CDN (not around it), send the required host/secret headers per the docs, avoid double caching with conflicting TTLs, and ensure purges or short TTLs on both layers.

8. Debugging

  • Local: send X-Dispatcher-Info: true to the SDK container; the response explains cache/filter decisions. Check dispatcher.log with trace level (DISP_LOG_LEVEL=trace1).
  • Cloud: CDN response headers (x-cache: HIT/MISS, Age), dispatcher and CDN logs from Cloud Manager; calculate hit ratio per path pattern.
  • 404 from dispatcher with a healthy publish = filter deny; 200 but always MISS = auth header/cookie, query string, Cache-Control: private, or /rules deny.

Deliverable format

For a config review, return a table per concern, then the corrected snippets:

URL / pattern Expected Actual (filter · cache · TTL) Risk Fix
/content/mysite/en.model.json 200, cached 5 min allowed, never cached (query param) Low hit ratio Ignore utm_* in /ignoreUrlParams
/content/mysite/en.infinity.json 404 200 Content structure leak Deny infinity selector

Close with: validator output, the negative test URL list, and expected cache hit ratio impact.

Anti-patterns to reject

  • Allow-all filters with a few denies; /url globs instead of structured matchers.
  • Editing immutable default_* files or dispatcher.any; skipping validate.sh.
  • /statfileslevel "0" on multi-site estates, or so high that shared fragments go stale.
  • Caching every query-string variant or never caching any; tracking params not ignored.
  • .json, .infinity.json, Query Builder, /crx or /system/console reachable on publish.
  • /allowAuthorized "1" without permission-sensitive caching.
  • Long CDN TTLs on HTML with no purge strategy; redirect chains and 302s for permanent moves.
  • Access-Control-Allow-Origin: * on authenticated or cached per-origin responses.