N
Naveenr.dev
Chapter 06
16 min read•2026-06-30
📖 Edge Delivery Services SeriesChapter 06 · 27 chapters

Universal Editor, Models, and Component Definitions

Understanding how Universal Editor component definitions, models, and filters form the authoring contract around an EDS block, how to model content instead of HTML, and how that contract connects to the frontend implementation.

Content Objective

This chapter covers:

  • How component-definition.json, component-models.json, and component-filters.json form the authoring contract
  • Modeling content meaning instead of HTML structure
  • Choosing required versus optional fields and preventing invalid states
  • When rich text, image fields, and CTA models are appropriate
  • Using filters to govern where components can be placed
  • Treating model changes as interface changes that affect existing content
  • Debugging authoring configuration separately from frontend rendering

Introduction

By this point, we can build an EDS block. But there is still an important question:

How does an author create that block without touching HTML or JavaScript?

That is where Universal Editor configuration enters the picture. For the setup used in this series, three files are important:

text
component-definition.json
component-models.json
component-filters.json

When I first saw these files, it was tempting to think of them as configuration that simply makes a block appear in Universal Editor. That is only part of the story. Together, they define the authoring contract around the block. They answer three different questions:

  • What components can the author use?
  • What content can the author provide?
  • Where can those components be placed?

The frontend implementation then has to honor that contract. If the model says Hero has an image, title, description, and CTA, hero.js should understand the resulting content structure. If the model changes but the frontend assumptions do not, we have a contract mismatch. That relationship is what matters in this chapter.

1. Start With the Author's Experience

Before writing JSON, I first think about what the author is trying to create. For our Hero, the requirement is still simple: image, title, description, CTA.

From the author's perspective, I want something understandable. They should not need to know about hero.js, blocks/hero/, or how decorate(block) works. They should see something closer to a Hero with fields that describe the content they are responsible for.

That separation is important. Developers think about implementation. Authors should think about content.

2. The Authoring Contract

I think of the Universal Editor configuration as a contract between three sides.

The author needs to know: what can I create? The content model needs to define: what data does that component contain? The frontend needs to know: what structure should I expect?

Those three sides need to stay aligned. A model can be valid JSON and still be a poor component model. A block can have perfect JavaScript and still be difficult to author.

The goal is not simply to make Universal Editor load. The goal is to create a content contract that works for both authoring and frontend implementation.

3. The Three Configuration Files

For the XWalk setup in this series, the main component configuration is split across three files.

component-definition.json defines which components are available. component-models.json defines the fields and content structure associated with those components. component-filters.json defines where components can be inserted.

I keep those responsibilities separate when debugging. If Hero does not appear as an available component, I investigate component availability. If Hero appears but its fields are wrong, I investigate the model. If Hero exists but cannot be inserted into the expected container, I investigate the filter and placement rules. That is much faster than treating all Universal Editor problems as one configuration problem.

4. component-definition.json

The component definition tells Universal Editor that a component exists. A simplified example might look conceptually like:

json
{
  "title": "Hero",
  "id": "hero",
  "plugins": {
    "xwalk": {
      "page": {
        "resourceType": "core/franklin/components/block/v1/block",
        "template": {
          "name": "Hero",
          "model": "hero"
        }
      }
    }
  }
}

The exact structure should follow the project and current Universal Editor configuration being used. What I care about here is the responsibility:

This definition makes Hero a component that the authoring system understands.

It does not implement Hero's frontend behavior. That still belongs under blocks/hero/.

5. Component Identity Matters

A component usually has several names around it. For example:

text
Display title: Hero
Component ID: hero
Model ID: hero
Block folder: blocks/hero/

These values serve different purposes, but alignment makes the project easier to understand. I would avoid a setup where:

text
Component ID = marketingBanner
Model ID = featureHeader
Block folder = hero-v2
Display title = Campaign Masthead

unless there is a real reason for those differences. Technically, mappings may make such a setup possible. Operationally, it makes debugging harder. Consistent naming reduces the number of translations a developer has to make mentally.

6. component-models.json

