Mode
-
{guardrailData.litellm_params?.mode || "-"}
+
{formatGuardrailMode(guardrailData.litellm_params?.mode) || "-"}
Default On
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/guardrails/_components/guardrail_info_helpers.test.tsx b/ui/litellm-dashboard/src/app/(dashboard)/guardrails/_components/guardrail_info_helpers.test.tsx
index ec910673b8f..c5e07fe9624 100644
--- a/ui/litellm-dashboard/src/app/(dashboard)/guardrails/_components/guardrail_info_helpers.test.tsx
+++ b/ui/litellm-dashboard/src/app/(dashboard)/guardrails/_components/guardrail_info_helpers.test.tsx
@@ -14,6 +14,7 @@ import {
choiceToSkipSystemForCreate,
skipToolMessageToChoice,
choiceToSkipToolForCreate,
+ formatGuardrailMode,
} from "./guardrail_info_helpers";
describe("guardrail_info_helpers", () => {
@@ -210,6 +211,34 @@ describe("guardrail_info_helpers", () => {
});
});
+ describe("formatGuardrailMode", () => {
+ it("renders a single mode and a list of modes", () => {
+ expect(formatGuardrailMode("pre_call")).toBe("pre_call");
+ expect(formatGuardrailMode(["pre_call", "post_call"])).toBe("pre_call, post_call");
+ });
+
+ it("flattens a tag-based mode object into deduped modes instead of returning it verbatim", () => {
+ const mode = {
+ tags: { "Service-Type: internal-service": "post_call", "Service-Type: batch": ["during_call", "post_call"] },
+ default: ["pre_call", "post_call"],
+ };
+
+ expect(formatGuardrailMode(mode)).toBe("pre_call, post_call, during_call (tag-based)");
+ });
+
+ it("handles a tag-based mode with no default and with no tags", () => {
+ expect(formatGuardrailMode({ tags: { "team: a": "post_call" } })).toBe("post_call (tag-based)");
+ expect(formatGuardrailMode({ default: "pre_call" })).toBe("pre_call (tag-based)");
+ });
+
+ it("returns an empty string for missing or unusable modes", () => {
+ expect(formatGuardrailMode(undefined)).toBe("");
+ expect(formatGuardrailMode(null)).toBe("");
+ expect(formatGuardrailMode({})).toBe("");
+ expect(formatGuardrailMode({ tags: {}, default: null })).toBe("");
+ });
+ });
+
describe("skipSystemMessageToChoice / choiceToSkipSystemForCreate", () => {
it("maps API values to form choices and back for create", () => {
expect(skipSystemMessageToChoice(undefined)).toBe("inherit");
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/guardrails/_components/guardrail_info_helpers.tsx b/ui/litellm-dashboard/src/app/(dashboard)/guardrails/_components/guardrail_info_helpers.tsx
index 12aaba0d696..c12529e6326 100644
--- a/ui/litellm-dashboard/src/app/(dashboard)/guardrails/_components/guardrail_info_helpers.tsx
+++ b/ui/litellm-dashboard/src/app/(dashboard)/guardrails/_components/guardrail_info_helpers.tsx
@@ -110,6 +110,19 @@ export const toModeArray = (raw: unknown): string[] => {
return [];
};
+// Turns a guardrail mode into a renderable string. A mode is a single mode, a list of modes, or a
+// tag-based `{ tags, default }` object, which React refuses to render as a child
+export const formatGuardrailMode = (raw: unknown): string => {
+ const flat: string[] = toModeArray(raw);
+ if (flat.length > 0) return flat.join(", ");
+ if (raw === null || typeof raw !== "object") return "";
+
+ const { tags, default: fallback } = raw as { tags?: Record
; default?: unknown };
+ const tagged: string[] = tags && typeof tags === "object" ? Object.values(tags).flatMap(toModeArray) : [];
+ const modes: string[] = Array.from(new Set([...toModeArray(fallback), ...tagged]));
+ return modes.length > 0 ? `${modes.join(", ")} (tag-based)` : "";
+};
+
// Resolves the supported modes for the selected provider, falling back to the global list
export const getSupportedModesForProvider = (
settings: { supported_modes?: string[]; supported_modes_by_provider?: Record } | null,
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/guardrails/_components/guardrail_table.test.tsx b/ui/litellm-dashboard/src/app/(dashboard)/guardrails/_components/guardrail_table.test.tsx
index ee619dc7468..561a89a191a 100644
--- a/ui/litellm-dashboard/src/app/(dashboard)/guardrails/_components/guardrail_table.test.tsx
+++ b/ui/litellm-dashboard/src/app/(dashboard)/guardrails/_components/guardrail_table.test.tsx
@@ -46,6 +46,18 @@ describe("GuardrailTable", () => {
expect(screen.getByText("m")).toBeInTheDocument();
});
+ it("renders a tag-based mode object instead of crashing the table", () => {
+ const guardrail = makeGuardrail({
+ litellm_params: {
+ guardrail: "bedrock",
+ mode: { tags: { "Service-Type: internal-service": "post_call" }, default: ["pre_call", "post_call"] },
+ default_on: true,
+ },
+ });
+ render();
+ expect(screen.getByText("pre_call, post_call (tag-based)")).toBeInTheDocument();
+ });
+
it("deletes a DB guardrail through the actions menu", async () => {
const user = userEvent.setup();
const onDeleteClick = vi.fn();
diff --git a/ui/litellm-dashboard/src/components/guardrails/types.ts b/ui/litellm-dashboard/src/components/guardrails/types.ts
index e8ed27d9e45..0f5ce1c883d 100644
--- a/ui/litellm-dashboard/src/components/guardrails/types.ts
+++ b/ui/litellm-dashboard/src/components/guardrails/types.ts
@@ -18,12 +18,17 @@ export interface PiiConfigurationProps {
entityCategories?: PiiEntityCategory[];
}
+export type GuardrailMode =
+ | string
+ | string[]
+ | { tags?: Record; default?: string | string[] | null };
+
export interface Guardrail {
guardrail_id: string;
guardrail_name: string | null;
litellm_params: {
guardrail: string;
- mode: string;
+ mode: GuardrailMode;
default_on: boolean;
pii_entities_config?: { [key: string]: string };
[key: string]: any;
From 881aa2080871052a2173f7b3352df39fc0e61e03 Mon Sep 17 00:00:00 2001
From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Date: Wed, 19 Aug 2026 18:31:42 +0000
Subject: [PATCH 2/3] fix(ui): format tag-based guardrail mode in delete modal,
playground, and policy picker
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
---
.../guardrails/_components/GuardrailTestPlayground.tsx | 8 ++++++--
.../guardrails/_components/GuardrailsPanel.tsx | 4 ++--
.../(dashboard)/guardrails/_components/guardrail_info.tsx | 4 +++-
.../policies/_components/guardrail_selection_modal.tsx | 5 ++++-
4 files changed, 15 insertions(+), 6 deletions(-)
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/guardrails/_components/GuardrailTestPlayground.tsx b/ui/litellm-dashboard/src/app/(dashboard)/guardrails/_components/GuardrailTestPlayground.tsx
index fd8ed22867b..c64b5d7cb5c 100644
--- a/ui/litellm-dashboard/src/app/(dashboard)/guardrails/_components/GuardrailTestPlayground.tsx
+++ b/ui/litellm-dashboard/src/app/(dashboard)/guardrails/_components/GuardrailTestPlayground.tsx
@@ -6,13 +6,15 @@ import { toast } from "@/lib/toast";
import { Card, CardContent } from "@/components/ui/card";
import { InputGroup, InputGroupAddon, InputGroupInput } from "@/components/ui/input-group";
import { UiLoadingSpinner } from "@/components/ui/ui-loading-spinner";
+import { GuardrailMode } from "@/components/guardrails/types";
+import { formatGuardrailMode } from "./guardrail_info_helpers";
interface GuardrailItem {
guardrail_id?: string;
guardrail_name: string | null;
litellm_params: {
guardrail: string;
- mode: string;
+ mode: GuardrailMode;
default_on: boolean;
};
guardrail_info: Record | null;
@@ -171,7 +173,9 @@ const GuardrailTestPlayground: React.FC = ({