# How to Customize the Logto UI: A Complete Guide to Custom Sign-In Experiences

> Learn how to customize the Logto UI and create unique sign-in experiences by uploading custom HTML, CSS, and JavaScript. Effortlessly replace the default interface.

- Repository: [Logto/logto](https://github.com/logto-io/logto)
- Tags: how-to-guide
- Published: 2026-06-30

---

**Logto enables full customization of the authentication interface by allowing developers to upload static HTML, CSS, and JavaScript bundles that replace the default sign-in experience.**

The `logto-io/logto` repository provides a comprehensive custom UI system that lets you completely replace the built-in sign-in flow with your own branded interface. This guide walks through the exact implementation details found in the source code, from preparing your asset bundle to configuring security policies.

## Architecture Overview of Logto Custom UI

The custom UI system follows a specific deployment and serving pipeline. When you customize the Logto UI, your static assets are stored in **Azure Blob Storage** and served through dedicated Koa middleware that handles routing and security.

The flow works as follows:

1. You create a ZIP archive containing your static files ([`index.html`](https://github.com/logto-io/logto/blob/main/index.html), CSS, JavaScript).
2. The bundle uploads via the Management API endpoint defined in [`packages/core/src/routes/sign-in-experience/custom-ui-assets/index.openapi.json`](https://github.com/logto-io/logto/blob/main/packages/core/src/routes/sign-in-experience/custom-ui-assets/index.openapi.json).
3. Logto stores the assets in Azure Blob Storage using the driver in [`packages/core/src/utils/storage/azure-storage.js`](https://github.com/logto-io/logto/blob/main/packages/core/src/utils/storage/azure-storage.js).
4. During authentication requests, the [`koa-serve-custom-ui-assets.ts`](https://github.com/logto-io/logto/blob/main/koa-serve-custom-ui-assets.ts) middleware streams files directly from Blob Storage, automatically redirecting directory requests to [`index.html`](https://github.com/logto-io/logto/blob/main/index.html).

## Preparing Your Custom UI Bundle

Before you can customize the Logto UI, you must structure your static assets correctly. The bundle must include an [`index.html`](https://github.com/logto-io/logto/blob/main/index.html) entry point that handles the authentication form and posts to `/callback`.

Create a directory structure like this:

```text
my-custom-ui/
├─ index.html        # Must expose a form posting to /callback

├─ style.css
└─ script.js

```

Package these files into a ZIP archive:

```bash
cd my-custom-ui
zip -r ../my-custom-ui.zip .

```

## Deploying Custom UI Assets

Logto offers two methods to upload your custom UI bundle: the **Tunnel CLI** for developer convenience, or direct **Management API** calls for automated pipelines.

### Using the Tunnel CLI

The Tunnel CLI provides the smoothest developer experience for customizing the Logto UI. The `deploy` command in [`packages/tunnel/src/commands/deploy/index.ts`](https://github.com/logto-io/logto/blob/main/packages/tunnel/src/commands/deploy/index.ts) handles authentication, packaging, and uploading in a single operation.

Deploy your custom UI using:

```bash
npx @logto/tunnel deploy \
  --auth <M2M_APP_ID>:<M2M_APP_SECRET> \
  --endpoint https://<tenant-id>.logto.app \
  --experience-path ./my-custom-ui

```

If you have already created a ZIP file, replace `--experience-path` with `--zip-path`:

```bash
npx @logto/tunnel deploy \
  --auth <M2M_APP_ID>:<M2M_APP_SECRET> \
  --endpoint https://<tenant-id>.logto.app \
  --zip-path ./my-custom-ui.zip

```

The CLI validates the endpoint URL, creates the ZIP if needed, and POSTs it to the upload API. It then stores the returned `customUiAssetId` and updates your tenant's Sign-in Experience configuration automatically.

### Manual Upload via Management API

For CI/CD pipelines or custom automation, call the upload endpoint directly. First obtain a Machine-to-Machine (M2M) access token, then POST to the custom UI assets endpoint:

```bash
curl -X POST "https://<tenant>.logto.app/api/sign-in-experience/custom-ui-assets" \
  -H "Authorization: Bearer <M2M_ACCESS_TOKEN>" \
  -F "file=@my-custom-ui.zip"

```

The API returns a JSON response containing the asset identifier:

```json
{
  "customUiAssetId": "abc123"
}

```

You must then update your tenant's Sign-in Experience settings to reference this `customUiAssetId` through the admin console or Management API.

## Configuring Content Security Policy

When you customize the Logto UI with external scripts or API calls, you must define a **Content Security Policy (CSP)** to whitelist permitted sources. The CSP schema and validator live in [`packages/toolkit/core-kit/src/custom-ui-csp.ts`](https://github.com/logto-io/logto/blob/main/packages/toolkit/core-kit/src/custom-ui-csp.ts).

The system validates only two directives: `scriptSrc` for JavaScript sources and `connectSrc` for API endpoints. Configure these in your Sign-in Experience settings:

```json
{
  "customUiCsp": {
    "scriptSrc": ["https://cdn.example.com"],
    "connectSrc": ["https://api.example.com"]
  }
}

```

The `customUiCspGuard` validator ensures no other CSP directives are accepted, maintaining a strict security boundary for custom authentication interfaces.

## How Logto Serves Custom UI Assets

Once uploaded, Logto serves your custom UI through specialized middleware. The [`koa-serve-custom-ui-assets.ts`](https://github.com/logto-io/logto/blob/main/koa-serve-custom-ui-assets.ts) file in `packages/core/src/middleware/` handles every aspect of asset delivery.

This middleware:

- Looks up the `customUiAssetId` associated with the current tenant.
- Streams files directly from Azure Blob Storage using the utilities in [`packages/core/src/utils/storage/azure-storage.js`](https://github.com/logto-io/logto/blob/main/packages/core/src/utils/storage/azure-storage.js).
- Supports HTTP range requests for efficient video and audio delivery.
- Automatically redirects directory paths (like `/`) to [`index.html`](https://github.com/logto-io/logto/blob/main/index.html).
- Applies the custom CSP headers you configured to prevent XSS attacks.

If no custom UI asset is configured for the tenant, Logto falls back to the default built-in sign-in experience.

## Summary

- **Logto custom UI** replaces the default sign-in experience with your own static HTML, CSS, and JavaScript.
- Assets must be packaged as a ZIP file with an [`index.html`](https://github.com/logto-io/logto/blob/main/index.html) entry point that posts to `/callback`.
- Upload via the **Tunnel CLI** (`@logto/tunnel deploy`) or the **Management API** endpoint at `/sign-in-experience/custom-ui-assets`.
- Files are stored in **Azure Blob Storage** and served by the [`koa-serve-custom-ui-assets.ts`](https://github.com/logto-io/logto/blob/main/koa-serve-custom-ui-assets.ts) middleware.
- Configure **CSP rules** through `customUiCsp` to whitelist external scripts (`scriptSrc`) and API endpoints (`connectSrc`).
- The middleware handles range requests and automatically serves [`index.html`](https://github.com/logto-io/logto/blob/main/index.html) for directory paths.

## Frequently Asked Questions

### What file formats are supported for custom UI bundles?

Logto accepts only ZIP archives containing static web assets. Your bundle must include HTML, CSS, and JavaScript files, with [`index.html`](https://github.com/logto-io/logto/blob/main/index.html) serving as the mandatory entry point. The system does not support server-side rendering or dynamic file generation.

### Where are custom UI assets stored after upload?

All custom UI assets are stored in **Azure Blob Storage**, which is currently the only supported storage provider in the Logto architecture. The [`koa-serve-custom-ui-assets.ts`](https://github.com/logto-io/logto/blob/main/koa-serve-custom-ui-assets.ts) middleware streams files directly from Blob Storage rather than local disk, enabling efficient multi-tenant isolation and CDN integration.

### How do I update my custom UI after the initial deployment?

To update your custom UI, create a new ZIP bundle and deploy it using the same method as the initial upload. Whether using the Tunnel CLI or the Management API, the new upload generates a fresh `customUiAssetId` that you must apply to your Sign-in Experience configuration. The old assets remain available until you explicitly update the configuration reference.

### Can I use external CDNs and APIs in my custom UI?

Yes, but you must explicitly whitelist them in the **Content Security Policy**. Define `scriptSrc` arrays for external JavaScript libraries and `connectSrc` arrays for API endpoints in your Sign-in Experience settings. The `customUiCspGuard` validator in [`packages/toolkit/core-kit/src/custom-ui-csp.ts`](https://github.com/logto-io/logto/blob/main/packages/toolkit/core-kit/src/custom-ui-csp.ts) enforces these restrictions to prevent unauthorized script execution or data exfiltration.