The model defines the content fields the author works with. For our Hero, we might need fields representing image, title, description, CTA text/link depending on the chosen model, and an optional presentation variant. Conceptually:

json
{
  "id": "hero",
  "fields": [
    {
      "component": "reference",
      "name": "image",
      "label": "Image"
    },
    {
      "component": "text",
      "name": "title",
      "label": "Title"
    },
    {
      "component": "richtext",
      "name": "description",
      "label": "Description"
    }
  ]
}

The exact field configuration depends on the supported field types and the component design. The important part is that these fields describe content meaning.

7. Model Content, Not HTML

I would not model Hero like this:

text
Left column
Right column
Row 1
Row 2
Heading wrapper
Button container

Those names describe implementation structure. Instead:

text
Image
Title
Description
CTA

describes content. That distinction gives the frontend freedom to evolve. Today, the desktop Hero may display an image on the left and text on the right. Later, the design may become a full-width image with text overlay. If the model describes business content rather than HTML layout, we can redesign the frontend without redesigning the author's content structure unnecessarily.

8. Field Names Become Long-Lived Contracts

Field naming deserves more thought than it initially appears to. Suppose we create:

text
heading1
text2
url1

The implementation may work. Six months later, nobody remembers what those names mean. Prefer semantic names:

text
title
description
image
cta

or another naming scheme that clearly describes the content. The author may never see the internal field name. Developers will. Those names appear in configuration, debugging, migrations, and future model changes. Good naming costs almost nothing when the model is created and saves time later.

9. Required vs Optional Fields

One of the first model decisions is which content is mandatory. For example:

Hero fieldPossible rule
TitleRequired
ImageOptional or required depending on design
DescriptionOptional
CTAOptional

I don't make everything required just because the component looks best with all fields populated. A field should be required because the component does not make sense without it. If Hero fundamentally requires a title, enforce that through the authoring contract where possible. If the description is optional, the frontend must handle its absence. The model and frontend should agree about those rules.

10. The Model Should Prevent Invalid States

Suppose a block only supports Default and Dark as design variants. I would rather give the author a controlled choice than a free-text field where they can enter:

text
dark
Dark
DARK
black
night
blue-dark

Controlled fields reduce invalid states. The same principle applies to alignment, theme, approved layout variants, allowed component types, and repeated content limits. Good authoring configuration removes unnecessary decisions from the frontend.

11. Don't Expose Implementation Details

I would not give an author fields such as:

text
CSS class
DOM ID
Grid template columns
Margin top
JavaScript mode
API timeout

for a normal content block. Those are implementation details. The authoring model should expose business and content choices. For example:

text
Theme: Default | Dark

is meaningful. The implementation can decide that Dark maps to:

css
.hero.dark

The author does not need to know that class name.

12. Rich Text Needs a Clear Reason

A rich-text field gives authors flexibility. That flexibility also increases the number of DOM structures the frontend may receive. Suppose description is plain text. The possible structure is narrow. If it is rich text, it might contain multiple paragraphs, emphasis, links, lists, and headings depending on configuration.

Neither approach is automatically better. I choose based on the content requirement. If the Hero description should always be short supporting copy, unrestricted rich text may be unnecessary. If the component genuinely needs formatted editorial content, rich text makes sense. Field capability should match authoring need.

13. Image Fields Are Content Decisions Too

An image field looks simple, but there are still design questions: Is the image required? Does the author control alt text? Is the image decorative? Can the same component work without it? Do we need separate desktop/mobile images? Would one responsive image be enough?

I would not immediately expose:

text
Desktop image
Tablet image
Mobile image

just because the frontend has three breakpoints. That transfers responsive implementation responsibility to the author. Only expose multiple assets when there is a real content or art-direction requirement.

14. CTA Modeling

CTA fields also need a clear model. A CTA usually has at least a label and a destination. Depending on the project, those may be modeled as a link field or through supported component/model structures. What I avoid is turning CTA configuration into:

text
CTA text
CTA URL
CTA color
CTA font size
CTA padding
CTA icon position
CTA border width

The first two describe the CTA. The rest describe the design system. Keep those responsibilities separate.

