npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

aem-eds-cli

v1.8.0

Published

CLI scaffolding tool for AEM Edge delivery services blocks with Universal editor authoring support

Readme

aem-eds-cli

The missing scaffolding tool for AEM Edge Delivery Services blocks with Universal Editor authoring support.

Adobe provides no official CLI for creating UE-compatible blocks. Writing block JSON manually takes ~58 minutes per block and is error-prone — wrong resourceType, missing child definitions, incorrect validation placement all cause silent UE failures. aem-eds-cli reduces this to 6 minutes with zero errors.

npm version Node.js License


Features

  • 5 block types — Simple, Simple with tabs, Container, Container with tabs, Section wrapper
  • 14 UE field types — text, textarea, richtext, reference, aem-content, aem-content-fragment, aem-experience-fragment, boolean, select, multiselect, radio-group, checkbox-group, number, aem-tag
  • Conditional field visibility — JSONLogic condition property — fields show/hide based on other field values
  • Variant select fieldsclasses as a proper select field with named options
  • Options builder — named + value pairs for select, multiselect, radio-group, checkbox-group
  • Default values — validated against field type, options list, and min/max
  • Validation — required, minLength, maxLength, min, max, step, rootPath, customErrorMsg, readOnly, hidden, description
  • Section wrappersection/v1/section resourceType with companion navigation block and automatic models/_section.json update
  • Correct JSON structure — flat definitions array, block/item child resourceType, no :items pre-population
  • ESLint-safe JS — generated stubs commit cleanly through Husky pre-commit hooks
  • README per block — UE-specific step-by-step authoring guide for content authors
  • list / remove / --dry-run commands

Installation

# Use without installing (recommended)
npx aem-eds-cli create carousel

# Global install
npm install -g aem-eds-cli
aem-eds-cli create carousel

# Project dev dependency
npm install --save-dev aem-eds-cli

Commands

aem-eds-cli create [block-name]    # Scaffold a new block interactively
aem-eds-cli remove <block-name>    # Delete block folder and all its files
aem-eds-cli list                   # Show all blocks with file status
aem-eds-cli create carousel --dry-run  # Preview without writing files

Block Types

Run aem-eds-cli create and choose from 5 block types:

1. Simple              Fixed fields — hero, banner, teaser
2. Simple with tabs    Fixed fields grouped into UE panel tabs
3. Container           Repeating child items — carousel, accordion
4. Container with tabs Container where parent config has tab groups
5. Section wrapper     Section-level tab panel (section/v1/section)

Block Type 1 — Simple

Use for: Hero, banner, teaser, quote — fixed fields, no repeating items.

Example: Hero block

Block name: hero
Block type: 1 (Simple)

Fields:
  eyebrow     → text
  heading     → text        required, maxLength: 100
  description → richtext
  ctaLabel    → text
  ctaUrl      → aem-content  rootPath: /content/my-site
  image       → reference
  theme       → select       options: Light→light, Dark→dark
                             default: light
Variants? n
Conditions? n

Generated files:

blocks/hero/
  hero.js           ← export default function decorate(block) { }
  hero.css          ← .hero { display: block; }
  _hero.json        ← definitions + models + filters
  README.md         ← UE authoring guide

Generated _hero.json:

{
  "definitions": [{
    "title": "Hero",
    "id": "hero",
    "plugins": { "xwalk": { "page": {
      "resourceType": "core/franklin/components/block/v1/block",
      "template": { "name": "Hero", "model": "hero" }
    }}}
  }],
  "models": [{
    "id": "hero",
    "fields": [
      { "component": "text",       "name": "eyebrow",     "label": "Eyebrow"      },
      { "component": "text",       "name": "heading",     "label": "Heading",
        "required": true, "validation": { "maxLength": 100 }                       },
      { "component": "richtext",   "name": "description", "label": "Description"  },
      { "component": "text",       "name": "ctaLabel",    "label": "CTA Label"    },
      { "component": "aem-content","name": "ctaUrl",      "label": "CTA URL",
        "validation": { "rootPath": "/content/my-site" }                           },
      { "component": "reference",  "name": "image",       "label": "Image",
        "multi": false                                                              },
      { "component": "select",     "name": "theme",       "label": "Theme",
        "options": [{"name":"Light","value":"light"},{"name":"Dark","value":"dark"}],
        "value": "light"                                                           }
    ]
  }],
  "filters": []
}

