Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions docs/source/guide/admin-interfaces.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ These settings apply uniformly to every Interface in your organization — embed

Access these settings from **Organization > Settings > Interfaces**.

!!! info Which section do I need?
- To let an Interface **call an external API/service** (`fetch`/XHR/WebSocket) → **API origins**.
- To let an Interface **load a third-party script/stylesheet** from a CDN → **Advanced: external scripts**.
- To use a library with **no external network at all**, bundle it into the Interface — nothing to configure here. See [Use external libraries and services](interfaces-libraries).


## Enable Interfaces

Expand Down
7 changes: 6 additions & 1 deletion docs/source/guide/interfaces-agent.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,17 @@ meta_description: "Build custom labeling interfaces with React"
section: "Interfaces"
---

You can create Interfaces in two ways:
You can create Interfaces in two ways — pick whichever you prefer:

* **Within Label Studio** - Use Label Studio's out-of-the-box Interface builder to prompt our agent to build and refine your Interface.

* **Developed locally** - Install the [`create-interface-skill`](/skills/interface.html) and use an agent of your choice to vibe code an Interface that you can then import into Label Studio. For more information, see [Develop an Interface locally](interfaces-local).

!!! note
These are two independent ways to build the same thing, and the resulting Interface is identical either way — only the authoring path differs.

On Label Studio Cloud, both **Create with Agent** and [Develop Locally](interfaces-local) are available — choose whichever fits your workflow. **Create with Agent** is a Label Studio Cloud feature: on self-hosted or on-prem deployments it isn't available, so build your Interface with **Develop Locally** instead.

<iframe width="560" height="315" src="https://www.youtube.com/embed/pdcZ6HDwpiI?si=ZpeuyeKYx71q1vCR" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

To begin, select **Interfaces** in the main menu and then select **Create Interface > Create with Agent**.
Expand Down
130 changes: 130 additions & 0 deletions docs/source/guide/interfaces-libraries.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
---
title: Use external libraries and services in an Interface
short: External libraries & APIs
tier: enterprise
type: guide
order: 0
order_enterprise: 146
meta_title: Use external libraries and network services in Label Studio Interfaces
meta_description: "How to call external APIs, load third-party packages, or bundle libraries directly into a Label Studio Interface — and which security setting each one needs."
section: "Interfaces"
---

An Interface runs as a **sandboxed module** inside the Label Studio editor. By default that sandbox has **no outbound network and cannot load third-party code** — a deliberate security posture, not a hard limitation. When your Interface genuinely needs an external API or a third-party library, you unlock exactly what you need, in one of three ways.

The single most common point of confusion is that **"call an external API" and "use an external package" are unlocked in different places.** Allow-listing a host in the wrong section silently does nothing. This page explains which is which.

## The three paths at a glance

| You want to… | Example | Where to enable |
|---|---|---|
| **Call an external HTTP service** from your Interface | `fetch()` your ML backend, a geocoder, an internal microservice, a tile server | **Organization > Settings > Interfaces → API origins** |
| **Load a third-party package from a CDN** at runtime | Pull `three.js` or `d3` from `cdn.jsdelivr.net` via a `<script>` tag | **Organization > Settings > Interfaces → Advanced: external scripts** |
| **Use a third-party package with no external network** | Bundle `three.js` *into* the Interface and publish it | Nothing to configure — the code is self-contained. |

You can combine them. A point-cloud Interface might **bundle `three.js` locally** (path 3) *and* **allow-list your tile/asset server** under **API origins** (path 1) so it can fetch the data to render.

!!! info Can I use a third-party library?
Yes. Interface source code can't use `import` / `require` — each Interface is one self-contained module, not an ES module — but that's a limit on **module syntax**, not a ban on third-party code. Bring a library in by loading it from an allow-listed CDN (path 2) or by bundling it into the Interface (path 3).

---

## Path 1 — Call an external API or service (API origins)

Use this when your Interface needs to talk to an HTTP service over `fetch`, `XHR`, or `WebSocket` — for example, sending a selected region to an ML backend for inference, geocoding an address, or pulling map tiles.

1. Go to **Organization > Settings > Interfaces → API origins**.
2. Add the exact origin — scheme **and** host, e.g. `https://api.example.com`. One host per entry.
3. Save. Your Interface's `fetch()` calls to that origin now succeed.

| Rule | Detail |
|---|---|
| Format | Full origin including scheme (`https://host`). |
| Wildcards | Not allowed (no `*` or `*.example.com`). List each host explicitly. |
| Your LS server | Always allowed automatically — don't add it. Only list third-party hosts. |
| Empty list | **No outbound network.** The Interface can only reach your Label Studio server. |

**API origins** governs **data requests only** — it does **not** let you load a `<script>` from that host. If you add a package CDN here expecting a library to load, nothing happens (use Path 2 for that).

!!! info Example
To connect a custom ML backend to an Interface, you allow-list the backend's origin here — that is what lets the Interface send region payloads to it and render the predictions. No package is being installed; it's an API call.

---

## Path 2 — Load a package from a CDN (Advanced: external scripts)

