Duration: 30 minutes
Quick navigation
- Context
- Hands-on Lab
- Step 1: Create Plugin HTML
- Step 1.5: Create Plugin CSS
- Step 2: Create Plugin JavaScript
- Step 2.5: Create TradingView Block Decorator
- Step 3: Commit Plugin Files
- Step 4: Test Plugin Locally First
- Step 5: Access Plugin on Your Branch
- Step 6: Preview the Page and Verify Widget Rendering
- Step 7: Optional - Register Plugin in Library
- Key Takeaways
Complete SETUP.md if not already done. Exercises can be done in sequence or independently; if independent, ensure SETUP is done and you have the items below.
Required:
- On your feature branch (
jsmith— first initial + last name, lowercase) - Local dev server at
http://localhost:3000 - Code editor open with the repository
- Exercises 1–6 completed (if doing in sequence)
- DA.live access
- Personal workspace:
/drafts/jsmith/(use your name, lowercase)
Verify you're on your branch: git branch → should show * jsmith (your name).
Context: You've been authoring in DA.live (Exercises 1–6); this exercise extends DA.live with custom plugins so authors can insert pre-formatted content from the library.
- How DA.live plugins work as library integrations
- How to use the DA App SDK (context, token, actions)
- How to insert content into documents programmatically via DA SDK
- How to develop plugins locally and deploy to your branch
- How plugins are accessed through the library palette
DA.live library plugins extend the authoring experience with custom content insertion capabilities.
The challenge: Authors often copy/paste third-party embed codes (widgets, scripts, config JSON), but raw custom HTML in documents is an anti-pattern and hard to maintain.
The solution: Build a plugin that accepts supported embed snippets and converts them into structured block content.
Plugins enable:
- Safe embed conversion - Turn third-party snippets into block rows
- Integration with external data sources - Fetch from APIs, databases
- Automated content generation - Dynamic content based on rules
- Streamlined authoring workflows - Paste once, insert clean content
- Consistency - Same structure across all pages
- Custom tooling - Build exactly what your authors need
The pattern:
1. Developer builds plugin (HTML + JavaScript)
2. Plugin uses DA App SDK (context, token, actions)
3. Plugin is committed to Git repository
4. Authors access via library palette in DA.live
5. Plugin inserts content into document via SDK actions
Real-world use cases:
- Widget embed converters - Convert embed snippets into block config
- Section libraries - Pre-formatted hero sections, CTAs, footers
- Product catalog - Fetch products from PIM, insert as tables
- Speaker directory - Insert speaker bio from API
- Asset browser - Browse DAM, insert images with correct paths
- Form builders - Generate form markup from templates
- Legal snippets - Insert disclaimers, copyright notices
This exercise demonstrates a practical DA pattern: let authors use familiar copy/paste embed workflows while still preventing raw custom HTML from entering the document.
Workflow:
- Author copies a third-party widget embed snippet
- Plugin parses and validates source + JSON config
- Plugin converts values into structured block rows
- DA inserts only block content (not raw embed markup)
Sample widget source:
- Company Profile example:
https://www.tradingview.com/widget-docs/widgets/symbol-details/company-profile/ - TradingView widget catalog:
https://www.tradingview.com/widget-docs/widgets/
DA.live plugins are HTML + JavaScript applications hosted on your Edge Delivery site that communicate with DA.live via the DA App SDK.
Architecture:
┌─────────────────────────────────────────────────┐
│ DA.live Editor │
│ │
│ ┌───────────────────┐ ┌──────────────────┐ │
│ │ Document Editor │ │ Library Palette │ │
│ │ │ │ │ │
│ │ [Your Content] │ │ [Plugin loads │ │
│ │ │ │ in iframe] │ │
│ └────────┬──────────┘ └────────┬─────────┘ │
│ │ │ │
│ │ PostMessage API │ │
│ │ (via DA App SDK) │ │
│ └──────────────────────┘ │
└─────────────────────────────────────────────────┘
│
│ Loads plugin from:
│ https://jsmith--nycmasterclass--cloudadoption.aem.page/
│ tools/plugins/embedwidget/embedwidget.html
▼
┌───────────────────────────┐
│ Your Plugin (iframe) │
│ │
│ • HTML + JavaScript │
│ • Uses DA App SDK │
│ • Sends content via SDK │
└───────────────────────────┘
How it works:
- Plugin is an HTML page on your Edge Delivery site
- Plugin loads in iframe within DA.live library palette
- Plugin uses DA App SDK (imported from da.live)
- SDK provides PostMessage API for secure communication
- SDK gives you: context (document info), token (auth), actions (insert content)
- Plugin calls
actions.sendText()oractions.sendHTML()to insert content - DA.live receives message and inserts into document
URL Structure:
- Your codebase:
https://jsmith--nycmasterclass--cloudadoption.aem.page/tools/plugins/embedwidget/embedwidget.html - DA.live Plugin URL:
https://da.live/app/cloudadoption/nycmasterclass/tools/plugins/embedwidget/embedwidget?ref=jsmith - Local development:
https://da.live/app/cloudadoption/nycmasterclass/tools/plugins/embedwidget/embedwidget?ref=local
Key concept: DA.live loads your plugin HTML from your AEM site into an iframe, then uses PostMessage for secure cross-origin communication.
Reference: Developing Apps and Plugins
The DA App SDK is the bridge between your plugin and DA.live. It provides three key objects:
Information about the current document being edited:
const { context } = await DA_SDK;
console.log(context.org); // "cloudadoption"
console.log(context.repo); // "nycmasterclass"
console.log(context.ref); // "main" or branch name
console.log(context.path); // "/drafts/jsmith/my-page"Use cases:
- Branch-aware plugins (behave differently on main vs feature branches)
- Path-based logic (different templates for different paths)
- Org/repo-aware plugins (multi-tenant plugins)
Authentication token for making authenticated API calls to DA.live Admin API:
const { token } = await DA_SDK;
// Use for authenticated API calls
const response = await fetch('https://admin.da.live/source/org/repo/path', {
headers: {
'Authorization': `Bearer ${token}`
}
});Methods to insert content into the active document:
const { actions } = await DA_SDK;
// Insert plain text (markdown)
await actions.sendText('# Heading\n\nParagraph text');
// Insert HTML
await actions.sendHTML('<div><h1>Heading</h1><p>Paragraph</p></div>');
// Close library palette
await actions.closeLibrary();Key insight: You don't manipulate the document directly. You send content to DA.live, and DA.live inserts it.
Why PostMessage?: Your plugin runs in an iframe (different origin). PostMessage is the secure way to communicate across origins.
For this exercise, you'll build an EmbedWidget plugin with three files:
-
HTML file (
embedwidget.html)- Imports DA App SDK from
https://da.live/nx/utils/sdk.js - Imports your plugin JavaScript
- Defines UI (embed textarea + insert button)
- Includes styles
- Imports DA App SDK from
-
CSS file (
embedwidget.css)- Styles plugin layout and form controls
- Keeps the authoring UI clear and readable
-
JavaScript file (
embedwidget.js)- Imports DA App SDK
- Waits for SDK initialization (
await DA_SDK) - Parses and validates supported embed HTML
- Handles clicks → sends structured block HTML → closes library
tools/
plugins/
embedwidget/
embedwidget.html ← Loaded by DA.live in iframe
embedwidget.js ← Your plugin logic
URL accessed by authors:
https://da.live/app/cloudadoption/nycmasterclass/tools/plugins/embedwidget/embedwidget?ref=jsmith
This loads your HTML from:
https://jsmith--nycmasterclass--cloudadoption.aem.page/tools/plugins/embedwidget/embedwidget.html
File: tools/plugins/embedwidget/embedwidget.html
NOTE: You can copy the embedwidget plugin from the answers branch.
branch in github
Copy this code:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>EmbedWidget</title>
<link rel="icon" href="data:,">
<link rel="stylesheet" href="/tools/plugins/embedwidget/embedwidget.css">
<script src="https://da.live/nx/utils/sdk.js" type="module"></script>
<script src="/tools/plugins/embedwidget/embedwidget.js" type="module"></script>
</head>
<body>
<main class="plugin-shell">
<h1>EmbedWidget</h1>
<p class="plugin-intro">
Paste a supported embed snippet. The plugin converts it into structured block content
so no raw custom HTML is inserted into the document.
</p>
<label for="embed-code-input">Embed Code</label>
<textarea
id="embed-code-input"
rows="14"
placeholder="Paste full embed HTML here"
aria-label="Embed code"
></textarea>
<div class="plugin-actions">
<button id="insert-block-btn" type="button">Insert Widget Block</button>
</div>
<p id="plugin-status" class="plugin-status" role="status" aria-live="polite"></p>
</main>
</body>
</html>File: tools/plugins/embedwidget/embedwidget.css
Copy this code:
.plugin-shell {
box-sizing: border-box;
padding: 16px;
margin: 0;
font-family: adobe-clean, "Segoe UI", sans-serif;
color: #2c2c2c;
}
.plugin-shell h1 {
margin: 0 0 10px;
font-size: 20px;
line-height: 1.2;
}
.plugin-intro {
margin: 0 0 14px;
font-size: 14px;
line-height: 1.45;
}
.plugin-shell label {
display: block;
margin-bottom: 8px;
font-size: 13px;
font-weight: 600;
}
#embed-code-input {
box-sizing: border-box;
width: 100%;
border: 1px solid #c6c6c6;
border-radius: 8px;
padding: 10px 12px;
resize: vertical;
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
font-size: 13px;
line-height: 1.4;
}File: tools/plugins/embedwidget/embedwidget.js
Copy this code:
import DA_SDK from 'https://da.live/nx/utils/sdk.js';
const TRADINGVIEW_HOST = 's3.tradingview.com';
const SCRIPT_PREFIX = 'embed-widget-';
const DEFAULT_HEIGHT = '500px';
function setStatus(statusEl, message, type = '') {
statusEl.textContent = message;
statusEl.className = `plugin-status ${type}`.trim();
}
function escapeHtml(value) {
return String(value)
.replaceAll('&', '&')
.replaceAll('<', '<')
.replaceAll('>', '>')
.replaceAll('"', '"')
.replaceAll("'", ''');
}
function normalizeHeight(config) {
const { height } = config;
if (typeof height === 'number' && Number.isFinite(height)) return `${height}px`;
if (typeof height === 'string' && height.trim()) return height.trim();
return DEFAULT_HEIGHT;
}
function parseTradingViewEmbedCode(rawEmbedCode) {
const parser = new DOMParser();
const doc = parser.parseFromString(rawEmbedCode, 'text/html');
const scripts = [...doc.querySelectorAll('script[src]')];
const tradingViewScript = scripts.find((script) => script.src.includes(TRADINGVIEW_HOST));
if (!tradingViewScript) throw new Error('No supported widget script found. Paste the full embed snippet.');
const scriptURL = new URL(tradingViewScript.src);
if (scriptURL.host !== TRADINGVIEW_HOST) throw new Error('Unsupported script source in embed code.');
const scriptFilename = scriptURL.pathname.split('/').pop();
if (!scriptFilename || !scriptFilename.startsWith(SCRIPT_PREFIX)) {
throw new Error('Unsupported widget type in script URL.');
}
const scriptConfigText = tradingViewScript.textContent.trim();
if (!scriptConfigText) throw new Error('Widget configuration is empty.');
let config;
try {
config = JSON.parse(scriptConfigText);
} catch {
throw new Error('Widget config must be valid JSON.');
}
if (typeof config !== 'object' || config === null || Array.isArray(config)) {
throw new Error('Widget config must be a JSON object.');
}
return { script: scriptFilename, height: normalizeHeight(config), config };
}
function toTradingViewBlockHTML({ script, height, config }) {
const safeScript = escapeHtml(script);
const safeHeight = escapeHtml(height);
const safeConfig = escapeHtml(JSON.stringify(config, null, 2));
return `
<table>
<tbody>
<tr><td colspan="2"><p>tradingview</p></td></tr>
<tr><td><p>script</p></td><td><p>${safeScript}</p></td></tr>
<tr><td><p>height</p></td><td><p>${safeHeight}</p></td></tr>
<tr><td><p>config</p></td><td><pre><code>${safeConfig}</code></pre></td></tr>
</tbody>
</table>`.trim();
}
(async function init() {
const { actions } = await DA_SDK;
const inputEl = document.getElementById('embed-code-input');
const insertButton = document.getElementById('insert-block-btn');
const statusEl = document.getElementById('plugin-status');
insertButton.addEventListener('click', async () => {
const rawEmbedCode = inputEl.value.trim();
if (!rawEmbedCode) {
setStatus(statusEl, 'Paste an embed snippet first.', 'error');
return;
}
try {
const parsed = parseTradingViewEmbedCode(rawEmbedCode);
await actions.sendHTML(toTradingViewBlockHTML(parsed));
setStatus(statusEl, 'Widget block inserted.', 'success');
await actions.closeLibrary();
} catch (error) {
setStatus(statusEl, error.message || 'Unable to convert embed code.', 'error');
}
});
}());What this does:
- Imports DA App SDK
- Waits for SDK initialization
- Validates and parses supported embed HTML
- Converts parsed values into structured block table markup
- Uses
actions.sendHTML()to insert into document - Uses
actions.closeLibrary()to close palette
The plugin inserts a structured tradingview block table, so your site also needs a tradingview block to render it.
Create:
blocks/tradingview/tradingview.jsblocks/tradingview/tradingview.css
NOTE: You can copy the tradingview block from the answers branch.
Use the same pattern you implemented in this repo:
- read block config with
readBlockConfig(block) - parse
configJSON from<pre><code> - inject the TradingView widget script lazily with
IntersectionObserver - apply
heightfrom block config
# Run linting (HTML files don't need linting)
npm run lint
# Add changes
git add tools/plugins/embedwidget/
git add blocks/tradingview/
# Commit
git commit -m "feat: add DA embedwidget plugin"
# Push
git push origin jsmithReplace jsmith with your branch name.
Before pushing to your branch, test the plugin loads correctly from localhost.
With your dev server running (aem up or npx @adobe/aem-cli up):
Plugin URL for local testing:
https://da.live/app/cloudadoption/nycmasterclass/tools/plugins/embedwidget/embedwidget?ref=local
To Test : EmbedWidget Plugin and TradingView Block:
-
Open DA.live: Go to
https://da.live/edit#/cloudadoption/nycmasterclass/drafts/jsmith/(use your name) -
Open any existing page (or create
/drafts/jsmith/plugin-test) -
Open library: Click the library icon in the left sidebar (puzzle piece icon)
-
Load your plugin: In a new browser tab, navigate to https://da.live/edit?ref=local#/cloudadoption/nycmasterclass/drafts/jsmith/plugin-test
-
Think like an author: Open the TradingView Company Profile widget page:
https://www.tradingview.com/widget-docs/widgets/symbol-details/company-profile/
-
Copy the generated embed HTML from TradingView
-
Return to EmbedWidget: You should see a textarea for embed code and an "Insert Widget Block" button
-
Paste and insert: A structured
tradingviewblock table should be inserted -
Library should close automatically
-
Preview the DA page to see the rendered TradingView widget
Debugging local issues:
- Verify
http://localhost:3000is running - Check browser console for errors
- Verify file paths:
/tools/plugins/embedwidget/embedwidget.html - Check Network tab for failed requests
How ?ref=local works: DA.live fetches your plugin HTML from http://localhost:3000 instead of the published site. This lets you develop without committing/pushing every change.
Once local testing works, access your plugin from your pushed branch.
Your plugin URL (replace jsmith with your branch):
https://da.live/app/cloudadoption/nycmasterclass/tools/plugins/embedwidget/embedwidget?ref=jsmith
To test on your branch:
- Ensure you've pushed to GitHub (
git push origin jsmith) - Wait 1-2 minutes for AEM Code Sync to deploy
- Open DA.live and navigate to any page
- Load your plugin URL with
?ref=jsmithin a new tab - Plugin loads from:
https://jsmith--nycmasterclass--cloudadoption.aem.page/tools/plugins/embedwidget/embedwidget.html - Test valid and invalid embed snippets to verify parsing and validation behavior
Testing checklist:
- Valid embed inserts a structured
tradingviewblock table - Invalid embed shows an error message
- Library palette closes after successful insertion
- Inserted table includes
script,height, andconfigrows
After inserting the block in DA, validate that the tradingview block decorator renders the actual widget on the page.
- Preview your DA document containing the inserted block (DA.live auto-saves)
- Open the preview URL in your browser (branch or local)
- Test on desktop and mobile: Use Chrome DevTools responsive view (F12 → device toolbar Cmd+Shift+M / Ctrl+Shift+M) to verify the widget layout at different widths.
- Verify rendered output:
- widget container is visible (not just a raw table)
- TradingView widget loads in that section
- configured height is applied
- If widget does not render, check DevTools Console/Network for blocked script or JSON parsing issues
For production use, plugins should be registered in the site configuration so authors can discover them automatically.
Note: For this lab exercise, the plugin has already been added to the DA Library.
To register your plugin (instructor may do this):
-
Edit site config: Open
https://da.live/config#/cloudadoption/nycmasterclass/ -
Add to library sheet:
title path experience EmbedWidget https://content.da.live/cloudadoption/nycmasterclass/tools/plugins/embedwidget dialog -
Config auto-saves; publish the config when ready.
Once registered:
- Plugin appears automatically in library palette
- Authors don't need to know the URL
- Works on main branch for production use
Problem: Plugin URL shows blank page or 404
Fixes:
- Check file paths: Verify files exist at
/tools/plugins/embedwidget/embedwidget.html - Check dev server: Ensure
aem upis running for?ref=local - Check branch deployment: Wait 1-2 minutes after
git pushfor Code Sync - Check URL: Verify
?ref=jsmithmatches your branch name exactly - Open plugin URL directly: Visit the .aem.page URL in a new tab to see errors
Problem: Console error: "Failed to import DA_SDK"
Fixes:
- Check SDK URL: Must be
https://da.live/nx/utils/sdk.js(exact case) - Check script type: Must have
type="module"in both HTML script tags - Check CORS: DA.live must be able to load your plugin (check browser console)
Problem: Clicking "Insert Widget Block" does nothing
Fixes:
- Check console: Look for JavaScript errors
- Verify SDK initialization:
await DA_SDKmust complete before usingactions - Verify embed source: Ensure script host is
s3.tradingview.com - Verify config JSON: Ensure the pasted embed contains valid JSON inside the script
- Test HTML insertion path: Try
await actions.sendHTML('<p>test</p>')first
Problem: Library stays open after insertion
Fixes:
- Check call: Ensure
await actions.closeLibrary()is called - Check await: Must use
await(it's async) - Check errors: Look for JavaScript errors preventing execution
Problem: Plugin UI broken or shows JavaScript errors
Fixes:
- Open browser DevTools: Check Console tab for errors
- Check file paths: Verify JavaScript import path matches file location
- Check pasted embed: Validate JSON in the embed script body
- Test locally first: Use
?ref=localto debug before pushing
Problem: DA.live doesn't let you load plugin URL
Fixes:
- Check permissions: Ensure you have access to cloudadoption/nycmasterclass
- Check URL format: Must start with
https://da.live/app/ - Check ref parameter: Must include
?ref=jsmithor?ref=local - Try main branch: Test with
?ref=mainto isolate branch issues
Debugging with Browser DevTools:
- Open plugin URL in new tab
- Right-click → Inspect
- Check Console tab for JavaScript errors
- Check Network tab for failed requests (SDK, JavaScript files)
- Check Elements tab to verify HTML structure
- Fetch speaker data from
/speakers.jsonor external API - Display list of speakers with photos and names
- Click to insert: Selected speaker bio as formatted table
- Bonus: Use
tokento fetch authenticated data from DA.live Admin API
Example flow:
const { token, actions } = await DA_SDK;
// Fetch speakers with auth
const response = await fetch('/speakers.json', {
headers: { 'Authorization': `Bearer ${token}` }
});
const { data } = await response.json();
// Display in plugin UI, insert on click
speakers.forEach(speaker => {
button.onclick = async () => {
await actions.sendText(`## ${speaker.Name}\n${speaker.Bio}`);
await actions.closeLibrary();
};
});- Pre-formatted section layouts: Hero, CTA, Testimonials, FAQ
- Click to insert: Full section markup (with blocks)
- Variant support: Multiple designs per section type
- Use case: Ensure consistent design system compliance
Example templates:
- Hero with centered text + background image
- CTA with button + icon
- Testimonial with quote + photo + name
- FAQ accordion with expandable items
- Browse DAM or external asset library (Cloudinary, Bynder)
- Search and filter images by tags, categories
- Insert with proper markdown: Optimized image syntax
- Auto-generate alt text: From asset metadata
Example flow:
// Fetch assets from DAM
const assets = await fetchFromDAM(token);
// Display thumbnails
assets.forEach(asset => {
thumbnail.onclick = async () => {
const markdown = ``;
await actions.sendText(markdown);
await actions.closeLibrary();
};
});- Fetch products from PIM (Product Information Management system)
- Search by SKU, name, category
- Insert as table: Product specs, price, images
- E-commerce sites: Consistent product page structure
- Select form type: Contact, Registration, Survey
- Configure fields: Name, email, message, etc.
- Generate markup: Form block with proper structure
- Validation rules: Built into generated code
- Pre-approved legal text: Privacy policies, disclaimers, copyright
- Version controlled: Always use latest approved version
- Compliance: Ensure legal requirements met
- Multi-language support: Insert in author's language
- Generate content via OpenAI, Claude, etc.
- Input: Title, keywords, tone
- Output: Generated paragraphs, summaries
- Insert directly: Author can edit after insertion
Common pattern:
// 1. Fetch/generate data
const data = await fetchData(context, token);
// 2. Display in UI
renderUI(data);
// 3. Handle clicks
buttons.forEach(btn => {
btn.onclick = async () => {
const content = formatContent(data);
await actions.sendText(content);
await actions.closeLibrary();
};
});- DA.live plugins are HTML + JavaScript applications hosted on your Edge Delivery site
- DA App SDK (
https://da.live/nx/utils/sdk.js) provides the interface to DA.live - PostMessage API enables secure cross-origin communication (plugin in iframe)
- Three SDK objects:
context(document info),token(auth),actions(insert content) - Two insertion methods:
actions.sendText()(markdown) andactions.sendHTML()(HTML) - Plugin URL pattern:
https://da.live/app/{org}/{repo}/{path}?ref={branch} - Local development: Use
?ref=localto load fromlocalhost:3000 - Branch testing: Use
?ref=jsmithto load from your feature branch - Production use: Register plugins in site config for automatic library discovery
- Plugin architecture: HTML (UI) + JavaScript (logic) + SDK (communication)
Why plugins?
- Consistency - Same conversion rules across all pages
- Speed - One-click insertion vs manual copy-paste
- Integration - Pull from external systems (APIs, DAMs, PIMs)
- Automation - Generate content programmatically
- Validation - Enforce structure and rules
The pattern: UI → Paste Embed → Parse + Validate → actions.sendHTML() → DA.live inserts → actions.closeLibrary()
- Created plugin files:
/tools/plugins/embedwidget/embedwidget.html/tools/plugins/embedwidget/embedwidget.css/tools/plugins/embedwidget/embedwidget.js
- Created block files:
/blocks/tradingview/tradingview.js/blocks/tradingview/tradingview.css
- Tested locally with
?ref=local(dev server running) - Plugin loads in library palette (no errors)
- Conversion works:
- valid embed inserts the
tradingviewblock table - invalid embed is rejected with clear messaging
- valid embed inserts the
- Content inserts correctly as structured HTML table
- Preview rendering works: inserted block decorates and widget loads on the page
- Tested in Chrome DevTools responsive view (desktop and mobile)
- Library closes automatically after insertion
- Committed and pushed to feature branch (
git push origin jsmith) - Tested on branch with
?ref=jsmithparameter - Understand DA App SDK:
contextprovides document infotokenprovides auth for API callsactions.sendHTML()inserts block markupactions.closeLibrary()closes palette
- Understand PostMessage pattern - Secure iframe communication
- Understand URL structure -
da.live/app/{org}/{repo}/{path}?ref={branch} - Know how to debug - Browser DevTools, Console, Network tab
- Optional: Understand plugin registration in site config
If you finish early, try these enhancements to your plugin:
Expand the parser to support additional embed providers and widget classes:
- Additional TradingView widget families
- Other approved providers
- Provider-specific validation rules
if (scriptURL.host === 's3.tradingview.com') {
// current parser branch
}Use context to make inserted config smarter:
const { context } = await DA_SDK;
const pageName = context.path.split('/').pop();
config.container_id = `widget-${pageName}`;Fetch supported widget definitions from a JSON file:
const response = await fetch('/tools/plugins/embedwidget/supported-widgets.json');
const widgets = await response.json();
// Validate against dynamically fetched definitions- Add search/filter for provider and widget type
- Add preview of generated block table
- Add form inputs for customization
- Add validation before insertion
Instead of markdown tables, insert rich HTML:
const html = `
<div class="widget-preview">
<table>
<tr><td>script</td><td>${script}</td></tr>
<tr><td>height</td><td>${height}</td></tr>
</table>
</div>
`;
await actions.sendHTML(html);- Developing Apps and Plugins - Official guide
- DA App SDK Source - SDK code (inspect for advanced usage)
- DA.live Documentation - Complete documentation
- TradingView Widgets Collection - Sample widget catalog
- TradingView Company Profile Widget - Example widget used in this lab
- da-blog-tools TradingView README - Source pattern for embed conversion
- PostMessage API - Understanding cross-origin communication
The complete solution for this exercise (embedwidget plugin, tradingview block) is on the answers branch. The same branch contains solutions for all lab exercises.
Exercise 8: Performance Optimization - You'll learn to analyze and optimize your Edge Delivery Services site for perfect Core Web Vitals and Lighthouse scores.