bkfdev/laravel-referrals
A Laravel package for adding referral programs to your app: generate referral codes and shareable links, track invited sign-ups, attribute conversions, and reward referrers. A simple way to build invite-based growth into any Laravel project.
Install the package and publish its assets before writing any referral logic:
composer require <vendor>/<package>
php artisan vendor:publish --provider="<Vendor>\<Package>\<Package>ServiceProvider"
php artisan migrate
Publishing typically yields a config file (cookie name, lifetime, link expiry, short-link driver) and a migration adding the referral_links table. Check the migration first — it is the fastest way to understand the package's data model, and in v1.1.1 you should confirm the short_link_id column is present and nullable.
Where to look first, in order:
ReferralLink model — the central object you will query and pass around.User model (commonly HasReferralLinks) — this defines the relationship API you will actually call.First use case: give every user a shareable referral URL.
$link = $user->createReferralLink(); // or ReferralLink::create([...])
return $link->url; // full referral URL
Capture the incoming referral on your landing route, stash it (session or cookie), and consume it at registration. That end-to-end path — create → capture → attribute — is the smallest thing worth building before you touch anything else.
Create links through the relationship, not the model directly. Using $user->referralLinks()->create([...]) guarantees the owner foreign key is set correctly and keeps your code working if the package adds owner scoping later.
Treat short_link_id as optional enrichment. The v1.1.1 addition links a referral record to a short link row. The pattern that holds up:
$link = $user->createReferralLink();
if ($shortLink = $link->shortLink) { // relation added in 1.1.1
return $shortLink->url; // short, shareable
}
return $link->url; // fallback: long-form URL
Always code the fallback branch. Existing rows created before v1.1.1 will have a null short_link_id, and so will rows created when short-link generation fails or is disabled.
Eager load the short link anywhere you render lists. A dashboard showing a user's links will otherwise fire one query per row the moment you access $link->shortLink. Use $user->referralLinks()->with('shortLink')->get().
Resolve the short link at render time, not at capture time. The referral token in your URL is what you attribute on; the short link is just a transport. Keep the redirect handler reading the referral token/id, so swapping or regenerating a short link never breaks attribution.
Attribution flow that stays simple:
referred_by (or whatever your schema calls it), then clear the cookie so a later signup on the same browser doesn't inherit it.Integrate through a service, not the controller. Wrap "create a referral link and, if configured, mint a short link" in a small ReferralLinkService. That is the single seam where the package's behaviour meets your business rules, and the place you'll edit if a future release changes the short-link contract.
Run the migration before deploying code that reads short_link_id. A nullable column added in a minor release is easy to miss; if your code touches $link->shortLink against an un-migrated database you get a "column not found" error deep in a view, which is a miserable way to discover it. Deploy migrations first, code second.
Existing referral links will not be backfilled. v1.1.1 adds the column but almost certainly doesn't retroactively shorten historical links. If users have already shared long URLs, that's fine — but don't advertise "short links" as universal until you either backfill via a queued job or regenerate short links on demand.
Short-link creation can fail independently. Third-party shorteners have rate limits, API keys, and outages. Because short_link_id is nullable, the intended design is graceful degradation. Catch failures, log them, persist the referral link anyway, and serve the long URL.
Don't expose the short link's numeric id. short_link_id is an internal foreign key. Keep it out of API resources and JSON payloads; expose the short link's URL, not its primary key.
Watch for silent config drift. If the package reads shortener credentials from its published config, a stale config file after an upgrade means your new setting is ignored — config('...') quietly returns the old default. When short links stop generating after a deploy, php artisan config:clear and diffing the published config against the package's current one is the first move.
Cookies and session both bite in edge cases. Referral cookies fail in cross-domain and strict-tracking-prevention contexts, and session-based capture fails across devices. Decide deliberately which you support and document the loss. A referral id echoed on the registration form as a hidden field is a useful third fallback.
Use the extension points before you fork. Link generation and URL formatting are usually overridable via config or a bound interface. If you need custom slugs, vanity tokens, or a different shortener, check for a binding or a make:/generate: config key before patching vendor code.
Test the null-short-link path explicitly. Write the feature test where the shortener is unavailable, because that is the branch that runs in production at the worst possible moment and the one least likely to be covered by the package's own suite.
How can I help you explore Laravel packages today?