diff --git a/apps/ui/content-collections.ts b/apps/ui/content-collections.ts
index 1f1f5ca4bd..0e404ffd81 100644
--- a/apps/ui/content-collections.ts
+++ b/apps/ui/content-collections.ts
@@ -21,6 +21,29 @@ const changelog = defineCollection({
}),
});
+const blog = defineCollection({
+ name: "blog",
+ directory: "src/content/blog",
+ include: "**/*.md",
+ schema: z.object({
+ id: z.string(),
+ slug: z.string(),
+ date: z.string(),
+ title: z.string(),
+ summary: z.string(),
+ draft: z.boolean().optional(),
+ categories: z.array(z.string()).default([]),
+ image: z
+ .object({
+ src: z.string(),
+ alt: z.string(),
+ width: z.number(),
+ height: z.number(),
+ })
+ .optional(),
+ }),
+});
+
export default defineConfig({
- collections: [changelog],
+ collections: [changelog, blog],
});
diff --git a/apps/ui/next.config.ts b/apps/ui/next.config.ts
index ecfe75fafe..f325f341f4 100644
--- a/apps/ui/next.config.ts
+++ b/apps/ui/next.config.ts
@@ -12,6 +12,30 @@ const nextConfig: NextConfig = {
// devtoolSegmentExplorer: true,
// globalNotFound: true,
// },
+ async redirects() {
+ return [
+ {
+ source: "/docs",
+ destination: "https://docs.llmgateway.com",
+ permanent: true,
+ },
+ {
+ source: "/discord",
+ destination: "https://discord.gg/3u7jpXf36B",
+ permanent: true,
+ },
+ {
+ source: "/github",
+ destination: "https://github.com/theopenco/llmgateway",
+ permanent: true,
+ },
+ {
+ source: "/twitter",
+ destination: "https://twitter.com/llmgateway",
+ permanent: true,
+ },
+ ];
+ },
};
// withContentCollections must be the outermost plugin
diff --git a/apps/ui/public/blog/blog-introducing-llm-gateway.png b/apps/ui/public/blog/blog-introducing-llm-gateway.png
new file mode 100644
index 0000000000..e089e5dd7d
Binary files /dev/null and b/apps/ui/public/blog/blog-introducing-llm-gateway.png differ
diff --git a/apps/ui/public/blog/custom-openai-compatible-providers.png b/apps/ui/public/blog/custom-openai-compatible-providers.png
new file mode 100644
index 0000000000..f6b565dcf7
Binary files /dev/null and b/apps/ui/public/blog/custom-openai-compatible-providers.png differ
diff --git a/apps/ui/public/blog/how-to-self-host-llm-gateway.png b/apps/ui/public/blog/how-to-self-host-llm-gateway.png
new file mode 100644
index 0000000000..1e5dd4c1d3
Binary files /dev/null and b/apps/ui/public/blog/how-to-self-host-llm-gateway.png differ
diff --git a/apps/ui/src/app/blog/[slug]/page.tsx b/apps/ui/src/app/blog/[slug]/page.tsx
new file mode 100644
index 0000000000..2e5e7964b9
--- /dev/null
+++ b/apps/ui/src/app/blog/[slug]/page.tsx
@@ -0,0 +1,123 @@
+import { allBlogs, type Blog } from "content-collections";
+import { ArrowLeftIcon } from "lucide-react";
+import Markdown from "markdown-to-jsx";
+import Image from "next/image";
+import Link from "next/link";
+import { notFound } from "next/navigation";
+
+import Footer from "@/components/landing/footer";
+import { HeroRSC } from "@/components/landing/hero-rsc";
+import { getMarkdownOptions } from "@/lib/utils/markdown";
+
+interface BlogEntryPageProps {
+ params: Promise<{ slug: string }>;
+}
+
+export default async function BlogEntryPage({ params }: BlogEntryPageProps) {
+ const { slug } = await params;
+
+ const entry = allBlogs.find((entry: Blog) => entry.slug === slug);
+
+ if (!entry) {
+ notFound();
+ }
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+ {entry.title}
+
+ {entry.summary && (
+
{entry.summary}
+ )}
+
+ {new Date(entry.date).toLocaleDateString("en-US", {
+ year: "numeric",
+ month: "long",
+ day: "numeric",
+ })}
+
+
+
+
+ {entry.image && (
+
+
+
+ )}
+
+
+
+ {entry.content}
+
+
+
+
+
+
+
+ >
+ );
+}
+
+export async function generateStaticParams() {
+ return allBlogs.map((entry: Blog) => ({
+ slug: entry.slug,
+ }));
+}
+
+export async function generateMetadata({ params }: BlogEntryPageProps) {
+ const { slug } = await params;
+
+ const entry = allBlogs.find((entry: Blog) => entry.slug === slug);
+
+ if (!entry) {
+ return {};
+ }
+
+ return {
+ title: `${entry.title} - Blog - LLM Gateway`,
+ description: entry.summary || "LLM Gateway blog post",
+ openGraph: {
+ title: `${entry.title} - Blog - LLM Gateway`,
+ description: entry.summary || "LLM Gateway blog post",
+ type: "article",
+ images: entry.image
+ ? [
+ {
+ url: entry.image.src,
+ width: entry.image.width || 800,
+ height: entry.image.height || 400,
+ alt: entry.image.alt || entry.title,
+ },
+ ]
+ : ["/opengraph.png"],
+ },
+ twitter: {
+ card: "summary_large_image",
+ title: `${entry.title} - Blog - LLM Gateway`,
+ description: entry.summary || "LLM Gateway blog post",
+ },
+ };
+}
diff --git a/apps/ui/src/app/blog/category/[category]/page.tsx b/apps/ui/src/app/blog/category/[category]/page.tsx
new file mode 100644
index 0000000000..e2ef7549b3
--- /dev/null
+++ b/apps/ui/src/app/blog/category/[category]/page.tsx
@@ -0,0 +1,68 @@
+import { BlogList } from "@/components/blog/list";
+import { HeroRSC } from "@/components/landing/hero-rsc";
+
+interface BlogItem {
+ id: string;
+ slug: string;
+ date: string;
+ title: string;
+ summary: string;
+ categories?: string[];
+}
+
+function slugify(label: string) {
+ return label
+ .toLowerCase()
+ .replace(/[^a-z0-9]+/g, "-")
+ .replace(/(^-|-$)/g, "");
+}
+
+interface CategoryPageProps {
+ params: Promise<{ category: string }>;
+}
+
+export default async function BlogCategoryPage({ params }: CategoryPageProps) {
+ const { category } = await params;
+ const slug = decodeURIComponent(category);
+ const { allBlogs } = (await import("content-collections")) as any;
+
+ const filtered = (allBlogs as any[])
+ .filter((entry: any) => !entry?.draft)
+ .filter((entry: any) =>
+ (entry.categories || []).some((c: string) => slugify(c) === slug),
+ )
+ .sort(
+ (a: any, b: any) =>
+ new Date(b.date).getTime() - new Date(a.date).getTime(),
+ ) as BlogItem[];
+
+ return (
+ <>
+
+
+ >
+ );
+}
+
+export async function generateStaticParams() {
+ const { allBlogs } = (await import("content-collections")) as any;
+ const slugs = new Set();
+ for (const post of allBlogs as any[]) {
+ (post.categories || []).forEach((c: string) => slugs.add(slugify(c)));
+ }
+ return Array.from(slugs).map((category) => ({ category }));
+}
+
+export async function generateMetadata({ params }: CategoryPageProps) {
+ const { category } = await params;
+ const decoded = decodeURIComponent(category);
+ return {
+ title: `Blog: ${decoded} - LLM Gateway`,
+ description: `Articles in the ${decoded} category at LLM Gateway`,
+ };
+}
diff --git a/apps/ui/src/app/blog/category/page.tsx b/apps/ui/src/app/blog/category/page.tsx
new file mode 100644
index 0000000000..bdbbeff5d7
--- /dev/null
+++ b/apps/ui/src/app/blog/category/page.tsx
@@ -0,0 +1,3 @@
+export default function Page() {
+ return null;
+}
diff --git a/apps/ui/src/app/blog/page.tsx b/apps/ui/src/app/blog/page.tsx
new file mode 100644
index 0000000000..38f4de9319
--- /dev/null
+++ b/apps/ui/src/app/blog/page.tsx
@@ -0,0 +1,50 @@
+import { BlogList } from "@/components/blog/list";
+import { HeroRSC } from "@/components/landing/hero-rsc";
+
+interface BlogItem {
+ id: string;
+ slug: string;
+ date: string;
+ title: string;
+ summary: string;
+}
+
+export default async function BlogPage() {
+ const { allBlogs } = (await import("content-collections")) as any;
+
+ const sortedEntries = (allBlogs as any[])
+ .sort(
+ (a: any, b: any) =>
+ new Date(b.date).getTime() - new Date(a.date).getTime(),
+ )
+ .filter((entry: any) => !entry?.draft)
+ .map(({ ...entry }: any) => entry as BlogItem);
+
+ return (
+
+
+
+
+ );
+}
+
+export async function generateMetadata() {
+ return {
+ title: "Blog - LLM Gateway",
+ description: "News, tutorials, and deep-dives from the LLM Gateway team.",
+ openGraph: {
+ title: "Blog - LLM Gateway",
+ description: "News, tutorials, and deep-dives from the LLM Gateway team.",
+ type: "website",
+ },
+ twitter: {
+ card: "summary_large_image",
+ title: "Blog - LLM Gateway",
+ description: "News, tutorials, and deep-dives from the LLM Gateway team.",
+ },
+ };
+}
diff --git a/apps/ui/src/app/changelog/[slug]/page.tsx b/apps/ui/src/app/changelog/[slug]/page.tsx
index f72b9e6770..78088cfa2b 100644
--- a/apps/ui/src/app/changelog/[slug]/page.tsx
+++ b/apps/ui/src/app/changelog/[slug]/page.tsx
@@ -5,8 +5,8 @@ import Image from "next/image";
import Link from "next/link";
import { notFound } from "next/navigation";
-import { AppHeader } from "@/components/changelog/app-header";
import Footer from "@/components/landing/footer";
+import { HeroRSC } from "@/components/landing/hero-rsc";
import { getMarkdownOptions } from "@/lib/utils/markdown";
interface ChangelogEntryPageProps {
@@ -25,59 +25,62 @@ export default async function ChangelogEntryPage({
}
return (
-
-
-
-
-
-
-
- Back to changelog
-
-
+ <>
+
-
-
- {entry.title}
-
- {entry.summary && (
-
{entry.summary}
- )}
-
- {new Date(entry.date).toLocaleDateString("en-US", {
- year: "numeric",
- month: "long",
- day: "numeric",
- })}
-
-
-
+
+
+
+
+
+
+ Back to changelog
+
+
- {entry.image && (
-
-
-
- )}
+
+
+ {entry.title}
+
+ {entry.summary && (
+
{entry.summary}
+ )}
+
+ {new Date(entry.date).toLocaleDateString("en-US", {
+ year: "numeric",
+ month: "long",
+ day: "numeric",
+ })}
+
+
+
-
-
- {entry.content}
-
-
-
-
-
-
-
+ {entry.image && (
+
+
+
+ )}
+
+
+
+ {entry.content}
+
+
+
+
+
+
+
+ >
);
}
diff --git a/apps/ui/src/app/changelog/page.tsx b/apps/ui/src/app/changelog/page.tsx
index e54f56ab27..c18d2dec7f 100644
--- a/apps/ui/src/app/changelog/page.tsx
+++ b/apps/ui/src/app/changelog/page.tsx
@@ -1,6 +1,7 @@
import { allChangelogs, type Changelog } from "content-collections";
import { ChangelogComponent } from "@/components/changelog";
+import { HeroRSC } from "@/components/landing/hero-rsc";
export default async function ChangelogPage() {
const sortedEntries = allChangelogs
@@ -13,6 +14,7 @@ export default async function ChangelogPage() {
return (
+
);
diff --git a/apps/ui/src/app/models/[name]/page.tsx b/apps/ui/src/app/models/[name]/page.tsx
index 892b0659d1..c20cb00120 100644
--- a/apps/ui/src/app/models/[name]/page.tsx
+++ b/apps/ui/src/app/models/[name]/page.tsx
@@ -4,7 +4,16 @@ import {
type StabilityLevel,
type ModelDefinition,
} from "@llmgateway/models";
-import { AlertTriangle } from "lucide-react";
+import {
+ AlertTriangle,
+ Play,
+ Zap,
+ Eye,
+ Wrench,
+ MessageSquare,
+ ImagePlus,
+} from "lucide-react";
+import Link from "next/link";
import { notFound } from "next/navigation";
import Footer from "@/components/landing/footer";
@@ -12,6 +21,7 @@ import { Navbar } from "@/components/landing/navbar";
import { CopyModelName } from "@/components/models/copy-model-name";
import { ProviderCard } from "@/components/models/provider-card";
import { Badge } from "@/lib/components/badge";
+import { Button } from "@/lib/components/button";
interface PageProps {
params: Promise<{ name: string }>;
@@ -76,13 +86,13 @@ export default async function ModelPage({ params }: PageProps) {
- {modelDef.id}
+ {modelDef.name}
{shouldShowStabilityWarning(modelDef.stability) && (
)}
-
+
{(() => {
const stabilityProps = getStabilityBadgeProps(
@@ -101,6 +111,16 @@ export default async function ModelPage({ params }: PageProps) {
);
})()}
+
+
+
+
+ Try in Playground
+
+
@@ -138,43 +158,85 @@ export default async function ModelPage({ params }: PageProps) {
-
- {modelDef.id} is available across multiple providers with
- different configurations, pricing, and performance
- characteristics. Choose the provider that best fits your needs.
-
- {shouldShowStabilityWarning(modelDef.stability) && (
-
-
-
-
-
- {modelDef.stability === "experimental"
- ? "Experimental"
- : "Unstable"}{" "}
- Model Warning
-
-
- This model is marked as {modelDef.stability} and may have
- issues with reliability, performance, or consistency. Use
- with caution in production environments.
-
+
+ {(() => {
+ const items: Array<{
+ key: string;
+ icon: any;
+ label: string;
+ color: string;
+ }> = [];
+ const hasStreaming = modelProviders.some((p) => p.streaming);
+ const hasVision = modelProviders.some((p) => p.vision);
+ const hasTools = modelProviders.some((p) => p.tools);
+ const hasReasoning = modelProviders.some((p) => p.reasoning);
+ const hasImageGen = Array.isArray((modelDef as any)?.output)
+ ? ((modelDef as any).output as string[]).includes("image")
+ : false;
+
+ if (hasStreaming) {
+ items.push({
+ key: "streaming",
+ icon: Zap,
+ label: "Streaming",
+ color: "text-blue-500",
+ });
+ }
+ if (hasVision) {
+ items.push({
+ key: "vision",
+ icon: Eye,
+ label: "Vision",
+ color: "text-green-500",
+ });
+ }
+ if (hasTools) {
+ items.push({
+ key: "tools",
+ icon: Wrench,
+ label: "Tools",
+ color: "text-purple-500",
+ });
+ }
+ if (hasReasoning) {
+ items.push({
+ key: "reasoning",
+ icon: MessageSquare,
+ label: "Reasoning",
+ color: "text-orange-500",
+ });
+ }
+ if (hasImageGen) {
+ items.push({
+ key: "image",
+ icon: ImagePlus,
+ label: "Image Generation",
+ color: "text-pink-500",
+ });
+ }
+
+ return items.map(({ key, icon: Icon, label, color }) => (
+
+
+ {label}
-
-
- )}
+ ));
+ })()}
+
- Providers for {modelDef.id}
+ Providers for {modelDef.name}
LLM Gateway routes requests to the best providers that are
- able to handle your prompt size and parameters, with fallbacks
- to maximize uptime.
+ able to handle your prompt size and parameters.
diff --git a/apps/ui/src/components/blog/list.tsx b/apps/ui/src/components/blog/list.tsx
new file mode 100644
index 0000000000..51e938fe6e
--- /dev/null
+++ b/apps/ui/src/components/blog/list.tsx
@@ -0,0 +1,153 @@
+import Image from "next/image";
+import Link from "next/link";
+
+import Footer from "@/components/landing/footer";
+
+interface BlogItemImage {
+ src: string;
+ alt: string;
+ width: number;
+ height: number;
+}
+
+interface BlogItem {
+ id: string;
+ slug: string;
+ date: string;
+ title: string;
+ summary: string;
+ categories?: string[];
+ image?: BlogItemImage;
+}
+
+function slugify(label: string) {
+ return label
+ .toLowerCase()
+ .replace(/[^a-z0-9]+/g, "-")
+ .replace(/(^-|-$)/g, "");
+}
+
+interface BlogListProps {
+ entries?: BlogItem[];
+ selectedCategory?: string; // slug form
+ heading?: string;
+ subheading?: string;
+}
+
+export function BlogList({
+ entries,
+ selectedCategory,
+ heading = "Blog",
+ subheading = "Latest news and updates from LLM Gateway",
+}: BlogListProps = {}) {
+ const blogEntries = entries || [];
+ const categoryList = ["Announcements", "Guides", "Engineering", "Changelog"];
+
+ return (
+
+
+
+
+ {heading}
+
+
{subheading}
+
+
+ {categoryList.length > 0 && (
+
+
+ Overview
+
+ {categoryList.map((cat) => {
+ const catSlug = slugify(cat);
+ const active = selectedCategory === catSlug;
+ return (
+
+ {cat}
+
+ );
+ })}
+
+ )}
+
+
+ {blogEntries.map((entry: BlogItem) => (
+
+
+
+ {entry?.image ? (
+
+ ) : (
+
+ )}
+
+
+
+
+ {entry.title}
+
+
+ {entry?.categories && entry.categories.length > 0 && (
+
+ {entry.categories.map((cat) => (
+
+ {cat}
+
+ ))}
+
+ )}
+
+ {entry.summary}
+
+
+ {new Date(entry.date).toLocaleDateString("en-US", {
+ year: "numeric",
+ month: "long",
+ day: "numeric",
+ })}
+
+
+
+
+ ))}
+
+
+
+
+ );
+}
diff --git a/apps/ui/src/components/changelog/changelog.tsx b/apps/ui/src/components/changelog/changelog.tsx
index 783bb5f5d4..705c4ae337 100644
--- a/apps/ui/src/components/changelog/changelog.tsx
+++ b/apps/ui/src/components/changelog/changelog.tsx
@@ -1,7 +1,6 @@
import Image from "next/image";
import Link from "next/link";
-import { AppHeader } from "./app-header";
import Footer from "@/components/landing/footer";
import type { ChangelogFrontmatter } from "@/lib/utils/markdown";
@@ -14,20 +13,26 @@ export function ChangelogComponent({ entries }: ChangelogComponentProps = {}) {
const changelogEntries = entries || [];
return (
-
-
+
-
-
- Changelog
-
- {/*
+
+
+ Changelog
+
+
+ {/*
Subscribe to updates
*/}
+
+
+ Stay up to date with the latest features, improvements, and fixes in
+ LLM Gateway.
+
@@ -36,16 +41,18 @@ export function ChangelogComponent({ entries }: ChangelogComponentProps = {}) {
key={entry.id}
className="grid md:grid-cols-[150px_1fr] gap-x-8 gap-y-4"
>
-
- {new Date(entry.date).toLocaleDateString("en-US", {
- year: "numeric",
- month: "long",
- day: "numeric",
- })}
-
+
+
+ {new Date(entry.date).toLocaleDateString("en-US", {
+ year: "numeric",
+ month: "long",
+ day: "numeric",
+ })}
+
+
diff --git a/apps/ui/src/components/landing/navbar.tsx b/apps/ui/src/components/landing/navbar.tsx
index 9bff5b2c4c..1acca3e69d 100644
--- a/apps/ui/src/components/landing/navbar.tsx
+++ b/apps/ui/src/components/landing/navbar.tsx
@@ -28,6 +28,7 @@ export const Navbar = ({ children }: { children?: React.ReactNode }) => {
];
const resourcesItems = [
+ { name: "Blog", href: "/blog" },
{ name: "Models", href: "/models" },
{ name: "Playground", href: "/playground" },
{ name: "Providers", href: "/providers" },
diff --git a/apps/ui/src/content/blog/2025-04-12-introducing-llm-gateway.md b/apps/ui/src/content/blog/2025-04-12-introducing-llm-gateway.md
new file mode 100644
index 0000000000..da7893e1ec
--- /dev/null
+++ b/apps/ui/src/content/blog/2025-04-12-introducing-llm-gateway.md
@@ -0,0 +1,46 @@
+---
+id: blog-introducing-llm-gateway
+slug: introducing-llm-gateway
+date: 2025-04-12
+title: Introducing LLM Gateway
+summary: Meet the open-source API gateway for routing, observability, and cost tracking across LLM providers.
+categories: ["Announcements"]
+image:
+ src: "/blog/blog-introducing-llm-gateway.png"
+ alt: "LLM Gateway"
+ width: 2282
+ height: 1198
+---
+
+# LLM Gateway
+
+LLM Gateway is an open-source API gateway for Large Language Models (LLMs). It acts as middleware between your apps and LLM providers so you can:
+
+- **Route**: Switch between providers like OpenAI, Anthropic, and Google with a single API
+- **Manage**: Centralize and rotate provider API keys
+- **Observe**: Track token usage, latency, and error rates
+- **Optimize**: Analyze cost and performance to pick the best models for your workload
+
+## Why LLM Gateway?
+
+Operating across multiple LLM providers quickly becomes complex credentials, SDK differences, changing models, and cost variance. LLM Gateway standardizes the interface and gives you the visibility to make data-driven choices.
+
+## One Compatible Endpoint
+
+LLM Gateway uses an OpenAI-compatible API format, so migrating is seamless:
+
+```bash
+curl -X POST https://api.llmgateway.io/v1/chat/completions \
+ -H "Content-Type: application/json" \
+ -H "Authorization: Bearer $LLM_GATEWAY_API_KEY" \
+ -d '{
+ "model": "gpt-4o",
+ "messages": [{"role": "user", "content": "Hello, how are you?"}]
+ }'
+```
+
+## Deep Observability
+
+Get **usage metrics**, **cost analysis**, and **performance tracking** broken down by model and provider, helping you reason about tradeoffs. You can compare latency, token usage, and error rates to choose the best fit per task.
+
+If you're new to LLM Gateway, read our [Docs](/docs) to get started.
diff --git a/apps/ui/src/content/blog/2025-05-01-self-host-llm-gateway.md b/apps/ui/src/content/blog/2025-05-01-self-host-llm-gateway.md
new file mode 100644
index 0000000000..7d1b14b48b
--- /dev/null
+++ b/apps/ui/src/content/blog/2025-05-01-self-host-llm-gateway.md
@@ -0,0 +1,47 @@
+---
+id: blog-self-host-llm-gateway
+slug: how-to-self-host-llm-gateway
+date: 2025-05-01
+title: How to Self-Host LLM Gateway
+summary: Deploy LLM Gateway locally or in the cloud using our unified Docker image or split services.
+categories: ["Guides"]
+image:
+ src: "/blog/how-to-self-host-llm-gateway.png"
+ alt: "LLM Gateway"
+ width: 2282
+ height: 1198
+---
+
+## Option 1: Unified Docker Image (Easiest)
+
+```bash
+docker run -d \
+ --name llmgateway \
+ --restart unless-stopped \
+ -p 3002:3002 -p 3005:3005 -p 4001:4001 -p 4002:4002 \
+ -v ~/llmgateway_data:/var/lib/postgresql/data \
+ -e OPENAI_API_KEY=sk-your_openai_key_here \
+ -e AUTH_SECRET=your-secret-key-here \
+ ghcr.io/theopenco/llmgateway-unified:latest
+```
+
+Prefer pinning the image to the latest release tag. You can also run it via Docker Compose.
+
+## Option 2: Split Services via Docker Compose
+
+```bash
+git clone https://github.com/theopenco/llmgateway.git
+cd llmgateway
+cp .env.example .env
+# edit .env
+docker compose -f infra/docker-compose.split.yml up -d
+```
+
+### Access
+
+- Web: http://localhost:3002
+- Docs: http://localhost:3005
+- API: http://localhost:4002
+- Gateway: http://localhost:4001
+
+See the full guide: [`Self Host`](https://raw.githubusercontent.com/theopenco/llmgateway/refs/heads/main/apps/docs/content/self-host.mdx).
diff --git a/apps/ui/src/content/blog/2025-05-10-custom-providers.md b/apps/ui/src/content/blog/2025-05-10-custom-providers.md
new file mode 100644
index 0000000000..8909a6bc44
--- /dev/null
+++ b/apps/ui/src/content/blog/2025-05-10-custom-providers.md
@@ -0,0 +1,31 @@
+---
+id: blog-custom-providers
+slug: custom-openai-compatible-providers
+date: 2025-05-10
+title: Custom OpenAI-Compatible Providers Are Now Supported
+summary: Bring your own OpenAI-compatible endpoints and route them via LLM Gateway.
+categories: ["Announcements"]
+image:
+ src: "/blog/custom-openai-compatible-providers.png"
+ alt: "LLM Gateway"
+ width: 2282
+ height: 1198
+---
+
+You can now register custom OpenAI-compatible providers in LLM Gateway. Perfect for internal deployments or specialized third-party APIs that speak the OpenAI Chat Completions format.
+
+### Configure a Custom Provider
+
+Add a provider in the UI (lowercase name, base URL, and token). Then call models via `{providerName}/{modelName}`:
+
+```bash
+curl -X POST "https://api.llmgateway.io/v1/chat/completions" \
+ -H "Authorization: Bearer $LLM_GATEWAY_API_KEY" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "model": "mycompany/custom-gpt-4",
+ "messages": [{"role": "user", "content": "Hello from my custom provider!"}]
+ }'
+```
+
+Requirements include a lowercase provider name and a valid HTTPS base URL. See details in the docs: [Custom Providers](https://docs.llmgateway.io/features/custom-providers).
diff --git a/apps/ui/src/types/content-collections.d.ts b/apps/ui/src/types/content-collections.d.ts
index 48aea7f040..9aa23ad541 100644
--- a/apps/ui/src/types/content-collections.d.ts
+++ b/apps/ui/src/types/content-collections.d.ts
@@ -16,4 +16,21 @@ declare module "content-collections" {
}
export const allChangelogs: Changelog[];
+
+ export interface Blog {
+ id: string;
+ slug: string;
+ date: string;
+ title: string;
+ summary: string;
+ image: {
+ src: string;
+ alt: string;
+ width: number;
+ height: number;
+ };
+ content: string;
+ }
+
+ export const allBlogs: Blog[];
}