15. Variants in the Authoring Model

The previous chapter discussed variants from the block side. Universal Editor is where an approved variant can become an authoring choice. Suppose Hero supports Default and Dark. The author can choose between those approved options. The frontend can then interpret that selection as the appropriate block style or variant.

The important rule remains:

A variant changes a controlled aspect of the same component.

If selecting a variant changes the content contract completely, it may be a separate component instead.

16. component-filters.json

Definitions answer: what components exist? Models answer: what content do they contain? Filters answer: where can they be placed?

This becomes important once the site has containers and component composition. I may want Hero to be allowed at page level but not nested inside Cards. Or I may want certain child components to be available only inside a specific container. Filters give us a governance boundary. Without placement rules, a flexible authoring environment can quickly allow combinations the frontend was never designed to support.

17. Authoring Freedom Needs Boundaries

Maximum author freedom sounds useful until the page can contain Hero inside Hero, Tabs inside Accordion inside Tabs, full-width banners inside card grids, or unsupported components inside specialized containers. The frontend may technically render some of these combinations. That does not mean they should be valid authoring states.

Filters help define supported composition. I think of them less as restrictions and more as part of the component contract. They tell authors:

These are the combinations this design system actually supports.

18. Definition, Model, Filter, and Block Are Separate Layers

This is one relationship worth keeping clear. For Hero, the definition makes Hero available to the author. The model defines Hero's editable content. The filter controls where Hero can be inserted. The block implementation turns the delivered Hero content into the frontend experience.

None of those layers replaces the others. That also means each can fail independently.

The Universal Editor component contract across four layers — component definition (available), component model (editable), component filter (placeable), and delivered block (rendered) — with the delivered block connecting to blocks/hero/hero.js and hero.css
The Universal Editor component contract across four layers — component definition (available), component model (editable), component filter (placeable), and delivered block (rendered) — with the delivered block connecting to blocks/hero/hero.js and hero.css

19. Debug Authoring and Frontend Separately

Suppose Hero appears in Universal Editor but does not render correctly on the page. That tells me something. The component definition probably exists. The problem may be in the delivered content structure, block discovery, hero.js, or hero.css.

Now suppose Hero works when already present on a page but does not appear in the component picker. That is a different problem. I inspect the component definition, filters, configuration loading, and authoring setup.

This separation prevents us from debugging hero.js for an authoring configuration issue.

20. A Model Change Is an Interface Change

Suppose Hero originally contains image, title, description, and cta. Later we add secondaryCta. That may look like a small authoring change. But the frontend may now receive a different structure. hero.js must be able to handle old Hero content without the new field and new Hero content with the field, depending on how existing content and the model evolve.

This is why I treat model changes as interface changes. They deserve the same care we give API contract changes.

21. Avoid Breaking Existing Content

Imagine we rename description to bodyCopy because the new name feels cleaner. Before making that change, I want to know: what existing content uses the old field? How is that content stored? Does the frontend depend on its current structure? Does migration need to happen? Can old and new content coexist during deployment?

Changing a field name is not automatically a harmless refactor. Once authors have created content, the model has state behind it.

22. Frontend Code Should Tolerate Model Evolution Where Reasonable

Suppose we add an optional secondary CTA. The frontend can handle both states:

javascript
const links = [...block.querySelectorAll('a')];

const [primaryCta, secondaryCta] = links;

if (primaryCta) {
  primaryCta.classList.add('button', 'primary');
}

if (secondaryCta) {
  secondaryCta.classList.add('button', 'secondary');
}

This does not mean the frontend should support every historical model forever. It means planned model evolution should not unnecessarily break existing pages. Model lifecycle is part of component architecture.

23. The Model Is Not the Final DOM

This is another distinction I keep in mind. The authoring model describes content. The block implementation deals with the delivered DOM. Those are related, but they are not the same representation.

For example, a model may describe:

text
Image
Title
Description
CTA

The browser may receive those values as:

html
<picture>...</picture>
<h2>...</h2>
<p>...</p>
<a href="...">...</a>

with rows and cells around them. I don't write frontend logic against an imagined JSON model unless that is actually what the block receives. I inspect the delivered DOM.

