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

Otp Input Bundle Laravel Package

nowo-tech/otp-input-bundle

nowo-tech/otp-input-bundle adds a configurable one-time-password input to Symfony applications, rendering individual code fields with paste, keyboard navigation, and form validation support. It simplifies building OTP verification and two-factor authentication screens.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and register

composer require nowo-tech/otp-input-bundle

Check whether a Symfony Flex recipe was applied (it will have added the bundle to config/bundles.php and possibly a config/packages/*.yaml). If not, register it manually:

// config/bundles.php
Nowo\OTPInputBundle\OTPInputBundle::class => ['all' => true],

Then confirm the service/alias it exposes:

php bin/console debug:container --show-private | grep -i otp

Where to look first

  • src/ — the FormType class. This is the real contract: read configureOptions() and buildView() before anything else. The type name string you pass to ->add() lives here.
  • src/Resources/views/ — the Twig block(s) that split the single field into multiple inputs. Check whether the bundle ships a form theme you must register.
  • src/Resources/assets/ — the TS/JS that drives focus-advance, paste, and backspace. This is where behavior actually lives; the PHP side is mostly a value container.
  • README / CHANGELOG — for the exact type name, required options, and Symfony/PHP compatibility (PHP >=8.1, Symfony 6/7/8 per the repo's own targets).

First use case: a 2FA / verification-code form

The canonical day-to-day use is a "enter the 6-digit code we emailed you" screen. In your FormType:

$builder->add('code', OtpInputType::class, [
    'length' => 6,          // confirm the real option name in configureOptions()
    'label'  => 'Verification code',
]);

The user sees six boxes; your controller and constraints see one string. That single-value contract is the whole point of the bundle — everything else (constraints, DTO setters, persistence) behaves as if it were a plain text field.

Implementation Patterns

Treat it as a scalar field everywhere downstream

The multi-input UI is a view-layer illusion. Once submitted, $form->get('code')->getData() returns the concatenated string. So:

->add('code', OtpInputType::class, [
    'constraints' => [
        new NotBlank(),
        new Regex('/^\d{6}$/', message: 'Enter the 6-digit code.'),
    ],
])

Do not add CollectionType-style constraints or All validators — you are validating the joined value, not the digits independently. If you need per-character rules, do it in a custom constraint over the string.

DTO / entity mapping is unchanged

Because the value maps to one field, standard data_class mapping, PropertyAccessor hydration, and isEmpty() checks all work normally. If your OTP DTO stores a string $code, no adapter is needed. This is the main reason to prefer the bundle over hand-rolling six separate form fields (which would force an array-to-string transformation layer in your controller).

Form theme registration

If the widget does not render with the default Symfony theme, register the bundle's theme in config/packages/twig.yaml:

twig:
    form_themes:
        - '@OTPInput/form/otp_input_layout.html.twig'   # confirm exact path

Prefer form_row() in templates unless you need to control the wrapper — the bundle's block structure expects its own markup around the individual boxes.

Wire the front-end assets once

The interactive behavior (auto-advance, paste distribution, arrow-key navigation) comes from the bundled TS. Depending on how the package ships it, either:

  1. Run php bin/console assets:install public and let the bundle's assets land under public/bundles/..., then include them via {% block javascripts %} / your base template; or
  2. Import the module from your own Vite/Webpack entrypoint if the bundle expects a bundler.

Check src/Resources/assets for an exported initializer or a Stimulus controller — call it from your existing bootstrap rather than adding a second parallel bootstrap.

Test the concatenated value, not the boxes

In a functional/form test, submit a scalar string for the field name:

$client->submitForm('Verify', ['otp_form[code]' => '123456']);

That mirrors what the JS actually posts and keeps the test free of DOM assumptions.

Gotchas and Tips

The form must post a scalar, not an array. If the rendered input names resolve to otp_form[code][], your Regex/Length constraints will fail against an array. Inspect the rendered HTML for name="...code" with no [] suffix. This is the single most common integration failure for multi-input widgets.

No-JS degradation is worth verifying explicitly. Disable JavaScript and submit. You want either the plain single input (good) or a clear error — not six boxes that each post separately. A login-adjacent OTP field is exactly where users hit broken JS.

Paste is the highest-value interaction to smoke test. Real users paste codes from mail clients and authenticator apps. Confirm pasting a full code distributes across boxes and lands in the model value, and that the final single field is updated — not just the visual boxes.

Autofill / autocomplete="one-time-code". Mobile keyboard OTP autofill only triggers on a single input with the right autocomplete token. If the bundle renders six <input>s, check whether it sets autocomplete="one-time-code" on the first box or provides an option to. If not, this is a legitimate local patch point via form type options or a custom widget block — keep it backward compatible.

Accessibility and labeling. Each visual box needs a label or aria-label indicating its position, otherwise screen readers announce six anonymous inputs. Check the shipped Twig template; if labels are absent, override the block in your own theme rather than editing the bundle.

Confirm the option names before relying on them. With a very small package (0 stars, 0 dependents, no dependents to copy idioms from) the README may lag the code. configureOptions() is the source of truth for length, separator, type, and any attr forwarding. Passing an unknown option throws InvalidOptionsException with a clear message — use debug:form for a fast check:

php bin/console debug:form "Nowo\OTPInputBundle\Form\Type\OtpInputType"

Don't edit generated assets. If the bundle publishes compiled files under src/Resources/public/build (or your own build output), patch the TS source in src/Resources/assets and rebuild, or override at the app level. Edits to build output vanish on the next assets:install/release.

Pin the version and read the type class before production. With zero dependents you have no ecosystem signal for edge cases. Pin an exact constraint in composer.json, and skim the FormType plus the Twig block — together they are small and give you the full behavioral surface. Budget for owning fixes rather than waiting on upstream.

Extension points to prefer, in order: form type options → Twig form theme block override → a thin wrapper FormType of your own that sets defaults. Avoid reimplementing the JS; keep the bundle's asset pipeline as the single source of truth so paste/focus behavior stays consistent across your app.

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.
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
adachsoft/api-version-bundle