immich-app/immich · error · URLError

URLError.badURL

Error message

URLError.badURL

What it means

The widget's random-search call builds its request URL from the shared server configuration; when buildRequestURL cannot construct a valid URL for the /search/random endpoint it throws URLError(.badURL).

Solutions

  1. Ensure the main app writes a valid, normalized serverUrl into the shared app-group config before widget use
  2. Validate URL formation (scheme + host) at save time and on widget launch
  3. Fall back to a configuration-required widget state when serverConfig is invalid

Example fix

// before
throw URLError(.badURL) // silent misconfig
// after
guard let serverUrl = serverConfig.serverUrl, URL(string: serverUrl)?.host != nil else {
  return .failure(.needsConfiguration) // explicit widget state
}
Defensive patterns

Strategy: validation

Validate before calling

let canFetch: Bool {
  guard let raw = serverConfig.serverUrl,
        let url = URL(string: raw),
        let scheme = url.scheme,
        ["http", "https"].contains(scheme),
        url.host != nil else { return false }
  return true
}

Type guard

func isValidServerURL(_ raw: String?) -> Bool {
  guard let raw, let url = URL(string: raw), url.host != nil else { return false }
  return ["http", "https"].contains(url.scheme ?? "")
}

Try / catch

do {
  let results = try await api.randomSearch(...)
} catch URLError.badURL {
  widgetState = .needsConfiguration
}

Prevention

When it happens

Trigger: randomSearch invoked while serverConfig lacks a usable serverUrl (nil/empty/invalid), making buildRequestURL return nil for the /search/random endpoint.

Common situations: Widget credentials/server config never synced into the app group; user logged out in the main app leaving stale/blank config; server URL entered without scheme.

Understand the failure class

Background: "Invalid URL" / "URL cannot be empty": fix the malformed or missing URL behind request-construction failures — this error's family across 50 libraries.

Related errors


AI-assisted analysis of immich-app/immich@e55ac299a4 (2026-09-15). Data as JSON: /api/errors/c6b03ee566a8709d. Report an issue: GitHub.

Appendix: source

Thrown at mobile/ios/WidgetExtension/ImmichAPI.swift:189

  
  func applyCustomHeaders(for request: inout URLRequest) {
    for (header, value) in serverConfig.customHeaders {
      request.addValue(value, forHTTPHeaderField: header)
    }
  }

  func fetchSearchResults(with filters: SearchFilter = Album.NONE.filter)
    async throws
    -> [Asset]
  {
    // get URL
    guard
      let searchURL = buildRequestURL(
        serverConfig: serverConfig,
        endpoint: "/search/random"
      )
    else {
      throw URLError(.badURL)
    }

    var request = URLRequest(url: searchURL)
    request.httpMethod = "POST"
    request.httpBody = try JSONEncoder().encode(filters)
    request.setValue("application/json", forHTTPHeaderField: "Content-Type")
    applyCustomHeaders(for: &request)
    
    let (data, _) = try await URLSession.shared.data(for: request)

    // decode data
    return try JSONDecoder().decode([Asset].self, from: data)
  }

  func fetchMemory(for date: Date) async throws -> [MemoryResult] {
    // get URL
    let localDay = date.formatted(
      Date.ISO8601FormatStyle(timeZone: .current).year().month().day().dateSeparator(.dash)

View on GitHub (pinned to e55ac299a4)