Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Filament Otp Input Laravel Package

murillo-cardoso-by-hasan-ahani/filament-otp-input

A Filament form component for entering one-time passwords and PIN codes with separate digit boxes. Add a clean, customizable OTP input field to admin panels and forms, with smooth keyboard navigation and paste support.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal steps

  1. Install into an existing Filament v3 application:
    composer require murillo-cardoso-by-hasan-ahani/filament-otp-input
    
  2. The package is a Filament form component, not a plugin with its own panel bootstrapping. In most cases you do not need to register anything in a PanelProvider; Filament auto-discovers form field classes and assets via the package's service provider. If a FilamentOtpInputServiceProvider is present, verify it extends the package's own provider and that filament:assets was run after install.
  3. Confirm the assets are built:
    php artisan filament:assets
    php artisan vendor:publish --tag=filament-otp-input-assets   # only if the package ships publishable assets
    

Where to look first

Given the 0-star / 0.1 score and a repo name (Unesc-PMP/filament-otp-input-component) that doesn't match the Packagist vendor, treat the source as the spec. Before writing any code:

  • src/ — find the field class (likely OtpInput.php or similar) and read its public setters. That is your real API; the README may be stale or absent.
  • resources/views/ — the Blade template shows exactly how state is bound and what JS is embedded.
  • composer.json — check the require block for the Filament constraint (^3.0 vs ^4.0) and PHP version. This is the single most likely source of install failures.
  • CHANGELOG.md / tags — last release was 2024-11-12, so align with the Filament major current at that time (v3).

First use case

The canonical integration is a verification form: a second step after login/registration where the user types a 6-digit code. In a Filament Login/Registration page, add a custom field to the form schema and validate it against a stored code:

use MurilloCardoso\FilamentOtpInput\Forms\Components\OtpInput;

public function form(Form $form): Form
{
    return $form->schema([
        OtpInput::make('code')
            ->length(6)
            ->numeric()
            ->required()
            ->rule('digits:6'),
    ]);
}

Confirm the actual namespace and method names against src/ before committing — the vendor path in the class name will differ from the repo owner.

Implementation Patterns

1. Verification-step pattern (most common)

Keep the OTP field on a separate Filament page or form step rather than on the credentials form. This keeps state small and makes auto-submit sane:

TextInput::make('phone')->tel()->required(),
OtpInput::make('code')
    ->length(6)
    ->required()
    ->afterStateUpdated(fn ($state, Set $set) => strlen((string) $state) === 6
        ? null // trigger your custom action
        : null),

Because Filament fields are Livewire-bound, every keystroke round-trips unless the package debounces. Prefer wire:model.debounce defaults already in the component, and only react when length is complete.

2. Storing the code

Do not add the OTP to the same $this->data array as the rest of the form schema if that array is later mass-assigned to a Model. Store it in a separate attribute:

protected array $data = [];
public string $otpCode = '';

…and map $this->otpCode into the field with ->statePath('otpCode') (or pass ->livewireModel() if the package exposes it). This avoids leaking verification codes into create()/update().

3. Validation and expiry

Treat the OTP field like any other Filament field — attach Laravel rules, not package-specific ones:

OtpInput::make('code')
    ->required()
    ->rule('digits:6')
    ->rule(fn () => function (string $attribute, $value, Closure $fail) {
        if (! Hash::check($value, $this->user->otp_hash)) {
            $fail('The code is invalid.');
        }
        if ($this->user->otp_expires_at->isPast()) {
            $fail('The code has expired.');
        }
    });

Keep rate limiting and attempt counting in a dedicated action/resend button rather than in validation — Filament re-runs validation on every Livewire update.

4. Resend / countdown workflow

Pair the field with a Filament\Forms\Components\Actions\Action (or a custom Action on the form) to resend the code. A Cache::put("otp:resend:{$user->id}", true, 60) guard combined with a polling Placeholder showing remaining seconds is the standard, dependency-free approach:

Action::make('resend')
    ->requiresConfirmation()
    ->visible(fn () => ! Cache::has("otp:resend:{$this->user->id}"))
    ->action(fn () => $this->sendOtp());

5. Integration with existing notification flows

