Skip to content

Error handling recipes

Use this page when wiring try/catch blocks or hook error state. For the full MediaError reference, see Errors.

Decision tree

mermaid
flowchart TD
  start["catch (error)"]
  abort{"isAbortError(error)?"}
  media{"error instanceof MediaError?"}
  code{"error.code set?"}
  status{"error.status set?"}
  ignore["Ignore — expected cancellation"]
  codeBranch["Branch on code:\nUNSUPPORTED_CAPABILITY\nUNSUPPORTED_FILTER\nINVALID_FILTER_VALUE"]
  statusBranch["Branch on status:\n401/403 auth\n429 rate limit\n404 not found\n5xx server"]
  generic["Generic Error:\nnetwork, parse, missing API key"]

  start --> abort
  abort -->|yes| ignore
  abort -->|no| media
  media -->|yes| code
  media -->|no| generic
  code -->|yes| codeBranch
  code -->|no| status
  status -->|yes| statusBranch
  status -->|no| generic
StepCheckAction
1isAbortError(error)Return early — hooks already swallow aborts in UI state
2error instanceof MediaError && error.codeMap to user-facing copy (capability, filter, invalid value)
3error instanceof MediaError && error.statusMap to auth, rate-limit, not-found, or server messages
4Plain ErrorTreat as network failure, JSON parse error, or unexpected failure

Import helpers from @media-sdk/core:

ts
import { isAbortError, MediaError } from "@media-sdk/core";

Core try/catch template

ts
import { isAbortError, MediaError } from "@media-sdk/core";

async function searchWithHandling(client: MediaClient, query: string) {
  try {
    return await client.searchPhotos({ query, page: 1, perPage: 20 });
  } catch (error) {
    if (isAbortError(error)) {
      return null;
    }

    if (error instanceof MediaError) {
      switch (error.code) {
        case "UNSUPPORTED_CAPABILITY":
          throw new Error("This action is not available for the selected provider.");
        case "UNSUPPORTED_FILTER":
          throw new Error("One or more filters are not supported.");
        case "INVALID_FILTER_VALUE":
          throw new Error("Invalid filter selection.");
        default:
          break;
      }

      if (error.status === 401 || error.status === 403) {
        throw new Error("Check your API key and provider permissions.");
      }
      if (error.status === 429) {
        throw new Error("Rate limit exceeded — try again later.");
      }
      if (error.status && error.status >= 500) {
        throw new Error("Provider API is temporarily unavailable.");
      }

      throw new Error(error.message);
    }

    // Network, JSON parse, missing ApiKeyProvider key, etc.
    throw error instanceof Error ? error : new Error(String(error));
  }
}

MediaError codes (copy-paste)

UNSUPPORTED_CAPABILITY

Thrown when the active client does not implement an operation (for example curated photos on Pixabay):

ts
import { MediaError } from "@media-sdk/core";

try {
  await client.getCuratedPhotos({ page: 1, perPage: 20 });
} catch (error) {
  if (error instanceof MediaError && error.code === "UNSUPPORTED_CAPABILITY") {
    console.error("Curated photos are not available for this provider");
  }
}

Prefer guarding with getCapabilities so this path is rare.

UNSUPPORTED_FILTER

Thrown when a filter key is not supported — filters are never silently ignored:

ts
try {
  await client.searchPhotos({
    query: "nature",
    photoFilters: { size: "large" },
  });
} catch (error) {
  if (error instanceof MediaError && error.code === "UNSUPPORTED_FILTER") {
    console.error("The size filter is not supported by this provider");
  }
}

INVALID_FILTER_VALUE

Thrown when the filter key is supported but the value is invalid:

ts
try {
  await client.searchPhotos({
    query: "nature",
    photoFilters: { color: "grayscale" },
  });
} catch (error) {
  if (error instanceof MediaError && error.code === "INVALID_FILTER_VALUE") {
    console.error("That color value is not valid for this provider");
  }
}

Hook error surface

useMediaSearch and useMediaVideos expose error: Error | null. At runtime, failures from core are often MediaError instances — narrow with instanceof before reading code or status:

tsx
import { MediaError } from "@media-sdk/core";
import { useMediaSearch } from "@media-sdk/react";

function SearchResults({ query }: { query: string }) {
  const { data, loading, error } = useMediaSearch({ query });

  if (loading) return <p>Loading…</p>;

  if (error) {
    if (error instanceof MediaError) {
      if (error.code === "UNSUPPORTED_FILTER") {
        return <p>Filters not supported by the active provider.</p>;
      }
      if (error.status === 401 || error.status === 403) {
        return <p>Invalid API key — check your environment variables.</p>;
      }
      if (error.status === 429) {
        return <p>Rate limit exceeded. Please wait and try again.</p>;
      }
      return <p>{error.message}</p>;
    }
    return <p>Something went wrong. Check your network connection.</p>;
  }

  return <p>{data?.items.length ?? 0} results</p>;
}

Aborted requests do not populate error. See Cancellation.

Anti-patterns

ts
// ❌ Do not parse error.message for control flow
if (error.message.includes("not supported")) { ... }

// ✅ Branch on error.code or error.status
if (error instanceof MediaError && error.code === "UNSUPPORTED_FILTER") { ... }