fopost / laravel
Official Laravel SDK for the FoPost API. Schedule and publish to +30 social platforms from your code.
Requires
- php: >=8.1
- fopost/sdk: ^0.1
- illuminate/support: ^10.0|^11.0|^12.0
Requires (Dev)
- orchestra/testbench: ^8.0|^9.0|^10.0
- phpunit/phpunit: ^10.0|^11.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
- dev-main
- v0.1.0
- dev-feature/blog-articles
- dev-feature/activity-resource
- dev-feature/whatsapp
- dev-feature/broadcasts-sequences
- dev-feature/google-business-management
- dev-feature/contacts
- dev-feature/remote-articles
- dev-fix/sdk-constraint
- dev-feature/meta-ads-depth
- dev-feature/inbox-ads-resources
- dev-feature/direct-media-upload
- dev-fix/dashboard-url
- dev-fix/ci-parent-and-laravel-matrix
- dev-docs/claude-md
- dev-fix/api-base-path
This package is auto-updated.
Last update: 2026-10-14 21:40:49 UTC
README
Official Laravel SDK for the FoPost API. Schedule and publish to +30 social platforms from your code.
This package is a thin wrapper around fopost/sdk: it binds the
API client into the container, adds a facade, and gives you a publishable config file. Every platform
connection, token refresh, and delivery happens on the hosted API, so there is nothing to run yourself.
Requirements
- PHP 8.2 or newer
- Laravel 12
- A FoPost API key from fopost.com/dashboard/api-keys
Laravel 10 and 11 are not supported. Those framework lines are out of active support and carry unpatched security advisories that Composer blocks by default, so there is no version of them this package can be tested against.
Installation
composer require fopost/laravel
The service provider and the Fopost facade alias are auto-discovered. Publish the config file if you
want to edit it:
php artisan vendor:publish --tag=fopost-config
Configuration
Add your key to .env:
FOPOST_API_KEY=fp_your_key_here
Everything else is optional:
| Variable | Default | What it does |
|---|---|---|
FOPOST_API_KEY |
none, required | Your API key |
FOPOST_API_URL |
https://api.fopost.com |
API base URL |
FOPOST_API_TIMEOUT |
30 |
Seconds to wait for one request |
FOPOST_API_MAX_RETRIES |
3 |
Attempts for a rate limited request |
FOPOST_WEBSITE_SECRET |
none | Secret FoPost signs published articles with, see Receive blog articles |
Quickstart
use Fopost\Laravel\Facades\Fopost; $workspace = Fopost::workspaces()->list()[0]; $accounts = Fopost::accounts()->list($workspace->id); $post = Fopost::posts()->create( workspaceId: $workspace->id, content: 'Shipping today: scheduled posting straight from Laravel.', accounts: $accounts, status: 'scheduled', scheduleAt: now()->addHour(), ); echo $post->id;
Prefer injection over the facade? Type hint the client and the container hands you the configured singleton:
use Fopost\Sdk\Client; public function __invoke(Client $fopost) { return $fopost->posts()->list(status: 'scheduled'); }
One example per resource
Posts
$page = Fopost::posts()->list(workspaceId: $workspaceId, status: 'scheduled'); $post = Fopost::posts()->get($postId); Fopost::posts()->schedule($postId, now()->addDay()); Fopost::posts()->publish($postId); Fopost::posts()->delete($postId); foreach (Fopost::posts()->iterate(workspaceId: $workspaceId) as $everyPost) { // walks page by page for you }
Accounts
$accounts = Fopost::accounts()->list($workspaceId); $account = Fopost::accounts()->get($accountId); Fopost::accounts()->disconnect($accountId);
Workspaces
$workspaces = Fopost::workspaces()->list(); $workspace = Fopost::workspaces()->get($workspaceId);
Labels
$label = Fopost::labels()->create($workspaceId, 'Launch week', '#2563eb'); Fopost::labels()->update($label->id, name: 'Launch'); Fopost::labels()->delete($label->id);
Activity
$page = Fopost::activity()->list($workspaceId); // The audit log: who joined, left, or changed role and access, plus changes to // two-step verification, passkeys, single sign-on, and signed-in devices. $audit = Fopost::activity()->list($workspaceId, 'security');
AI
$balance = Fopost::ai()->credits(); $caption = Fopost::ai()->generateCaption( currentCaption: 'New release is live', platforms: ['instagram', 'linkedin'], workspaceId: $workspaceId, ); $rewritten = Fopost::ai()->rewrite( content: 'Long form announcement text', platforms: ['x', 'threads'], workspaceId: $workspaceId, );
Inbox
Needs the inbox scope.
$unread = Fopost::inbox()->list(workspaceId: $workspaceId, state: 'unread'); Fopost::inbox()->reply($unread[0]->id, 'Thanks for the kind words'); Fopost::inbox()->update($unread[0]->id, 'resolved');
Contacts
The people behind that inbox: one row per human, however many handles they write from. Needs the inbox scope, except conversationAnalytics(), which answers counts per thread and needs analytics.
$contacts = Fopost::contacts()->list($workspaceId, search: 'ada'); $threads = Fopost::contacts()->conversations($contacts->items[0]->id); Fopost::contacts()->import($workspaceId, file_get_contents('contacts.csv'));
Broadcasts and sequences
A broadcast is one message into every conversation you already have with a segment of your contacts; a sequence is a series of them on a delay. Neither opens a cold DM.
Nothing is sent into a closed messaging window: Messenger and Instagram take a business-initiated message only within 24 hours of the contact's last one, so recipients outside it come back skipped with window_closed rather than attempted. The number sent is therefore often lower than the audience, and that is correct rather than a failure.
Reading needs the inbox scope; send(), cancel(), enroll() and unenroll() also need publish.
use Fopost\Sdk\Model\SequenceStep; $broadcast = Fopost::broadcasts()->create( $workspaceId, $accountId, 'September check-in', 'New colours just landed. Want a look?', audience: ['platforms' => ['instagram']], ); // The recipients count is how many contacts matched, not how many will be messaged. Fopost::broadcasts()->send($broadcast->id); // Who was skipped, and why. foreach (Fopost::broadcasts()->recipients($broadcast->id, status: 'skipped') as $recipient) { echo $recipient->displayName, ': ', $recipient->skipReason, PHP_EOL; } $sequence = Fopost::sequences()->create($workspaceId, $accountId, 'Welcome', [ SequenceStep::make(0, 'Thanks for the follow'), SequenceStep::make(48, 'Here is what people usually ask us first.'), ]); Fopost::sequences()->enroll($sequence->id, [$contactId]); Fopost::sequences()->unenroll($sequence->id, [$contactId]);
Ads
Needs the ads scope; boost(), create(), setStatus(), delete(), bulkSetStatus() and every campaign, ad set and network ad write also need publish. Anything created starts paused unless paused: false is passed.
$ads = Fopost::ads()->list($workspaceId); Fopost::ads()->setStatus($ads[0]->id, $workspaceId, 'active'); $tree = Fopost::ads()->accountTree('act_123', $connectionId); // campaigns, ad sets and ads $report = Fopost::ads()->insights($connectionId, $tree->campaigns[0]->id, '2026-09-01', '2026-09-07', daily: true); $leads = Fopost::ads()->leadsFeed($workspaceId, cursor: $cursor);
Blog
An article goes to a website; a post goes to a social network. Reads and keyword edits need the posts scope, requestArticle(), planKeywords() and regenerateArticle() need ai, publishArticle() needs publish.
$article = Fopost::blog()->requestArticle($workspaceId, keyword: 'scheduling posts from laravel'); foreach (Fopost::blog()->listArticles($workspaceId, status: 'draft') as $draft) { echo $draft->title, PHP_EOL; } Fopost::blog()->publishArticle($workspaceId, $article->id);
Media
$asset = Fopost::media()->uploadDirect( workspaceId: $workspaceId, filename: 'launch.png', mimeType: 'image/png', bytes: file_get_contents('launch.png'), );
Receive blog articles
Point the blog at your site with Fopost::blog()->updateSettings($workspaceId, ['targetKind' => 'website', 'websiteUrl' => 'https://yourbrand.com/fopost/articles']). The result carries websiteSecret once; put it in .env as FOPOST_WEBSITE_SECRET. Every published article is then delivered to that URL as a signed article.published webhook.
Mount the receiver in routes/web.php or routes/api.php:
Route::fopostArticles('/fopost/articles');
It reads the raw body, verifies the signature through fopost/sdk, answers 401 when it does not match, { "ok": true } for the article.test ping, and hands a published article to your callback. Register it in a service provider's boot(); return the live URL and the receiver echoes it back as { "url": "..." }:
use Fopost\Laravel\Facades\Fopost; use Fopost\Sdk\Model\BlogArticleWebhookPayload; Fopost::onArticlePublished(function (BlogArticleWebhookPayload $article): string { $post = Article::updateOrCreate(['fopost_id' => $article->id], [ 'title' => $article->title, 'slug' => $article->slug, 'body' => $article->bodyHtml, 'excerpt' => $article->excerpt, 'tags' => $article->tags, ]); return route('articles.show', $post); });
Prefer events? Every verified delivery also dispatches Fopost\Laravel\Events\ArticlePublished with the same $article; a listener that returns a string is echoed as the URL when no callback is registered. Image URLs on the payload are fetchable for six hours, so download and store your own copy.
Errors
Every failure is a Fopost\Sdk\Exception\FopostException subclass, so one catch covers the lot:
use Fopost\Sdk\Exception\FopostException; use Fopost\Sdk\Exception\RateLimitException; use Fopost\Sdk\Exception\ValidationException; try { Fopost::posts()->publish($postId); } catch (ValidationException $e) { report($e); } catch (RateLimitException $e) { // retry after $e->retryAfter seconds } catch (FopostException $e) { report($e); }
Testing your app
Swap the container binding for a client wired to your own transport, and nothing touches the network:
use Fopost\Sdk\Client; use Fopost\Sdk\Http\Response; use Fopost\Sdk\Http\Transport; $this->app->instance(Client::class, new Client('fp_test', 'https://api.fopost.com', 30.0, 3, $fakeTransport));
Looking for the free self-hosted toolkit?
This package talks to the FoPost Cloud API with a FoPost API key. If you want to publish straight to the
social platforms using your own app credentials, with no FoPost account involved, use
fopost/social-laravel instead. The two families are
separate on purpose and never depend on each other.
Links
- Documentation: fopost.com/docs
- API keys: fopost.com/dashboard/api-keys
- Support: fopost.com/contact
License
MIT. Copyright Porter Bridge, LLC. See LICENSE.