---
url: https://docs.ridgedata.ai/guide/javascript.md
---
# JavaScript embed quickstart

Load a dashboard in your page with the Embed SDK's JavaScript embed. Your server creates an access
token, and the browser loads the dashboard inside a shadow root. The returned object provides
methods for controlling the dashboard.

## 1. Create a token endpoint

Your backend exchanges a Ridge API key for a dashboard-scoped JWT. Never send the API key to
the browser.

```js
// Your server, after authenticating the current user.
const response = await fetch("https://app.ridgedata.ai/api/embed/token", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.RIDGE_API_KEY}`,
    "Content-Type": "application/json"
  },
  body: JSON.stringify({ dashboardIds: ["dashboard-id-123"] })
});

if (!response.ok) throw new Error("Could not create a Ridge embed token");
const { token } = await response.json();

// Return { token } as JSON from your own authenticated endpoint.
```

See [JavaScript authentication](./javascript-authentication) for authentication details and
[Partitioning](./partitioning) before adding row-level restrictions.

## 2. Add a container

```html
<div id="ridge-dashboard" style="min-height: 400px"></div>
```

## 3. Import the loader, fetch a JWT, and mount

Import the loader from `/api/embed/loader` inside a module script. A classic
`<script src="...">` cannot parse the loader's ES module exports.

The loader is public and contains no customer data. It loads the JavaScript and CSS needed
for the dashboard and sends the JWT with its requests. Dashboard and data access require
a valid credential; downloading JavaScript or CSS alone does not grant access to either.

Place this script after the container:

```html
<script type="module">
  import { RidgeEmbed } from "https://app.ridgedata.ai/api/embed/loader";

  try {
    const tokenResponse = await fetch("/api/ridge-token", { credentials: "same-origin" });
    if (!tokenResponse.ok) throw new Error("Could not authorize dashboard");
    const { token } = await tokenResponse.json();

    const ridge = await RidgeEmbed.load({
      jwt: token,
      dashboardId: "dashboard-id-123",
      rootElementId: "ridge-dashboard"
    });

    ridge.on("statechange", ({ state }) => console.log(state));
    await ridge.ready;
    console.log(ridge.getState());
  } catch (error) {
    console.error("Could not load the Ridge dashboard", error);
  }
</script>
```

`/api/ridge-token` is the endpoint you create in step 1, on your app's server. It returns
`{ "token": "...", "expiresIn": 86400 }`. The Ridge endpoint that accepts your API key is the absolute URL
`https://app.ridgedata.ai/api/embed/token`; call it only from your server.

`load()` returns the dashboard handle once the bundle has loaded. Attach listeners before
awaiting `ridge.ready` and before updating filters. Wait for `ready` before reading state or
schemas. Methods that update state wait for readiness automatically.

## Loader options

These options belong to `RidgeEmbed.load()`. The separate `EmbedOptions` type describes the
lower-level `embed()` function, which takes a dashboard URL and an HTML element directly.

| Option          | Required        | Description                                                                                                                                                                                                                                                                              |
| --------------- | --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `jwt`           | yes             | Token returned by your backend.                                                                                                                                                                                                                                                          |
| `expiresIn`     | with `getToken` | Lifetime of `jwt` in seconds, as returned with it by `POST /api/embed/token`.                                                                                                                                                                                                            |
| `getToken`      | no              | Function `() => Promise<{ token, expiresIn }>` that returns a new token from your backend, so the embed outlives `jwt`. Return the `POST /api/embed/token` response body. Mint each with the same `sessionId`; see [Expiry and refresh](./javascript-authentication#expiry-and-refresh). |
| `dashboardId`   | yes             | Published dashboard id allowed by the token.                                                                                                                                                                                                                                             |
| `rootElementId` | yes             | Id of a container element already present in the document.                                                                                                                                                                                                                               |
| `apiBaseUrl`    | no              | Defaults to `https://app.ridgedata.ai`.                                                                                                                                                                                                                                                  |
| `theme`         | no              | Theme overrides for this embed; see [Theming](./theming).                                                                                                                                                                                                                                |
| `state`         | no              | Object keyed by component id, then state key, for example `{ "region-menu": { filter: "EMEA" } }`. See [Dashboard state](./state).                                                                                                                                                       |
| `onError`       | no              | Callback `(error: Error) => void` for errors reported during bundle initialization or dashboard loading. It does not receive every possible failure; use `try`/`catch` around `load()` and `ready` as shown above.                                                                       |

### Loading errors

`load()` throws synchronously if `jwt`, `dashboardId`, or `rootElementId` is missing, or the
container cannot be found. These validation errors do not call `onError`. Bundle fetch errors
reject the returned promise and also bypass `onError`. Dashboard loading errors can reject
`ridge.ready`. A `try`/`catch` around both awaited calls handles these cases.

### Loader versions and caching

The unversioned URL serves the current loader. Use its response's `Cache-Control` header to
check how long a browser may cache it; do not depend on an hourly refresh interval.

`?v=<published-version>` requests a stored loader version. Only use a version confirmed as
published by Ridge. An unavailable version returns `404`; `1.2.3` is not a verified release.
Pinning the loader does not pin the dashboard bundle: the current loader requests the
unversioned JavaScript and CSS bundle URLs.

## Cleanup

Call `ridge.unmount()` when your app removes the dashboard, such as when navigating to another
page. This stops the dashboard's listeners and queries.
