graystackit / laravel-brave-api
Laravel package for the Brave Search Images API, built on Saloon 4
Requires
- php: ^8.2
- illuminate/http: ^10.0|^11.0|^12.0|^13.0
- illuminate/support: ^10.0|^11.0|^12.0|^13.0
- saloonphp/saloon: ^4.0
Requires (Dev)
- orchestra/testbench: ^8.0|^9.0|^10.0|^11.0
- pestphp/pest: ^2.0|^3.0
- pestphp/pest-plugin-laravel: ^2.0|^3.0
This package is auto-updated.
Last update: 2026-05-05 18:49:25 UTC
README
A Laravel package for the Brave Search API, built on Saloon 4.
Supports Web Search, Image Search, Video Search, and News Search.
Requirements
- PHP 8.3+
- Laravel 11, 12, or 13
Installation
composer require graystackit/laravel-brave-api
The service provider is auto-discovered by Laravel.
Publish the config file:
php artisan vendor:publish --tag=brave-search-config
Add your API key to .env:
BRAVE_API_KEY=your-api-key
Get a key at api.search.brave.com.
Configuration
After publishing, the config file lives at config/brave-search.php:
return [ 'api_key' => env('BRAVE_API_KEY'), 'base_url' => env('BRAVE_BASE_URL', 'https://api.search.brave.com'), 'defaults' => [ 'count' => 20, 'safesearch' => 'strict', 'search_lang' => 'en', 'country' => 'us', ], ];
Usage
Resolve BraveSearchClient from the container or inject it via the constructor:
use GraystackIT\BraveSearch\BraveSearchClient; class SearchController extends Controller { public function __construct(private BraveSearchClient $brave) {} }
Web Search
Endpoint: GET /res/v1/web/search
use GraystackIT\BraveSearch\Enums\Freshness; use GraystackIT\BraveSearch\Enums\SafeSearch; $results = $this->brave->searchWeb('laravel tutorial'); foreach ($results as $result) { echo $result->title; // page title echo $result->url; // page URL echo $result->description; // meta description snippet echo $result->thumbnailUrl; // preview image URL (may be empty) echo $result->age; // e.g. "2 days ago" or ISO datetime echo $result->language; // detected language code echo $result->familyFriendly; // bool print_r($result->extraSnippets); // array of extra text snippets }
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
$query |
string |
— (required) | Search query |
$count |
int |
10 (max 20) |
Number of results |
$offset |
int |
0 |
Pagination offset |
$safesearch |
SafeSearch |
SafeSearch::Moderate |
Safe search level |
$searchLang |
string |
'en' |
Language code |
$country |
string |
'us' |
Country code |
$freshness |
Freshness|null |
null |
Restrict by recency |
$spellcheck |
bool |
true |
Enable spell-check |
$options |
array |
[] |
Extra query parameters (override defaults) |
Filter by recency or safe-search level
use GraystackIT\BraveSearch\Enums\Freshness; use GraystackIT\BraveSearch\Enums\SafeSearch; $results = $brave->searchWeb( query: 'php 8.4 features', count: 5, freshness: Freshness::PastWeek, safesearch: SafeSearch::Strict, );
Freshness enum values:
| Case | API value | Meaning |
|---|---|---|
PastDay |
pd |
Past 24 hours |
PastWeek |
pw |
Past week |
PastMonth |
pm |
Past month |
PastYear |
py |
Past year |
SafeSearch enum values:
| Case | API value |
|---|---|
Off |
off |
Moderate |
moderate |
Strict |
strict |
Video Search
Endpoint: GET /res/v1/videos/search
$results = $this->brave->searchVideos('laravel tutorial', count: 5); foreach ($results as $result) { echo $result->title; // video title echo $result->url; // video page URL echo $result->description; // video description echo $result->thumbnailUrl; // thumbnail image URL echo $result->duration; // e.g. "15:32" echo $result->views; // view count (int or null) echo $result->creator; // uploader/creator name echo $result->publisher; // platform (e.g. "YouTube") echo $result->age; // e.g. "1 week ago" echo $result->familyFriendly; // bool }
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
$query |
string |
— (required) | Search query |
$count |
int |
10 (max 20) |
Number of results |
$offset |
int |
0 |
Pagination offset |
$safesearch |
SafeSearch |
SafeSearch::Moderate |
Safe search level |
$searchLang |
string |
'en' |
Language code |
$country |
string |
'us' |
Country code |
$freshness |
Freshness|null |
null |
Restrict by recency |
$options |
array |
[] |
Extra query parameters (override defaults) |
$results = $brave->searchVideos( query: 'php conference talks', freshness: Freshness::PastMonth, country: 'gb', );
News Search
Endpoint: GET /res/v1/news/search
$results = $this->brave->searchNews('laravel 12 release', count: 10); foreach ($results as $result) { echo $result->title; // article headline echo $result->url; // article URL echo $result->description; // article summary echo $result->thumbnailUrl; // article image URL (may be empty) echo $result->age; // e.g. "3 hours ago" echo $result->sourceName; // e.g. "Laravel News" echo $result->sourceUrl; // source website URL echo $result->breaking; // bool — true for breaking news echo $result->familyFriendly; // bool }
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
$query |
string |
— (required) | Search query |
$count |
int |
10 (max 20) |
Number of results |
$offset |
int |
0 |
Pagination offset |
$safesearch |
SafeSearch |
SafeSearch::Moderate |
Safe search level |
$searchLang |
string |
'en' |
Language code |
$country |
string |
'us' |
Country code |
$freshness |
Freshness|null |
null |
Restrict by recency |
$spellcheck |
bool |
true |
Enable spell-check |
$options |
array |
[] |
Extra query parameters (override defaults) |
// Latest breaking tech news from the past day $results = $brave->searchNews( query: 'artificial intelligence', count: 10, freshness: Freshness::PastDay, country: 'us', );
Image Search
Endpoint: GET /res/v1/images/search
$results = $this->brave->searchImages('mountain landscape', count: 20); foreach ($results as $result) { echo $result->url; // full-size image URL echo $result->thumbnailUrl; // small preview URL echo $result->title; // image title echo $result->sourceDomain; // e.g. "example.com" }
Override search options per call
$results = $brave->searchImages('running shoes', count: 10, options: [ 'country' => 'de', 'search_lang' => 'de', 'safesearch' => 'moderate', ]);
Download an image
use GraystackIT\BraveSearch\BraveImageDownloader; use GraystackIT\BraveSearch\Exceptions\BraveApiException; class ImageController extends Controller { public function __construct(private BraveImageDownloader $downloader) {} public function download(string $url) { try { $bytes = $this->downloader->download($url); $mime = $this->downloader->detectMimeType($bytes); // "image/jpeg", "image/png", ... return response($bytes, 200)->header('Content-Type', $mime ?? 'application/octet-stream'); } catch (BraveApiException $e) { abort(502, 'Image download failed.'); } } }
Exceptions
| Exception | When thrown |
|---|---|
BraveApiException |
API returned 4xx/5xx, network failure, or non-JSON response |
InvalidArgumentException |
Empty query string passed to searchWeb, searchVideos, or searchNews |
Testing
This package uses Saloon's MockClient so you can test without making real HTTP calls:
use GraystackIT\BraveSearch\BraveSearchClient; use GraystackIT\BraveSearch\Connectors\BraveSearchConnector; use GraystackIT\BraveSearch\Requests\SearchWebRequest; use Saloon\Http\Faking\MockClient; use Saloon\Http\Faking\MockResponse; $mockClient = new MockClient([ SearchWebRequest::class => MockResponse::make([ 'web' => [ 'results' => [ [ 'title' => 'Example', 'url' => 'https://example.com', 'description' => 'An example page.', ], ], ], ], 200), ]); $connector = app(BraveSearchConnector::class); $connector->withMockClient($mockClient); $results = (new BraveSearchClient($connector))->searchWeb('example');
Run the package test suite:
composer install vendor/bin/pest
API Endpoints Reference
| Method | Brave API Endpoint |
|---|---|
searchWeb() |
GET /res/v1/web/search |
searchVideos() |
GET /res/v1/videos/search |
searchNews() |
GET /res/v1/news/search |
searchImages() |
GET /res/v1/images/search |
License
MIT