Use this when you want the convenience of pulling a library straight from a CDN at runtime and you **trust that host completely**.

1. Go to **Organization > Settings > Interfaces → Advanced: external scripts**.
2. Turn on **Allow external scripts / stylesheets**.
3. Add one or more script origins, e.g. `https://cdn.jsdelivr.net`.
4. In your Interface, load the library from that origin (for example, inject a `<script>` and use its global).

| Rule | Detail |
|---|---|
| Scheme | Must be `https://`. HTTP origins are rejected. |
| Format | Origin only — no paths, query strings, credentials, or fragments. |
| Wildcards | Not allowed. |
| Toggle | Origins are ignored unless the checkbox is on. Turning it off clears the list on save. |

This is a different setting from **API origins**. A host listed here can serve executable code to your Interface; a host listed under **API origins** cannot, and vice versa.

!!! warning
Scripts loaded from these origins run with the Interface's **full privileges** and can read any task data the Interface is rendering. Only add hosts you trust completely. Most organizations should leave this section disabled and prefer **Path 3**.

---

## Path 3 — Bundle a package locally (nothing to allow-list, no network)

**Recommended for locked-down, on-premise, or air-gapped deployments, and for any team that would rather not open an outbound origin or trust a CDN.**

Because an Interface is a **single self-contained module**, a third-party library can travel *inside* your Interface code. The library becomes part of what you publish — there is no CDN, no `<script>` tag, no allow-list entry, and no outbound network at label time.

**How to do it**

1. Get a **browser build that attaches to a variable** — a prebuilt single-file `dist`, or produce one with a bundler (for example, `esbuild --bundle --format=iife --global-name=MyLib`). Interface source is compiled but **not** bundled for you, so resolve the dependency into this one file first. Avoid an **ES-module** build: `import` / `export` statements aren't supported.
2. Include that code in your Interface source and reference the library through the variable/global it defines. Do **not** use `import` / `require` in your own code.
3. Validate, preview, and publish as usual (via the Interfaces editor, or `label-studio-sdk interface validate/preview/sync`). The library ships with the published Interface.

**What works well this way**

- Pure-computation and rendering libraries: date/number utilities, validation (e.g. `zod`), charting/geometry, and in-browser renderers.
- Standard browser rendering APIs — **Canvas and WebGL are available in the sandbox**, so libraries like `three.js` can render inside an Interface. (The *iframe capabilities* list governs device features such as camera and microphone, not 2D/3D rendering.)

**Constraints to keep in mind**

- **Use an IIFE / global build.** `import` / `export` statements aren't supported, and a build that relies on top-level `module.exports` or `require(...)` may not pass validation. You also can't `require()` an npm package at label time — bundle the dependency into the file rather than requiring it, and reference the variable/global it defines.
- The library must run **in-browser within the sandbox**: no Node.js APIs, no access to the parent page's `window`/`document`, no persistent `localStorage`/`sessionStorage`, and any network the library itself performs is still governed by **API origins** (Path 1).
- The bundled code becomes part of the Interface's stored body. There's no fixed size limit, but very large bundles add to load and parse time — keep an eye on it.

---

## Worked example: rendering large point clouds

Suppose you need to review LiDAR point clouds and expect to use `three.js` to render them.

- **Air-gapped / strict security:** **bundle `three.js` locally** (Path 3). Nothing is allow-listed; the renderer ships inside the Interface. If the point-cloud data is fetched from a service, add that data host under **API origins** (Path 1).
- **CDN acceptable:** allow-list the CDN under **Advanced: external scripts** (Path 2) and load `three.js` from there.

Point-cloud **rendering** works because WebGL is available in the sandbox. The real limit is **data size and performance** — very large clouds stress browser/WebGL memory regardless of how the library is packaged, so test with representative data and down-sample or tile if needed.

!!! info
A heavy third-party renderer already ships inside a stock Interface today: the **Document AI** template renders PDFs with **PDF.js** from its bundled code — a concrete precedent that large libraries run inside Interfaces, including ones that compile internally or offload work to a web worker.

---

## Choosing a path

- **Calling a service over HTTP?** → **API origins** (Path 1).
- **Need a third-party library and OK trusting a CDN?** → **Advanced: external scripts** (Path 2).
- **Need a third-party library with no external dependencies / air-gapped / strict security?** → **Bundle locally** (Path 3). *Preferred default.*

## Troubleshooting

| Symptom | Likely cause | Fix |
|---|---|---|
| `fetch()` to my service is blocked | The host isn't in **API origins**, or it's mistakenly listed under **external scripts**. | Add the exact origin under **API origins** (Path 1). |
| My CDN `<script>` won't load, but I added the host to **API origins** | **API origins** governs data requests, not script loading. | Add the host under **Advanced: external scripts** and enable the toggle (Path 2). |
| "Interfaces can't use packages" | Misreading the no-`import` rule as a ban on all third-party code. | You can't `import` npm packages, but you can bundle them in (Path 3) or load them from an allow-listed CDN (Path 2). |
| Bundled library works in the app but fails local validation | The build relies on top-level `module.exports` / `require`. | Use an **IIFE / global** build (e.g. `esbuild --format=iife --global-name=…`) and reference the variable it defines. |
| Library expects Node APIs or the parent `window` | It isn't browser/sandbox-compatible. | Use a browser build; keep any network calls behind **API origins**. |
4 changes: 2 additions & 2 deletions docs/source/guide/interfaces-local.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ meta_description: "Build and iterate on custom labeling Interfaces from your ter
section: "Interfaces"
---