24. Authoring UX and Frontend UX Are Different

A field structure that is convenient for developers may be awkward for authors. Likewise, a completely unrestricted authoring model may be difficult for the frontend to support. We need both perspectives.

For example, suppose a Card needs image, title, description, and CTA. From a frontend perspective, four fields are easy. From an authoring perspective, we should also ask: are the labels clear? Is the field order natural? Which fields are required? Is the author likely to understand the variant choices? Can invalid combinations be prevented?

A good model reduces author mistakes instead of expecting frontend code to recover from all of them.

25. Field Order Matters

Even basic field ordering affects authoring usability. For Hero, this is understandable:

text
Title
Description
Image
CTA
Variant

A random order such as:

text
Variant
CTA URL
Image
Description
CTA Label
Title

may still work technically. But the form becomes harder to scan. I prefer grouping fields by the way an author thinks about the component rather than by how the JavaScript reads them.

26. Keep the Model Small

A component with 25 fields deserves a design review. It may genuinely need them. But I first ask whether several fields are implementation controls, whether the component is actually multiple components, whether variants have grown too far, whether content belongs to another referenced object, or whether some fields belong at page/site level.

Large models create large testing matrices. If five fields are optional, many content combinations become possible. The frontend then has to support those states. Authoring flexibility has a maintenance cost.

27. Reusable Model Pieces

Some content structures appear repeatedly — links, images, CTA patterns, shared page properties. Where the project supports reusable model definitions, reuse can keep configuration consistent. But I use the same rule as JavaScript utilities:

Reuse stable concepts, not coincidentally similar fields.

Two components both having a title does not necessarily mean they need a shared model abstraction. Reuse should simplify the contract, not make it harder to understand.

28. A Practical Hero Contract

For our Hero, I would keep the contract intentionally small.

FieldRequired?Ownership
TitleYesAuthor
DescriptionNoAuthor
ImageDepends on designAuthor
CTANoAuthor
VariantNoApproved design choice

The frontend owns layout, responsive behavior, typography implementation, CTA styling, image presentation, accessibility behavior, and supported variant implementation. The author owns the content. That is a clean boundary.

29. What I Would Not Put in the Hero Model

I would avoid:

text
Heading font size
Heading font weight
Description font size
Desktop padding
Mobile padding
Background hex color
CTA border radius
CTA font size
Image height
Image object position
Custom CSS class

Those options would make the Hero more configurable. They would also make it harder to maintain. If the design system changes, we would now have old content carrying presentation decisions from the previous design. Approved variants are usually easier to evolve.

30. Component Placement Is an Architecture Decision

Filters may look like a small authoring detail. At scale, they become part of site architecture. Suppose the project has Page, Section, Cards, Card, Tabs, and Tab. The authoring system needs to know which relationships are valid. For example:

text
Cards → Card
Tabs → Tab

may be supported relationships. But:

text
Card → Hero

may not be. If we leave every combination open, the frontend effectively has to support an unbounded component tree. Placement rules reduce that problem.

31. Universal Editor Configuration Should Be Reviewed With the Block

I would not review hero.js in one pull request and the Hero model in isolation without considering their relationship. For a component change, I want to understand model changes, definition changes, filter changes, block changes, and existing content impact.

Not every change touches all four. But they belong to the same component contract. That makes code review much more useful.

32. Troubleshooting: Component Missing From the Picker

If Hero is missing from Universal Editor, I check the authoring side first. Questions include: is the component definition present? Is its identifier correct? Is the configuration valid? Is the component allowed in the current location? Is the expected configuration version actually loaded?

I do not start by changing blocks/hero/hero.js, because block JavaScript does not decide whether Hero appears in the authoring component picker.

33. Troubleshooting: Fields Are Missing

Suppose Hero appears, but Title or Image is missing from its properties. Now the component exists, so I move toward the model. I check the model identifier, the component-to-model relationship, the field definition, the supported field type, the configuration syntax, and whether the latest configuration is being used. Again, that is different from a frontend rendering problem.

34. Troubleshooting: Component Cannot Be Added

