Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Firecrawl Sdk Laravel Package

firecrawl/firecrawl-sdk

Official Firecrawl SDK for PHP and Laravel. Scrape web pages into clean markdown or structured data, crawl entire sites, search the web, and run extraction jobs through a simple client wrapper around the Firecrawl API. Handy for RAG pipelines, AI agents, and content ingestion.

View on GitHub
Deep Wiki
Context7

Getting Started

1. Install and key up

composer require firecrawl/firecrawl-sdk
FIRECRAWL_API_KEY=fc-xxxxxxxxxxxxxxxx
// config/services.php
'firecrawl' => [
    'key' => env('FIRECRAWL_API_KEY'),
    'base_url' => env('FIRECRAWL_BASE_URL', 'https://api.firecrawl.dev'),
],

2. Where to look first

Open src/ before README.md. With 2 stars and 0 dependents this is a thin, early-stage wrapper — the constructor signature, method names, and response DTOs are the real contract, and the README may lag behind. Look for:

  • the main client class (constructor takes API key + optional base URL)
  • the scrape / crawl / map / search / extract methods
  • how errors surface (exception class vs. ->success flag on the response)

3. First use case: URL → Markdown

The 80% case is turning a page into clean Markdown for storage, search, or LLM context.

use Firecrawl\FirecrawlClient; // confirm exact class name in src/

$client = app(FirecrawlClient::class);

$result = $client->scrape('https://example.com/blog/post', [
    'formats' => ['markdown'],
    'onlyMainContent' => true,
]);

$markdown = $result['data']['markdown'] ?? $result->markdown;

Get that working in php artisan tinker before building anything around it. Everything else in the SDK is a variation on this call.

Implementation Patterns

Bind the client as a singleton

The SDK client is stateless HTTP, so don't re-instantiate per call. Register it once:

// app/Providers/AppServiceProvider.php
$this->app->singleton(FirecrawlClient::class, fn () => new FirecrawlClient(
    apiKey: config('services.firecrawl.key'),
    baseUrl: config('services.firecrawl.base_url'),
));

Then inject it anywhere. This also gives you a single seam to mock in tests.

Wrap it in your own service class

Given the package's maturity (v0.x-style churn is likely), don't let FirecrawlClient leak into controllers. Put a WebScraper / SiteCrawler service in front of it so an SDK breakage is a one-file fix:

class PageScraper
{
    public function __construct(private FirecrawlClient $client) {}

    public function toMarkdown(string $url): string
    {
        $res = retry(3, fn () => $this->client->scrape($url, [
            'formats' => ['markdown'],
            'onlyMainContent' => true,
        ]), 1000);

        return $res['data']['markdown'];
    }
}

Always queue scrapes and crawls

Scrapes take 2–30s (JS rendering, PDF parsing), and crawls take minutes. A synchronous call in a controller will blow through max_execution_time on anything real.

class ScrapePageJob implements ShouldQueue
{
    public int $timeout = 120;
    public int $tries = 3;
    public array $backoff = [10, 60, 300];

    public function __construct(public string $url, public int $documentId) {}

    public function handle(PageScraper $scraper): void
    {
        $markdown = $scraper->toMarkdown($this->url);
        Document::whereKey($this->documentId)
            ->update(['markdown' => $markdown, 'scraped_at' => now()]);
    }
}

Use map before crawl

crawl is the expensive, slow, credit-hungry call. Run map first to see the URL inventory, filter it (drop /tag/, /author/, query-string permutations), then crawl a scoped includePaths list. This is the single biggest cost lever.

$urls = $client->map('https://docs.example.com')['links'];
$paths = collect($urls)
    ->filter(fn ($u) => str_contains($u, '/docs/'))
    ->map(fn ($u) => parse_url($u, PHP_URL_PATH))
    ->values()->all();

$job = $client->crawl('https://docs.example.com', [
    'includePaths' => $paths,
    'limit' => 200,
    'scrapeOptions' => ['formats' => ['markdown'], 'onlyMainContent' => true],
]);

Crawl is asynchronous — poll or webhook

crawl returns a job ID immediately. Either poll getCrawlStatus($id) in a scheduled job until status === 'completed', or register a webhook if the SDK version supports it and let Laravel handle it via a route. Polling is simpler but burns worker time; for anything long-running, prefer the webhook endpoint in your app.

Cache aggressively

The same URL does not change often. Cache by URL + options hash for hours or days; it's the difference between a $5/month and a $500/month bill.

