Get Active Theme
Public read of the platform's currently active client-web theme preset. Includes layout + assets (full theming surface). MUST pass ?target=CLIENT — default differs.
GET /api/v1/themes/active?target=CLIENT — 🌐 Public
Returns the currently active client-web theme preset — the one fan / creator bio pages render with right now. Includes the full theming surface (layout + assets) needed for live application — more fields than the list endpoint.
Always pass ?target=CLIENT explicitly. Omitting the query parameter does NOT default to client-web — pass it explicitly every time. The example URL above shows the canonical form.
preset: null is a normal response. When no theme is currently active for client-web (e.g. fresh install, admin temporarily disabled all presets), the response is { preset: null } — NOT a 404. Frontend should fall back to a built-in default theme bundled at build time when this happens.
Request
Query parameters
| Param | Type | Required | Notes |
|---|---|---|---|
target | string | ✓ | Pass CLIENT literal (case-insensitive). Always send this — see callout above. |
No headers required.
Response
200 OK — ApiResponseOf<ActiveThemeResponseDto>
{
"success": true,
"data": {
"preset": {
"id": "tp1a2b3c4-d5e6-7890-abcd-ef1234567890",
"name": "Default Light",
"slug": "default-light",
"target": "CLIENT",
"lightTokens": { "color": { "bg": "#FFFFFF", "fg": "#0F172A" } },
"darkTokens": { "color": { "bg": "#0F172A", "fg": "#FFFFFF" } },
"typography": { "fontFamily": "Inter, sans-serif" },
"layout": { "maxWidth": "1280px" },
"assets": { "logo": "https://cdn.bio.re/themes/default-light/logo.svg" },
"publishedAt": "2026-04-29T20:00:00.000Z"
}
}
}When no active client-web preset exists:
{
"success": true,
"data": {
"preset": null
}
}preset fields
| Field | Type | Notes |
|---|---|---|
id | string (UUID) | ThemePreset.id |
name | string | Display name |
slug | string | URL-friendly identifier |
target | string | Always "CLIENT" here (echoes the query) |
lightTokens / darkTokens | object | JSON color/spacing/etc tokens |
typography | object | JSON typography settings |
layout | object | JSON layout settings (max-width, gutters, etc) — NOT in the list endpoint |
assets | object | JSON asset references (logo URLs, etc) — NOT in the list endpoint |
publishedAt | string (ISO 8601) | null | When the preset was published |
Stripped fields
status (always PUBLISHED+isActive here, filter excludes others), isActive (always true here), tokenSchema, description, thumbnail, createdBy, publishedBy, activatedBy, activatedAt, createdAt, updatedAt — internal-only.
Errors
This endpoint has no documented error responses. preset: null is the "no active preset" path; there's no 404.
Side effects
prisma.themePreset.findFirst({ where: { target: 'CLIENT', isActive: true } })(Prisma returns the first match — there's expected to be at most one active preset).- Missing → return
{ preset: null }. - Found → map to the 10-field sanitized projection (excludes internal metadata) and return
{ preset }.
Code samples
curl 'https://api.bio.re/api/v1/themes/active?target=CLIENT'type ActiveThemePreset = {
id: string;
name: string;
slug: string;
target: 'CLIENT';
lightTokens: object;
darkTokens: object;
typography: object;
layout: object;
assets: object;
publishedAt: string | null;
};
async function getActiveTheme(): Promise<ActiveThemePreset | null> {
const url = new URL('https://api.bio.re/api/v1/themes/active');
url.searchParams.set('target', 'CLIENT');
const res = await fetch(url);
const json = await res.json();
if (!res.ok || !json.success) {
throw Object.assign(new Error(json?.error?.message ?? 'Active theme fetch failed'), {
code: json?.error?.code,
});
}
return json.data.preset;
}import { useQuery } from '@tanstack/react-query';
export const themeKeys = {
active: () => ['themes', 'active'] as const,
};
export function useActiveTheme() {
return useQuery({
queryKey: themeKeys.active(),
queryFn: async () => {
const url = new URL('/api/v1/themes/active', window.location.origin);
url.searchParams.set('target', 'CLIENT');
const res = await fetch(url);
const json = await res.json();
if (!res.ok || !json.success) {
throw Object.assign(new Error(json?.error?.message ?? 'Active theme fetch failed'), {
code: json?.error?.code,
i18nKey: json?.error?.i18nKey,
});
}
return json.data.preset as ActiveThemePreset | null;
},
// Active theme rarely flips during a session
staleTime: 5 * 60_000,
});
}Try it
Query Parameters
Theme target (CLIENT or ADMIN)
"CLIENT" | "ADMIN"Response Body
application/json
curl -X GET "https://loading/api/v1/themes/active?target=CLIENT"{
"success": true,
"data": {
"preset": {
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"name": "Default Light",
"slug": "default-light",
"target": "CLIENT",
"lightTokens": {},
"darkTokens": {},
"typography": {},
"layout": {},
"assets": {},
"publishedAt": "2019-08-24T14:15:22Z"
}
}
}Source
| Source | Path | Lines |
|---|---|---|
| Controller | apps/api-core/src/modules/theme-preset/theme-preset-public.controller.ts | 40–64 (getActive) |
| DTO (response) | apps/api-core/src/modules/theme-preset/dto/theme-preset-response.dto.ts | 164–167 (ActiveThemeResponseDto), 127–157 (ActiveThemePresetDto) |
| Service | apps/api-core/src/modules/theme-preset/theme-preset.service.ts | 139–143 (findActive(target)) |
| Prisma model | packages/prisma/prisma/schema.prisma | ThemePreset (filter target = CLIENT + isActive = true) |
List Theme Presets
Public list of PUBLISHED theme presets for the creator bio editor / client-web theming. Sanitized output — only id/name/slug + light/dark/typography tokens.
Get Presigned Upload URL
Two-step upload flow — server returns a signed PUT URL; client uploads file bytes directly to object storage. Three types (avatar / media / document) with admin-managed MIME and size allowlists.