Suppose Hero appears in the system but cannot be inserted into the expected container. That points toward placement configuration. I check the filter rules and the current parent/container. The useful debugging question is:

Is the component unavailable, or is it unavailable here?

Those are different failures.

35. Troubleshooting: Authoring Works but Page Is Wrong

Now suppose Hero can be inserted, fields can be edited, and content can be saved, but the frontend looks wrong. The authoring contract has already passed several checks. I move forward through the delivery flow: was the expected content delivered? What DOM did the browser receive? Was Hero discovered? Did hero.js load? Did decorate(block) run? Did hero.css load?

This is where the debugging model from the project structure and content flow chapter becomes useful again.

36. Developer Perspective

For me, the biggest improvement is treating Universal Editor configuration as part of component development rather than separate setup work. A block is not complete just because blocks/hero/hero.js works. For an authorable component, I also need to understand how the author finds it, which fields they see, which values are valid, where they can place it, and what structure reaches the frontend. That is the complete developer contract.

37. AEM Developer Perspective

Traditional AEM developers already know the idea behind authoring contracts. We have worked with component dialogs, policies, allowed components, Sling Models, and HTL. The Universal Editor implementation uses different files and a different frontend model, but some architectural questions are familiar: What can the author edit? What is required? What components are allowed here? Who owns validation? What does the frontend expect?

I would reuse that architectural thinking without trying to force Universal Editor into the traditional AEM component implementation model.

38. Architect Perspective

At architecture level, component models become governance. If every developer independently decides field naming, CTA structure, variant naming, required fields, placement rules, and rich-text capabilities, the project will eventually contain several different ways to model the same idea.

For an enterprise project, I would define conventions for naming, labels, field types, required fields, reusable field patterns, variants, placement, model evolution, and backward compatibility. The goal is not to create a huge modeling framework. It is to prevent avoidable inconsistency.

39. What I Learned From Connecting Authoring to the Block

Building the Hero showed me the frontend boundary. Adding the Universal Editor model makes the other side visible. The author does not create DOM. The author provides content through a controlled model. That content is stored and delivered. The browser receives a structure derived from that content. The block implementation then enhances it.

Once I treat the model and the block as two sides of the same contract, several design decisions become easier: required versus optional content, naming, variants, rich text, placement, and backward compatibility. The JSON configuration is only the implementation mechanism. The real design work is defining a stable authoring contract.

Key Takeaways

  • Universal Editor configuration is part of the component contract, not just setup.
  • Component definitions describe which components are available.
  • Component models describe the content authors can provide.
  • Component filters control where components can be placed.
  • Authoring configuration and block implementation solve different parts of the same component.
  • Use semantic field names instead of implementation-oriented names.
  • Model content meaning rather than HTML structure.
  • Required fields should represent genuinely required content.
  • Optional fields must be handled by the frontend.
  • Controlled choices are safer than free-text configuration for approved variants.
  • Avoid exposing raw CSS and implementation details to authors.
  • Use rich text only when the content requirement needs that flexibility.
  • Do not create breakpoint-specific authoring fields without a real content or art-direction requirement.
  • CTA models should describe the CTA, not its CSS.
  • Filters are part of authoring governance.
  • Model changes should be treated as interface changes.
  • Renaming fields can affect existing content and should not be treated as a harmless refactor.
  • The authoring model is not the same thing as the final browser DOM.
  • Authoring UX and frontend UX both matter.
  • Large models create larger testing and maintenance costs.
  • Review model, definition, filter, and block changes as parts of one component contract.
  • Debug authoring configuration separately from frontend rendering.
  • At scale, component-model conventions become part of architecture governance.

Next Steps

We now understand both sides of an authorable block: Universal Editor defines the content contract, and the block implementation consumes the delivered structure.

The next chapter, Understanding the EDS Block DOM, focuses on exactly where many implementation bugs start — assumptions about the delivered DOM. It works through block.children, rows and cells, images delivered as <picture>, links, optional content, and the extra states an API-backed block moves through, plus the Universal Editor re-decoration gotchas we hit on the POC.

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.