Integrating DynoStatic
DynoStatic widgets use one dependency-free classic loader. The loader derives the installation address from its own URL, fetches the published widget by ULID, and mounts it into a matching host element.
Register the website
In Admin → Websites, choose Add website and enter the embedding website's exact origin. An origin contains only the scheme, hostname, and optional non-default port:
https://www.example.comhttp://127.0.0.1:4321
Do not enter an article path. Browser security treats HTTP and HTTPS, different hostnames, and different ports as separate origins.
Local staging is supported. Register the local origin while developing. At launch, edit the website address under General. DynoStatic can replace the origin on existing discussion URLs while preserving paths, query strings, comments, reactions, and moderation state.
Additional exact origins can be added under the website's General → Website addresses screen for staging, documentation, or alternate front ends. Removing an alias immediately stops new public reads and writes from that origin; the primary address is replaced in the same screen so discussion URLs move safely.
The same Integrations screen manages optional signed HTTPS webhooks for comment, form, and CMS content events (content.published, content.unpublished, media.updated, content.schema_changed). A signing secret is shown once. Endpoints can be paused without losing delivery history, and the panel reports attempt count, latest attempt time, and latest HTTP outcome. Delivery is asynchronous: configure the optional once-per-minute worker shown under Tools → Optimization for predictable timing, or use Run background jobs now under Tools → System while testing. Use content.published webhooks to trigger Astro/Hugo host rebuilds.
Form widgets can also enable per-widget email notifications in the builder. Recipients must be valid email addresses. By default the message links to the private admin inbox and omits decrypted answers unless the operator opts in.
Headless CMS for Astro and Hugo
For marketing pages, blog posts, and site settings that your static generator should render at build time, use Content (CMS) instead of widgets. Create collections and singletons, publish entries, mint a Bearer API token, and load JSON from api/?action=cms.* during CI. Full details: cms.md and examples/astro-cms/.
Widgets remain the right tool for live comments, forms, and other interactive embeds on the published site.
Create and publish a widget
Select the website, choose Add Widget, and create a widget whose catalog status is Ready, Official embed, or Link only. Configure it, inspect the desktop/tablet/mobile preview, then publish it. The builder provides a 26-character widget identifier and copyable embed markup.
Comments use the complete discussion runtime. Forms, polls, consent, calendars, menus, and other compatible widgets use the shared runtime. Catalog entries marked Preview, Credentials required, Approval required, or Deferred cannot be published as if they were complete.
Static HTML
Place the host and loader where the widget should appear:
<div data-dynostatic-widget="WIDGET_ULID"></div>
<script data-cfasync="false" src="https://widgets.example.com/embed.js" defer></script>
For an installation in a subfolder, retain that path:
<script data-cfasync="false" src="https://example.com/tools/widgets/embed.js" defer></script>
Load embed.js once per page even when several widget hosts exist. The loader scans existing and newly inserted [data-dynostatic-widget] elements and does not mount one host twice.
An explicitly shared widget also names the exact recipient website. Keep the source widget ID and use the recipient—not the source—website ID:
<div data-dynostatic-widget="WIDGET_ULID" data-dynostatic-site="RECIPIENT_SITE_ULID"></div>
This attribute identifies an existing authorized placement; it does not create or infer sharing authority. Existing self placements omit it unchanged.
Widgets load lazily by default and activate within 600 pixels of the viewport. Add data-loading="eager" to the loader only when immediate initialization is necessary. A particular host may opt in with data-dynostatic-loading="eager".
Set data-dynostatic-mode="light" on a host to request light DOM for compatible shared widgets. Comments choose Shadow DOM or light DOM from the website's Appearance setting so host-owned themes work consistently.
Astro and view transitions
Use a host-only component for each widget and put the loader once in the shared
layout. The complete copy-paste components are under
examples/astro-widgets/. The resulting markup
uses the same canonical primitives:
<div data-dynostatic-widget="WIDGET_ULID"></div>
<script
is:inline
data-cfasync="false"
src="https://widgets.example.com/embed.js"
defer
></script>
The loader listens for astro:before-swap and astro:page-load, releases timers and pending observers before a swap, and rescans the new page afterward.
Astro's is:inline prevents bundling; the script is still an external deferred
request. CMS posts and pages use the separate private build client under
examples/astro-cms/. Never put its Bearer token in a
widget component or browser bundle.
Hugo
The host shortcode and once-per-layout loader partial under
examples/hugo-widgets/ retain root or subfolder
installation paths. Put each widget ID in a dynostatic-widget shortcode, and
include the loader partial once before </body>.
Hugo CMS builds use the separate server-side partial under
examples/hugo-cms/. It reads the Content API URL and
Bearer token from the private build environment; neither belongs in the public
widget partial or generated HTML.
WordPress
Build the separately installable plugin archive from a development checkout:
php bin/build-wordpress-connector.php /absolute/output/dynostatic-connector.zip
Upload and activate that ZIP in WordPress. Under Settings → DynoStatic, enter
the public installation URL and the 26-character ID shown by the published
widget. Then insert the DynoStatic Widget block or use
[dynostatic_widget]. Repeating either host still enqueues only one deferred
loader. The plugin stores no DynoStatic password or token and does not load
panel code.
Shopify Custom Liquid
Add one Custom liquid section where the widget should appear. Paste the ordinary canonical snippet; replace only the widget ID and installation URL:
<div data-dynostatic-widget="WIDGET_ULID"></div>
<script data-cfasync="false" src="https://widgets.example.com/embed.js" defer></script>
If several sections contain widgets, keep the host in each section but retain
the script only once, preferably in theme.liquid before </body>. This is a
generic embed, not a Shopify app, and it does not synchronize Shopify content.
Webflow Embed
Add an Embed element where the widget should appear and paste:
<div data-dynostatic-widget="WIDGET_ULID"></div>
<script data-cfasync="false" src="https://widgets.example.com/embed.js" defer></script>
For multiple widgets, keep one host per Embed element and put the loader once in
the site's Before </body> tag custom code. Webflow may require a paid site
plan for custom code. This is a generic embed, not a native Webflow app.
Discussion identity
For comments, the current full page URL is sent to the discussion API and must belong to the registered origin. A deliberate stable identity may be supplied as data-page-key on the widget host. Most sites should use URL identity so discussion administration can display and update the real address.
Fragments never identify a different discussion. Website settings determine which query keys, if any, participate in page identity. Moving from a local or old origin to a live origin preserves each normalized path and allowed query.
Browser events
The shared runtime emits dynostatic:ready, dynostatic:submitted, dynostatic:consent, and dynostatic:error on window. The comments element additionally emits bubbling, composed dynostatic-comments:ready and dynostatic-comments:metadata events. Metadata is public, read-only state; it does not include email addresses, visitor credentials, administrator data, or anti-spam evidence.
Verify the integration
- Confirm
embed.jsloads from the intended installation and version. - Confirm the host contains a valid published widget ULID.
- Confirm
api/index.php?action=widgetsucceeds for the registered origin. - For comments, confirm bootstrap and thread requests identify the intended URL or explicit page key.
- Check narrow widths, keyboard focus, reduced motion, light/dark themes, empty/loading/error states, and the widget's write workflow.
- Submit real data only with the website owner's authorization.
Never put Turnstile secrets, SMTP credentials, Redis passwords, API tokens, or administrator credentials into embed markup.