Embed Creator
Author or modify a Datex Studio embed (configurationTypeId=20) on a branch — a thin UI component whose entire surface is a single <iframe>. An embed renders either an external URL (a hosted map, dashboard, document viewer) or an in-memory HTML string (a generated email/report preview) and is almost always opened as a dialog via $shell.open<referenceName>Dialog(...) (prefixed with the embed's package when it's registered under a module).
References
- ../datex-studio-shared/branch-setup.md — Branch/connection selection (shared across skills)
- references/embeds.md — Authoritative embed authoring reference: file shape, minimal-valid skeleton, the iframe-only rule, the
data:URI HTML-string pattern, dialog wiring, the print pattern, CSP caveats, pre-flight checklist - ../datex-studio-conventions/file-format.md —
configurationTypeIdtable and the TypeScript-expression encoding rule (applies toiframeConfig.href) - ../datex-studio-conventions/naming-conventions.md —
_embed/-embedsuffix, filename-stem matching, display-name rule - ../datex-studio-runtime/runtime-globals.md — platform-injected globals available in embed code (
$embed,$shell,$datasources,$utils, ...) - ../form-creator/references/forms.md — sibling component; pick a form when you need field controls/buttons alongside the content (the form-vs-embed decision)
- ../component-wiring-check/references/component-wiring.md — host reference contracts, vars-must-be-declared rule,
moduleIdrule for the component that opens the embed
Dependencies
requirements-gatheringskill — invoked to produce a requirements brief if one doesn't already exist in the conversation contextform-creatorskill — invoked when the requirement actually needs field controls or a toolbar next to the content (an embed is iframe-only; it has no button surface)component-wiring-checkskill — invoked to audit theconfigParameters↔inParamscontract on the component that opens the embed before push
CLI Lifecycle
Embed authoring goes through dxs configuration — the generic CRUD primitive over every platform configuration type. There is no dxs embed subcommand and no field-level patching; you build (or fetch + extract) the whole JSON body, edit it, and push the whole thing back. The type identifier in the CLI is embed (lowercase), mapping to configurationTypeId: 20.
Create a new embed:
# 1. Build body.json from scratch (see references/embeds.md → Minimal Valid Skeleton)
# 2. Validate — gates the push; exit 1 = errors found, not a broken CLI. Catches the "HREF is required" failure before push
dxs configuration validate embed -b <branchId> -D body.json
# 3. Create (upsert creates or updates by referenceName)
dxs configuration upsert embed -b <branchId> -D body.json
Edit an existing embed:
# 1. Fetch — note the envelope wrapper
dxs configuration get embed <configId> -b <branchId> -O envelope.json
# 2. EXTRACT THE INNER BODY (round-trip footgun guard)
jq .json envelope.json > body.json
# 3. Edit body.json
# 4. Validate — gates the push. Exit 1 = errors found (read validation_errors, fix, re-run), not a broken CLI
dxs configuration validate embed -b <branchId> -D body.json
# 5. Push
dxs configuration upsert embed -b <branchId> -D body.json
Round-trip rule (critical)
When editing an existing config, never pipe the envelope.json directly into dxs configuration upsert — it silently destroys configuration content. Always jq .json envelope.json > body.json before editing. See ../datex-studio-shared/configuration-roundtrip.md for the canonical round-trip and the underlying bug.
Workflow
[Phase 1: Setup + Requirements]
Follow branch-setup.md for branch/connection selection
|
[requirements brief in context?] ── NO ─> invoke `requirements-gathering`
|
[Phase 2: Embed vs Form decision]
Consult references/embeds.md → "Purpose & When to Use":
- render a URL or an HTML blob, no controls needed -> embed
- needs field inputs, a toolbar, or a Print button
that lives outside the rendered content -> form (or put the
control inside the HTML)
If field controls/toolbar are required -> invoke `form-creator` and stop here.
|
[Phase 3: Pick the source — URL or HTML string]
URL -> iframeConfig.href points at the URL (directly or via a var
computed in on_init). Example: a hosted map.
HTML string -> on_init sets a var to a data:text/html URI built from the
HTML; iframeConfig.href points at that var.
|
[Phase 4: Author embed body]
Build body.json from references/embeds.md → Minimal Valid Skeleton:
- type: "iframe" (the only supported designer type)
- iframeConfig.href (REQUIRED — a TS expression, usually $embed.vars.<url>)
- inParams[] (the URL / HTML / id the host passes in)
- vars[] (the computed href var)
- onInitFlowConfig -> on_init flow that computes the href var
|
[Phase 5: Validate + push]
dxs configuration validate embed -b <branchId> -D body.json
dxs configuration upsert embed -b <branchId> -D body.json
|
[Phase 6: Wire the opener + verify]
Caller opens it: $shell.open<referenceName>Dialog(inParamsObj, 'flyout', EModalSize.Xlarge) (+ <Package>. segment if the embed is in a module)
Verify in Studio: iframe renders; if HTML preview, the in-document Print button works
|
[invoke `post-edit-verification`; then `component-validator`]
Phase Details
Phase 2: Embed vs Form decision
An embed's entire visible surface is the iframe — it has no field controls, no toolbar, and no button surface. Pick an embed when the requirement is purely "render this URL/HTML in a dialog or panel." Pick a form instead when the dialog needs inputs, a validate-then-confirm flow, or chrome (a header, a toolbar) around the content. If you need a button and rendered HTML, the pragmatic move is to put the button inside the HTML (see the Print pattern) rather than reaching for a form — a form cannot host an iframe (there is no iframe field control).
Phase 3: Pick the source — URL or HTML string
iframeConfig.href is the only content channel. There is no srcdoc and no inline-HTML embed type — for this skill type is always iframe (the enum's other member, powerBi, is unsupported by codegen and restricted in the Studio UI; see Common Mistakes). So:
External URL — compute or hardcode the URL into the href var in
on_init. Build query params from$embed.inParams.In-memory HTML string — convert the string to a
data:URI inon_init:$embed.vars.ref_url = "data:text/html;charset=utf-8," + encodeURIComponent($embed.inParams.html);and point
iframeConfig.hrefat"$embed.vars.ref_url". This is the canonical way to render generated HTML (an email preview, a report proof) in a dialog. See references/embeds.md → Rendering an HTML String.
Phase 4: Author embed body
Build body.json from references/embeds.md → Minimal Valid Skeleton. Key points:
- File basics.
configurationTypeId: 20, suffix-embed.json,referenceNameends_embedand matches the filename stem. Plus the universal checks (../datex-studio-conventions/universal-checklist.md) —descriptionnon-null and ≤100 chars. type: "iframe". The only codegen-supportedEEmbedDesignerType. The enum also definespowerBi, but it is not fully supported by codegen and is restricted in the Studio UI — never author it. Non-member values (html,script,content,code) fail validation outright.iframeConfig.hrefis required and is a TypeScript expression. A bare$embed.vars.ref_urlis a raw expression (unwrapped). A literal URL must be a TS string literal ("'https://example.com'") — but prefer computing it inon_initand binding the var, as the URL almost always depends oninParams. See ../datex-studio-conventions/file-format.md → Declarative String Values Are TypeScript Expressions.- Declare every
$embed.vars.<id>you write. The href var (and any other) must appear in top-levelvars[], or the write fails. Same rule as editors/forms. on_initcomputes the href. TheonInitFlowConfigflow (aconfigurationTypeId: 9embedded flow) is where you read$embed.inParams, build the URL ordata:URI, and assign the href var.
Phase 6: Wire the opener + verify
The component that opens the embed (an editor flow, a hub toolbar button, a grid row action) calls the auto-generated shell method:
// top-level embed (no package):
await $shell.opencustom_email_preview_embedDialog({ html: previewHtml }, 'flyout', EModalSize.Xlarge);
// embed registered under a package:
await $shell.<Package>.opencustom_email_preview_embedDialog({ html: previewHtml }, 'flyout', EModalSize.Xlarge);
The method is open + the embed's referenceName + Dialog (snake_case preserved). It carries a package segment only when the embed is registered under a package/module — a top-level application embed is opened as $shell.open<referenceName>Dialog(...) with no segment. When there is a package, <Package> is the embed's own module, not the caller's. The inParam object is generated from the embed's inParams; the host must carry a full configParameters contract for those inParams (and, for a packaged embed, set moduleId to the embed's package) — audit with component-wiring-check.
Verify in Studio: the iframe renders the URL/HTML; for an HTML preview, confirm the in-document Print button prints just the embedded content (see the Print pattern and its caveats).
Pre-Flight Checklist
Walk the full checklist in references/embeds.md → Pre-Flight Checklist. The fast version:
- File basics.
configurationTypeId: 20, suffix-embed.json,referenceNameends in_embedand matches the filename stem,titlea distinct sentence-case display name — plus the universal checks (../datex-studio-conventions/universal-checklist.md). type: "iframe"— the only codegen-supported type; never authorpowerBi(defined but unsupported).iframeConfig.hrefpresent — omitting it fails withHREF is required.- Href var declared in top-level
vars[]and assigned inon_init. - HTML-string embeds build the
data:text/html;charset=utf-8,+encodeURIComponent(...)URI; they do not rely onsrcdoc. - Print button (if a preview) lives inside the HTML (
onclick="window.print()"), hidden via@media print— never assume the parent can call into the iframe. - Opener contract — the host declares a
configParametersentry for every embedinParam; audit viacomponent-wiring-check. descriptionnon-null, non-empty, ≤100 chars.
Common Mistakes
The authoritative symptom → cause → fix table is in references/embeds.md → Common Failure Modes. The gotchas that bite most often when authoring:
srcdoc, or a non-iframetype(html/script/content), to inject markup inline — none are supported; render the HTML through adata:text/htmlURI onhref.- Omitting
iframeConfig.href— fails validation withHREF is required; it is the only content channel. - Writing
$embed.vars.<id>without declaring it invars[]— the write fails. - Hardcoding a literal URL unwrapped in
href—hrefis a TS expression; TS-quote it ("'https://...'") or compute it inon_init. - Upserting the envelope instead of the inner
.json— silently destroys config content;jq .json envelope.json > body.jsonfirst.
After your edit, invoke post-edit-verification to surface description/JSON/schema violations. For a final review, invoke component-validator.