Skip to content
Charming Docs
Esc
navigateopen⌘Jpreview
On this page

Use JavaScript modules and libraries

Fetch a small browser module or library while authoring an app, review and pin it, then inline its UMD or IIFE build into the app UI.

Charming runs the ui field as one inline classic script. An app cannot install a package or load JavaScript from a CDN at runtime. External <script src> tags, module imports, dynamic imports, and CDN imports are blocked by the app’s content security policy (CSP).

To use a small browser module or library, fetch an exact UMD or IIFE file while authoring the app, review it, pin its version and integrity digest, and inline its bytes at the start of ui. The finished app then runs one self-contained script and makes no request to the library host.

Pick a browser-ready build

Choose a library build that:

  • ships as one UMD or IIFE file and exposes a browser global;
  • fits within the app source limits;
  • does not fetch more JavaScript, CSS, workers, WebAssembly, models, or other chunks at runtime; and
  • has source and a license you can review.

An ECMAScript module cannot be imported directly from ui. If a package only ships modules, bundle the code outside Charming into one reviewed IIFE file, then inline that output. Do not add an import map or a runtime bundler to the app.

Example: fetch Day.js while authoring

This example uses Day.js from jsDelivr. Neither Day.js nor jsDelivr is required. You can use any CDN or official package host that serves the exact, immutable UMD or IIFE file you reviewed. Pin the package version and file path; do not use a moving URL such as latest.

For this example, the source and expected digest are:

https://cdn.jsdelivr.net/npm/[email protected]/dayjs.min.js
sha384-1Ft4/JTNbt7S/44V0himpcjx+YfQ1a6W6SYBJMr94ba6jwZhWoWoLaZsMmtJILgq

Ask your agent to perform the fetch as an authoring step:

Fetch the exact bytes from
https://cdn.jsdelivr.net/npm/[email protected]/dayjs.min.js while
authoring the app. Verify that their SHA-384 digest is
sha384-1Ft4/JTNbt7S/44V0himpcjx+YfQ1a6W6SYBJMr94ba6jwZhWoWoLaZsMmtJILgq,
review the source and license, then place those exact bytes at the
start of the app's ui string. Put my app code after the library.
Do not emit a script tag, import, or runtime fetch for the library.

The resulting ui contains the library source followed by the app source:

/* Exact reviewed contents of dayjs.min.js are inlined above this line. */

const app = document.querySelector('#app');
app.textContent = dayjs().format('MMMM D, YYYY');

The comment marks the boundary for this example. The real ui must contain the library bytes above it.

Keep the execution environments separate

The inlined library runs in the browser with the rest of ui. Use window.charming for Charming browser APIs. Use env only in server routes. Inlined code receives the same window.charming authority as your own browser code, so treat the library as part of the app rather than as a harmless asset.

Declaring permissions.server.fetch does not let the browser load a library. It permits backend fetch calls from server routes and does not widen browser script-src or connect-src. Declaring an image origin changes only img-src.

Before you ship

  • Record the exact package version, file path, and digest with the app source.
  • Review minified code against the package source when possible.
  • Check that the file creates only the globals you expect.
  • Confirm that it makes no follow-up network requests.
  • Test the app at its standalone URL and in a chat embed.
  • Prefer a small local helper when a library would add much more code than the app needs.

Was this page helpful?