Using AI Tools for AEM Development

 

Using AI Tools for AEM Development: Code Faster with Copilot & Claude

Introduction

AEM development has always required deep expertise — Sling models, OSGi configurations, HTL templates, Oak queries, and dispatcher rules all have their own nuances. AI coding assistants like GitHub Copilot, Claude, and ChatGPT now understand AEM well enough to dramatically speed up day-to-day development tasks.

In this post, we'll look at practical examples of how to use AI tools effectively for AEM development — including the right prompts that produce high-quality AEM code.


       

1. Writing Sling Models with AI

Prompt that works well:

Write an AEM Sling Model for a component called "HeroComponent".
It should:
- Be adaptable from Resource
- Inject title, description, ctaLabel, ctaLink from the component's JCR properties
- Use @PostConstruct to build a full link with a .html extension
- Include @Optional annotations where appropriate
- Follow AEM best practices with proper null checks

AI-Generated Output (Claude / Copilot):

package com.yourproject.core.models;

import com.day.cq.wcm.api.Page;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.models.annotations.Default;
import org.apache.sling.models.annotations.Model;
import org.apache.sling.models.annotations.Optional;
import org.apache.sling.models.annotations.injectorspecific.ScriptVariable;
import org.apache.sling.models.annotations.injectorspecific.ValueMapValue;
import javax.annotation.PostConstruct;

@Model(
    adaptables = { SlingHttpServletRequest.class, Resource.class },
    adapters = HeroComponent.class,
    resourceType = "your-project/components/hero"
)
public class HeroComponentImpl implements HeroComponent {

    @ValueMapValue
    @Optional
    private String title;

    @ValueMapValue
    @Optional
    private String description;

    @ValueMapValue
    @Optional
    private String ctaLabel;

    @ValueMapValue
    @Optional
    private String ctaLink;

    @ScriptVariable
    @Optional
    private Page currentPage;

    private String resolvedCtaLink;

    @PostConstruct
    protected void init() {
        if (ctaLink != null && !ctaLink.isEmpty()) {
            // Append .html for internal AEM links only
            resolvedCtaLink = ctaLink.startsWith("/content")
                ? ctaLink + ".html"
                : ctaLink;
        }
    }

    @Override
    public String getTitle() { return title; }

    @Override
    public String getDescription() { return description; }

    @Override
    public String getCtaLabel() { return ctaLabel != null ? ctaLabel : "Learn More"; }

    @Override
    public String getCtaLink() { return resolvedCtaLink; }
}

2. Generating OSGi Configurations with AI

Prompt:

Generate an AEM OSGi configuration class for a scheduler that:
- Runs every 30 minutes
- Calls a method cleanupExpiredSessions()
- Can be enabled/disabled via OSGi config
- Is properly annotated for AEM as a Cloud Service

AI Output:

@Component(service = Runnable.class, immediate = true)
@Designate(ocd = SessionCleanupScheduler.Config.class)
public class SessionCleanupScheduler implements Runnable {

    private static final Logger log =
        LoggerFactory.getLogger(SessionCleanupScheduler.class);

    @ObjectClassDefinition(name = "Session Cleanup Scheduler")
    public @interface Config {
        @AttributeDefinition(name = "Enabled", description = "Enable/disable scheduler")
        boolean enabled() default true;

        @AttributeDefinition(
            name = "Cron Expression",
            description = "Scheduler expression (default: every 30 min)"
        )
        String scheduler_expression() default "0 0/30 * * * ?";
    }

    private boolean enabled;

    @Activate
    protected void activate(Config config,
                            BundleContext bundleContext,
                            Map<String, Object> properties) {
        this.enabled = config.enabled();

        if (enabled) {
            Scheduler scheduler = // inject via @Reference
            ScheduleOptions opts = scheduler.EXPR(config.scheduler_expression());
            opts.name("SessionCleanupJob");
            opts.canRunConcurrently(false);
            scheduler.schedule(this, opts);
        }
    }

    @Override
    public void run() {
        if (!enabled) return;
        log.info("Running session cleanup...");
        cleanupExpiredSessions();
    }

    private void cleanupExpiredSessions() {
        // Your cleanup logic here
        log.info("Expired sessions cleaned up.");
    }
}

3. Debugging AEM Log Files with AI

Paste an AEM error log into Claude or ChatGPT with this prompt:

Prompt:

I'm getting this error in my AEM logs. I'm running AEM as a Cloud Service.
Explain what's causing it and give me the exact fix:

[ERROR] com.day.cq.wcm.core.impl.page.PageImpl Error while getting
page properties for /content/mysite/en
org.apache.sling.api.resource.LoginException: Cannot login:
javax.jcr.LoginException: Impersonation not allowed for system user

What AI Returns:

A clear explanation: the service user doesn't have the correct impersonation permissions, along with the exact repoinit script to fix it:

create service user my-service-user
set ACL for my-service-user
    allow jcr:read on /content
    allow rep:impersonateWith on /home/users/system/my-service-user
end

4. Writing Oak Queries with AI

Oak SQL-2 queries are notoriously tricky. AI can write and optimize them instantly.

Prompt:

Write an AEM Oak SQL-2 query to find all cq:Page nodes under
/content/mysite that have a property "brand" equal to "nike"
and were last modified in the last 7 days.
Also show me the Java code to execute it with QueryManager.

