---
url: https://docs.sunlicense.com/how-to/laravel-app.md
description: >-
  How to add license keys to a self-hosted Laravel app you sell (for example on
  CodeCanyon) - a middleware that checks the buyer's key with the SunLicense PHP
  SDK, cached for hours, a clear page on a rejection, per-install limits and
  Envato purchase codes.
---

# How to add license keys to a Laravel app

**Short answer:** `composer require sunlicense/sunlicense`, put the buyer's key in their `.env`, and check it in a
middleware, cached for 6 hours so requests stay fast. When SunLicense says no, show a "license problem" page instead
of the app; when it can't be reached, keep serving and check later.

```php
// app/Http/Middleware/EnsureLicensed.php
use Illuminate\Support\Facades\Cache;
use SunLicense\{SunLicenseClient, SunLicenseException};

public function handle($request, Closure $next)
{
    $state = Cache::remember('myapp.license', now()->addHours(6), function () {
        $key = trim((string) config('myapp.license_key'));
        if ($key === '') return 'NOT_CONFIGURED';
        try {
            $r = (new SunLicenseClient('https://api.sunlicense.com', config('myapp.api_key')))
                ->validate($key, ['host' => hash('sha256', config('app.url'))]);
            return $r->getStatusCode() === 400 ? ($r->toArray()['code'] ?? 'INVALID') : 'OK';  // EXPIRED, REVOKED…
        } catch (SunLicenseException $e) {
            return 'OK';   // unreachable: keep serving, check again when the cache expires
        }
    });
    return $state === 'OK' ? $next($request) : response()->view('license-problem', ['code' => $state], 402);
}
```

Register it on your app's routes (not on the page where the buyer enters the key), and add
`'license_key' => env('MYAPP_LICENSE_KEY')` and `'api_key' => …` to `config/myapp.php`.

**Or let your AI agent do it:** paste the [setup prompt](https://sunlicense.com/prompt) into Claude Code, Cursor or
Codex in your project.

Free plan: 3 products, 100 licenses, no credit card. [Create a free account](https://sunlicense.com/start?ref=docs-howto-laravel).

## Step by step

1. **In the dashboard:** a product, a policy where a machine is an **install** (for example "Regular" with *1 machine*,
   "Extended" with *5*) and an **API key**, which can only validate.
2. **Install:** `composer require sunlicense/sunlicense` (PHP 8.0+, cURL when available).
3. **One install = one machine.** Send a hash of `APP_URL` as `host`, so moving servers keeps the slot and a second
   domain counts as a second install.
4. **Your API key** is in the code you ship (`config/myapp.php`), since buyers run it themselves. It can only validate,
   so that's safe; keep it out of your public repository and inject it when you build the release zip.
5. **The buyer's key** goes in `.env` (`MYAPP_LICENSE_KEY`) or an installer step that writes it. When they change it,
   `Cache::forget('myapp.license')`.
6. **Results:** 200 serves the app, 400 renders `license-problem` with the [reason](/guide/concepts#validation-result-codes)
   and how to fix it (mask the key), anything else serves and checks after the cache expires. A scheduled command
   (`php artisan myapp:license-check`, hourly) can refresh the cache so no visitor waits for it.
7. **Updates:** check the license in your update endpoint too, so a revoked key stops getting new versions.

## Honest limits

PHP source is readable, and SunLicense's [obfuscation](/guide/obfuscation) doesn't cover PHP. A license key controls
what you provide (updates, support, hosted services) and lets you switch off a leaked or refunded copy that keeps
checking; someone determined can delete the middleware. Keep a piece that matters on your side, such as a hosted API
the app calls with its key, if that's a concern.

## Selling it

[Envato](/integrations/envato) (CodeCanyon purchase codes become SunLicense licenses), [Lemon Squeezy](/integrations/lemon-squeezy),
[Gumroad](/integrations/gumroad), [Payhip](/integrations/payhip) and [Stripe](/integrations/stripe) issue a key per
purchase and revoke it on a refund. Selling a WordPress plugin instead? See [WordPress](/how-to/wordpress-plugin).
