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

Laravel Contact Laravel Package

jeroennoten/laravel-contact

A Laravel package that adds contact form functionality to your application, letting visitors send messages to site administrators. Includes configurable routes, validation, and views so you can drop a working contact form into any Laravel project quickly.

View on GitHub
Deep Wiki
Context7

Getting Started

Before you composer require anything, check the constraint. The last tagged release is from February 2017, which puts it in the Laravel 5.4/5.5 era. Run composer show jeroennoten/laravel-contact --all (or read the require block on Packagist) and compare the declared illuminate/* range and PHP floor against your app. If you're on Laravel 8+, expect composer require to either fail outright or succeed only with an ignored platform requirement — both are signals, not obstacles to work around.

If the constraint matches the app you're maintaining (a legacy 5.x codebase is the realistic scenario here):

  1. composer require jeroennoten/laravel-contact — the service provider should be auto-discovered; on older Laravel, register it manually in config/app.php.
  2. php artisan vendor:publish and inspect the tags offered (--tag=config, --tag=views, --tag=migrations are the conventional set). Publish config and migrations only; leave views in the vendor path until you actually need to change markup.
  3. php artisan migrate and confirm the submissions table landed with the columns and indexes you expect.
  4. Set your mail driver to log or point at Mailhog before your first submit, so a misconfigured recipient doesn't silently drop the first real inquiry.

Where to look first, in order: the published config file (route prefix, middleware, recipients, view name — this is the whole public surface of the package), then the service provider (to see what it binds, registers, and whether it fires events), then the migration (your data model). The controller is worth one read to understand exactly which fields it validates and what it persists.

First use case: point the configured route at your existing "Contact" nav link, submit the form once, and verify both halves of the workflow — a row in the table and an email in the log. Do that before you touch styling or copy. If either half is missing, you've found a config issue early, with nothing else in the way.

Implementation Patterns

Let the config own the routing, but own the URL yourself. The package routes are configurable, which is usually enough for a single contact page. For anything beyond that — a support form, a sales form, a locale-prefixed route — register your own route in routes/web.php that points at the package controller (or a thin subclass), and keep the package's route registration disabled via config. That gives you middleware, throttling, and naming under your control while still reusing the validation-and-store logic.

Write a thin seam controller. Even if the package does everything you need, don't let Blade or your routes reference its controller directly. One local controller that delegates keeps the swap cheap:

class ContactController extends \JeroenNoten\LaravelContact\Http\Controllers\ContactController
{
    // adjust redirects, add throttling, fire your own domain event
}

Check the actual namespace before copy-pasting — this is a 2017 package and its namespace is the most likely thing to have shifted between tags.

Treat the storage layer as your own. The submissions table is your data. Add an Eloquent model in App\Models mapped to the same table (or extend the package's if one exists), and query it from your own admin screen rather than relying on any package-provided listing. This is the highest-value pattern: the package writes rows, you own how they're read, filtered, exported, and retained.

Recipients belong in env, not in the published config. The config file gets committed; recipient addresses are environment-specific and change without a deploy. Reference env('CONTACT_RECIPIENTS') from the config and explode on commas, so staging mail never lands in the sales inbox.

Wrap notifications rather than replacing them. If the package ships a Mailable or sends directly via Mail::send, route it through your own Mailable subclass or a Laravel Notification so the message is queueable, localizable, and testable with Mail::fake(). If the send is a closure inside the package with no extension point, subclass and override — don't monkey-patch.

Custom views: publish, then diverge deliberately. Publish --tag=views only when you need to change markup, then keep a note that the published copy no longer receives upstream fixes. Since upstream is dormant anyway, this costs you nothing — but it does mean the published view is your code now, so it gets your code review.

Testing pattern. Two assertions cover the whole workflow:

Mail::fake();

$this->post('/contact', ['name' => 'A', 'email' => '[email protected]', 'message' => 'Hi'])
     ->assertRedirect();

$this->assertDatabaseHas('contact_submissions', ['email' => '[email protected]']);
Mail::assertSent(ContactSubmission::class, fn ($m) => $m->hasTo('[email protected]'));

Plus the negative cases: missing email, malformed email, oversized message, and an empty POST (honeypot/spam fields if present). Those four tests are the difference between "we adopted a package" and "we adopted a package we understand."

Integration tip — throttle at the edge. Contact forms are the most-hit spam target on a marketing site. Add throttle:5,1 (or a honeypot field in the published view) at your own route layer. Do not assume a 2017 package has anti-abuse built in.

Gotchas and Tips

The maintenance risk is the headline, not a footnote. Four stars, one release in 2017, no demonstrated survival of any Laravel major. Treat adoption as a fork-in-waiting: vendor the source into app/ or a local packages/ path, or accept that the next composer update is where this breaks. For a workflow this small — validate, store, mail — reimplementing in a single controller plus a Mailable is a few hours of work. Measure that against the debugging time before you commit.

Version-skew landmines to check on first read: the base controller (App\Http\Controllers\Controller vs Illuminate\Routing\Controller), constructor $this->middleware() calls (removed in Laravel 11), string-based Route::controller / 'Controller@method' syntax, Mail::send closures instead of Mailables, and the str_/array_ global helpers. Each one is a trivial fix and a fatal error until fixed.

config:cache will bite you. Change the recipients or route prefix, forget to recache, and in production you'll see the old behaviour with a config file that clearly says otherwise. php artisan config:clear locally, config:cache in the deploy pipeline.

Migration collisions. Check the table name against your existing schema before running. Also check the timestamp column type — 2017-era migrations often use MySQL TIMESTAMP where you've standardised on timestampTz, and timezone handling on inquiry timestamps becomes a support question six months later. Adjust the published migration before migrate.

Re-publishing overwrites silently. vendor:publish --force will discard your edited views and config. When you re-pull or fork, diff the published files rather than re-publishing blindly.

Debugging checklist, in order: php artisan route:list | grep contact (is it even registered, and with what middleware?), the merged config (config('laravel-contact') via tinker), the mail log, and finally the controller source. Ninety percent of "it doesn't work" cases are the route not registered because the provider wasn't loaded, or mail going to log.

Data you're now storing is personal data. Names, emails, and free-text messages are PII. Decide retention on day one, not after a compliance review: a scheduled command that prunes rows older than N months, an export path, and a documented answer for "delete my data" requests. The package won't do this for you and shouldn't be expected to.

Extension points to verify before building on them: whether the package dispatches an event on submission (listen and attach your own side effects — CRM push, Slack ping, auto-responder), whether the controller is container-resolvable so you can bind a subclass, and whether validation rules are config-driven or hardcoded. If any of those are hardcoded, that's your fork list — keep it to three items maximum, or reconsider the adoption entirely.

The honest exit plan. Keep the seam controller and your own model in place so that replacing the package is a one-file change plus deleting the dependency. Given the release history, write that exit down somewhere your team will find it, and set a review date against your next Laravel major upgrade — that's the moment this decision needs revisiting, and it's cheaper to plan for it now than to discover it mid-upgrade.

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.
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console