sammyk/laravel-facebook-sdk
sammyk/laravel-facebook-sdk wraps the official Facebook PHP SDK for Laravel, providing a convenient facade, configuration, and dependency injection. It simplifies Facebook login, access token handling, and Graph API requests in your Laravel applications.
Reality check first: the repository is archived and facebook/graph-sdk (v5) is abandoned. Before you invest, confirm you're on Laravel 5.x/6-ish — Composer constraints on illuminate/support and Guzzle will usually block installs on Laravel 8+ anyway. On anything modern, skip the package and call https://graph.facebook.com/{version}/... directly with Http:: — the day-to-day API surface you actually use is about five endpoints.
If you're on a compatible stack:
composer require sammyk/laravel-facebook-sdk
php artisan vendor:publish --provider="SammyK\LaravelFacebookSdk\LaravelFacebookSdkServiceProvider"
Laravel 5.5+ auto-discovers the provider and the Facebook facade alias. On older versions add to config/app.php:
SammyK\LaravelFacebookSdk\LaravelFacebookSdkServiceProvider::class,
// aliases
'Facebook' => SammyK\LaravelFacebookSdk\FacebookFacade::class,
Then set in .env:
FACEBOOK_APP_ID=...
FACEBOOK_APP_SECRET=...
FACEBOOK_DEFAULT_GRAPH_VERSION=v2.5
Look at these three things, in this order:
config/laravel-facebook-sdk.php — every behaviour (graph version, persistence, HTTP handler, beta mode) is decided here.src/LaravelFacebookSdk.php — it's a thin wrapper, roughly 300 lines. Read it once; it removes all mystery about what the facade does versus the underlying Facebook\Facebook.First use case — Facebook Login + one API call. Two routes:
// routes/web.php — must be inside the 'web' middleware group
Route::get('auth/facebook', function () {
// v3 signature is (callbackUrl, scopes); v1/v2 swapped these.
// Check the README for your pinned version before copying.
return redirect(Facebook::getLoginUrl(route('fb.callback'), ['email', 'public_profile']));
})->name('fb.login');
Route::get('auth/facebook/callback', function () {
try {
$token = Facebook::getAccessTokenFromRedirect(route('fb.callback'));
} catch (\Facebook\Exceptions\FacebookSDKException $e) {
return redirect('/')->withErrors($e->getMessage());
}
Facebook::setDefaultAccessToken($token);
$user = Facebook::get('/me?fields=id,name,email')->getGraphUser();
auth()->login($userRecord = User::updateOrCreate(
['facebook_id' => $user['id']],
['name' => $user['name'], 'email' => $user['email'] ?? null, 'fb_token' => (string) $token]
));
return redirect('/dashboard');
})->name('fb.callback');
That's the whole package in one screen: build a login URL, exchange the code for a token, set it as default, make Graph calls.
Wrap the facade in your own service. The facade is a singleton with mutable state (setDefaultAccessToken), which makes it awkward in tests and dangerous in long-lived processes. Inject SammyK\LaravelFacebookSdk\LaravelFacebookSdk into a FacebookService and let callers deal in your domain objects, not GraphNodes.
class FacebookService
{
public function __construct(private LaravelFacebookSdk $fb) {}
public function profile(AccessToken $token): array
{
$node = $this->fb->get('/me?fields=id,name,email,picture.width(200)', [], $token)
->getGraphUser();
return ['id' => $node['id'], 'name' => $node['name'], 'avatar' => $node['picture']['url'] ?? null];
}
}
Note the third argument: get(), post(), delete(), and sendRequest() all accept an explicit token, so you can avoid setDefaultAccessToken() entirely — that's the single biggest code-quality win with this package.
Token lifecycle. Short-lived user tokens die in ~1–2 hours. On callback, immediately exchange for a long-lived one (~60 days) and persist it:
$short = Facebook::getAccessTokenFromRedirect(route('fb.callback'));
$longLived = Facebook::getLongLivedAccessToken($short); // returns AccessToken
$user->update([
'fb_token' => (string) $longLived,
'fb_token_expires_at' => $longLived->getExpiresAt()?->format('Y-m-d H:i:s'),
]);
For app-scoped or page-scoped jobs use Facebook::getAppAccessToken() (good for /app, webhook subscriptions, debugging) or fetch a page token once and store it — page tokens obtained from a long-lived user token generally don't expire.
Pagination. Graph returns edges; the SDK handles cursors:
$response = Facebook::get('/me/feed', ['limit' => 50], $token);
$edge = $response->getGraphEdge();
do {
foreach ($edge as $post) { /* ... */ }
} while ($edge = Facebook::getFacebook()->next($edge)); // underlying SDK
Batches. When hydrating a feed (post + likes + comments), batch instead of N calls — this is the main reason to keep the SDK rather than raw HTTP:
$batch = [
'post' => Facebook::getFacebook()->request('GET', '/'.$id.'?fields=message,created_time'),
'likes' => Facebook::getFacebook()->request('GET', '/'.$id.'/likes?summary=true'),
'comments' => Facebook::getFacebook()->request('GET', '/'.$id.'/comments?limit=10'),
];
$responses = Facebook::getFacebook()->sendBatchRequest($batch, $token);
getFacebook() is the escape hatch to the full v5 SDK — use it whenever the wrapper is too narrow.
Observability via events. The package fires FacebookResponseEvent, FacebookResponseErrorEvent, and FacebookResponseExceptionEvent. One listener that logs method, path, graph version, and error subcode will save you hours; register it in EventServiceProvider and you get a free audit trail for every call.
Queue jobs. Never rely on the session-held token inside a job. Load the token from your users table, pass it into the call explicitly, and catch FacebookResponseException — a revoked token in a nightly sync otherwise fails silently for weeks.
Webhooks. The package does not help here. Verify manually and compare against the app secret, then return the challenge:
abort_unless(hash_equals(
'sha1='.hash_hmac('sha1', $request->getContent(), config('laravel-facebook-sdk.app_secret')),
$request->header('X-Hub-Signature')
), 403);
Testing. Swap the facade (Facebook::shouldReceive('get')->andReturn($fakeResponse)) for unit tests, or, for integration-ish tests, override http_client_handler with a Guzzle MockHandler so real SDK serialization and error mapping run against recorded payloads. Bind your FacebookService with a fake in the container and you never touch the network in CI.
persistent_data_handler must not be memory for the redirect flow. The default memory handler throws away the OAuth state between the login redirect and the callback, producing FacebookSDKException: Cross-site request forgery validation failed. Set it to session (requires the web middleware group and a real session driver) or LaravelCache for stateless/API setups.www. and apex domains, or terminating TLS in front of a proxy with mismatched APP_URL, silently breaks state validation. Same for the OAuth redirect URI registered in the Facebook app dashboard — it must match route('fb.callback') character for character, including trailing slash.default_graph_version defaults to something like v2.5; calls will fail with unsupported-version errors. Bump the config value to a version Facebook still serves, then re-test every endpoint — the SDK's field assumptions (e.g. /me returning only id,name) and response shapes around pagination changed across versions. This is the highest-risk edit you'll make./me returns almost nothing by default. You get id and name. Always pass explicit ?fields=; if you need email you must have requested the email scope and the user must have a confirmed email — plenty of accounts return it as null.php artisan config:cache bakes in FACEBOOK_APP_SECRET. Rotating secrets in production without re-caching gives you Invalid appsecret_proof and hours of confusion. Lumen additionally needs $app->configure('laravel-facebook-sdk') and $app->withFacades().facebook/graph-sdk pins Guzzle 5/6 and illuminate/support to old majors. If composer require won't resolve, that's the message — don't fight it with --ignore-platform-reqs.http_client_handler is your extension point. Set it to a custom Guzzle client with retry middleware, a proxy, or a logger. stream works everywhere; curl needs ext-curl and is faster under load.Facebook::enableAppSecretProof()); it's on by default in recent SDKs but confirm — it's cheap protection against token replay.Facebook\Exceptions\FacebookResponseException and branch on getCode()/subcode: 190 = dead/revoked token → mark the user record and re-auth; 4/17/32/613 = rate limit → exponential backoff, not retry-immediately; 100 = bad field or permission → your bug. Let FacebookSDKException (transport/config) bubble to your generic handler.getFacebook()->setDefaultAccessToken(...) then replay the exact URL in the Graph API Explorer. If Explorer works and your app doesn't, the difference is almost always the token's scopes or the graph version in config.getLoginUrl() in a queue or console context — it derives state/session data and will behave unpredictably without a request.FacebookService; replace its internals with Http::withToken($token)->get("https://graph.facebook.com/v20.0/...") and you can drop both dependencies without touching callers. Keep response mapping in that class and the swap is a day's work.How can I help you explore Laravel packages today?