Download the PHP package siberfx/linkedin-autopost without Composer

On this page you can find all versions of the php package siberfx/linkedin-autopost. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.

FAQ

After the download, you have to make one include require_once('vendor/autoload.php');. After that you have to import the classes with use statements.

Example:
If you use only one package a project is not needed. But if you use more then one package, without a project it is not possible to import the classes with use statements.

In general, it is recommended to use always a project to download your libraries. In an application normally there is more than one library needed.
Some PHP packages are not free to download and because of that hosted in private repositories. In this case some credentials are needed to access such packages. Please use the auth.json textarea to insert credentials, if a package is coming from a private repository. You can look here for more information.

  • Some hosting areas are not accessible by a terminal or SSH. Then it is not possible to use Composer.
  • To use Composer is sometimes complicated. Especially for beginners.
  • Composer needs much resources. Sometimes they are not available on a simple webspace.
  • If you are using private repositories you don't need to share your credentials. You can set up everything on our site and then you provide a simple download link to your team member.
  • Simplify your Composer build process. Use our own command line tool to download the vendor folder as binary. This makes your build process faster and you don't need to expose your credentials for private repositories.
Please rate this library. Is it a good library?

Informations about the package linkedin-autopost

LinkedIn Autopost for Laravel

tests Packagist PHP

Connect one LinkedIn account to your Laravel app and share your models on LinkedIn — automatically when they go live, or whenever you choose. Posts go through LinkedIn's Posts API as link posts with a preview.

Requires PHP 8.4+ and Laravel 12 or 13.

Contents

Quick start

The shortest path from composer require to a first post, with the Tailwind UI:

Open the settings page, click Connect LinkedIn, run php artisan queue:work, and publish an article: it appears on LinkedIn. Each step is explained below.

1. Set up the LinkedIn app

  1. Create an app at https://www.linkedin.com/developers/apps.
  2. Products tab: add Share on LinkedIn and Sign In with LinkedIn using OpenID Connect. Both are self-serve.
  3. Auth tab → Authorized redirect URLs for your app: add your callback URL exactly, e.g. https://example.com/linkedin/callback — scheme, host (with or without www), path, no trailing slash. Add one per environment (https://myapp.test/linkedin/callback for local).
  4. Copy the Client ID and Primary Client Secret.

"The redirect_uri does not match the registered value" on LinkedIn's page means the URL your app sent is not in that list character for character. Run php artisan route:list --name=linkedin-autopost.callback to see the URL, or set LINKEDIN_REDIRECT_URI to the one you registered.

2. Install

linkedin:install options:

It publishes config/linkedin-autopost.php and the two migrations, writes LINKEDIN_UI_THEME (or LINKEDIN_UI=false) to .env when it is not there yet, and prints the redirect URI to register and the gate to define. To publish pieces by hand:

Using UUID or ULID keys? Edit the published create_linkedin_posts_table migration: replace morphs('shareable') with uuidMorphs('shareable') / ulidMorphs('shareable') before migrating.

.env:

