Provider configuration
Multi-provider apps wire API keys and client construction in app-local modules — not via a ProviderFactory export from @media-sdk/core. Copy the pattern from the starters or reference demo; keep it in your repo so you control env naming and defaults.
Canonical createMediaClient recipe
Both examples/basic-react and examples/basic-react-native ship the same ~30-line factory. Add a src/createMediaClient.ts file:
import {
ApiKeyProvider,
MemoryCache,
PixabayMediaClient,
PexelsMediaClient,
type Cache,
type MediaClient,
} from "@media-sdk/core";
export type MediaProviderId = "pexels" | "pixabay";
export interface CreateMediaClientOptions {
provider: MediaProviderId;
apiKey: string;
cache?: Cache;
}
export function createMediaClient(
options: CreateMediaClientOptions,
): MediaClient {
const auth = new ApiKeyProvider(options.apiKey);
const cache = options.cache ?? new MemoryCache(60_000);
if (options.provider === "pexels") {
return new PexelsMediaClient(auth, { cache });
}
return new PixabayMediaClient(auth, { cache });
}provider === "pexels" branching is allowed here (and in a provider selector) — it chooses which client to construct. It must not gate UI features; use getCapabilities or useMediaCapabilities for that.
Environment variable matrix
| Variable | Node / server | Vite (web) | Expo (React Native) |
|---|---|---|---|
| Pexels API key | PEXELS_API_KEY | VITE_PEXELS_API_KEY | EXPO_PUBLIC_PEXELS_API_KEY |
| Pixabay API key | PIXABAY_API_KEY | VITE_PIXABAY_API_KEY | EXPO_PUBLIC_PIXABAY_API_KEY |
| Default provider (optional) | MEDIA_PROVIDER | VITE_MEDIA_PROVIDER | EXPO_PUBLIC_MEDIA_PROVIDER |
Values for the default provider: pexels or pixabay. When omitted, starters default to Pexels.
Copy .env.example from the starter that matches your bundler:
# Web (examples/basic-react/.env.example)
VITE_PEXELS_API_KEY=
VITE_PIXABAY_API_KEY=
VITE_MEDIA_PROVIDER=pexels
# Native (examples/basic-react-native/.env.example)
EXPO_PUBLIC_PEXELS_API_KEY=
EXPO_PUBLIC_PIXABAY_API_KEY=
EXPO_PUBLIC_MEDIA_PROVIDER=pexelsRestart the dev server after changing env files (Vite and Expo do not hot-reload env vars).
Provider selection vs capability-driven UI
Split responsibilities into two layers:
| Layer | Responsibility | Allowed patterns |
|---|---|---|
| App shell | Read env keys, pick provider, build MediaClient, wrap MediaProvider | provider === in createMediaClient; ProviderSelector updates provider state |
| Presentation | Tabs, filters, curated feed, preview actions | useMediaCapabilities() / getCapabilities(client) only |
App shell (web starter)
MediaAppShell holds provider state, builds a client with useMemo, and passes it to MediaProvider. When the user switches providers, the client prop changes and hooks re-fetch with the new capabilities:
import { useMemo, useState } from "react";
import { MediaProvider } from "@media-sdk/react";
import { createMediaClient, type MediaProviderId } from "./createMediaClient";
export function MediaAppShell({
initialProvider,
pexelsApiKey,
pixabayApiKey,
}: {
initialProvider: MediaProviderId;
pexelsApiKey: string;
pixabayApiKey: string;
}) {
const [provider, setProvider] = useState(initialProvider);
const apiKey = provider === "pexels" ? pexelsApiKey : pixabayApiKey;
const client = useMemo(
() => createMediaClient({ provider, apiKey }),
[provider, apiKey],
);
return (
<MediaProvider client={client}>
<App provider={provider} onProviderChange={setProvider} />
</MediaProvider>
);
}The same structure exists in examples/basic-react-native with Expo env reads.
Presentation layer
Gate tabs and filters with capabilities, not provider strings:
import { useMediaCapabilities } from "@media-sdk/react";
function SearchTabs() {
const caps = useMediaCapabilities();
return (
<>
<PhotosTab />
<VideosTab />
{caps.operations.curatedPhotos && <CuratedTab />}
</>
);
}See Provider comparison for the full anti-pattern list.
providerConfig.ts helper
Starters also include providerConfig.ts for labels, env var names, and readProviderApiKeys() / resolveDefaultProvider(). Keep provider-specific strings in this module so App.tsx stays provider-neutral.
Curated photos (app recipe)
getCuratedPhotos is Pexels-only. The SDK does not export useCuratedPhotos — copy the app-local hook from examples/basic-react/src/useCuratedPhotos.ts (or the native starter equivalent) if you need a curated tab:
// App-local pattern — not part of @media-sdk/react
import { useMediaCapabilities } from "@media-sdk/react";
import { useCuratedPhotos } from "./useCuratedPhotos";
function CuratedPanel() {
const caps = useMediaCapabilities();
const { data, loading, error } = useCuratedPhotos({ enabled: caps.operations.curatedPhotos });
if (!caps.operations.curatedPhotos) return null;
// render grid...
}Gate the tab with caps.operations.curatedPhotos before calling the hook.
Runtime provider swap
Changing MediaProvider's client prop (as in apps/web and the starters) is the supported way to swap providers at runtime. Hooks abort in-flight searches, reset state, and useMediaCapabilities() reflects the new client immediately.
Related pages
- Adoption journeys — step-by-step doc paths
- Provider comparison — capability matrix
- Pixabay hotlinking — required when using Pixabay
- Installation — package versions and API keys
- Quick Start — first search in minutes
- React hooks —
MediaProviderand search hooks - Native MediaProvider — same pattern on React Native