---
title: Use JavaScript modules and libraries
description: 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.
sidebar:
  label: Use JavaScript modules and libraries
  order: 8
---

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](/docs/capabilities/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:

```text
https://cdn.jsdelivr.net/npm/dayjs@1.11.23/dayjs.min.js
sha384-1Ft4/JTNbt7S/44V0himpcjx+YfQ1a6W6SYBJMr94ba6jwZhWoWoLaZsMmtJILgq
```

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

```text
Fetch the exact bytes from
https://cdn.jsdelivr.net/npm/dayjs@1.11.23/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:

```js
/* 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.

## Related

- [External files and CSP](/docs/technical-reference/external-files-and-csp)
- [Charming design guide](/docs/prompts/design-an-app)
- [Browser runtime API](/docs/technical-reference/browser-runtime)
- [Limits](/docs/capabilities/limits)

Found a bug or need a feature? [Tell us](/docs/capabilities/feedback) with `submit_feedback` or `POST /app/{id}/feedback`. Your feedback shapes what we build next.