Required: say who may manage LinkedIn. Everything — connecting, disconnecting, seeing the connection (including the member's email) and sharing — is guarded by the manage-linkedin-autopost gate. The package defines it to deny everyone until your app defines it, for example in AppServiceProvider::boot():

Any rule works — a role package, a policy-style check, a list of emails:

Your definition wins whatever the provider order. The routes sit behind ['web', 'auth', 'can:manage-linkedin-autopost']; the Livewire actions check the same gate, and the components show the connection read-only and the share button disabled for people it refuses. Change the middleware, the URL prefix or the page you come back to in config/linkedin-autopost.php:

Changing the prefix changes the callback URL: register the new one on your LinkedIn app.

3. Connect

Send an admin to route('linkedin-autopost.redirect'). After LinkedIn's consent screen they land on after_connect with one of these query values:

?linkedin= Meaning
connected Done.
cancelled The person cancelled on LinkedIn.
error&reason=… LinkedIn refused, e.g. Scope "profile" is not authorized for your application (add the OpenID Connect product).
exchange_failed The code could not be exchanged; the reason is in your log. Usually a redirect URL mismatch.
invalid_state The login attempt expired or was replayed. Connect again.
missing_code LinkedIn came back without an authorization code. Connect again.
not_configured Client id or secret missing.

The connection card and <x-linkedin-autopost::flash /> turn these into messages for you. With your own page, a plain link and a message are enough:

Check the connection anywhere:

connection() asks LinkedIn (token introspection and userinfo) at most once every status_cache_ttl seconds (300 by default) and serves the cached status in between. isConnected() only checks that a token is stored and never calls LinkedIn.

4. Make a model shareable

The two methods:

LinkPost Limit Default
make(string $url, string $title) title cut to 200 characters —
->description(string) cut to 256 characters empty
->commentary(string $template) — "{title}\n\n{url}"

LinkPost is immutable: each method returns a copy.

More isLiveForLinkedIn() rules:

More toLinkedInPost() styles:

With LINKEDIN_AUTOPOST=true, an Article is queued for LinkedIn when it is created published or changes from draft to published — once. Editing a published article does not post again. Seeders and Artisan commands do not auto-post (set autopost.in_console to allow it). Auto-post reacts to model saves through Eloquent events, so mass update() queries and saveQuietly() do not auto-post.

Run a queue worker; the job is dispatched after the database transaction commits, is unique per model while it waits, and retries 3 times when LinkedIn is busy or unreachable (429, 5xx, network). A request LinkedIn refuses outright (401, 403, 422 …) or an invalid post fails at once, without retries. With the sync queue the share runs inside the request that saved the model, so a LinkedIn failure surfaces there — use a real queue.

URLs are normalised for you: a non-ASCII path such as /blog/çalışma is percent-encoded and an internationalised host (şirket.com.tr) is converted to punycode when ext-intl is installed. LinkPost::make() throws InvalidArgumentException for a relative or non-http(s) URL.

Write the commentary as plain text: characters LinkedIn reserves for mentions and hashtags (# @ [ ] ( ) _ * …) are escaped for you, so they appear exactly as written. LinkedIn does not fetch the link's own preview; the title and description you set are what it shows.

5. Share on demand

share() throws NotConnected, NotShareable (not live), LinkedInRequestFailed (LinkedIn's message) or InvalidArgumentException (your toLinkedInPost() built an invalid URL). A post LinkedIn accepted without returning its id is recorded as posted with a null post_urn.

A complete controller action with every outcome handled:

LinkedInRequestFailed carries the HTTP status ($e->status, 0 for a network error), isUnauthorized() (401) and isPermanent() (a 4xx other than 429: retrying cannot help).

Share only once, from your own code:

Read the history:

Column Values
status posted, failed
trigger auto (auto-post), manual (button, endpoint, share()), console (linkedin:share)
post_urn e.g. urn:li:share:7212345678901234567; open it at https://www.linkedin.com/feed/update/{post_urn}/
error LinkedIn's message for a failed automatic share

6. Ready-made admin UI

Pick the theme your admin uses (LINKEDIN_UI_THEME=tailwind or bootstrap) and drop in the components. Building your own UI, or none at all? See headless mode.

Settings page: the connection card

Not connected, it shows Connect LinkedIn. Connected, it shows the member, when the token was issued and until when it is valid, a status badge (amber in the last 14 days, red when expired or revoked), Reconnect and Disconnect, plus the result of the last connect. People the manage-linkedin-autopost gate refuses see the status only, without the buttons.

A full settings page (Bootstrap layout shown; the Tailwind one is the same with your classes):

Remember to set routes.after_connect to this page's URL so the browser returns here after LinkedIn.

Lists and edit pages: the share button

Livewire: <livewire:linkedin-autopost.share-button :model="$article" :wire:key="'li-'.$article->id" /> (listen for the linkedin-autopost-shared browser event to react).

Props: label, size (sm / md), :confirm="false" to skip the confirmation. The button reads Share again once something was posted, shows the last date as its tooltip, and is disabled, with the reason, while the manage-linkedin-autopost gate refuses the viewer, LinkedIn is not connected or the model is not live. It posts to a signed URL, so it works without a morph map.

More share-button samples:

Things to know:

The flash message is stored in the session under linkedin-autopost.flash as ['type' => 'success'|'error', 'message' => '…'], if you prefer to render it in your own layout:

Tailwind

Tailwind only generates classes it finds. Add the package views to your sources:

Bootstrap 5.3 needs nothing extra.

Restyling, or a theme of your own

Edit resources/views/vendor/linkedin-autopost/{tailwind,bootstrap}/, or copy one folder to e.g. daisyui/ and set LINKEDIN_UI_THEME=daisyui. A theme is three views: connection, share-button, flash.

7. Worked example: your own modules

php artisan vendor:publish --tag=linkedin-autopost-stubs copies a commented example model to stubs/linkedin-autopost/ShareableModel.stub. Two typical modules:

Give each module a morph-map alias so the share endpoint, linkedin:share and the history use short, stable names instead of class names:

Scheduled publishing

Auto-post reacts to saves. A model that becomes live only because time passes (a future published_at, an opening date) is never saved at that moment, so nothing posts it. Schedule a small job that queues what has become live and was not posted yet:

The same idea as an invokable class, when you prefer it out of routes/console.php:

The queued job is unique per model and checks again that nothing was posted, so overlapping runs cannot post twice.

React when a post goes out, e.g. to log the link:

8. Build your admin UI on the endpoints

Method URL Returns
GET /linkedin/redirect Redirect to LinkedIn's consent screen
GET /linkedin/callback Redirect to after_connect with ?linkedin=…
GET /linkedin/connection {"data": {connected, status, name, email, picture, author_urn, scopes, connected_at, expires_at, days_left}}
DELETE /linkedin/connection Revokes and forgets the token; {"message", "revoked", "data"}
POST /linkedin/share/{type}/{id} 201 {"data": {post_urn, posted_at}} (post_urn is null when LinkedIn accepted the post without returning its id), 422 not connected / not live / invalid post URL, 502 LinkedIn refused, 404 unknown

Every endpoint answers 403 to people the manage-linkedin-autopost gate refuses. Route names: linkedin-autopost.redirect, .callback, .connection.show, .connection.destroy, .share, .share.signed.

status is active, expired, revoked or unknown (LinkedIn unreachable). The share endpoint accepts morph-map aliases only:

Without a morph map, call LinkedIn::share() from your own controller.

Sample responses:

The routes use the web middleware, so a browser client sends the session cookie and the CSRF token. With fetch:

With axios (Laravel's default bootstrap.js sets the CSRF header for you):

A Vue/Inertia or React admin uses the same three calls; for a token-authenticated SPA or mobile client, see headless mode to put the routes behind auth:sanctum.

9. Events

Event When
Connected After a successful connect ($event->connection).
Disconnected After disconnect ($event->revoked).
Shared After every successful post ($event->shareable, $event->post).
ShareFailed An automatic share gave up after its last attempt ($event->shareableType, $event->shareableId, $event->exception).
TokenExpiringSoon From linkedin:check-token ($event->connection, $event->daysLeft).

All live in Siberfx\LinkedInAutopost\Events.

More listeners:

Use updateQuietly() in a Shared listener: a normal save() fires the model's events again (it will not post twice, but there is no reason to run the check).

10. Commands and the 60-day token

Command Does
linkedin:status Table of the member, author URN, status, scopes, issue and expiry dates, days left.
linkedin:share {model} {id} Shares now (trigger = console). model is a morph alias or a class name. Refuses an already posted model unless --force.
linkedin:disconnect Revokes and forgets the token; asks first unless --force.
linkedin:check-token Fires TokenExpiringSoon when fewer than --days (default expiry_warning_days) remain or the token is expired/revoked.
linkedin:test-notification Sends a test message to the enabled Telegram/Slack/email/custom channels.
linkedin:install See Install.

LinkedIn member tokens last 60 days, and self-serve apps get no refresh token — someone has to click Connect again before it runs out. Schedule the check and listen for TokenExpiringSoon:

With notifications switched on, that daily check also sends the expiry warning to Telegram, Slack or email — no listener needed.

11. Telegram, Slack and email notifications

Get a message when a post goes out, when an automatic share gives up, and when the token is about to expire. Everything is off by default; each switch is in the notifications section of the config:

Turn on any combination of channels; each one gets every message. Choose which events send a message:

Then check the settings:

The test command always sends at once (not queued), even while LINKEDIN_NOTIFY is off, so you can check the settings before switching notifications on.

Setting up Telegram

  1. Talk to @BotFather, send /newbot, and copy the token (123456789:AAE…) into LINKEDIN_TELEGRAM_BOT_TOKEN.
  2. Add the bot to the group or channel that should receive the messages (in a channel, as an admin allowed to post). For messages to yourself, open the bot and press Start.
  3. Find the chat id: send any message in that chat, then open https://api.telegram.org/bot<token>/getUpdates and read message.chat.id — negative for groups (-100… for supergroups and channels). A public channel can also use @channelname.
  4. Groups with topics: put the topic's message_thread_id in LINKEDIN_TELEGRAM_THREAD_ID.

Setting up Slack

  1. Create an app at https://api.slack.com/apps → From scratch, pick the workspace.
  2. Incoming Webhooks → switch on → Add New Webhook to Workspace → choose the channel.
  3. Copy the URL (https://hooks.slack.com/services/T…/B…/…) into LINKEDIN_SLACK_WEBHOOK_URL.

Setting up email

Email goes through your app's own mailer, so configure config/mail.php (MAIL_MAILER, MAIL_FROM_ADDRESS, …) as for any Laravel mail; the package adds nothing of its own.

The email is the Siberfx\LinkedInAutopost\Notifications\NotificationMail mailable, so your tests can assert it:

Need a different recipient list per environment, or your own template? Leave the mail channel off and register your own mail channel (see A channel of your own).

What the messages look like

The headline is bold (HTML in Telegram and email, mrkdwn in Slack); titles are escaped, so <, > and & in your content cannot break the formatting. In email the headline is also the subject.

Common setups

The event switches apply to every channel. To route events to different channels, keep only Slack on here and send the failure email from a ShareFailed listener (section 9).

Queued or immediate

Notifications are queued by default (queued = true, LINKEDIN_NOTIFY_QUEUED):

queued = true (default) queued = false
How One SendNotification job per channel Sent at once, one channel after the other
Where Your queue worker Inside the request, job or command that shared
Retries tries times with backoff per channel None
A failure Retried, then in failed_jobs Reported to your exception handler
Needs A running queue worker (or QUEUE_CONNECTION=sync) Nothing

Either way a failing channel never fails the share or blocks the other channels. Auto-posts already run in a queued job, so with queued = false their notifications are sent from that job; a manual share from the button waits for Telegram, Slack and the mail server before it answers (each HTTP call is limited by http.timeout). On the sync queue connection the queued jobs also run at once, and errors are reported the same way.

The token-expiry message needs the scheduled linkedin:check-token from the previous section. Turning a switch off also drops messages that are already waiting in the queue.

A channel of your own

To add a channel (Teams, Discord, a webhook of your own…), implement Siberfx\LinkedInAutopost\Contracts\NotificationChannel and list it; the container builds it with its config array as $config:

Message has level (success, error, warning, info), headline, lines (a list of plain strings) and emoji(). Throw NotificationFailed to have the job retry. Your channel gets the master switch, per-event switches, queueing, retries and linkedin:test-notification for free.

Without the built-in channels

Prefer Laravel notifications, Mattermost, or a database log? Leave LINKEDIN_NOTIFY off and listen to the events yourself:

12. Headless: no UI

Set LINKEDIN_UI=false (or install with php artisan linkedin:install --headless) to use the package without any of its UI:

Connect by sending an admin to route('linkedin-autopost.redirect'); the callback returns to routes.after_connect with ?linkedin=connected|cancelled|error|…. To drop the routes as well, set routes.enabled to false and drive the OAuth flow yourself through LinkedIn::authorizationUrl() and LinkedIn::completeConnection().

Three common headless setups:

a) Backend only: auto-post, nothing else

No admin screens at all; someone connects once, then models post themselves.

Connect from the browser by visiting /linkedin/redirect while logged in as someone the gate allows, and check it from the terminal:

b) SPA or mobile app with Sanctum

Keep the package routes, but behind token auth and under your API prefix:

The OAuth redirect and callback routes need a session to keep the state value between them, so an api-only middleware stack cannot start the connection. Either register the redirect URL of an extra web route for connecting (option c), or keep a small web page for the one-off Connect click.

c) Your own routes and controllers

Turn the package routes off and write the few you need. The redirect URI must be set explicitly, because the package callback route no longer exists:

completeConnection() stores the token, fires Connected and returns the Connection; disconnect() revokes the token at LinkedIn, forgets it and returns whether LinkedIn confirmed.

13. Keeping the token somewhere else

The token is stored encrypted (your APP_KEY) in linkedin_connections. Rotating APP_KEY makes it unreadable; the package then reports not connected and you connect again. To store it elsewhere, implement Siberfx\LinkedInAutopost\Contracts\TokenStore and set token_store in the config. The package reads the store once per request (or queued job) and remembers the result; connect and disconnect through the package so it stays in step.

For example, in an existing key-value settings table:

Always encrypt the token: it can post as the connected member.

The connection status is cached as plain data (never objects), so it works with Laravel 13's default cache.serializable_classes = false.

14. LinkedIn API versions

Posts are sent with LinkedIn-Version: 202609. LinkedIn retires each version about a year after release; when they announce a sunset, set LINKEDIN_API_VERSION to a newer YYYYMM.

15. Testing your app

Your own tests never need to reach LinkedIn, Telegram or Slack. Put a connection in the token store and fake the HTTP calls:

Tests run in the console, so set autopost.in_console to true in tests that check auto-posting. Define the manage-linkedin-autopost gate (or act as a user it allows) in tests that call the routes.

16. Configuration reference

Key Env Default
client_id, client_secret LINKEDIN_CLIENT_ID, LINKEDIN_CLIENT_SECRET — Your LinkedIn app's credentials.
redirect_uri LINKEDIN_REDIRECT_URI the callback route Must match a registered redirect URL exactly.
scopes openid profile email w_member_social
api_version LINKEDIN_API_VERSION 202609 LinkedIn-Version header.
visibility LINKEDIN_VISIBILITY PUBLIC or CONNECTIONS.
routes.enabled true false registers no routes.
routes.prefix / routes.name linkedin / linkedin-autopost.
routes.middleware web, auth, can:manage-linkedin-autopost
routes.after_connect / Where the OAuth callback returns.
autopost.enabled LINKEDIN_AUTOPOST false
autopost.in_console false Auto-post from seeders and commands.
autopost.queue_connection / autopost.queue null
ui.enabled LINKEDIN_UI true false = headless.
ui.theme LINKEDIN_UI_THEME tailwind bootstrap or a published folder.
notifications.enabled LINKEDIN_NOTIFY false Master switch.
notifications.events.* all true shared, failed, token_expiring.
notifications.queue_connection / .queue null
notifications.queued LINKEDIN_NOTIFY_QUEUED true false sends at once, without retries.
notifications.tries / .backoff 3 / [30, 120] Queued mode only.
notifications.channels.telegram.* LINKEDIN_NOTIFY_TELEGRAM, LINKEDIN_TELEGRAM_BOT_TOKEN, LINKEDIN_TELEGRAM_CHAT_ID, LINKEDIN_TELEGRAM_THREAD_ID off
notifications.channels.slack.* LINKEDIN_NOTIFY_SLACK, LINKEDIN_SLACK_WEBHOOK_URL off
notifications.channels.mail.* LINKEDIN_NOTIFY_MAIL, LINKEDIN_NOTIFY_MAIL_TO, LINKEDIN_NOTIFY_MAILER off Uses config/mail.php.
token_store DatabaseTokenStore Any TokenStore class.
http.timeout 20 Seconds, for LinkedIn, Telegram and Slack calls.
job.tries / job.backoff 3 / [60, 300] The auto-post job.
status_cache_ttl 300 Seconds the connection status is cached.
expiry_warning_days 7 Default for linkedin:check-token.

17. Troubleshooting

Symptom Cause and fix
403 on every LinkedIn route The manage-linkedin-autopost gate is not defined, or refuses this user. Define it (section 2).
redirect_uri does not match on LinkedIn Register the exact URL from php artisan route:list --name=linkedin-autopost.callback, or set LINKEDIN_REDIRECT_URI.
?linkedin=error&reason=Scope "profile" is not authorized Add Sign In with LinkedIn using OpenID Connect to the app's products.
?linkedin=invalid_state The session was lost between redirect and callback: same domain for both, SESSION_DOMAIN, SESSION_SECURE_COOKIE on http.
Publishing does not post LINKEDIN_AUTOPOST=true? A queue worker running? Saved through Eloquent (not a mass update())? Was it already live before this save? Saved from a command or seeder (autopost.in_console)?
Status expired / share answers 401 The 60-day token ran out or was revoked: connect again. Schedule linkedin:check-token.
Status unknown LinkedIn could not be reached for the status check; sharing may still work.
Share button disabled Its tooltip/reason says why: not connected, not live, or the gate refuses you.
Signed share URL answers 403 behind a proxy Configure TrustProxies so the scheme and host match.
Tailwind components unstyled Add the @source line (section 6).
No Telegram/Slack/email message php artisan linkedin:test-notification shows which switch or setting is missing; check failed_jobs for SendNotification, and that a queue worker runs (or set LINKEDIN_NOTIFY_QUEUED=false).
Email not arriving Test the app's mail first (MAIL_MAILER, MAIL_FROM_ADDRESS); LINKEDIN_NOTIFY_MAIL_TO must hold a valid address.
Telegram chat not found The bot is not in the chat, or the chat id is wrong (groups are negative).
Not connected after rotating APP_KEY The stored token can no longer be decrypted: connect again.

Testing

License

MIT. See LICENSE.


All versions of linkedin-autopost with dependencies

PHP Build Version
Package Version
Requires php Version ^8.4
guzzlehttp/guzzle Version ^7.8
illuminate/auth Version ^12.0|^13.0
illuminate/bus Version ^12.0|^13.0
illuminate/cache Version ^12.0|^13.0
illuminate/console Version ^12.0|^13.0
illuminate/contracts Version ^12.0|^13.0
illuminate/database Version ^12.0|^13.0
illuminate/encryption Version ^12.0|^13.0
illuminate/events Version ^12.0|^13.0
illuminate/filesystem Version ^12.0|^13.0
illuminate/http Version ^12.0|^13.0
illuminate/log Version ^12.0|^13.0
illuminate/mail Version ^12.0|^13.0
illuminate/queue Version ^12.0|^13.0
illuminate/routing Version ^12.0|^13.0
illuminate/session Version ^12.0|^13.0
illuminate/support Version ^12.0|^13.0
illuminate/view Version ^12.0|^13.0
Composer command for our command line client (download client) This client runs in each environment. You don't need a specific PHP version etc. The first 20 API calls are free. Standard composer command

The package siberfx/linkedin-autopost contains the following files

Loading the files please wait ...