Authoring in UE:

Click + in Section → Hero
Click on block → Properties panel:
  Eyebrow      [                    ]
  Heading *    [                    ]
  Description  [ rich text editor  ]
  CTA Label    [                    ]
  CTA URL      [ page picker       ]
  Image        [ DAM picker        ]
  Theme        [ Light ▾           ]
→ Save → Publish

Example: Pricing Banner

Block name: pricing-banner
Block type: 1 (Simple)

Fields:
  badge       → text
  heading     → text        required
  price       → text        required
  period      → select      options: Monthly→monthly, Yearly→yearly
                            default: monthly
  features    → richtext
  ctaLabel    → text
  ctaUrl      → aem-content
  highlighted → boolean     default: false
Variants? n

Block Type 2 — Simple with Tabs

Use for: Blocks with many fields — group into labelled tabs in the UE properties panel.

Example: Talent Hub (Profile + Skills tabs)

Block name: talent-hub
Block type: 2 (Simple with tabs)

Tab 1: Profile
  name        → text        required
  age         → number      min:18, max:60
  gender      → radio-group options: Male→male, Female→female, Other→other
  bio         → richtext
  photo       → reference

Tab 2: Skills
  skills      → checkbox-group  options: AEM→aem, EDS→eds, React→react
  experience  → number          min:0, max:40, default: 2
  links       → aem-content     multi: true

Variants? n
Conditions? n

UE Properties panel:

[ Profile ] [ Skills ]

  Name *   [                ]
  Age      [ 28 ↕          ]
  Gender   ● Male ○ Female ○ Other
  Bio      [ rich text     ]
  Photo    [ DAM picker    ]

Clicking Skills tab switches to skills fields.


Example: Product Card (Content + Media tabs)

Block name: product-card
Block type: 2 (Simple with tabs)

Tab 1: Content
  title       → text        required, maxLength: 80
  description → richtext
  price       → text
  badge       → select      options: New→new, Sale→sale, Featured→featured

Tab 2: Media
  image       → reference
  videoUrl    → aem-content

Tab 3: Actions
  ctaLabel    → text
  ctaUrl      → aem-content
  secondaryLabel → text
  secondaryUrl   → aem-content

Block Type 3 — Container

Use for: Blocks with repeating child items — each child has its own properties panel.

Example: Carousel with config

Block name: carousel
Block type: 3 (Container)

Phase 1 — Parent config:
  interval    → number    default: 5, min: 1, max: 10
  autoplay    → boolean   default: true

Phase 2 — Child item name: carousel-slide
  image       → reference
  caption     → text
  ctaLabel    → text
  ctaUrl      → aem-content

Variants? n
Conditions? n

Generated _carousel.json:

{
  "definitions": [
    {
      "title": "Carousel", "id": "carousel",
      "plugins": { "xwalk": { "page": {
        "resourceType": "core/franklin/components/block/v1/block",
        "template": { "name": "Carousel", "model": "carousel", "filter": "carousel" }
      }}}
    },
    {
      "title": "Carousel Slide", "id": "carousel-slide",
      "plugins": { "xwalk": { "page": {
        "resourceType": "core/franklin/components/block/v1/block/item",
        "template": { "name": "Carousel Slide", "model": "carousel-slide" }
      }}}
    }
  ],
  "models": [
    { "id": "carousel", "fields": [
      { "component": "number",  "name": "interval", "value": 5, "min": 1, "max": 10 },
      { "component": "boolean", "name": "autoplay", "value": true }
    ]},
    { "id": "carousel-slide", "fields": [
      { "component": "reference",   "name": "image",    "multi": false },
      { "component": "text",        "name": "caption"                  },
      { "component": "text",        "name": "ctaLabel"                 },
      { "component": "aem-content", "name": "ctaUrl"                   }
    ]}
  ],
  "filters": [{ "id": "carousel", "components": ["carousel-slide"] }]
}

Authoring in UE:

Click + in Section → Carousel
Click on block → Properties:
  Interval  [ 5 ↕ ]   (1–10)
  Autoplay  [ on  ]

Click + INSIDE Carousel → Carousel Slide
Click on slide → Properties:
  Image    [ DAM picker  ]
  Caption  [             ]
  CTA Label[             ]
  CTA URL  [ page picker ]

Repeat + for each slide.

Example: Cards block with conditional fields

