oi-lab/oi-laravel-sign is a Laravel package for online e-signature for any laravel model.
It currently has 0 GitHub stars and 337 downloads on Packagist (latest version v1.1.0).
Install it with composer require oi-lab/oi-laravel-sign.
Discover more Laravel packages by oi-lab
or browse all Laravel packages to compare alternatives.
Last updated
Online e-signature for any model of a Laravel application — a quote, a contract, a purchase order. The signer opens a link without an account, reads the document, declares their identity, consents, and confirms with a one-time code sent by e-mail or SMS. The package then appends a "Signature certificate" page to the PDF, seals it with the server's certificate, and keeps a timestamped evidence trail. No third-party provider is involved: it is a simple electronic signature in the sense of eIDAS and article 1367 of the French Civil Code, enough to accept a quote.
Signable contract and use the HasSignatureRequests trait.owner morph ties each request to its organization.RequestSignature ──► document.pdf + attachments copied, SHA-256 stored, link mailed
│
Signer opens /sign/{token} ──► viewed ──► identity + consent ──► code sent
│
Code verified ──► fingerprint and version checked ──► certificate page + seal ──► signed.pdf
A request is pending, then viewed, until it is signed, declined by the signer, cancelled by the
issuer or expired (past its date, document modified, or model no longer signable).
composer require oi-lab/oi-laravel-sign
php artisan vendor:publish --tag=oi-laravel-sign-config
php artisan migrate
Publish the screens, the certificate page and the translations to customize them:
php artisan vendor:publish --tag=oi-laravel-sign-stubs
The default pades sealer needs a certificate. For development, create a self-signed one:
php artisan sign:make-certificate
and copy the two printed lines into .env. In production, use a seal certificate issued by a trusted
authority (PEM + key, or a .p12 bundle with OI_SIGN_CERTIFICATE_PASSWORD).
// config/oi-laravel-sign.php
return [
'route' => [
'enabled' => env('OI_SIGN_ROUTES', true), // public screens under /sign/{token}
'prefix' => 'sign',
'middleware' => ['web', 'throttle:oi-sign'],
'rate_limit' => env('OI_SIGN_RATE_LIMIT', 30), // requests per minute and IP
],
'disk' => env('OI_SIGN_DISK', env('FILESYSTEM_DISK', 'local')),
'retention_years' => 10,
'expires_in_days' => env('OI_SIGN_EXPIRES_IN_DAYS', 30),
'consent_text' => 'Bon pour accord',
'otp' => [
'channel' => env('OI_SIGN_OTP_CHANNEL', 'mail'), // mail | sms
'length' => 6, 'ttl_minutes' => 10, 'max_attempts' => 5, 'lockout_minutes' => 15,
'sms' => ['via' => env('OI_SIGN_SMS_CHANNEL', 'vonage')],
],
'sealer' => [
'driver' => env('OI_SIGN_SEALER', 'pades'), // pades | none | a PdfSealer class
'certificate' => env('OI_SIGN_CERTIFICATE_PATH'),
'private_key' => env('OI_SIGN_PRIVATE_KEY_PATH'),
],
'expiration' => ['enabled' => true], // schedules sign:expire hourly
];
use OiLab\OiLaravelSign\Concerns\HasSignatureRequests;
use OiLab\OiLaravelSign\Contracts\Signable;
use OiLab\OiLaravelSign\Data\BrandingData;
use OiLab\OiLaravelSign\Data\SignableAttachmentData;
class Quote extends Model implements Signable
{
use HasSignatureRequests;
public function signableTitle(): string { return "Quote {$this->number}"; }
public function signablePdf(): string { return app(QuotePdf::class)->render($this); }
public function signableAttachments(): array
{
return [new SignableAttachmentData(name: 'Terms.pdf', contents: $this->organization->termsPdf())];
}
public function canBeSigned(): bool { return $this->state === 'validated'; }
public function signableVersion(): ?string { return (string) $this->revision; } // content only, not updated_at
public function signableBranding(): BrandingData
{
return new BrandingData(name: $this->organization->name, logo_url: $this->organization->logo_url, color: '#2563eb');
}
}
use OiLab\OiLaravelSign\Data\SignerData;
$request = $quote->requestSignature(
new SignerData(name: 'Jeanne Martin', email: '[email protected]', phone: '+33612345678'),
[
'owner' => $quote->organization,
'otp_channel' => 'sms',
'consent_text' => 'Bon pour accord',
'notify' => false, // send the link in your own e-mail instead
],
);
$request->sign_url; // only available now: the token is stored hashed
Creating a request cancels the open ones of the same model ('supersede' => false to keep them).
$quote->latestSignature()?->status; // SignatureStatus
$quote->isSigned();
use OiLab\OiLaravelSign\Actions\{CancelSignature, ResendSignature, VerifySignedDocument};
ResendSignature::handle($signatureRequest); // new link, previous one retired
CancelSignature::handle($signatureRequest, 'Wrong price');
VerifySignedDocument::handle($signatureRequest)->valid;
$signatureRequest->evidence()->toArray(); // the full evidence file
| Event | When |
|-------|------|
| SignatureRequested | A request was created |
| SignatureViewed | The signer opened the link for the first time |
| SignatureSigned | The document was signed and the signed PDF stored |
| SignatureDeclined | The signer declined (decline_reason) |
| SignatureCancelled | The issuer cancelled the request |
| SignatureExpired | Past its date, document modified or no longer signable ($reason) |
Every event carries $signatureRequest. Listen to them to update your own model:
Event::listen(SignatureSigned::class, fn ($event) => $event->signatureRequest->signable->markAsWon());
php artisan sign:expire # expire stale requests (scheduled hourly)
php artisan sign:verify {uuid} [--file=] # check the stored (or a given) signed PDF
php artisan sign:evidence {uuid} [--output=] # export the evidence file as JSON
php artisan sign:make-certificate # self-signed seal certificate for development
This package ships an AI skill teaching assistants how to use it. Install it in your project with:
php artisan oi:skills oilab-laravel-sign --project
composer test
Contributions are welcome! Please feel free to submit a Pull Request.
When contributing:
vendor/bin/pestThe MIT License (MIT). Please see the License File for more information.
Olivier Lacombe - Creator and maintainer
Olivier is a Product & Technology Director based in Montpellier, France, with over 20 years of experience innovating in UX/UI and emerging technologies. He specializes in guiding enterprises toward cutting-edge digital solutions, combining user-centered design with continuous optimization and artificial intelligence integration.
Projects & Resources:
For support, please open an issue on the GitHub repository.
Get paid by clients abroad
Wise gives you local bank details in several currencies and converts at the mid-market rate. Useful when your invoices are not in your own currency.
Try WiseNew Laravel packages worth a look, once a month. No spam, one-click unsubscribe.