AI Output:

SELECT * FROM [cq:Page] AS page
WHERE ISDESCENDANTNODE(page, '/content/mysite')
AND page.[jcr:content/brand] = 'nike'
AND page.[jcr:content/cq:lastModified] > CAST('2026-04-02T00:00:00.000Z' AS DATE)
@Reference
private ResourceResolverFactory resolverFactory;

public List<String> findBrandPages() throws RepositoryException, LoginException {
    List<String> results = new ArrayList<>();
    Map<String, Object> param = new HashMap<>();
    param.put(ResourceResolverFactory.SUBSERVICE, "queryService");

    try (ResourceResolver resolver =
             resolverFactory.getServiceResourceResolver(param)) {

        Session session = resolver.adaptTo(Session.class);
        QueryManager qm = session.getWorkspace().getQueryManager();

        String sql = "SELECT * FROM [cq:Page] AS page "
            + "WHERE ISDESCENDANTNODE(page, '/content/mysite') "
            + "AND page.[jcr:content/brand] = 'nike' "
            + "AND page.[jcr:content/cq:lastModified] > "
            + "CAST('" + getSevenDaysAgo() + "' AS DATE)";

        Query query = qm.createQuery(sql, Query.JCR_SQL2);
        query.setLimit(100); // Always limit Oak queries!

        NodeIterator it = query.execute().getNodes();
        while (it.hasNext()) {
            results.add(it.nextNode().getPath());
        }
    }
    return results;
}

5. Writing Unit Tests with AI

Prompt:

Write a JUnit 5 test for the HeroComponentImpl Sling Model above.
Use AEM Mocks (io.wcm.testing.mock.aem). Test:
1. All properties are correctly injected
2. CTA link gets .html appended for /content paths
3. Default CTA label is "Learn More" when not set

AI Output:

@ExtendWith(AemContextExtension.class)
class HeroComponentImplTest {

    private final AemContext ctx = new AemContext();

    @BeforeEach
    void setUp() {
        ctx.addModelsForClasses(HeroComponentImpl.class);
    }

    @Test
    void testAllPropertiesInjected() {
        ctx.currentResource(ctx.create().resource(
            "/content/test/hero",
            "sling:resourceType", "your-project/components/hero",
            "title", "Welcome to AEM",
            "description", "Your content platform",
            "ctaLabel", "Get Started",
            "ctaLink", "/content/mysite/en"
        ));

        HeroComponent model = ctx.request().adaptTo(HeroComponent.class);

        assertNotNull(model);
        assertEquals("Welcome to AEM", model.getTitle());
        assertEquals("Your content platform", model.getDescription());
        assertEquals("Get Started", model.getCtaLabel());
        assertEquals("/content/mysite/en.html", model.getCtaLink());
    }

    @Test
    void testDefaultCtaLabel() {
        ctx.currentResource(ctx.create().resource(
            "/content/test/hero2",
            "sling:resourceType", "your-project/components/hero",
            "title", "Test"
        ));

        HeroComponent model = ctx.request().adaptTo(HeroComponent.class);
        assertEquals("Learn More", model.getCtaLabel());
    }

    @Test
    void testExternalLinkNotModified() {
        ctx.currentResource(ctx.create().resource(
            "/content/test/hero3",
            "sling:resourceType", "your-project/components/hero",
            "ctaLink", "https://external.com/page"
        ));

        HeroComponent model = ctx.request().adaptTo(HeroComponent.class);
        assertEquals("https://external.com/page", model.getCtaLink());
    }
}

6. Best Prompt Patterns for AEM Development

Task Prompt Pattern
Sling Model "Write a Sling Model for [component] that injects [fields] and does [logic]. Follow AEM best practices."
OSGi Config "Generate an OSGi config for [service]. It should support [feature] and be compatible with AEMaaCS."
HTL Template "Write an HTL (Sightly) template for [component] using the Sling Model [ModelName]. Include proper data-sly-use and null checks."
Dispatcher Rule "Write an AEM Dispatcher filter rule to [requirement]. Use dispatcher.any syntax."
Debug Error "Explain this AEM error and give the fix: [paste stack trace]"
Oak Query "Write an Oak SQL-2 query to [find X] under [path]. Include Java code to execute it with QueryManager."
Unit Test "Write JUnit 5 tests for [class] using AEM Mocks. Test [scenario 1], [scenario 2]."

Key Takeaways

  • AI tools are most effective for AEM when you provide context — mention AEMaaCS vs AEM 6.x, the component type, and expected behavior.
  • Always review AI-generated Oak queries — add limits and check that properties are indexed to avoid full repository traversal.
  • Use AI to generate test scaffolding — it's one of the biggest time-savers in AEM development.
  • Keep a prompt library for your team — reusable prompts for common AEM tasks dramatically improve consistency.

Comments

Popular Posts

Configure/Decoding AEM AuditLogs

How to Increase Apache Request Per Second ?

AdobeDispatcherHacks ".statfile"

how to clear dispatcher cache in aem ?

How Does S3 works with AEM ?

AEM Security Headers

How to Sync HMAC in AEM ?

OakAccess0000: Access denied

AEM ACL and how they are evaluated

Dispatcher flush from AEM UI