Visual Editor
The visual editor allows content editors to click and edit content directly on your site, with changes saving to Aether’s database in real-time.
How It Works
Section titled “How It Works”sequenceDiagram participant Editor as Visual Editor participant SDK as Aether SDK participant Site as Your Site participant API as Aether API
Editor->>Site: Load in iframe (?aether-editor=true) Site->>SDK: Initialize SDK SDK->>SDK: Scan for data-aether-field SDK->>Site: Highlight editable fields Editor->>SDK: User clicks field SDK->>Editor: Send field metadata Editor->>Editor: Show edit panel Editor->>API: Save changes API->>SDK: Content updated SDK->>Site: Update DOM1. Initialize the SDK
Section titled “1. Initialize the SDK”The SDK must be initialized when your site loads in editor mode:
<script> import { AetherSDK } from '@aether-official/sites-sdk';
// Check if in editor mode const params = new URLSearchParams(window.location.search); if (params.get('aether-editor') === 'true') { const sdk = new AetherSDK({ siteId: 'YOUR_SITE_ID', organizationId: 'YOUR_ORG_ID', editorOrigin: 'https://app.aether.com', debug: true, // Enable logging });
sdk.on('ready', () => { console.log('SDK ready for editing!'); }); }</script>2. Use Helper API (Recommended)
Section titled “2. Use Helper API (Recommended)”The helper API automatically adds all required data-aether-* attributes:
---import { fetch, aether, getConfig } from '@aether-official/sites-sdk/astro';import { SECTIONS } from '../aether-types';
const config = getConfig(import.meta.env);const section = await fetch.section(SECTIONS.HERO, config);const content = section.data;
// Create helperconst helper = aether.section(SECTIONS.HERO);---
<!-- Helper automatically adds data-aether-section --><section {...helper.section()}>
<!-- Helper adds data-aether-field + section ID --> <h1 {...helper.field('title')}> {content.title} </h1>
<p {...helper.field('subtitle')}> {content.subtitle} </p>
</section>Benefits:
- ✅ ~60% less boilerplate
- ✅ Type-safe field names
- ✅ Automatic attribute generation
- ✅ Consistent naming
Manual Annotation (Alternative)
Section titled “Manual Annotation (Alternative)”You can also manually add attributes if needed:
<section data-aether-section="hero-section-123"> <h1 data-aether-field="hero.title"> Welcome to Our Site </h1></section>Annotations Reference
Section titled “Annotations Reference”Section Containers
Section titled “Section Containers”Every editable field must be within a section:
<div data-aether-section="SECTION_ID"> <!-- Editable fields go here --></div>The SECTION_ID must match a PageSection.id in your Aether database.
Field Types
Section titled “Field Types”Text Field (Single Line)
Section titled “Text Field (Single Line)”<h1 data-aether-field="hero.title"> Content here</h1>Textarea (Multi-line)
Section titled “Textarea (Multi-line)”<p data-aether-field="hero.description" data-aether-field-type="textarea"> Multi-line content here</p>Image Field
Section titled “Image Field”<img src="/path/to/image.jpg" data-aether-field="hero.image" data-aether-field-type="image" alt="Hero image"/>Field Paths
Section titled “Field Paths”Use dot notation to structure your content:
<!-- Top-level field --><h1 data-aether-field="title">Main Title</h1>
<!-- Nested object --><h2 data-aether-field="hero.subtitle">Hero Subtitle</h2>
<!-- Deep nesting --><a href="#" data-aether-field="hero.cta.url"> <span data-aether-field="hero.cta.text">Click Here</span></a>Resulting JSON structure:
{ "title": "Main Title", "hero": { "subtitle": "Hero Subtitle", "cta": { "url": "#", "text": "Click Here" } }}Repeater Fields
Section titled “Repeater Fields”Repeaters allow editing arrays of content (team members, features, etc.):
<div data-aether-section="team-section" data-aether-repeater="team.members"> <!-- Each item --> <div data-aether-repeater-item="0"> <img src="/alice.jpg" data-aether-field="team.members.0.photo" /> <h3 data-aether-field="team.members.0.name"> Alice Johnson </h3> <p data-aether-field="team.members.0.role"> Lead Developer </p> </div>
<div data-aether-repeater-item="1"> <img src="/bob.jpg" data-aether-field="team.members.1.photo" /> <h3 data-aether-field="team.members.1.name">Bob Smith</h3> <p data-aether-field="team.members.1.role">Designer</p> </div></div>Dynamic Repeaters (Astro)
Section titled “Dynamic Repeaters (Astro)”---const members = section.data.team.members || [];---
<div data-aether-repeater="team.members"> {members.map((member, i) => ( <div data-aether-repeater-item={i}> <h3 data-aether-field={`team.members.${i}.name`}> {member.name} </h3> </div> ))}</div>SDK Events
Section titled “SDK Events”Listen to SDK events for custom behavior:
const sdk = new AetherSDK(config);
// SDK initializedsdk.on('ready', () => { console.log('Editor ready!');});
// Content updatedsdk.on('content:updated', (event) => { console.log('Content changed:', event); // event = { sectionId, fieldPath, newValue }});
// Error occurredsdk.on('error', (event) => { console.error('SDK error:', event.error);});Testing in the Editor
Section titled “Testing in the Editor”Local Development
Section titled “Local Development”- Start your site:
npm run dev(usuallylocalhost:4321) - Open Aether app: Navigate to Sites
- Configure site URL: Set to your local URL
- Open editor: Click “Open Visual Editor”
- Editor loads: Your site loads with
?aether-editor=true
Production Testing
Section titled “Production Testing”- Deploy your site: Push to Vercel, Netlify, etc.
- Update site URL: In Aether Sites settings
- Open editor: Loads your production URL
- Test editing: Changes save to database
Security
Section titled “Security”Origin Validation
Section titled “Origin Validation”The SDK validates postMessage origins for security:
// SDK only accepts messages from editorOriginconst sdk = new AetherSDK({ editorOrigin: 'https://app.aether.com', // Must match exactly // ...});CORS Configuration
Section titled “CORS Configuration”Your site must allow iframe embedding:
module.exports = { async headers() { return [ { source: '/:path*', headers: [ { key: 'X-Frame-Options', value: 'ALLOW-FROM https://app.aether.com', }, ], }, ]; },};Styling Editable Fields
Section titled “Styling Editable Fields”Highlight Styles
Section titled “Highlight Styles”By default, the SDK adds inline styles to editable fields. Customize with CSS:
/* Override SDK highlights */[data-aether-field] { outline: 2px dashed #3b82f6 !important; outline-offset: 4px !important; cursor: pointer !important; transition: outline 0.2s;}
[data-aether-field]:hover { outline-style: solid !important; outline-color: #2563eb !important;}Editor-Only Styles
Section titled “Editor-Only Styles”Apply styles only in editor mode:
/* Only in editor */body[data-aether-editor="true"] [data-aether-field] { outline: 2px dashed blue;}Set the body attribute in your HTML:
<script> const params = new URLSearchParams(window.location.search); if (params.get('aether-editor') === 'true') { document.body.setAttribute('data-aether-editor', 'true'); }</script>Advanced Patterns
Section titled “Advanced Patterns”Conditional Editing
Section titled “Conditional Editing”Only make certain fields editable:
---const isEditable = Astro.url.searchParams.get('aether-editor') === 'true';---
<h1 data-aether-field={isEditable ? 'hero.title' : undefined}> {title}</h1>Custom Field Validation
Section titled “Custom Field Validation”sdk.on('content:updated', (event) => { const { fieldPath, newValue } = event;
// Validate email if (fieldPath.includes('email')) { if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(newValue)) { console.error('Invalid email format'); // Revert or show error } }
// Max length if (fieldPath.includes('title') && newValue.length > 100) { console.warn('Title too long'); }});Preview Mode
Section titled “Preview Mode”Show draft content before publishing:
---const isDraft = Astro.url.searchParams.get('draft') === 'true';const section = await getAetherSection( 'hero-123', config, { draft: isDraft });---Troubleshooting
Section titled “Troubleshooting”Fields Not Highlighting
Section titled “Fields Not Highlighting”Check:
- ✅ SDK initialized (
sdk:readyin console) - ✅
data-aether-fieldattribute present - ✅ Parent has
data-aether-section - ✅ Section ID exists in database
- ✅ URL has
?aether-editor=true
Clicks Not Working
Section titled “Clicks Not Working”Check:
- ✅ PostMessage communication (check console)
- ✅ Origin validation (editorOrigin matches)
- ✅ No JavaScript errors blocking SDK
- ✅ Element is not behind another element (z-index)
Changes Not Saving
Section titled “Changes Not Saving”Check:
- ✅ User authenticated in Aether
- ✅ Section ID matches database record
- ✅ Network tab shows API call
- ✅ No 401/403 errors in console
Next Steps
Section titled “Next Steps”- API Reference - Complete SDK API
- Examples - Working code examples
- Best Practices - Production tips