$markdown = Cache::remember(
    'fc:'.sha1($url.json_encode($options)),
    now()->addDay(),
    fn () => $this->scraper->toMarkdown($url)
);

Batch for URL lists

If you have a known set of URLs, prefer the batch-scrape endpoint over a foreach of individual scrapes — fewer round trips, better throughput, and progress reporting.

Combine search + scrape for research flows

search gives you URLs and snippets; feed the top N into scrape to get full content. That's a natural fit for a queued ResearchTopicJob that writes results into an articles table.

Structured extraction

For schema-shaped output (pricing tables, product specs, article metadata), use extract with a JSON schema rather than parsing markdown with regex. Keep the schema small and flat — large schemas are where extraction quality degrades and costs climb.

Gotchas and Tips

It's a hosted API, not a library. Every method is a paid network call against Firecrawl's servers. There is no local fallback. Treat it like Stripe: retries, idempotency, timeouts, and cost monitoring around every call.

Pin the version and read the diff on upgrade. At 2 stars and 0 dependents, this package has essentially no community pressure testing it. Use "firecrawl/firecrawl-sdk": "0.0.x" with a caret only if you're comfortable, and check composer.lock diffs. Better: your service wrapper means an upgrade is a one-day task, not a rewrite.

Response shape is not a DTO you control. Older/current versions may return arrays, stdClass, or typed response objects depending on the endpoint. Always null-coalesce ($result['data']['markdown'] ?? '') and never assume a key exists. Log the raw response on unexpected shapes so you can see what the API returned.

Handle rate limits and 429s explicitly. Firecrawl enforces concurrency limits per plan. Use retry() with exponential backoff, and cap concurrent scrape jobs via a rate-limited queue or a WithoutOverlapping middleware on the crawl job — otherwise a bulk import will just 429-loop.

Crawl can silently return partial results. status: completed doesn't mean every URL succeeded. Inspect per-page errors; a JS-heavy page can fail while the crawl as a whole "succeeds." Persist failures and retry them individually.

Markdown is truncated on large pages. onlyMainContent helps by stripping nav/footer, but huge pages still get cut. Check for a truncation flag or length threshold in the response and handle oversize content (chunk it, or re-scrape a specific section).

Respect robots.txt, ToS, and rate etiquette on the target site. Firecrawl crawls politely by default, but you own the legality of what you crawl. Never point a crawl at a site you don't have permission to index, and honor includePaths/excludePaths to avoid hammering login pages, search results, or infinite pagination.

Testing: mock the client, not HTTP. Don't Http::fake() and hope the SDK routes through Laravel's HTTP client — it almost certainly uses Guzzle directly. Mock your own PageScraper/WebScraper service in feature tests, and write one narrow unit test against FirecrawlClient with a Guzzle MockHandler if you need to verify request shaping.

Store the raw response, not just the markdown. Keep the full JSON (or a json column) alongside the extracted markdown. When the SDK changes shape or you realize you need metadata.title or links later, you won't have to re-pay for re-scrapes.

Extension points are thin — plan to fork. There's likely no Laravel service provider, no config file to publish, no events, no middleware. Don't wait for them. Your own singleton binding + service wrapper + events (PageScraped, CrawlCompleted) is the right integration layer, and it's ~50 lines.

Timeouts matter more than you think. Default Guzzle timeouts (~30s) are borderline for scrape and too short for crawl status checks on big sites. Set an explicit longer timeout on the client constructor or per-request options, and make sure your ShouldQueue::$timeout exceeds it.

Watch credits in logs. Log the credit cost or URL count per call. A single accidental full-site crawl without a limit can consume a month of credits in one job — limit and maxDepth are not optional.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
robertmarney/lara-hierarchial-collections
simonschaufi/rule-doc-generator
emissaoja/emissaoja
blackbricksoftware/gravityforms-civicrm-form-processor
al-rahabi/fancy-crud
jinas/dhivatar-component
mltstephane/laravel-quality
bernskiold/laravel-talentlms
bernskioldmedia/laravel-form-architect
youbar/easy-crud
c-point-labs/laravel-hostyourai
dbravoan/dbcms-admin-theme-dbcms
dbravoan/dbcms-theme-db-cyber
dbravoan/dbcms-blocks-basic
dbravoan/dbcms-module-content-audit
dbravoan/dbcms-module-ai-assistant
paygate-to/paygate-php
padosoft/askmydocs-connector-base
defstudio/supercharged-carbon
bernskioldmedia/laravel-matomo