Customer portals
++ Bring account tools, forms and service workflows into the product + your customers already use. +
+ Pass the customer context → +diff --git a/.github/workflows/check-lowcoder-sdk.yml b/.github/workflows/check-lowcoder-sdk.yml new file mode 100644 index 0000000000..0160777fec --- /dev/null +++ b/.github/workflows/check-lowcoder-sdk.yml @@ -0,0 +1,40 @@ +name: Check Lowcoder SDK + +on: + pull_request: + paths: + - 'client/**' + - '.github/workflows/*lowcoder-sdk.yml' + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: check-lowcoder-sdk-${{ github.ref }} + cancel-in-progress: true + +jobs: + check: + if: github.repository == 'lowcoder-org/lowcoder' + runs-on: ubuntu-latest + defaults: + run: + working-directory: client + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 22.22.2 + cache: yarn + cache-dependency-path: client/yarn.lock + - name: Install dependencies + run: node .yarn/releases/yarn-3.6.4.cjs install --immutable + - name: Test browser SDK contract + run: node .yarn/releases/yarn-3.6.4.cjs workspace lowcoder-sdk-webpack-bundle test + - name: Build browser SDK and website + env: + NODE_OPTIONS: --max_old_space_size=6144 + run: node .yarn/releases/yarn-3.6.4.cjs workspace lowcoder-sdk-webpack-bundle build + - name: Check release sizes and files + run: node packages/lowcoder-sdk-webpack-bundle/scripts/check-dist.cjs diff --git a/.github/workflows/deploy-lowcoder-sdk-webpack-netlify.yml b/.github/workflows/deploy-lowcoder-sdk-webpack-netlify.yml deleted file mode 100644 index 13f64a6742..0000000000 --- a/.github/workflows/deploy-lowcoder-sdk-webpack-netlify.yml +++ /dev/null @@ -1,55 +0,0 @@ -# Builds client/packages/lowcoder-sdk-webpack-bundle and deploys its dist/ folder to Netlify. -# -# Deploy uses --no-build so Netlify CLI does not run the site UI "build command" (e.g. expo). -# The webpack bundle is built in the prior CI step. -# -# Repository secrets (Netlify: Site settings → General → Site details → Site ID; -# User settings → Applications → Personal access tokens): -# NETLIFY_AUTH_TOKEN — Netlify personal access token -# NETLIFY_SITE_ID — Site API ID for the Netlify site - -name: Deploy SDK Webpack Bundle to Netlify - -on: - push: - branches: - - main - -permissions: - contents: read - -concurrency: - group: deploy-sdk-webpack-netlify-${{ github.ref }} - cancel-in-progress: true - -jobs: - deploy: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - - name: Use Node.js - uses: actions/setup-node@v3 - with: - node-version: 20.x - cache: yarn - cache-dependency-path: client/yarn.lock - - - name: Install dependencies - uses: borales/actions-yarn@v4.2.0 - with: - cmd: install - dir: client - - - name: Build lowcoder-sdk-webpack-bundle - uses: borales/actions-yarn@v4.2.0 - with: - cmd: workspace lowcoder-sdk-webpack-bundle build - dir: client - - - name: Deploy dist to Netlify - working-directory: client/packages/lowcoder-sdk-webpack-bundle - env: - NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} - NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SDK_SITE_ID }} - run: npx --yes netlify-cli deploy --prod --dir=dist --no-build diff --git a/.github/workflows/release-lowcoder-sdk.yml b/.github/workflows/release-lowcoder-sdk.yml new file mode 100644 index 0000000000..d72797c37a --- /dev/null +++ b/.github/workflows/release-lowcoder-sdk.yml @@ -0,0 +1,45 @@ +name: Release Lowcoder SDK + +on: + release: + types: [released] + +permissions: + contents: read + +concurrency: + group: release-lowcoder-sdk + cancel-in-progress: false + +jobs: + publish: + if: github.repository == 'lowcoder-org/lowcoder' && !github.event.release.prerelease && !github.event.release.draft + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.event.release.tag_name }} + - uses: actions/setup-node@v4 + with: + node-version: 22.22.2 + - name: Verify this stable release is the current main commit + run: node client/packages/lowcoder-sdk-webpack-bundle/scripts/check-stable-release.cjs + - name: Request the Cloudflare production build + env: + LOWCODER_SDK_DEPLOY_HOOK: ${{ secrets.LOWCODER_SDK_DEPLOY_HOOK }} + run: | + node <<'NODE' + const hook = process.env.LOWCODER_SDK_DEPLOY_HOOK; + if (!hook) throw new Error('Configure the LOWCODER_SDK_DEPLOY_HOOK repository secret.'); + const url = new URL(hook); + if (url.origin !== 'https://api.cloudflare.com' || !url.pathname.startsWith('/client/v4/workers/builds/deploy_hooks/')) { + throw new Error('Unexpected Cloudflare deploy hook URL.'); + } + (async () => { + const response = await fetch(url, { method: 'POST', signal: AbortSignal.timeout(30000) }); + const result = await response.json(); + if (!response.ok || !result.success || !result.result?.build_uuid) throw new Error('Cloudflare did not accept the build request.'); + console.log('Cloudflare build accepted: ' + result.result.build_uuid); + console.log('Publication is confirmed by the Cloudflare build, not by this trigger request.'); + })().catch(error => { console.error(error.message); process.exitCode = 1; }); + NODE diff --git a/client/packages/lowcoder-sdk-webpack-bundle/README.md b/client/packages/lowcoder-sdk-webpack-bundle/README.md index 0b465a65a4..7911e62d31 100644 --- a/client/packages/lowcoder-sdk-webpack-bundle/README.md +++ b/client/packages/lowcoder-sdk-webpack-bundle/README.md @@ -1,9 +1,133 @@ -# lowcoder-sdk-webpack-bundle -The code to use Lowcoder Apps on any Web Project +# Lowcoder browser SDK and playground -STEPS to use +The public pages share the Lowcoder website's Ubuntu typography, plum and purple +palette, gradient buttons, floating navigation, logo and hero artwork. Original +assets from the `lowcoder-cloud` website are served locally from `site/brand/`; +the pages do not depend on an external font service. Keep these assets and the +brand tokens in `site/site.css` aligned when the marketing website changes. -1) build the project with `npm run build` -2) host `dist` folder on server -3) add `bundle.js` as an external script on the HTML page where you want to include the app -4) Add the apps as given in demo under `/dist/index_test.html` +This workspace builds the native DOM embedding SDK and the public site at +https://sdk.lowcoder.cloud. The npm SDK lives alongside it in `../lowcoder-sdk`. + +## Build and verify + +From `client/`, with Node 22 and the repository's Yarn version: + +```sh +node .yarn/releases/yarn-3.6.4.cjs install --immutable +node .yarn/releases/yarn-3.6.4.cjs workspace lowcoder-sdk-webpack-bundle test +node .yarn/releases/yarn-3.6.4.cjs workspace lowcoder-sdk-webpack-bundle build +node .yarn/releases/yarn-3.6.4.cjs workspace lowcoder-sdk-webpack-bundle check:dist +``` + +`index.html` and `site/` are lightweight static pages. The playground loads the SDK +only on request and mounts `site/demo.json` through the real SDK. It exchanges +module inputs, outputs and events, and demonstrates unmount/remount. + +## Browser integration + +```html +
+ + +``` + +`ready` covers SDK initialization. Mount resolves after app data is loaded and +rendering is scheduled, not after every query/component finishes. One backend per +host page is supported: SDK config, Redux, history, styles and plugin globals are +shared. CSS is not sandboxed. Configure backend access/authentication separately. +The full embedding contract is maintained in `site/reference.html`; integration +recipes live in `site/recipes.html`. The documentation test checks every public +instance member, option, event and React prop against the source, and validates +anchors, snippet syntax and search behavior. + +Existing `.lowcoder-module-container` markup and callbacks still work; call +`LowcoderSDK.scan()` for later markup. See `/guide.html` for the complete example. + +## Chunk boundaries + +Webpack partitions its dependency graph, with one runtime and one set of React, +styled-components and plugin globals. The loader resolves URLs from its own script, +fetches chunks concurrently, evaluates in dependency order, propagates CSP nonces, +and exposes explicit failure/readiness. Content-hashed assets have immutable cache +headers; `bundle.js` and manifests must revalidate. Never split generated JS bytes. + +The SDK still includes component authoring/editor dependencies. These changes make +transfer/caching more granular; they do not claim a small independent renderer. +`check:dist` measures the emitted initial scripts and enforces release size limits. + +## Cloudflare deployment + +`wrangler.jsonc` deploys the static `dist/` folder to the `lowcoder-sdk` Worker. +The GitHub workflow tests and builds pull requests. Cloudflare Builds handles +production builds through its existing GitHub connection and build token. +The release policy is **stable releases from main**, not every main push: + +- Repository: `lowcoder-org/lowcoder`; production branch: `main`. +- Root: `client`; Node version: `22.22.2`. +- Build variables: `NODE_VERSION=22.22.2`, `SKIP_DEPENDENCY_INSTALL=true`. +- Build: `node packages/lowcoder-sdk-webpack-bundle/scripts/check-stable-release.cjs && node packages/lowcoder-sdk-webpack-bundle/scripts/build-release.cjs`. +- Deploy: `node packages/lowcoder-sdk-webpack-bundle/scripts/check-stable-release.cjs && npx --yes wrangler@4.129.0 deploy --config packages/lowcoder-sdk-webpack-bundle/wrangler.jsonc`. +- Disable preview builds: this production config owns the public SDK route. + +Keep push-triggered production builds disabled. Create a Cloudflare Deploy Hook +for this Worker's `main` branch and store its URL in the GitHub repository secret +`LOWCODER_SDK_DEPLOY_HOOK`. `.github/workflows/release-lowcoder-sdk.yml` requests +that build only for a published stable release. Treat the hook URL as a credential; +it allows its holder to trigger this Worker's builds. Never put it in source. + +The release guard runs before the request, before building and before deploying. +It requires the checked-out commit, current `main`, and the latest stable release +tag to resolve to the same commit. A pre-release, moved main branch, older release +or unavailable GitHub verification fails closed. Publish the stable release while +main still points at that commit. The guard is deliberately stricter than ancestry. + +Activation requires these files on `main` and the deploy-hook secret. The GitHub +job confirms that Cloudflare accepted the request; check Cloudflare's build result +and the live domain to confirm publication. No npm package is published here. + +Setup status (2026-10-11): the `sdk-stable-release-main` hook exists for `main`, +and its URL is stored as `LOWCODER_SDK_DEPLOY_HOOK` in the repository's Actions +secrets. The guarded build and deploy commands above are saved in Cloudflare; +push-triggered production and preview builds remain disabled. This source must +be on `main` before a subsequent stable release can use the workflow. Creating +or merging these files does not itself deploy the SDK or publish a release. + +Cloudflare documents branch-head hooks at +https://developers.cloudflare.com/workers/ci-cd/builds/deploy-hooks/. + +Before publishing an update: + +```sh +node scripts/prepare-release.cjs https://sdk.lowcoder.cloud +node scripts/check-dist.cjs +npx wrangler deploy +``` + +The release step downloads the previous public asset manifest, checks SHA-256, +and retains old runtime files so cached loaders/open tabs survive deployments. +It deliberately fails if the previous release cannot be verified. The first +migration seeded this manifest from the verified Netlify deployment +`684309148dd28d5fbb335d1e` (870 public files; Netlify's deployment configuration is +not a served asset). Netlify remains available for rollback; no site deletion is +part of migration. Retained assets are not loaded by new visitors. + +For a standalone first preview, omit the previous origin. For production updates, +never omit asset retention. Cloudflare serves true 404 responses for missing files, +not the landing page as JavaScript. Keep custom-component sandbox files in place. + +Production uses the `sdk.lowcoder.cloud/*` Worker route and a proxied placeholder +DNS A record (`192.0.2.0`); the static Worker serves every path without an origin. +To roll back to Netlify, restore the proxied CNAME `sdk` → +`lowcoder-sdk.netlify.app`, then remove the SDK Worker route. Leave the Worker +assets available until caches and any new integrations have been considered. diff --git a/client/packages/lowcoder-sdk-webpack-bundle/index.html b/client/packages/lowcoder-sdk-webpack-bundle/index.html index 43d6604063..ba7fcb6304 100644 --- a/client/packages/lowcoder-sdk-webpack-bundle/index.html +++ b/client/packages/lowcoder-sdk-webpack-bundle/index.html @@ -1,37 +1,348 @@ - + - - - -+ Build the parts that make your product yours. Compose the rest + visually — and embed them directly into your app’s DOM. +
+ +Your design. Your navigation. Your product.
++ The shell is ordinary HTML. The panel inside it is a real Lowcoder + module. Change the customer and watch the input travel. +
++Run the demo to see real module outputs and events.+
+ Inspect #embed in your browser. The module renders as
+ DOM elements inside the host page.
+
+ Keep your product’s shell in code. Give the workflows inside it a + visual home your team can maintain. +
++ CRM, website, ticketing & POS integration recipes ↗ +
++ Bring account tools, forms and service workflows into the product + your customers already use. +
+ Pass the customer context → ++ Add approval screens, data tables and internal processes without + rebuilding the surrounding application. +
+ Return outputs and decisions → ++ Ship a visually maintained module alongside your existing UI, then + react to its events in your own code. +
+ Connect to host actions → ++ Load the browser SDK once, wait for it to be ready, then mount your + published Lowcoder app. +
+ Read the integration guide ↗ ++ Explore every function, option and event ↗ +
+npm install lowcoder-sdkFor React projects
+
+<div id="my-app"></div>
+<script src="https://sdk.lowcoder.cloud/bundle.js"></script>
+<script>
+ LowcoderSDK.ready.then(async (sdk) => {
+ const app = await sdk.mount('#my-app', {
+ appId: 'YOUR_PUBLISHED_APP_ID',
+ moduleInputs: { customerId: 'customer-42' }
+ });
-
-
+ app.on('moduleOutputChange', (output) => {
+ console.log(output);
+ });
+ }).catch(console.error);
+</script>
+ Start with one module. Keep ownership of the product around it.
+ Build your first module ↗ +This page or SDK asset does not exist.
+ Back to the SDK playground ++ The SDK renders Lowcoder components directly in a DOM element. Your + host app controls where they live and how data moves between the two. +
++ Publish an app or module in Lowcoder and copy its application ID. + Create a mount element with enough space for its layout, then load + the browser SDK once. +
+
+<div id="customer-panel" style="min-height: 360px"></div>
+<script src="https://sdk.lowcoder.cloud/bundle.js"></script>
+<script>
+ async function start() {
+ const sdk = await LowcoderSDK.ready;
+ const app = await sdk.mount('#customer-panel', {
+ appId: 'YOUR_PUBLISHED_APP_ID',
+ baseUrl: 'https://api-service.lowcoder.cloud',
+ webUrl: 'https://app.lowcoder.cloud',
+ moduleInputs: { customerId: 'customer-42' }
+ });
+ app.on('moduleOutputChange', console.log);
+ app.on('moduleEventTriggered', console.log);
+ }
+ start().catch(console.error);
+</script>
+
+ LowcoderSDK.ready resolves when the SDK scripts have
+ loaded. mount() resolves after the application
+ definition is loaded and rendering is scheduled. Individual
+ components and queries may still be loading.
+
+ The npm package provides LowcoderAppView. Install it
+ with npm install lowcoder-sdk and import the stylesheet
+ once in your application.
+
+import { LowcoderAppView } from 'lowcoder-sdk';
+import 'lowcoder-sdk/dist/style.css';
+
+export function CustomerPanel({ customerId }) {
+ return (
+ <LowcoderAppView
+ appId="YOUR_PUBLISHED_APP_ID"
+ moduleInputs={{ customerId }}
+ onModuleOutputChange={(output) => {
+ console.log(output);
+ }}
+ onModuleEventTriggered={(eventName) => {
+ console.log(eventName);
+ }}
+ />
+ );
+}
+ + Use the npm package for React integration or the browser loader for + a plain DOM integration. The published npm 3.0.0 package does not + yet include the hosted SDK’s readiness and disposal fixes, and its + appView subpath points to a missing file. Use the root import above; + check the + distribution table before + choosing lifecycle APIs. +
++ Define inputs, outputs, events and methods in a Lowcoder + module. Use those same names in your host + application. A normal app can be embedded too; the module interface + is what provides this explicit communication contract. +
+| Direction | +API | +Use it for | +
|---|---|---|
| Host → module | +app.setModuleInputs(values) |
+ Customer context, filters and other named inputs. | +
| Module → host | +app.on('moduleOutputChange', handler) |
+ Receive the module’s current output object. | +
| Module → host | +app.on('moduleEventTriggered', handler) |
+
+ React to a named event such as openCustomer.
+ |
+
| Host → module | +app.invokeMethod(name, args) |
+ + Call a method explicitly exposed by the module after it has + rendered. + | +
+app.setModuleInputs({ customerId: 'customer-99' });
+
+const unsubscribe = app.on('moduleEventTriggered', (name) => {
+ if (name === 'openCustomer') {
+ // Run your host application's navigation or action here.
+ }
+});
+
+// Each event has one active handler per instance.
+unsubscribe();
+ + Keep the returned instance while the panel is visible. Update inputs + when context changes. Unmount it when your host removes that screen. +
+
+// Browser SDK
+await LowcoderSDK.unmount('#customer-panel');
+
+// Or dispose the instance directly.
+app.destroy();
+
+// Mounting again on the same element replaces its previous instance.
+const next = await LowcoderSDK.mount('#customer-panel', {
+ appId: 'ANOTHER_PUBLISHED_APP_ID'
+});
+
+ The browser SDK reports failed downloads through the rejected
+ ready promise and the
+ lowcoder:error window event. Handle mount errors in
+ your host UI so visitors see a useful retry message.
+
+ Set baseUrl to your Lowcoder API origin and
+ webUrl to your Lowcoder frontend. For
+ workspace-specific login, pass orgId. Hosting the SDK
+ assets elsewhere does not move your backend or change application
+ permissions.
+
bundle.js and the complete emitted asset
+ directory together when self-hosting. Do not concatenate, rename or
+ selectively omit the chunks.
+ + The landing page does not load the SDK until you start the demo. + Splitting the runtime improves transfer parallelism and cache reuse; + it does not by itself remove the cost of rendering components. +
+
+ The existing .lowcoder-module-container markup
+ continues to work, including the historically spelled
+ locoder-backend-url and
+ locoder-frontend-url inputs. Global
+ opChange and
+ onModuleEventTriggered callbacks remain supported.
+
+<div class="lowcoder-module-container"> + <input type="hidden" class="module-id" + value="YOUR_PUBLISHED_APP_ID"> + <input type="hidden" class="locoder-backend-url" + value="https://api-service.lowcoder.cloud"> + <div class="lowcoder-module-display"></div> +</div> +<script src="https://sdk.lowcoder.cloud/bundle.js"></script>+
+ For markup added after page load, call
+ await LowcoderSDK.ready followed by
+ LowcoderSDK.scan(). For new integrations, use the
+ explicit mount API so ownership and cleanup are clear.
+
+ Legacy custom-component example ↗ + · Full API reference ↗ +
++ Practical module contracts and host code for CRM, websites, ticketing + and point of sale. +
++ No matching sections. Try a function name, an option or a shorter + phrase. +
++ These are integration recipes for your own host application, not + prebuilt connectors to a specific CRM, ticket vendor or payment + provider. Each names the module inputs, outputs and events you need + to create in Lowcoder first. Replace the placeholder application IDs + and host actions. +
+
+ Load bundle.js once as shown in the
+ quickstart, then use these examples
+ inside an async function or JavaScript module. They use the hosted
+ browser SDK. Remove each embed through
+ await sdk.unmount(element) when its screen closes.
+
+ Keep customer navigation and account selection in your CRM. Embed a + visually maintained account panel that receives the selected + customer and can ask the host to open their record. +
+| Module contract | +Create in Lowcoder | +
|---|---|
| Inputs | +
+ customerId (String),
+ customerName (String)
+ |
+
| Outputs | +
+ healthScore (Number) for the host’s summary
+ |
+
| Event | +
+ openCustomer triggered by a module button
+ |
+
const sdk = await LowcoderSDK.ready;
+let selected = { id: "customer-42", name: "Morgan Lee" };
+const app = await sdk.mount("#crm-panel", {
+ appId: "YOUR_CRM_MODULE_ID",
+ moduleInputs: { customerId: selected.id, customerName: selected.name }
+});
+app.on("moduleOutputChange", ({ healthScore }) => {
+ document.querySelector("#health-score").textContent = healthScore ?? "—";
+});
+app.on("moduleEventTriggered", (name) => {
+ if (name === "openCustomer") {
+ // Use the authoritative selection already held by the host.
+ location.assign("/customers/" + encodeURIComponent(selected.id));
+ }
+});
+function selectCustomer(customer) {
+ selected = customer;
+ app.setModuleInputs({ customerId: customer.id, customerName: customer.name });
+}
+
+ Create #crm-panel and #health-score in
+ your host. Wire its customer picker to selectCustomer.
+ The health output may arrive later than mount, so the host should
+ display a placeholder until it receives a value.
+
+ Place a configuration module inside a product page. The site owns + the product route; Lowcoder owns the form, calculations and visual + editing. A named event can direct the host to its contact section. +
+| Module contract | +Create in Lowcoder | +
|---|---|
| Inputs | +
+ productId (String),
+ options (Array)
+ |
+
| Outputs | +
+ estimatedTotal (Number), calculated by the
+ module
+ |
+
| Event | +requestContact triggered by the visitor |
+
const sdk = await LowcoderSDK.ready;
+const app = await sdk.mount("#configurator", {
+ appId: "YOUR_CONFIGURATOR_MODULE_ID",
+ moduleInputs: {
+ productId: "desk-01",
+ options: JSON.stringify([
+ { id: "oak", label: "Oak", price: 120 },
+ { id: "walnut", label: "Walnut", price: 160 }
+ ])
+ }
+});
+app.on("moduleOutputChange", ({ estimatedTotal }) => {
+ document.querySelector("#estimate").textContent =
+ typeof estimatedTotal === "number"
+ ? new Intl.NumberFormat("en", { style: "currency", currency: "EUR" })
+ .format(estimatedTotal)
+ : "Choose your options";
+});
+app.on("moduleEventTriggered", (name) => {
+ if (name === "requestContact") {
+ document.querySelector("#contact").scrollIntoView();
+ }
+});
+
+ Create the #configurator, #estimate and
+ #contact elements. Publish the module for the intended
+ audience. Treat the displayed estimate as client-side information;
+ calculate an authoritative price on your backend before accepting an
+ order.
+
+ Embed a triage panel alongside a ticket. Send the ticket context as + a Data input. Return an escalation request as one structured output, + so its ticket ID, reason and request ID travel together. +
+| Module contract | +Create in Lowcoder | +
|---|---|
| Input | +
+ ticket (Data):
+ { id, subject, priority }
+ |
+
| Output | +
+ escalationRequest (Data): null, or
+ { requestId, ticketId, reason }
+ |
+
| Module action | ++ On explicit user escalation, assign a fresh request ID and + set the complete output object + | +
const sdk = await LowcoderSDK.ready;
+const ticket = { id: "T-1042", subject: "Export failed", priority: "normal" };
+const app = await sdk.mount("#ticket-tools", {
+ appId: "YOUR_TRIAGE_MODULE_ID",
+ moduleInputs: { ticket: JSON.stringify(ticket) }
+});
+const handled = new Set();
+app.on("moduleOutputChange", ({ escalationRequest: request }) => {
+ if (!request || typeof request.requestId !== "string" || !request.requestId ||
+ request.ticketId !== ticket.id || typeof request.reason !== "string" ||
+ handled.has(request.requestId)) return;
+ handled.add(request.requestId);
+ // Show a host-owned confirmation using safe text rendering.
+ // Its submit action calls your ticket API, which validates access
+ // and deduplicates requestId. This output is a request, not authorization.
+ document.querySelector("#escalation-reason").textContent = request.reason;
+ document.querySelector("#escalation-confirmation").hidden = false;
+});
+
+ Create #ticket-tools and a host confirmation panel
+ containing #escalation-reason. Initialize the module’s
+ request output to null and change it only on a
+ deliberate user action. Keep backend deduplication across remounts;
+ the in-memory set only covers this screen’s lifetime.
+
+ Keep the cart, tender and payment flow in your POS. Embed a loyalty + or service panel that receives the current cart and requests an + optional lookup or host action. +
+| Module contract | +Create in Lowcoder | +
|---|---|
| Inputs | +
+ cart (Array), customerId (String)
+ |
+
| Event | +requestCustomerLookup |
+
| Method | +
+ refreshOffers, with a single
+ customerId parameter
+ |
+
| Output | +
+ offerStatus (String): module-owned
+ loading/success/failure information
+ |
+
const sdk = await LowcoderSDK.ready;
+const app = await sdk.mount("#loyalty-panel", {
+ appId: "YOUR_LOYALTY_MODULE_ID",
+ moduleInputs: {
+ customerId: "customer-42",
+ cart: JSON.stringify([{ sku: "coffee-01", quantity: 2 }])
+ }
+});
+app.on("moduleEventTriggered", (name) => {
+ if (name === "requestCustomerLookup") {
+ document.querySelector("#customer-lookup").showModal();
+ }
+});
+app.on("moduleOutputChange", ({ offerStatus }) => {
+ document.querySelector("#offer-status").textContent = offerStatus ?? "";
+});
+// Enable this host control only once the module is visibly usable.
+const refreshOffers = () => app.invokeMethod("refreshOffers", ["customer-42"]);
+document.querySelector("#refresh-offers").addEventListener("click", refreshOffers);
+
+// When leaving the POS screen, remove host-owned listeners too:
+async function closeLoyaltyPanel() {
+ document.querySelector("#refresh-offers").removeEventListener("click", refreshOffers);
+ await sdk.unmount("#loyalty-panel");
+}
+
+ Create the panel, a
+ <dialog id="customer-lookup">, status text and
+ refresh button. A method call is not awaitable and can do nothing
+ before rendering; a real POS should make loading and action
+ availability explicit. Your POS/backend remains responsible for
+ prices, discounts and payment state. The SDK does not provide a
+ payment-terminal or offline transaction API.
+
+ Every embedding function, option and event: what it does, how to use + it, and what to expect. +
++ No matching sections. Try a function name, an option or a shorter + phrase. +
++ A host owns the page and its navigation. Lowcoder owns the app + rendered inside one element. For communication, build a + module and define its inputs, outputs, events and + methods in the editor. Their names form your integration contract. +
++ A regular Lowcoder app can also be mounted, but these module APIs do + not give the host arbitrary access to its components, queries or + internal state. +
+| Host needs to… | +Use | +
|---|---|
| Load the browser runtime | +
+ LowcoderSDK.ready
+ |
+
| Create or remove an embed | +
+ mount() ·
+ unmount()
+ |
+
| Send context into a module | +
+ setModuleInputs()
+ |
+
| Read values or react to an action | +
+ on()
+ |
+
| Run a module’s exposed action | +
+ invokeMethod()
+ |
+
| Render in React | +
+ LowcoderAppView
+ |
+
| Mount with the npm package | +
+ bootstrapAppAt()
+ |
+
| Use existing declarative markup | +
+ scan()
+ |
+
+ Use the quickstart for your first embed or + jump to the + CRM, website, ticketing and POS recipes. + This reference covers the complete native embedding interface. + Component builders, editor controls, datasource plugin APIs and + re-exported utilities in the broader npm package are separate + extension APIs. +
++ Verified 11 October 2026 The + hosted browser SDK and npm package are released separately. This + page describes the current hosted SDK, with npm differences called + out below. +
+| Capability | +Hosted browser SDK | +npm lowcoder-sdk@3.0.0 | +
|---|---|---|
| Entry point | +https://sdk.lowcoder.cloud/bundle.js |
+ import … from "lowcoder-sdk" |
+
| Browser facade | +
+ LowcoderSDK.ready / mount / unmount / scan
+ |
+
+ Not exported; use React or bootstrapAppAt
+ |
+
| Instance methods | +
+ on / setModuleInputs / invokeMethod / destroy
+ |
+
+ on / setModuleInputs / invokeMethod; no
+ destroy
+ |
+
| Instance readiness | +
+ instance.ready; mount awaits definition loading
+ |
+
+ No instance.ready; bootstrap resolves before
+ definition loading finishes
+ |
+
| Initial false / 0 / empty string inputs | +Preserved | +Can fall back to the module default | +
| React lifecycle fixes | +Present in current repository source | +Not yet in the published 3.0.0 package | +
| Import subpath | +Not applicable | +
+ lowcoder-sdk/appView points to a missing file
+ in 3.0.0; use the root import
+ |
+
+ The npm tarball was checked directly against the
+ published 3.0.0 metadata
+ and package contents. Its bundled declarations also omit
+ webUrl and orgId even though the runtime
+ accepts them. Do not infer installed behavior from the repository
+ version number alone.
+
+ For the complete lifecycle documented here, use the hosted browser
+ interface. npm users should pin and test their installed version
+ before adopting newer source APIs. The npm build needs a bundler,
+ React and React DOM (declared peers: ≥18), and
+ import 'lowcoder-sdk/dist/style.css'. It is not a
+ standalone script-tag build. Import it only on the client in
+ server-rendered applications.
+
const sdk = await LowcoderSDK.ready;
+// sdk.mount, sdk.unmount and sdk.scan are now available.
+
+ Promise<LowcoderSDK> · Browser only
+
+ Resolves after the initial SDK scripts have loaded and the mount API + exists. It does not wait for apps discovered by the automatic markup + scan, component rendering or queries. +
+
+ Rejects if an initial chunk fails to load or initialization does not
+ install the mount API. Catch the promise to show a retry message. A
+ repeated bundle.js tag reuses an existing loader and
+ its promise; it does not reset a failed load. After correcting a
+ network or policy failure, reload the page.
+
const sdk = await LowcoderSDK.ready;
+const app = await sdk.mount("#customer-panel", {
+ appId: "YOUR_PUBLISHED_MODULE_ID",
+ moduleInputs: { customerId: "customer-42" }
+});
+
+ mount(target: string | Element, options: MountOptions):
+ Promise<AppViewInstance>
+
| Parameter | +Meaning | +
|---|---|
target |
+ + An existing DOM element or CSS selector. A selector uses its + first match. + | +
options.appId |
+ Required application or module ID. Must be non-empty. | +
options |
+
+ The shared options below, plus
+ appId.
+ |
+
+ Creates a React root in the element and returns the instance after + definition loading and scheduling its render. Calling mount again on + an element managed by this facade waits for the previous + initialization and destroys its instance before replacing it. +
+
+ Rejects for a missing/invalid target, invalid selector, missing app
+ ID or an initialization failure. Failed initialization cleans up its
+ root. An initial API 401 instead renders the built-in
+ login prompt and resolves; resolution is not proof of authorization.
+ Later component or query errors are outside this promise.
+
+ Give the container dimensions through your host CSS. The SDK has no
+ width, height, timeout or
+ AbortSignal mount options.
+
const container = document.querySelector("#customer-panel");
+await LowcoderSDK.unmount(container);
+container.remove(); // The host owns removal of the element.
+
+ unmount(target: string | Element): Promise<void>
+
+ Waits for a mount tracked by this browser facade, destroys the + instance and removes its tracking entry. The container stays in the + DOM. An unknown or already unmounted element is a no-op. A pending + initialization failure can reject this promise; an invalid CSS + selector can also reject. +
+
+ Unmount before removing the element, or keep and pass the element
+ reference. A selector cannot find a detached element. Instances
+ created directly with bootstrapAppAt are not tracked
+ here.
+
| Option | +Type / default | +Behavior | +
|---|---|---|
baseUrl |
+
+ string · https://api-service.lowcoder.cloud
+ |
+
+ Lowcoder backend base URL, without a trailing
+ /api. The SDK requests
+ /api/applications/{appId}/view beneath it.
+ |
+
webUrl |
+ string · https://app.lowcoder.cloud |
+ Lowcoder frontend URL for the authentication flow. | +
orgId |
+ string · omitted | ++ Workspace ID used to select the workspace login URL. Does + not grant access or set the authenticated identity. + | +
moduleInputs |
+ input-name map · omitted | ++ Initial values for declared inputs on a root module. See + input encoding and updates. + | +
appDsl |
+ application definition object · omitted | ++ Supply a Lowcoder application DSL instead of fetching the + initial definition. The hosted SDK clones it before applying + initial inputs. + | +
moduleDslMap |
+ map of module ID → DSL · omitted | +
+ Definitions for nested modules when supplying
+ appDsl. Without appDsl, the server
+ response supplies and replaces this map.
+ |
+
+ appId is additionally required by browser mount and
+ React, and is the first argument to bootstrapAppAt. Use
+ the actual ID of your published app; the local demo uses a synthetic
+ ID because it supplies its own complete definition.
+
+ Supplying appDsl skips the initial app-definition
+ request and JavaScript datasource discovery. It does not promise
+ offline execution: queries, remote components, assets or missing
+ nested definitions may still use the network. Keep DSL compatible
+ with the runtime that loads it.
+
+ There are no public token, headers,
+ fetch, theme, onReady or
+ onError initialization options.
+
const app = await LowcoderSDK.mount("#module", {
+ appId: "YOUR_PUBLISHED_MODULE_ID",
+ baseUrl: "https://lowcoder.example.com",
+ webUrl: "https://lowcoder.example.com",
+ orgId: "YOUR_WORKSPACE_ID",
+ moduleInputs: { customerId: "customer-42" }
+});
+ await app.ready; // Already fulfilled when hosted sdk.mount() returns.
+
+ Promise<void> · Hosted/current source; absent in
+ npm 3.0.0
+
+ Waits for initial definition loading (and datasource discovery when + applicable), then schedules rendering. It rejects on initial loading + failures. It does not wait for a React commit, lazy + components, queries or callable module methods. +
+
+ There is no universal “everything rendered” event. If the host needs
+ business readiness, design a named output or event in the module,
+ such as dataLoaded. That name is your contract, not a
+ built-in SDK event. Event subscriptions do not replay previous
+ values; an early event can be missed. Do not use a one-time startup
+ event as your only readiness guarantee.
+
app.setModuleInputs({
+ customerName: "Ada", // String input
+ quantity: 0, // Number input
+ enabled: false, // Boolean input
+ customer: JSON.stringify({ id: "customer-42" }), // Data input
+ rows: JSON.stringify([{ id: 1, total: 25 }]) // Array input
+});
+
+ setModuleInputs(inputs: InputMap): void
+
+ Updates declared inputs on the mounted root module and schedules
+ rendering. Unknown input names are ignored. Omitted keys and
+ undefined leave existing module values unchanged; this
+ is not a reset or deep merge. Pass a fresh map for each update.
+
+ The editor supports Data, String, Array, Number, Boolean and Query
+ input types. Values go through Lowcoder’s code controls: they are
+ source values converted to text, not an arbitrary JavaScript object
+ bridge. Serialize structured Data and Array values with
+ JSON.stringify. Raw objects become
+ [object Object]; arrays are not automatically
+ JSON-encoded.
+
+ Plain strings, numbers and booleans work with the corresponding
+ scalar controls. The hosted SDK preserves 0,
+ false and ""; npm 3.0.0 can drop them at
+ initial load. JavaScript null is not a general “clear”
+ operation; for a Data input containing JSON null, pass
+ "null".
+
{{ … }} is interpreted by Lowcoder’s template engine,
+ including inside serialized strings. JSON serialization does not
+ turn this into a guaranteed literal-value channel. Define bounded
+ input contracts and validate untrusted text. Inputs are
+ client-visible context, never authentication or permission checks.
+
+ Query inputs represent Lowcoder query bindings. Passing a host
+ function does not create a supported callback bridge. Use module
+ events to request host actions. These update rules also apply to the
+ moduleInputs initialization option and React prop.
+
let latestOutput;
+const stopOutputs = app.on("moduleOutputChange", (output) => {
+ latestOutput = output;
+ console.log("Current module outputs", output);
+});
+const stopEvents = app.on("moduleEventTriggered", (eventName) => {
+ console.log("Module event", eventName);
+});
+
+// When these subscriptions are no longer needed:
+stopOutputs();
+stopEvents();
+
+ on(event, handler?): () => void
+
| Event | +Handler payload | +When it fires | +
|---|---|---|
+ moduleOutputChange
+ |
+ (output: OutputMap) => void |
+ + The whole named output object when its values change by deep + equality. Not a patch. May initially be empty. + | +
+ moduleEventTriggered
+ |
+ (eventName: string) => void |
+ + The name of an event explicitly fired by the module. No + custom payload or DOM event object. + | +
+ Each instance has one handler per event type.
+ Registering another replaces the previous one. Fan out within your
+ own handler if several host features need notifications. The
+ returned function removes that subscription; there is no separate
+ off() method.
+
+ In the hosted SDK, an old unsubscribe function cannot remove a newer
+ replacement handler. npm 3.0.0 does not have that safeguard. Calling
+ on(event, undefined) returns a no-op function; it does
+ not clear a previous handler.
+
+ Subscriptions do not replay a current snapshot. Register immediately + after mounting, but do not assume the first output or startup event + cannot have occurred already. To exchange results, expose named + outputs. There is no atomic event-with-data payload or guaranteed + ordering between an event and a separate output update. +
+
+ Only the two names above are instance events.
+ lowcoder:ready and lowcoder:error belong
+ to the separate
+ browser DOM event interface.
+
// In the Lowcoder editor, define a method named "selectCustomer"
+// with parameters in this order: customerId, focusDetails.
+// Call after the user can interact with the rendered module.
+app.invokeMethod("selectCustomer", ["customer-42", true]);
+
+ invokeMethod(methodName: string, params: any[] = []): void
+
+ Runs an action exposed as a named module method. Arguments are
+ positional, in the order configured in the editor. A missing,
+ undefined or null argument uses that
+ parameter’s default; 0, false and
+ "" are retained. Extra arguments are ignored.
+
+ The name must identify a configured module method, not an arbitrary + component function or query. An unknown method, a normal app instead + of a module, or a call before the module root exists silently does + nothing. Calls are not queued. +
+await app.invokeMethod(...) does not wait for the
+ action to finish and does not catch its asynchronous errors. Model
+ completion, results and failures as module outputs/events.
+ + Use a request ID in your module contract when correlating + asynchronous work with host actions. For actions such as saving, + submitting or charging, perform authoritative validation and state + changes on your backend. +
+app.destroy(); // Synchronous; safe to call more than once.
+
+ destroy(): void · Hosted/current source; absent in npm
+ 3.0.0
+
+ Clears instance handlers, releases the module reference and unmounts + its React root. It leaves the host element in place. Further input + updates and method calls have no effect. +
+
+ It does not cancel outstanding HTTP requests or undo shared SDK
+ configuration, global CSS or page-level state. For instances owned
+ by the browser facade, prefer
+ await LowcoderSDK.unmount(container), which also clears
+ that facade’s tracking entry. Do not manually destroy an instance
+ owned by a React wrapper that is managing its lifecycle.
+
+ AppViewInstance<I, O> represents an embedded app.
+ I describes its input-name map and O its
+ output object. Keep the instance returned by a factory; do not
+ construct a second React root on an element already owned by an
+ embed.
+
+ The runtime class constructor is
+ new AppViewInstance(appId, node, root, options = {}),
+ where root is a React DOM Root. It is a
+ low-level implementation surface: use bootstrapAppAt or
+ browser mount so component registration, root creation
+ and initialization cleanup happen together.
+
AppViewInstanceOptions<I>: the six
+ initialization options, with moduleInputs?: I.
+ LowcoderAppViewProps<I, O>: those options plus
+ appId, className and the two callbacks.
+ OutputChangeHandler<O> = (output: O) => void.
+ EventTriggerHandler = (eventName: string) => void.
+ + InputMap, OutputMap and MountOptions in this reference describe the + shapes of values; they are not named npm exports. Generic types do + not validate values at runtime. The browser facade is a script + global, not an npm export. Published npm 3.0.0 has incomplete + declaration routing as well as the missing members listed in the + distribution table; verify your toolchain instead of assuming + current source declarations are installed. +
+import { bootstrapAppAt } from "lowcoder-sdk";
+import "lowcoder-sdk/dist/style.css";
+
+const app = await bootstrapAppAt(
+ "YOUR_PUBLISHED_MODULE_ID",
+ document.getElementById("customer-panel"),
+ { moduleInputs: { customerId: "customer-42" } }
+);
+
+ bootstrapAppAt(appId: string, node: Element, options = {}):
+ Promise<AppViewInstance>
+
+ The low-level imperative factory exported by the npm package. Unlike
+ browser mount, it requires an element, not a selector.
+ Supply the same initialization options, with appId as
+ the first argument. The browser bundle also exposes the broader
+ exports through window.$lowcoder_sdk; prefer
+ LowcoderSDK.mount for browser integrations.
+
+ In current source, the factory checks for a node, waits for
+ instance.ready, and cleans up on initialization
+ failure. It does not track or replace an existing root on that
+ element. The published npm 3.0.0 factory returns before definition
+ loading finishes, and its instance has neither
+ ready nor destroy. Its declaration
+ requires the options argument, so pass {} when using
+ defaults. Use the hosted facade for screens requiring the documented
+ cleanup lifecycle until an npm release includes those fixes.
+
import { useMemo } from "react";
+import { LowcoderAppView } from "lowcoder-sdk";
+import "lowcoder-sdk/dist/style.css";
+
+export function CustomerPanel({ customerId }) {
+ const inputs = useMemo(() => ({ customerId }), [customerId]);
+ return (
+ <div style={{ minHeight: 360 }}>
+ <LowcoderAppView
+ appId="YOUR_PUBLISHED_MODULE_ID"
+ className="customer-module"
+ moduleInputs={inputs}
+ onModuleOutputChange={(output) => console.log(output)}
+ onModuleEventTriggered={(name) => console.log(name)}
+ />
+ </div>
+ );
+}
+ | Prop | +Meaning | +
|---|---|
appId |
+ + Required string: the published application or module ID. + | +
| All six initialization options | +
+ baseUrl, webUrl,
+ orgId, moduleInputs,
+ appDsl, moduleDslMap.
+ |
+
className |
+ + Optional string on the wrapper element. Set size through a + host wrapper or CSS. + | +
+ onModuleOutputChange
+ |
+
+ Optional (output) => void; same payload as
+ the instance output event.
+ |
+
+ onModuleEventTriggered
+ |
+ Optional (eventName: string) => void. |
+
ref |
+ + Forwarded instance reference; initially undefined. Not a + guarantee that all components or queries have finished. + | +
+ Changes to appId, baseUrl,
+ webUrl or orgId recreate the instance. A
+ new moduleInputs object updates inputs; callback prop
+ changes replace subscriptions. Changes to appDsl or
+ moduleDslMap alone do not reload the app: change the
+ React key when replacing a supplied definition. Cleanup
+ disposes the owned instance.
+
+ These lifecycle fixes are not in npm 3.0.0. Do not + rely on cleanup or backend switching described above in that + published version. The npm React wrapper also does not provide the + new instance readiness/disposal API. +
+
+ Arbitrary HTML props such as style and
+ id are not forwarded; put them on your host wrapper.
+ There is no public onError prop. The current wrapper
+ logs initial mount failures to the console. Use the hosted
+ imperative API if the host needs to own loading, failure and retry
+ UI.
+
+ The wrapper uses the same single event-handler slots as
+ instance.on. Registering a second handler through the
+ ref can replace the corresponding callback prop subscription.
+
await LowcoderSDK.ready;
+await LowcoderSDK.scan(document.getElementById("new-widgets"));
+
+ scan(root: ParentNode = document): Promise<void[]>
+
+ Finds descendant .lowcoder-module-container elements
+ and mounts their display elements. The root itself is not included.
+ The browser entry scans the document automatically at DOM readiness.
+ Call this again for markup inserted later.
+
+ Skips containers without a display element and display elements
+ already tracked by the facade. It returns an array of undefined
+ results, not app instances. Individual JSON or mount errors are
+ caught and emitted on the affected container as
+ lowcoder:error; the scan can resolve even if an embed
+ failed. An invalid root can reject.
+
+ Listen directly to each container for its events. Use explicit mount + when you need an instance, individual failure handling or lifecycle + control. +
+<div class="lowcoder-module-container" id="customer-widget">
+ <input type="hidden" class="module-id" value="YOUR_PUBLISHED_MODULE_ID">
+ <input type="hidden" class="module-input" value='{"customerId":"customer-42"}'>
+ <input type="hidden" class="locoder-backend-url" value="https://api-service.lowcoder.cloud">
+ <input type="hidden" class="locoder-frontend-url" value="https://app.lowcoder.cloud">
+ <input type="hidden" class="locoder-org-id" value="YOUR_WORKSPACE_ID">
+ <div class="lowcoder-module-display"></div>
+</div>
+<script src="https://sdk.lowcoder.cloud/bundle.js"></script>
+ | Class | +Meaning | +
|---|---|
lowcoder-module-container |
+ Parent container found by the scanner. | +
lowcoder-module-display |
+ Descendant DOM element where the app is mounted. | +
module-id |
+ Input whose value is the required app ID. | +
module-input |
+
+ Input containing a JSON object of module inputs; defaults to
+ {}.
+ |
+
locoder-backend-url |
+
+ Optional backend URL; same default as baseUrl.
+ |
+
locoder-frontend-url |
+
+ Optional frontend URL; same default as webUrl.
+ |
+
locoder-org-id |
+ Optional workspace login ID. | +
+ The historical spelling locoder-… is required. Changing
+ a hidden field after mounting does not update that instance; the
+ scanner skips existing mounts. Unmount its display element before
+ scanning again, or use explicit mount and
+ setModuleInputs.
+
+ The scanner also calls optional globals
+ window.opChange(output) and
+ window.onModuleEventTriggered(eventName). They are
+ shared across all scanned embeds and carry no instance identity.
+ Prefer listeners on the individual container. A throwing global
+ callback can interrupt subsequent container event delivery.
+
| Event target | +Name | +event.detail | +
|---|---|---|
window |
+ lowcoder:ready |
+ No payload. Initial browser runtime loaded. | +
window |
+ lowcoder:error |
+ + Error from initial loader failure; not a catch-all runtime + error event. + | +
| Scanned container | +lowcoder:output |
+ Whole module output object. | +
| Scanned container | +lowcoder:event |
+ Module event name string. | +
| Scanned container | +lowcoder:error |
+ Error parsing its JSON or mounting its app. | +
const container = document.getElementById("customer-widget");
+container.addEventListener("lowcoder:output", (event) => {
+ console.log(event.detail);
+});
+container.addEventListener("lowcoder:error", (event) => {
+ console.error("This embed failed", event.detail);
+});
+
+ These CustomEvents do not bubble. Register on the
+ stated target before the event can fire. Explicit
+ mount() does not emit the scanner’s container events;
+ subscribe on its returned instance instead. Prefer the ready promise
+ to a window readiness listener that could be attached too late.
+
+ The initial app request uses credentials/cookies. An initial
+ 401 displays a built-in login prompt. Login opens
+ {webUrl}/user/auth/login, or
+ {webUrl}/org/{orgId}/auth/login when a workspace is
+ supplied. The prompt’s Refresh action reloads the
+ entire host page. Other initial failures, including
+ 403, reject hosted mounting.
+
+ Configure backend CORS and cookie settings for the actual host + origin, and test the browser’s cross-site cookie behavior. CORS on + the SDK’s static files does not grant access to the Lowcoder API. + There is no public bearer-token option, request-header hook, logout + method or automatic transfer of your host’s SSO session. +
++ Multiple embeds can coexist, but the runtime shares configuration, + Redux state, history and CSS. Use one Lowcoder deployment per page. + Passing a different backend to each instance does not provide + independent isolation. Native DOM integration is not a Shadow DOM or + CSS sandbox; test host styles, overlays and routing. +
+
+ To self-host, serve bundle.js with its complete emitted
+ asset directory. The loader resolves chunks relative to its own URL.
+ Retain old hashed assets during upgrades so already open pages can
+ still load their lazy chunks. The loader propagates its script nonce
+ to initial scripts; this is not a guarantee that the full runtime
+ supports your strict CSP.
+
| Symptom | +What to check | +
|---|---|
| The page stays blank | +
+ Catch LowcoderSDK.ready and
+ mount() errors. Check chunk requests, backend
+ access, container size and runtime console errors.
+ |
+
| Inputs appear unchanged | ++ Embed a module; declare the exact input names and types. + Send a fresh map. Serialize Data/Array values. Omitted keys + do not reset values. + | +
| A method does nothing | ++ Use a declared module method, wait for the rendered module + to be usable, and match parameter order. Mount readiness + alone is insufficient. + | +
| An awaited method finishes instantly | +
+ invokeMethod returns void. Expose completion
+ through your module contract.
+ |
+
| A listener stops firing | ++ There is one handler per event. Another registration or a + React callback prop may have replaced it. + | +
| An initial output was missed | ++ Subscriptions have no replay. Avoid depending on a single + startup emission for a handshake. + | +
| A private embed shows Login | ++ Mount can resolve to the auth prompt. Verify cookies, + allowed origins, publication/access settings and frontend + login URL. + | +
| destroy / ready / appView import is missing | ++ Check the distribution table; npm + 3.0.0 is not equivalent to the hosted bundle. + | +
| Host styles or another embed change | ++ Configuration, routing and CSS are shared. Keep the same + deployment across embeds and check host integration + boundaries. + | +
+ This reference is derived from the browser facade, loader,
+ AppViewInstance, bootstrap factory, React wrapper and
+ module input/method implementations in
+ the Lowcoder client source. The distribution notes record the separately inspected npm
+ artifact. Repository changes and published artifacts can differ; the
+ version table makes that distinction explicit.
+

From an App, one can call Methods of a embedded Module.