Integrating with Adobe Experience Manager
Adobe Experience Manager (AEM) is an enterprise-grade content management system. This guide covers multiple approaches for integrating Needle Engine 3D content into AEM, from simple iframe embeds to advanced component-based integrations that content authors can manage.
Overview: Integration Approaches
Choose the approach that fits your AEM setup and authoring requirements:
| Approach | Best For | Author-Friendly | Technical Complexity |
|---|---|---|---|
| iframe Embed | Quick integration, isolated content | ⭐⭐ | Low |
| AEM Component | Authorable content, reusability | ⭐⭐⭐⭐ | Medium |
| Direct Integration | Maximum control, custom layouts | ⭐⭐⭐ | Medium-High |
| Experience Fragment | Multi-channel content, reusability | ⭐⭐⭐⭐⭐ | Medium |
Approach 1: iframe Embedding
Best for: Quick integration when you have limited access to AEM development resources.
This is the simplest approach and works with any AEM setup, including AEM as a Cloud Service.
Steps
Deploy your Needle project to Needle Cloud or your preferred hosting platform
Get your project URL
https://your-project.needle.run/Add iframe to AEM page using one of these methods:
Option A: Using the Embed Component (AEM Core Components)
If your AEM site uses Core Components:
- Drag the "Embed" component onto your page
- Select "Embeddable" type
- Paste your Needle project URL
- Configure size and other options
Option B: Using HTML in Text Component
<iframe src="https://your-project.needle.run/" allow="xr; xr-spatial-tracking; fullscreen; accelerometer; gyroscope" style="width: 100%; height: 600px; border: none;" allowfullscreen> </iframe>
Get Embed Code from Needle Cloud
Needle Cloud provides ready-to-use embed code. Open your project, click "Share", and copy the iframe snippet.
iframe Considerations
Pros:
- ✅ No AEM development required
- ✅ Works with any AEM version
- ✅ Easy updates (just redeploy Needle project)
- ✅ Content isolation
Cons:
- ❌ Limited integration with page content
- ❌ Less flexible sizing options
- ❌ Authors can't edit 3D content properties in AEM
Approach 2: Custom AEM Component
Best for: Content authors who need to configure and manage 3D experiences directly in AEM.
Create a custom AEM component that allows authors to add and configure Needle Engine content through AEM's authoring interface.
Architecture Overview
Unity/Blender → Needle Engine → Deploy → AEM Component → Author configuresImplementation Steps
1. Create the AEM Component
Create a new component in your AEM project:
Component Structure:
/apps/myproject/components/content/needle-engine/
├── .content.xml
├── _cq_dialog.xml
├── needle-engine.html
└── clientlibs/
├── .content.xml
├── css.txt
├── js.txt
└── js/
└── needle-loader.js.content.xml (Component Definition):
<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:cq="http://www.day.com/jcr/cq/1.0"
xmlns:jcr="http://www.jcp.org/jcr/1.0"
jcr:primaryType="cq:Component"
jcr:title="Needle Engine 3D"
jcr:description="Embeds a Needle Engine 3D experience"
componentGroup="My Project - Content"/>2. Create the Author Dialog
_cq_dialog.xml (Author Interface):
<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0"
xmlns:cq="http://www.day.com/jcr/cq/1.0"
xmlns:jcr="http://www.jcp.org/jcr/1.0"
xmlns:nt="http://www.jcp.org/jcr/nt/1.0"
jcr:primaryType="nt:unstructured"
jcr:title="Needle Engine Configuration"
sling:resourceType="cq/gui/components/authoring/dialog">
<content
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/container">
<items jcr:primaryType="nt:unstructured">
<tabs
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/tabs">
<items jcr:primaryType="nt:unstructured">
<basic
jcr:primaryType="nt:unstructured"
jcr:title="Basic Settings"
sling:resourceType="granite/ui/components/coral/foundation/container">
<items jcr:primaryType="nt:unstructured">
<projectUrl
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/textfield"
fieldLabel="Project URL"
fieldDescription="URL to your deployed Needle Engine project"
name="./projectUrl"
required="{Boolean}true"/>
<integrationMode
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/select"
fieldLabel="Integration Mode"
name="./integrationMode">
<items jcr:primaryType="nt:unstructured">
<iframe
jcr:primaryType="nt:unstructured"
text="iframe (Isolated)"
value="iframe"
selected="{Boolean}true"/>
<webcomponent
jcr:primaryType="nt:unstructured"
text="Direct (Integrated)"
value="webcomponent"/>
</items>
</integrationMode>
<height
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/textfield"
fieldLabel="Height"
fieldDescription="Height in pixels (e.g., 600)"
name="./height"
value="600"/>
<enableAR
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/checkbox"
fieldDescription="Enable AR/VR features"
name="./enableAR"
text="Enable AR/VR"
value="true"/>
<autoplay
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/checkbox"
fieldDescription="Start animations automatically"
name="./autoplay"
text="Autoplay"
value="true"
checked="{Boolean}true"/>
</items>
</basic>
<advanced
jcr:primaryType="nt:unstructured"
jcr:title="Advanced"
sling:resourceType="granite/ui/components/coral/foundation/container">
<items jcr:primaryType="nt:unstructured">
<backgroundColor
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/textfield"
fieldLabel="Background Color"
fieldDescription="Hex color or 'transparent' (e.g., #ffffff)"
name="./backgroundColor"
value="transparent"/>
<loadingStyle
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/select"
fieldLabel="Loading Style"
name="./loadingStyle">
<items jcr:primaryType="nt:unstructured">
<default
jcr:primaryType="nt:unstructured"
text="Default"
value="default"
selected="{Boolean}true"/>
<light
jcr:primaryType="nt:unstructured"
text="Light"
value="light"/>
<dark
jcr:primaryType="nt:unstructured"
text="Dark"
value="dark"/>
</items>
</integrationMode>
</items>
</advanced>
</items>
</tabs>
</items>
</content>
</jcr:root>3. Create the HTL Template
needle-engine.html (Component Rendering):
<sly data-sly-use.clientlib="/libs/granite/sightly/templates/clientlib.html">
<sly data-sly-call="${clientlib.js @ categories='myproject.needle-engine'}"/>
</sly>
<div class="needle-engine-wrapper"
data-project-url="${properties.projectUrl}"
data-integration-mode="${properties.integrationMode || 'iframe'}"
data-height="${properties.height || '600'}"
data-enable-ar="${properties.enableAR}"
data-autoplay="${properties.autoplay}"
data-background-color="${properties.backgroundColor}"
data-loading-style="${properties.loadingStyle}">
<sly data-sly-test="${properties.integrationMode == 'webcomponent'}">
<!-- Direct Web Component Integration -->
<script type="module" src="https://cdn.jsdelivr.net/npm/@needle-tools/engine/dist/needle-engine.min.js"></script>
<needle-engine
src="${properties.projectUrl}"
background-color="${properties.backgroundColor || 'transparent'}"
loading-style="${properties.loadingStyle || 'default'}"
style="width: 100%; height: ${properties.height}px;">
</needle-engine>
</sly>
<sly data-sly-test="${properties.integrationMode != 'webcomponent'}">
<!-- iframe Integration -->
<iframe
src="${properties.projectUrl}"
allow="xr; xr-spatial-tracking; fullscreen; accelerometer; gyroscope; camera; microphone"
style="width: 100%; height: ${properties.height}px; border: none;"
allowfullscreen>
</iframe>
</sly>
</div>
<sly data-sly-test="${wcmmode.edit}">
<div class="needle-engine-placeholder" style="background: #f5f5f5; padding: 20px; text-align: center; border: 2px dashed #ccc;">
<p><strong>Needle Engine 3D Experience</strong></p>
<p style="font-size: 0.9em; color: #666;">
<sly data-sly-test="${properties.projectUrl}">
URL: ${properties.projectUrl}
</sly>
<sly data-sly-test="${!properties.projectUrl}">
Configure this component to add a 3D experience
</sly>
</p>
</div>
</sly>4. Add Client Library (Optional for Enhanced Features)
clientlibs/.content.xml:
<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:cq="http://www.day.com/jcr/cq/1.0"
xmlns:jcr="http://www.jcp.org/jcr/1.0"
jcr:primaryType="cq:ClientLibraryFolder"
categories="[myproject.needle-engine]"
embed="[core.wcm.components.commons.site.link]"/>clientlibs/js/needle-loader.js (Optional enhancements):
(function() {
'use strict';
// Add responsive behavior or custom initialization
document.addEventListener('DOMContentLoaded', function() {
const wrappers = document.querySelectorAll('.needle-engine-wrapper');
wrappers.forEach(function(wrapper) {
// Add responsive height adjustments
const iframe = wrapper.querySelector('iframe');
const webComponent = wrapper.querySelector('needle-engine');
if (iframe || webComponent) {
// Custom initialization logic here
console.log('Needle Engine component initialized');
}
});
});
})();5. Deploy and Use
- Deploy the component to your AEM instance
- Add component to page template policy (if using editable templates)
- Authors can now:
- Drag the "Needle Engine 3D" component onto pages
- Configure project URL and options through the dialog
- Preview in edit mode
- Publish with the page
Content Workflow
Recommended workflow: 3D artists update content in Unity/Blender → Export via Needle → Deploy to Needle Cloud → AEM authors update URL in component → Publish
Approach 3: Direct Web Component Integration
Best for: Custom AEM templates where you want full control over layout and integration with existing page JavaScript.
This approach embeds Needle Engine directly into AEM templates without using iframes.
Steps
Add Needle Engine to your AEM clientlib
/apps/myproject/clientlibs/clientlib-site/js.txt:#base=js needle-engine.js/apps/myproject/clientlibs/clientlib-site/js/needle-engine.js:// Load Needle Engine from CDN const script = document.createElement('script'); script.type = 'module'; script.src = 'https://cdn.jsdelivr.net/npm/@needle-tools/engine/dist/needle-engine.min.js'; document.head.appendChild(script);Add to your page template (HTL):
<div class="container"> <div class="row"> <div class="col-md-6"> <!-- Your AEM content --> <h1>${properties.title}</h1> <p>${properties.description}</p> </div> <div class="col-md-6"> <!-- Needle Engine content --> <needle-engine src="https://your-project.needle.run/assets/scene.glb" background-color="transparent" style="width: 100%; height: 500px;"> </needle-engine> </div> </div> </div>For dynamic content, use Sling Models:
Java Sling Model:
@Model(adaptables = Resource.class) public class NeedleEngineModel { @ValueMapValue private String projectUrl; @ValueMapValue private String backgroundColor; @ValueMapValue private Integer height; public String getProjectUrl() { return projectUrl; } public String getBackgroundColor() { return backgroundColor != null ? backgroundColor : "transparent"; } public Integer getHeight() { return height != null ? height : 600; } }HTL Template:
<sly data-sly-use.model="com.myproject.models.NeedleEngineModel"> <needle-engine src="${model.projectUrl}" background-color="${model.backgroundColor}" style="width: 100%; height: ${model.height}px;"> </needle-engine> </sly>
Approach 4: Experience Fragments
Best for: Reusable 3D content across multiple pages or channels (email, mobile app, etc.).
Experience Fragments let you create reusable Needle Engine content that can be shared across pages and exported to other channels.
Steps
Create an Experience Fragment template with Needle Engine component
Content authors can:
- Create an Experience Fragment with 3D content
- Configure the Needle Engine component once
- Reuse across multiple pages
- Export to Adobe Target for personalization
- Use in email campaigns or mobile apps
Reference the fragment on pages:
<sly data-sly-resource="${'/content/experience-fragments/myproject/needle-3d-hero' @ resourceType='cq/experience-fragments/components/experiencefragment'}"></sly>
Multi-Channel Content
Experience Fragments are especially powerful when you need the same 3D content across web, mobile apps, and email campaigns. Configure once, use everywhere.
Advanced Integration: Communication Between AEM and Needle
For advanced use cases where AEM content needs to interact with Needle Engine (e.g., clicking an AEM button triggers an animation in the 3D scene).
Example: Triggering 3D Animations from AEM
AEM Component JavaScript:
// In your AEM component's clientlib
document.querySelector('.my-aem-button').addEventListener('click', function() {
// Get reference to Needle Engine
const needleEngine = document.querySelector('needle-engine');
if (needleEngine && needleEngine.context) {
// Dispatch custom event that Needle scripts can listen to
const event = new CustomEvent('aem-trigger', {
detail: { action: 'playAnimation', animationName: 'Rotate' }
});
needleEngine.dispatchEvent(event);
}
});Needle Engine Component (TypeScript):
import { Behaviour } from "@needle-tools/engine";
export class AEMIntegration extends Behaviour {
start() {
// Listen for events from AEM
this.context.domElement.addEventListener('aem-trigger', (evt: CustomEvent) => {
const { action, animationName } = evt.detail;
if (action === 'playAnimation') {
this.playAnimation(animationName);
}
});
}
private playAnimation(name: string) {
// Your animation logic here
console.log('Playing animation:', name);
}
}Asset Management Considerations
Where to Host Needle Engine Projects
| Option | Best For | Pros | Cons |
|---|---|---|---|
| Needle Cloud | Quick iteration, dev/staging | Fast deployment, preview sharing | May want custom domain for production |
| AEM DAM | Enterprise compliance, single platform | Integrated with AEM, compliance | Larger asset sizes, may need CDN |
| Separate CDN | Performance-critical apps | Fast global delivery, cache control | Additional infrastructure |
| AEM Publish | Simple setups | No extra infrastructure | Not optimized for 3D assets |
Recommended Setup
- Development: Host on Needle Cloud for rapid iteration
- Staging: Host on AEM staging environment or dedicated CDN
- Production: Host on CDN (CloudFront, Akamai, etc.) with AEM URLs stored in component properties
Content Author Workflow
A typical workflow for content authors:
Receive 3D content from design/development team
- Deployed to Needle Cloud or CDN
- Provided with URL
Add to AEM page
- Drag Needle Engine component onto page
- Configure URL and options in dialog
- Preview in edit mode
Adjust properties as needed
- Height, background color, AR features
- Integration mode (iframe vs direct)
Publish the page
For Content Authors
The Needle Engine component works like any other AEM component. Just paste the URL of your 3D experience and configure the display options. No coding required!
Performance Best Practices
Use CDN for assets
- Host
.glbfiles and textures on a CDN - Leverage AEM Dispatcher caching for static resources
- Host
Lazy loading
- Load Needle Engine only when the component is visible
- Use Intersection Observer API
Optimize 3D content
- Keep
.glbfiles under 10MB when possible - Use texture compression
- See optimization guide
- Keep
AEM Dispatcher configuration
# Allow caching of Needle Engine assets /cache { /rules { /0001 { /glob "*.glb" /type "allow" } /0002 { /glob "*.bin" /type "allow" } } }
Security Considerations
Content Security Policy (CSP)
Add Needle Engine domains to your AEM CSP:
Content-Security-Policy: script-src 'self' https://cdn.jsdelivr.net; connect-src 'self' https://needle.run https://*.needle.run; worker-src 'self' blob:;Permissions for XR Features
Ensure your AEM pages allow necessary permissions:
<meta http-equiv="Permissions-Policy" content=" xr-spatial-tracking=*, accelerometer=*, gyroscope=*, camera=* ">Asset Access Control
- Use AEM's built-in authentication if hosting on AEM
- Consider signed URLs for sensitive 3D content
Troubleshooting
Component not appearing in edit mode
Check:
- Component is added to page template policy
- Component group is correct
- Author has necessary permissions
3D content not loading
Check:
- Project URL is accessible (open in browser)
- CORS headers are set correctly on hosting server
- CSP allows loading from external domains
- Browser console for errors
Performance issues
Check:
- File sizes of
.glbfiles - Number of polygons in 3D models
- Texture resolutions
- Run optimization guide
Example: Complete Integration
Here's a complete example combining multiple approaches:
Your AEM Site
├── Page Template
│ ├── Header (standard AEM)
│ ├── Hero with Needle 3D (Custom Component)
│ ├── Feature Grid (standard AEM)
│ └── Product Showcase (Experience Fragment with Needle)
│
└── Content Structure
├── /content/mysite/en/products
│ ├── product-a (uses inline Needle component)
│ └── product-b (uses shared Experience Fragment)
│
└── /content/experience-fragments/mysite
└── product-3d-viewer (reusable across site)Next Steps
Learn More:
- Embedding Guide – Additional embedding techniques
- Web Component Attributes – Configure Needle Engine
- Needle Cloud – Quick deployment platform
Get Help: