> ## Documentation Index
> Fetch the complete documentation index at: https://docs.runflow.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Embed the Studio

> Drop the Runflow Studio UI into any website. Three-line integration via npm, runs through a server-side proxy.

[`@runflow-io/studio`](https://www.npmjs.com/package/@runflow-io/studio) ships the production Runflow Studio UI as a mountable component. Drop it into a `<div>`, point it at a proxy you control, and end users get the full workflow surface without leaving your site.

## What you'll do

End-to-end embed in two files: a server route that runs the proxy, and a browser entry that mounts the Studio.

## Prerequisites

* A Runflow API key. [Create one.](https://app.runflow.io/settings/api-keys)
* A server framework that speaks Web Standards `Request`/`Response` (Next.js App Router, Hono, Workers, SvelteKit, Bun, Deno), or use the Node adapter for Express/Fastify.
* React 18 or 19 in the host page (or use the npm package, which lists React as a peer dependency).

## Install

For the bundled embed (steps 1 and 2 below), you only need the Studio and the proxy:

```bash theme={"dark"}
bun add @runflow-io/studio @runflow-io/proxy
```

Add `@runflow-io/sdk` if you also use [headless mode](#headless-mode).

## Step 1: mount the proxy

The Studio talks to `api.runflow.io` through your server so your API key never reaches the browser bundle. The proxy is a Web Standards handler.

```ts theme={"dark"}
// app/api/runflow/[...path]/route.ts  (Next.js App Router)
import { runflowProxy } from "@runflow-io/proxy";

export const { GET, POST } = runflowProxy({
  apiKey: process.env.RUNFLOW_API_KEY!,
  authenticate: async (req) => {
    const session = await getSession(req);
    if (!session) return null;
    return { userId: session.userId };
  },
});
```

<Warning>
  Without an `authenticate` hook, the proxy is reachable by anyone with the URL. The CSRF defaults block browser drive-bys but not direct server-to-server callers (curl, scripts, other backends) — they let your Runflow budget burn against any allowed model.
</Warning>

Full proxy options, including `rateLimit`, `allowedModels`, and `onRun`, are in [the SDK guide](/guides/javascript-sdk#auth-rate-limit-telemetry-hooks).

## Step 2: mount the Studio

```ts theme={"dark"}
import { mount } from "@runflow-io/studio";

const studio = mount("#studio", {
  urls: { runflowProxy: "/api/runflow" },
  theme: "auto",
});

// later, to tear it down
studio.unmount();
```

```html theme={"dark"}
<div id="studio"></div>
```

The Studio injects its own stylesheet into `<head>` on mount. Pass `injectStyles: false` if you ship your own CSS.

## Mount options

```ts theme={"dark"}
mount(target: string | HTMLElement, options?: {
  urls?: {
    runflowProxy?: string;     // /api/runflow         dispatch + poll
    runflowDevProxy?: string;  // unreleased models    (off by default)
    imageProxy?: string;       // /api/runflow/image   same-origin image fetch
    upload?: string;           // /api/runflow/upload  multipart to public URL
    chat?: string;             // /api/runflow/chat    chat agent (SSE)
  };
  theme?: "light" | "dark" | "auto" | ThemeOverrides;
  injectStyles?: boolean;
}): { unmount(): void };
```

If you only need single-step dispatch (no uploads, no chat), `runflowProxy` alone is enough. The upload-dependent and chat-dependent paths in the UI degrade gracefully when the routes return 404 or 403.

## Companion endpoints

The Studio dispatches against multiple endpoints. `@runflow-io/proxy` handles the Runflow API paths; the others are customer-provided because they touch your storage and your AI provider keys.

| URL key        | What lives there                                                      | Provided by         | Security must-haves                                                                                                          |
| -------------- | --------------------------------------------------------------------- | ------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
| `runflowProxy` | `POST /v1/models/{model}/runs`, `GET /v1/runs/{id}`, `GET /v1/health` | `@runflow-io/proxy` | Add `authenticate`, see [hooks](/guides/javascript-sdk#auth-rate-limit-telemetry-hooks).                                     |
| `imageProxy`   | `GET /?url=<external>` returns the bytes same-origin                  | You                 | Validate the URL host against a CDN allowlist and reject RFC1918 / `169.254.0.0/16` / `localhost` to prevent SSRF (CWE-918). |
| `upload`       | `POST` multipart returns `{ url }` (writes to your storage)           | You                 | Enforce content-type and size limits before writing; the Studio does not sanitize uploads.                                   |
| `chat`         | SSE chat agent (wraps your AI provider plus the tool catalogue)       | You                 | Gate behind your existing user auth; your AI provider key is on the line.                                                    |

The `StudioUrls` type also includes additional optional keys for internal Runflow paths (an evaluation endpoint, a dev-only proxy for unreleased models). They default off and most embeds do not configure them. Check `packages/studio/src/lib/urls.ts` in [`runflow-js`](https://github.com/runflow-io/runflow-js) for the live shape.

## Theming

The Studio uses CSS custom properties under the `--rfs-*` prefix. Pass `theme: "light" | "dark" | "auto"` for the bundled defaults, or override specific tokens:

```ts theme={"dark"}
mount("#studio", {
  theme: {
    accent: "#FBBF24",
    bg0: "#0a0a0b",
    ink0: "#fafafa",
  },
});
```

All theme values become `--rfs-*` custom properties on the mount root, so you can also override them from your own CSS.

## Workflows that ship

`BUILTIN_TOOLS` ships 13 single-step workflows: `ai-edit`, `ai-scene`, `reference-inpaint`, `product-isolation`, `smart-resize`, `outpaint`, `background-color`, `background-removal`, `tag-removal`, `object-removal`, `model-removal`, `skin-fix`, `topaz-upscale`. Each maps to a model in the [catalog](/models). Iterate `BUILTIN_TOOLS` from the headless entry for the live set.

## Headless mode

To skip the bundled UI and build your own with the same tool catalogue:

```ts theme={"dark"}
import { findTool } from "@runflow-io/studio/headless";
import { Runflow } from "@runflow-io/sdk";

const rf = new Runflow({ baseUrl: "/api/runflow" });

const tool = findTool("background-removal");
if (!tool) throw new Error("Tool 'background-removal' not in BUILTIN_TOOLS");

const { output } = await rf.tools.run(tool, {
  image: "https://example.com/photo.jpg",
});
```

The headless entry also exports `BUILTIN_TOOLS`, `WORKFLOWS`, `SAMPLES`, the workflow dispatcher, and `setStudioUrls`. The `WORKFLOWS` surface is pre-1.0 and may change between patch releases.

## Related

<CardGroup cols={2}>
  <Card title="JavaScript SDK" icon="js" href="/guides/javascript-sdk">Server-side, proxy, and the tool DSL.</Card>
  <Card title="Browse models" icon="grid" href="/models">Workflows backed by real models.</Card>
  <Card title="Authentication" icon="key" href="/concepts/authentication">Bearer header.</Card>
  <Card title="Pick a model" icon="signs-post" href="/guides/pick-a-model">Decision tree.</Card>
</CardGroup>
