BookStackApp/BookStack · error · HttpFetchException
errors.cannot_get_image_from_url
Error message
errors.cannot_get_image_from_url
What it means
BookStack's UserAvatars::getAvatarImageData() fetches an external image via an HTTP client and throws HttpFetchException carrying the translated message errors.cannot_get_image_from_url when the response status is not 200. It is a guard against writing non-image/garbage responses (redirects, 403/404 pages, rate-limit blocks) into user avatar storage. The URL is included in the message so the failing remote source can be identified.
Source
Thrown at app/Uploads/UserAvatars.php:158
protected function getAvatarImageData(string $url): string
{
try {
$client = $this->http->buildClient(5);
$responseCount = 0;
do {
$response = $client->sendRequest(new Request('GET', $url));
$responseCount++;
$isRedirect = ($response->getStatusCode() === 301 || $response->getStatusCode() === 302);
$url = $response->getHeader('Location')[0] ?? '';
} while ($responseCount < 3 && $isRedirect && str_starts_with($url, 'http'));
if ($responseCount === 3) {
throw new HttpFetchException("Failed to fetch image, max redirect limit of 3 tries reached. Last fetched URL: {$url}");
}
if ($response->getStatusCode() !== 200) {
throw new HttpFetchException(trans('errors.cannot_get_image_from_url', ['url' => $url]));
}
return (string) $response->getBody();
} catch (ClientExceptionInterface $exception) {
throw new HttpFetchException(trans('errors.cannot_get_image_from_url', ['url' => $url]), $exception->getCode(), $exception);
}
}
/**
* Check if fetching external avatars is enabled.
*/
public function avatarFetchEnabled(): bool
{
$fetchUrl = $this->getAvatarUrl();
return str_starts_with($fetchUrl, 'http');
}
View on GitHub (pinned to 18f8469a1c)
Solutions
- Open the URL in the message from a server-side context (curl) and confirm it returns 200 with image content-type; fix or replace the URL
- Check for hotlink protection / User-Agent blocking on the remote host and use a CDN or locally-uploaded avatar instead
- Verify outbound HTTP (proxy/firewall) allows the request and no middleware rewrites the URL to an error page
- Catch HttpFetchException in calling code and surface a friendly message pointing at the avatar URL
Example fix
// before
$userAvatars->assignToUserFromUrl($user, 'https://example.com/avatar.png');
// after
try {
$userAvatars->assignToUserFromUrl($user, $verifiedUrl);
} catch (HttpFetchException $e) {
// pre-validate or fall back to default avatar
} Defensive patterns
Strategy: try-catch
Validate before calling
// Validate URL is fetchable and returns an image before assigning
$resp = \Illuminate\Support\Facades\Http::timeout(5)->get($url);
if ($resp->failed() || !str_starts_with($resp->header('Content-Type') ?? '', 'image/')) {
throw new \InvalidArgumentException("Avatar URL not fetchable: {$url}");
} Type guard
function isHttpOkResponse(?\Psr\Http\Message\ResponseInterface $r): bool {
return $r !== null && $r->getStatusCode() === 200;
} Try / catch
try {
$avatars->assignToUserFromUrl($user, $url);
} catch (\BookStack\Exceptions\HttpFetchException $e) {
Log::warning('Avatar fetch failed', ['url' => $url, 'msg' => $e->getMessage()]);
// fall back to default avatar
} Prevention
- Curl the URL server-side before storing it as an avatar source
- Prefer locally uploaded avatars over remote hotlinked images
- Check remote hotlink/UA-blocking policies
- Set sensible timeouts and monitor outbound fetch failures
When it happens
Trigger: Calling UserAvatars::assignToUserFromUrl or saveAvatarImage with a URL whose server replies 404/403/401/429/500 etc. (anything !== 200). Notably NOT thrown for >3 redirects (that throws a distinct max-redirect message).
Common situations: Hotlinking-protection returning 403 for non-browser user agents; typo'd or deleted image URLs; expired signed URLs (S3 presigned links); URLs requiring auth cookies; remote hosts rate-limiting BookStack's server-side fetch.
Related errors
- errors.users_cannot_delete_only_admin
- errors.users_cannot_delete_guest
- errors.role_cannot_remove_only_admin
- errors.http_ssr_url_no_match
- Unexpected type of key value provided
AI-assisted analysis of BookStackApp/BookStack@18f8469a1c (2026-09-02).
Data as JSON: /api/errors/5b0f749373b54cdd.
Report an issue: GitHub.