Block name: cards
Block type: 3 (Container)

Phase 1 — Parent config:
  layoutVariant → select
    Grid                  → grid
    Carousel (Peek)       → carousel
    Carousel (Side/Center)→ side-carousel
    default: grid

  visibleCards  → select
    2 → 2 / 3 → 3 / 4 → 4
    default: 4

  autoplay      → boolean   default: false
  infiniteLoop  → boolean   default: false
  sectionContent → richtext
  backgroundImage → reference  multi: false

Phase 2 — Child item name: card
  image       → reference   multi: false
  title       → text
  description → richtext
  ctaLabel    → text   (label: CTA Link Text)
  ctaUrl      → aem-content (label: CTA Link URL)

Variants? n

Add conditional visibility? y
  visibleCards  → shows when layoutVariant === carousel
  autoplay      → shows when layoutVariant is carousel OR side-carousel
  infiniteLoop  → shows when layoutVariant is carousel OR side-carousel

In UE — Grid selected:

Layout Variant     [ Grid ▾ ]
Section Content    [ richtext ]
Background Image   [ DAM picker ]

visibleCards, autoplay, infiniteLoop hidden

In UE — Carousel (Peek) selected:

Layout Variant     [ Carousel (Peek) ▾ ]
Visible Cards      [ 4 ▾ ]              ← appears
Autoplay           [ off ]              ← appears
Infinite Loop      [ off ]              ← appears
Section Content    [ richtext ]
Background Image   [ DAM picker ]

Block Type 4 — Container with Tabs

Use for: Container blocks where the parent config has many fields — group parent config into tabs.

Example: Media Tabs

Block name: media-tabs
Block type: 4 (Container with tabs)

Phase 1 — Parent config (tabbed):
  Tab 1: Config
    defaultTab  → number    default: 1, min: 1
    theme       → select    options: Light→light, Dark→dark

  Tab 2: Layout
    alignment   → radio-group  options: Left→left, Right→right
    columns     → number       default: 3, min: 1, max: 4

Phase 2 — Child item name: media-tab
  tabLabel    → text        required
  image       → reference
  content     → richtext
  ctaLabel    → text
  ctaUrl      → aem-content

Variants? y
  Compact → compact
  Full    → full
  default: full

Example: FAQ Block

Block name: faq
Block type: 4 (Container with tabs)

Phase 1 — Parent config (tabbed):
  Tab 1: Appearance
    theme       → select    options: Light→light, Dark→dark  default: light
    iconStyle   → select    options: Arrow→arrow, Plus→plus  default: arrow

  Tab 2: Behaviour
    allowMultiple → boolean  default: false
    firstOpen     → boolean  default: true
    animate       → boolean  default: true

Phase 2 — Child item name: faq-item
  question    → text        required
  answer      → richtext    required
  category    → select      options: General→general, Technical→technical
  featured    → boolean     default: false

Block Type 5 — Section Wrapper

Use for: Tab panel where each section IS a tab. Different blocks can go inside each tab.

Example: Tab Panel

Block name: tab-panel
Block type: 5 (Section wrapper)

Tab label field name:  tab-label
Tab label field label: Tab Label
Required: y
Custom error message:  Please enter a tab label

Allowed blocks inside (pick from project):
  hero, banner, carousel, cards, accordion

Create companion navigation block? y
  Companion name: tab-list
  Fields:
    listPosition → select
      Left-aligned   → left
      Center-aligned → center
    default: left

Generated _tab-panel.json:

{
  "definitions": [
    {
      "title": "Tab Panel", "id": "tab-panel",
      "plugins": { "xwalk": { "page": {
        "resourceType": "core/franklin/components/section/v1/section",
        "template": {
          "name": "Tab Panel", "model": "tab-panel",
          "filter": "tab-panel", "tab-label": "Tab Label"
        }
      }}}
    },
    {
      "title": "Tab List", "id": "tab-list",
      "plugins": { "xwalk": { "page": {
        "resourceType": "core/franklin/components/block/v1/block",
        "template": { "name": "Tab List", "model": "tab-list" }
      }}}
    }
  ],
  "models": [
    { "id": "tab-panel", "fields": [
      { "component": "text", "name": "tab-label", "label": "Tab Label", "required": true,
        "validation": { "customErrorMsg": "Please enter a tab label" }}
    ]},
    { "id": "tab-list", "fields": [
      { "component": "select", "name": "listPosition", "label": "List Position",
        "options": [{"name":"Left-aligned","value":"left"},{"name":"Center-aligned","value":"center"}],
        "value": "left" }
    ]}
  ],
  "filters": [{
    "id": "tab-panel",
    "components": ["hero","banner","carousel","cards","accordion","tab-list"]
  }]
}