The OTP component is input-only — it does not generate or send codes. Reuse your existing User::sendOtp() (Fortify, Jetstream, or a custom Notification) and have the Filament page call it. This keeps the package swappable: if you later replace it with a plain TextInput, the backend doesn't change.

6. Testing

Livewire component tests still work; the field is just HTML inputs bound to wire:model:

Livewire::test(VerifyPhone::class)
    ->fillForm(['code' => '123456'])
    ->call('verify')
    ->assertHasNoFormErrors();

If the package splits the code into per-digit inputs (likely), fillForm with a single string still works because the field's getState() reassembles it — but assert on the resulting state to be sure, since a per-digit implementation may require ['1','2','3','4','5','6'].

Gotchas and Tips

Package maturity risk is the headline issue. Zero stars, zero dependents, one release in Nov 2024, and a repo owner (Unesc-PMP) that doesn't match the Packagist vendor namespace (murillo-cardoso-by-hasan-ahani) are all red flags. Pin the exact version ("murillo-...": "0.1.*"), commit the lockfile, and read the field class before designing around it. Budget a fork option — the component is small enough that copying src/ and resources/ into app/Filament/Forms/Components/ is viable and removes the dependency entirely.

Filament major mismatch. A November 2024 release targets Filament v3. Filament v4 renamed several field internals (Field lifecycle, getState/setState signatures, asset registration). If you're on v4, expect a Class not found or a silently non-rendering field. Check composer.json's filament/support constraint before anything else.

State shape is the #1 debugging trap. OTP widgets usually render N separate <input maxlength="1"> elements. Depending on the implementation the Livewire property is either:

  • a single string "123456", or
  • an array ["1","2","3","4","5","6"].

If validation fails with "must be 6 digits" but the field visually looks full, dd($this->data['code']) first. Cast/normalize in ->dehydrateStateUsing(fn ($state) => is_array($state) ? implode('', $state) : $state) or the reverse, and keep the normalization in one place.

Paste is not free. Per-input OTP widgets often break on paste (browser fills only the first box) and on mobile numeric keyboards (inputmode="numeric" must be set — the package may not do this). Test on a real iOS/Android keyboard; a one-line JS inputmode and paste handler is a common local patch.

Auto-submit fires too eagerly. If the package auto-triggers on full length and you also have a "Verify" button, you get double submissions. Guard the action with a $this->verifying flag and RateLimiter::attempt() so a stuck network retry doesn't burn through your 5-attempt limit.

Asset registration silently fails. If the field renders as a plain text input with no styling or JS behavior, run php artisan filament:assets and hard-refresh. In production, php artisan filament:cache-components + view:cache will not pick up published assets unless they were published at build time — include filament:assets in your deploy script.

afterStateUpdated runs per keystroke. Anything you hook there (DB lookup, notification, hash check) executes up to six times per code. Debounce via the field's ->debounce(300) if available, or move the logic to a submit action.

Extension points are thin. With no documented API, your safest extension is the published Blade view: php artisan vendor:publish --tag=filament-otp-input-views (tag name likely *-views; check the service provider's publishes() call). Once the view is in resources/views/vendor/..., you can add inputmode, autocomplete=one-time-code, and paste handling without forking the PHP class — this survives minor package updates.

Accessibility and autofill. autocomplete="one-time-code" is what triggers iOS/Android SMS autofill. If the packaged view lacks it, add it via the published view. Screen readers also need a single aria-label on the wrapper group, not six unlabeled boxes.

Fallback plan. If the package proves unmaintained mid-project, swap it for a plain TextInput::make('code')->length(6)->tel()->autocomplete('one-time-code') plus a small Alpine mask in a custom Blade view. The migration cost is one form schema line — keep your OTP generation/verification logic entirely outside the component so this swap stays trivial.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
gmostafa/php-graphql-client
xeviant/paystack
snowsoft/faker
meditationsteps/blogetc
animalyoung/laravel-influxdb
onix-solutions/sms-telera
lonely-lights/prosetta
oxygen/data
oxygen/core
jengo/base
xiloryx/log-collector
a21ns1g4ts/filament-stripe
la-php/laravel-admin
artisan-build/community-prompts
vleroy/smart
ayles-software/laravel-australian-public-holidays
cboxdk/laravel-id-spatie
cboxdk/laravel-id-billing
cboxdk/laravel-id-billing-bridge
simple-as-fuck/php-performance-log