You can build or iterate on an Interface from your own editor and your own coding agent (Claude, Codex, or Cursor) instead of using the in-product agent.
You can build or iterate on an Interface from your own editor and your own coding agent (Claude, Codex, or Cursor) instead of using the in-product agent. **This is a first-class option on any deployment — many teams prefer it — and it's the way to build Interfaces on self-hosted or on-prem (including air-gapped) deployments, where Create with Agent (a Label Studio Cloud feature) isn't available.**

You write JSX on disk, preview it live against your Label Studio instance, and sync the result back when you're ready to publish.

Expand Down Expand Up @@ -188,7 +188,7 @@ The full command reference is in the [**Interface CLI Guide**](https://github.co
| --- | --- |
| `node is required for interface validation` | Install Node.js and make sure `node` is on your `PATH`. |
| `npm is required to install validator dependencies` | Install npm and re-run `interface doctor`. |
| `source did not compile` | Fix JSX syntax errors. Interface files are compiled as self-contained JSX snippets — `import` statements and bundler-specific syntax aren't supported. |
| `source did not compile` | Fix JSX syntax errors. Interface files are compiled as self-contained JSX snippets — `import` / `require` and bundler-specific syntax aren't supported. To use a third-party library, **pre-bundle it into the single source file** (or load it from an allow-listed host). See [Use external libraries and services](interfaces-libraries). |
| `Module did not return an object` | Make the final expression in the file an object literal wrapped in parentheses, for example `({ default: Screen })`. |
| `Missing getResults` or `Missing parseResults` | Warnings during static validation, but required for scenario validation. Add both functions when you need reliable save/load behavior. |
| `no sidecar entry` | Run `interface sync` against the target Label Studio URL, or pass `--id` to commands that support it. |
Expand Down
16 changes: 14 additions & 2 deletions docs/source/guide/interfaces.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,18 @@ Instead of choosing a template and tuning [out-of-the-box XML tags](/tags), with

<iframe width="560" height="315" src="https://www.youtube.com/embed/pdcZ6HDwpiI?si=ZpeuyeKYx71q1vCR" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

!!! note Availability
Interfaces are available in Label Studio Enterprise (including self-hosted) and Starter Cloud. If you're on a self-hosted or on-prem deployment and don't see Interfaces, contact your account manager to enable it for your organization.

There are two ways to build an Interface:

- **Create with Agent** — the in-product builder. Available on **Label Studio Cloud only**.
- **[Develop Locally](interfaces-local)** — build with your own coding agent and sync the result to Label Studio. Available on any deployment.

On Label Studio Cloud you can use either path, whichever you prefer. On self-hosted or on-prem deployments (including air-gapped), **Develop Locally is the way to build Interfaces** — Create with Agent is a Cloud-only feature.

Everything else about Interfaces (versioning, projects, exports) works the same across both paths. See [Create with the agent](interfaces-agent).

## When to use Interfaces

Interfaces are Label Studio's most powerful UI builder. Use them when:
Expand Down Expand Up @@ -99,9 +111,9 @@ You can enable or disable the ability to create and edit Interfaces for your org

Interfaces run as sandboxed modules rendered inside the Label Studio editor shell. To keep them safe, portable, and upgrade-friendly, the runtime enforces a narrow contract:

- **React-only, no external packages.** The code runs with `React` and the standard hooks (`useState`, `useRef`, `useEffect`, `useCallback`, `useMemo`) plus a small `getField` helper. You cannot `import` arbitrary npm packages, UI libraries, or design systems.
- **React-only source, closed by default.** The code runs with `React` and the standard hooks (`useState`, `useRef`, `useEffect`, `useCallback`, `useMemo`) plus a small `getField` helper. You can't `import` npm packages in the source — but you *can* use a third-party library by **bundling it into the Interface** or by **loading it from a host you allow-list**. See [Use external libraries and services](interfaces-libraries).
- **Plain JavaScript with JSX.** No TypeScript, no type annotations, no `import` / `export` statements. The module is compiled and evaluated at runtime, and must expose a default component plus optional helpers (`getResults`, `parseResults`, `outputSchema`, `inputSchema`, `paramsSchema`, `GridCell`).
- **No direct network access.** Interfaces render task data that Label Studio provides; they don't make their own API calls. Use project settings, ML backends, or Prompter for anything that needs external services.
- **No network access by default.** Out of the box an Interface can only reach your Label Studio server. To let it call an external service (for example an ML backend), an Owner/Admin allow-lists that origin under **Organization > Settings > Interfaces → API origins**. See [Use external libraries and services](interfaces-libraries).
- **Limited persistence.** State lives only for the duration of a labeling session. Anything you want to keep has to come back out through the annotation results.


Expand Down
Loading