Automatically updates models/_section.json — adds tab-panel to section filter so it appears in UE section picker.

Authoring in UE:

Click + at section level → Tab Panel
  Set Tab Label: "Products"
  Click + INSIDE → Tab List (sets nav position)
  Click + INSIDE → Hero, Cards (content blocks)

Click + at section level → Tab Panel (Tab 2)
  Set Tab Label: "Services"
  Click + INSIDE → Tab List
  Click + INSIDE → Carousel

Each section = one tab. No limit.

Conditional Fields

Fields can show or hide based on the value of a select, radio-group, or boolean field. After defining all fields the scaffold asks:

Add conditional visibility to any fields? [y/N]: y

Which field should show/hide conditionally?
  1. layoutVariant  (select)
  2. visibleCards   (select)
  3. autoplay       (boolean)
  4. infiniteLoop   (boolean)

Pick field: 2

Based on which field:
  1. layoutVariant  (select)
  3. autoplay       (boolean)

Pick controlling field: 1

Condition type:
  1. === equals one value
  2. or  equals any of multiple values
  3. !== not equal to

Type: 1

Available values for "layoutVariant":
  1. Grid             → grid
  2. Carousel (Peek)  → carousel
  3. Carousel (Side)  → side-carousel

Select value: 2

✔ "visibleCards" shows when "layoutVariant" === carousel

Generated JSON:

{
  "component": "select",
  "name": "visibleCards",
  "label": "Visible Cards",
  "condition": {
    "===": [{ "var": "layoutVariant" }, "carousel"]
  }
}

OR condition (multiple values):

{
  "component": "boolean",
  "name": "autoplay",
  "condition": {
    "or": [
      { "===": [{ "var": "layoutVariant" }, "carousel"] },
      { "===": [{ "var": "layoutVariant" }, "side-carousel"] }
    ]
  }
}

Field Types Reference

| Type | Component | multi | Notes | |---|---|---|---| | text | text | ✔ | Single line | | textarea | text-area | ✖ | Multi-line plain text | | richtext | richtext | ✖ | WYSIWYG editor | | reference | reference | ✔ | DAM asset picker | | aem-content | aem-content | ✔ | Page / link picker | | aem-content-fragment | aem-content-fragment | ✖ | CF picker | | aem-experience-fragment | aem-experience-fragment | ✖ | XF picker | | boolean | boolean | ✖ | Toggle on/off | | select | select | ✖ | Single choice dropdown | | multiselect | multiselect | ✖ | Multi choice dropdown | | radio-group | radio-group | ✖ | Radio buttons | | checkbox-group | checkbox-group | ✖ | Checkboxes | | number | number | ✖ | Numeric input | | aem-tag | aem-tag | ✖ | cq:tags picker |


Validation Reference

| Property | Applies to | Where | |---|---|---| | required | all | top-level | | readOnly | all | top-level | | hidden | all | top-level | | description | all | top-level | | min / max / step | number | top-level | | minLength / maxLength | text, textarea | inside validation | | rootPath | aem-content, aem-content-fragment | inside validation | | customErrorMsg | all | inside validation |


After scaffolding

# 1. Add block to section filter (simple/container blocks only)
#    Open component-filters.json → add block name to section entry

# 2. Rebuild global JSON files
npm run build:json

# 3. Commit and push
git add .
git commit -m "feat: add carousel block"
git push

Section wrapper blocks automatically update models/_section.json. All other block types require a one-line manual edit to component-filters.json.


Why not Adobe's tools?

| Tool | What it does | Generates UE JSON | |---|---|---| | AEM Boilerplate XWalk | Project template | ✖ | | AEM CLI | Local dev server | ✖ | | Block Collection | Copy-paste library | ✖ | | GitHub Copilot | Code suggestions | Partial | | aem-eds-cli | Block scaffolding | ✔ Complete |


Requirements

  • Node.js 16+
  • AEM Edge Delivery Services project with Universal Editor setup
  • npm run build:json script (standard in AEM boilerplate)

License

Apache 2.0