DigitalArtisan's avatar

DigitalArtisan wrote a reply+100 XP

5d ago

Fair points on convenience — I’m not against tools that reduce setup time. I think where I’m pushing back is more on the wording and positioning than the idea itself.

Phrases like “pre-bundled native binaries” and “more optimized for Windows” are a bit unclear from a technical standpoint. Most of these components already ship as native Windows builds, so it’s hard to tell what’s actually being improved or optimized beyond packaging them together.

For newer developers or people who just want to get up and running quickly, I can absolutely see the appeal of something like this. My perspective is just that understanding how the stack fits together (Nginx/Apache, PHP, database, etc.) pays off pretty quickly once you move beyond simple setups.

So yeah — different tools for different workflows. If the goal is speed and reducing repetition, I get why something like Larabox exists. I just think the technical claims could be explained a bit more clearly.

DigitalArtisan's avatar

DigitalArtisan wrote a reply+100 XP

6d ago

I develop on a Mac. Tag line:

Escape the complexity of Docker and WSL. Larabox provides isolated, pre-bundled native binaries for Nginx, PHP, MariaDB, Node.js and more optimized for Windows and live in seconds. 

Pre-bundled native binaries...who is compiling these on or for Windows, there already native binaries.

Docker isn't complex, neither is WSL.

I am a bit old school, if you use Nginx or Apache, you should know how to install and configure them.

More optimized for windows, as to 'less optimized'? How...

DigitalArtisan's avatar

DigitalArtisan liked a comment+100 XP

6d ago

@tykus @digitalartisan

The problem came from the installation process of the dependencies.

I forgot to install php8.4-zip extension and the zip packages couldn't be unzipped, so they were retrieved from git.

I don't know why this process generated the error, but now all works perfectly.

DigitalArtisan's avatar

DigitalArtisan was awarded Best Answer+1000 XP

1w ago

This directory should not exist:

var/www/meeplesperdusaurillac/vendor/league/flysystem/src/Local

I'd delete vendor/league/flysystem then run composer again.

DigitalArtisan's avatar

DigitalArtisan wrote a reply+100 XP

1w ago

Im confused because I have the latest version of composer and php,

No you don't.

The latest stable PHP version is 8.5.5, released on April 9, 2026.

DigitalArtisan's avatar

DigitalArtisan wrote a reply+100 XP

1w ago

This directory should not exist:

var/www/meeplesperdusaurillac/vendor/league/flysystem/src/Local

I'd delete vendor/league/flysystem then run composer again.

DigitalArtisan's avatar

DigitalArtisan wrote a reply+100 XP

2w ago

The client specifically wants a much faster version of their existing WordPress site 

Why is the site slow, where is the bottle neck and why?

DigitalArtisan's avatar

DigitalArtisan wrote a reply+100 XP

2w ago

I’d recommend using PostgreSQL with PostGIS for GIS points.

  • You get real location types (no need to store lat/lon separately)
  • Easy queries like “find points within X distance”
  • Fast with built-in spatial indexing
  • Works well with GIS tools

Basically, it’s much simpler and more powerful than trying to handle coordinates yourself.

DigitalArtisan's avatar

DigitalArtisan wrote a reply+100 XP

2w ago

80% of the work takes 20% of the time. The other 20% of the work takes 80% of the time.

What you are saying to me is that you have 5-10% of your project complete by AI, by stating that 90-95% of the goals are met, which is the easy stuff, and what about the other 5-10%?

Who is going to spend the rest of the 90-95% of the time that still needs to be done?

DigitalArtisan's avatar

DigitalArtisan wrote a reply+100 XP

3w ago

And you state I’m intentionally avoiding AI/ML solutions.

You started with an AI.

DigitalArtisan's avatar

DigitalArtisan wrote a reply+100 XP

4w ago

I would rename your Dashboard, as I think I know what is happening. The default dashboard is being loaded, as well as your dashboard, but they have the same name "Dashboard".

Change to the following and should work:

namespace App\Filament\Pages;
use Filament\Pages\Dashboard;

class CustomDashboard extends Dashboard
{
    protected static ?string $title = 'Custom Dashboard';
    protected  ?string $heading = 'Custom Dashboard'';
}

This could be a bug or by design, I'm not sure. If the names do not match, it appears to load the dashboard twice.

Make sure the name of your Dashboard matches the title/heading, as I have shown above with CustomDashboard => "Custom Dashboard" will only show once in the menu, and the title/heading gets changed. If no heading is set, it will default to using title.

DigitalArtisan's avatar

DigitalArtisan wrote a reply+100 XP

4w ago

I use this in my custom dashboards:

    protected static ?string $navigationLabel = 'Custom Dashboard';
    
    public function getTitle(): string
    {
        return 'Custom Dashboard';
    }
DigitalArtisan's avatar

DigitalArtisan wrote a reply+100 XP

1mo ago

MacOS Firefox 150.0b1 (aarch64)

Broken for a week for me - NS_ERROR_CORRUPTED_CONTENT:

The resource at “https://laracasts.com/fonts/shadow-noir/ShadowNoir.woff2” preloaded with link preload was not used within a few seconds. Make sure all attributes of the preload tag are set correctly. laracasts.com
The resource at “https://laracasts.com/build/assets/js/app-HWIx8gQn.js” preloaded with link preload was not used within a few seconds. Make sure all attributes of the preload tag are set correctly. laracasts.com
GET
https://laracasts.com/build/assets/js/Index-Z2l_0yzy.js
NS_ERROR_CORRUPTED_CONTENT

Loading module from “https://laracasts.com/build/assets/js/Index-Z2l_0yzy.js” was blocked because of a disallowed MIME type (“text/html”). laracasts.com
Uncaught (in promise) TypeError: error loading dynamically imported module: https://laracasts.com/build/assets/js/Index-Z2l_0yzy.js
    vue https://laracasts.com/build/assets/js/app-HWIx8gQn.js:2
    xe https://laracasts.com/build/assets/js/components-BxsoGmWQ.js:2
app-HWIx8gQn.js:2:21205
DigitalArtisan's avatar

DigitalArtisan wrote a reply+100 XP

1mo ago

Its a play on words. Read the paragraph beside the images:

Laravel has opinions on everything: ...

Just add this code if you cannot rename the classes:

class_alias(
    App\Http\Controllers\UserController::class,
    App\Http\Controllers\FlightController::class
);
DigitalArtisan's avatar

DigitalArtisan wrote a reply+100 XP

1mo ago

Be very clear about the distinction between the client and the end users. Users will sometimes assume that a staging, test, or demo environment is the production system. This confusion can lead to misunderstandings and unnecessary issues.

DigitalArtisan's avatar

DigitalArtisan wrote a reply+100 XP

1mo ago

Depends on your data. Is your XML well formatted and do you have the XML Schema Definition (XSD) file?

Can be easy, and can be hard, again, depends on your data.

DigitalArtisan's avatar

DigitalArtisan was awarded Best Answer+1000 XP

2mos ago

Struggling to identify ALL permissions without missing edge cases or over-engineering.

You should write tests.

Authorization tests help you discover missing permissions and edge cases early, and prevent over-engineering. If a permission rule is hard to test, it’s usually too complex or unclear.

DigitalArtisan's avatar

DigitalArtisan wrote a reply+100 XP

2mos ago

To clarify, I said replication is very common when you want a copy of your data. I wasn’t suggesting that syncing dev → prod in a shared hosting environment is common practice.

My point was simply that replication is widely used for maintaining data copies, and it’s a more appropriate term than “sync” in this context.

There are multiple ways to setup replication from a dev box without a static IP: VPN, SSH tunneling, Master/Slave, Pull/Push based, etc.

Anyways, I do not recommend what the OP is doing, nor would I want to.

I would use migrations and seeding to populate the production db.

DigitalArtisan's avatar

DigitalArtisan wrote a reply+100 XP

2mos ago

To me, you could set up replication with master slave. One database as the source(dev), the other as the replica(prod).

Very common when you want or need to have a 'copy' of you data.

DigitalArtisan's avatar

DigitalArtisan wrote a reply+100 XP

2mos ago

Struggling to identify ALL permissions without missing edge cases or over-engineering.

You should write tests.

Authorization tests help you discover missing permissions and edge cases early, and prevent over-engineering. If a permission rule is hard to test, it’s usually too complex or unclear.

DigitalArtisan's avatar

DigitalArtisan wrote a reply+100 XP

2mos ago

Islands can't be used in loops or conditionals:

Because islands don't have access to loop variables or conditional context, they cannot be used inside @foreach, @if, or other control structures:

DigitalArtisan's avatar

DigitalArtisan wrote a reply+100 XP

2mos ago

I can see why it feels like a change, but the first-time confirmation actually makes sense. PsySH had a security issue (CVE‑2026‑25129) where it could unintentionally load and execute a .psysh.php file from the current directory. The dialog helps make users aware of this risk before running Tinker interactively.

It’s really just a one-time prompt, so after confirming, your usual workflow continues unchanged. I think it’s a small adjustment for a legitimate security reason, and having the library handle the prompt seems appropriate.

DigitalArtisan's avatar

DigitalArtisan wrote a reply+100 XP

2mos ago

When a PHP bool is echo'd, it is automatically type casted to a string.

Now for database storage:

  • PostgreSQL: Has a true, native BOOLEAN data type (1 byte). Accepts values like true/false, yes/no, 1/0, 't'/'f', 'y'/'n', and supports NULL.
  • MySQL: No native boolean type. BOOLEAN/BOOL are aliases for TINYINT(1). Uses 1 for true and 0 for false; any non-zero value evaluates as true.
  • SQLite: No boolean storage class. Uses INTEGER (NUMERIC affinity), where 1 = true and 0 = false. TRUE and FALSE are just aliases for 1 and 0.

Key takeaway: PostgreSQL is SQL-standard with a real boolean type, while MySQL and SQLite represent booleans using integers—important to remember for cross-database compatibility.

PHP stores booleans as booleans, not numbers

  • echo converts them to strings:

    • true → "1"

    • false → ""

Use var_dump() to see the real type

Cast to (int) if you truly want 0 / 1

DigitalArtisan's avatar

DigitalArtisan wrote a reply+100 XP

3mos ago

The best place to deploy your application depends on several factors:

  • Budget
  • Your experience, knowledge, and comfort level with hosting and deploying Laravel applications
  • Your test benchmarks and performance results

To move forward, here are a few questions for you:

  1. What is your budget?
  2. What is your experience / comfort level with hosting and deploying Laravel apps?
  3. What do your performance and load-testing benchmarks show so far?
DigitalArtisan's avatar

DigitalArtisan wrote a reply+100 XP

3mos ago

When you prompt AI correclty, you should get this:

The Correct Way to Throttle Emails in Laravel with Jobs and Queues

Step 1: Use Queue Middleware (RateLimited)

This is the core part of the solution. To throttle emails, you add middleware to your job to enforce the rate limit:

use Illuminate\Queue\Middleware\RateLimited;

class SendEmailJob implements ShouldQueue
{
    public function middleware()
    {
        return [
            new RateLimited('mailing-list'),
        ];
    }

    public function handle()
    {
        // Logic to send the email
    }
}

This ensures the job respects the rate limit defined later.


Step 2: Define Rate Limit in AppServiceProvider

In your AppServiceProvider@boot(), you define the rate-limiting logic:

use Illuminate\Cache\RateLimiter;
use Illuminate\Cache\RateLimiting\Limit;

public function boot()
{
    RateLimiter::for('mailing-list', function (SendEmailJob $job) {
        return Limit::perMinute(10)->by($job->userId);
    });
}

This sets a 10 emails per minute limit for each user (based on userId).


Step 3: Use Redis as Queue and Cache Driver

Redis is required for distributed rate-limiting. Ensure your .env file is configured like this:

QUEUE_CONNECTION=redis
CACHE_DRIVER=redis

Redis ensures the limit is applied safely across multiple workers or instances.


Step 4: Run the Queue Worker

Run the queue worker to process the jobs:

php artisan queue:work redis

This worker will now respect the rate limit (10 emails per minute in this example) and retry any jobs that exceed the rate limit.


Key Points:

  • Only one correct method, with 4 steps to implement.
  • Redis is mandatory for safe, scalable rate-limiting.
  • RateLimited middleware handles job throttling.
  • No emails are dropped—they’re released back to the queue when the limit is hit.
DigitalArtisan's avatar

DigitalArtisan wrote a reply+100 XP

3mos ago

Its 2026 now, that article is OLD.

Here is a current AI response:


Laravel + Next.js vs Laravel + Inertia + React for SEO

1. Laravel + Next.js

Pros:

  • Server-Side Rendering (SSR): Next.js renders pages on the server before sending them to the browser, which is great for SEO.
  • SEO Optimized: Built-in features like image optimization, fast page loads, and prefetching help with SEO.
  • Static Site Generation (SSG): You can generate static pages, improving performance and SEO.

Cons:

  • More Complex: Managing both Laravel and Next.js can be tricky. You'll need separate setups and integration.
  • API Management: You'll need to set up APIs for Laravel to serve data to Next.js.

2. Laravel + Inertia + React

Pros:

  • Simple Integration: Keeps everything within Laravel, no need for a separate front-end framework.
  • Server-side Rendering: You can still use Laravel to serve initial HTML, making it SEO-friendly.

Cons:

  • Client-Side Rendering (CSR): By default, Inertia uses React on the client-side, which can be problematic for SEO if not handled correctly.
  • Manual SEO Work: You may need additional tools like React Helmet or implement SSR manually for optimal SEO.

Conclusion:

  • For better SEO out of the box: Go with Laravel + Next.js. It has built-in SSR and other SEO optimizations.
  • For simplicity: Laravel + Inertia + React works if you don't mind handling SEO optimization yourself, but it might require more effort to get it right.

DigitalArtisan's avatar

DigitalArtisan wrote a reply+100 XP

3mos ago

First, you need a Learning Management System (LMS) that supports Learning Tools Interoperability (LTI).

LTI is a standard defined by 1EdTech (formerly IMS Global) that allows an LMS to securely integrate external learning tools. It’s often described as an API, but in practice it’s a standards-based protocol for authentication, launch, and grade/roster exchange between systems.

LTI Roles

LTI involves two roles:

  • LTI Platform (formerly “Consumer”) — the LMS (e.g., Canvas, Moodle, D2L)
  • LTI Tool (formerly “Provider”) — the external application being launched

LTI Versions

As of today, LTI 1.3 with LTI Advantage is the recommended version. It replaces older LTI 1.1 implementations and is based on OAuth 2.0 and OpenID Connect.

Official tutorials and specifications:
https://www.imsglobal.org/spec/lti/v1p3/

Laravel and LTI

Regarding Laravel:
There still aren’t any widely adopted, full-featured Laravel-based LMS platforms that natively act as an LTI Platform. However, Laravel works well for building LTI Tools, and there are PHP/Laravel libraries that help implement LTI 1.3 launches, deep linking, and grade passback.

In a previous role, I used Desire2Learn (D2L / Brightspace) extensively, which has solid LTI support:
https://docs.valence.desire2learn.com/res/lti.html

Common Approach Today

So today, the common approach is:

  • Use an established LMS (Canvas, Moodle, D2L, etc.) as the LTI Platform
  • Build custom learning tools in Laravel as LTI Tools
DigitalArtisan's avatar

DigitalArtisan wrote a reply+100 XP

3mos ago

There’s a lot of debate around this.

From my experience, AI significantly reduces time to production—projects that once took months or weeks can now be completed in days or hours. Refactoring is faster as well.

That said, when AI breaks down, a developer still has to understand and fix the code. I’ve heard of teams abandoning AI-generated projects because no one fully understood the codebase.

Bottom line: AI is a productivity tool, not a replacement. It can help, but it’s not a silver bullet.

Marketing is still marketing—SEO, targeting, and messaging matter. Relying entirely on AI for marketing content can be risky if it gets things wrong.

Used carefully, AI is valuable. Used blindly, it can create problems.

DigitalArtisan's avatar

DigitalArtisan wrote a reply+100 XP

3mos ago

When people say you only need a “high-level” understanding of DevOps, cloud, and system design for junior roles, they mean knowing what these things are, why they’re used, and how they fit into the web development process, not being able to fully build or operate production infrastructure yourself. You should be able to explain concepts like CI/CD, cloud hosting, authentication, scalability, and how a backend system is structured, but you’re not expected to manage Kubernetes, complex AWS networking, or large-scale distributed systems.

For projects, the backend projects on roadmap.sh are more than enough for junior roles if done properly. “Real-world” doesn’t mean huge SaaS platforms or advanced AI apps—it means projects with realistic business logic, data relationships, authentication, validation, error handling, and clear tradeoffs. A few well-built APIs (auth system, CRUD app, blog, e-commerce, task manager) that you understand deeply are far more impressive than an over-scoped SaaS or Indeed-style clone.

It may be worth considering a more structured or formal learning route to help solidify the fundamentals.

DigitalArtisan's avatar

DigitalArtisan wrote a reply+100 XP

3mos ago

Hiring teams primarily look for learning ability, strong fundamentals, and practical problem-solving skills—not senior-level infrastructure expertise.

To get a junior Laravel job you should build a few solid, real-world Laravel projects that demonstrate core PHP/Laravel knowledge (CRUD, authentication, validation, relationships, testing, and Git), basic SQL, and simple deployment, while understanding DevOps, cloud, and system design concepts only at a high level rather than fully implementing them.

DigitalArtisan's avatar

DigitalArtisan wrote a reply+100 XP

3mos ago

You are probably missing the following in your view:

<form method="POST" action="/payers/{{ $payer->id }}">
    @csrf
    @method('PUT')   {{-- or PATCH / DELETE --}}
</form>
DigitalArtisan's avatar

DigitalArtisan wrote a reply+100 XP

3mos ago

Exactly, not working. You have these routes:

  GET|HEAD        payers .............. payers.index › PayerController@index
  PUT             payers .............. payers.store › PayerController@store
  PATCH           payers/{payer} ...... payers.update › PayerController@update
  DELETE          payers/{payer} ...... payers.destroy › PayerController@destroy

I would think you need this:

  GET|HEAD      payers .............. payers.index › PayerController@index
  POST          payers .............. payers.store › PayerController@store
  PUT|PATCH     payers/{payer} ...... payers.update › PayerController@update
  DELETE        payers/{payer} ...... payers.destroy › PayerController@destroy

Your error message is correct for your routing.

DigitalArtisan's avatar

DigitalArtisan wrote a reply+100 XP

3mos ago

PUT updates all data, PATCH allows partial update, and POST creates data.

So, your store route should be a POST in my opinion. And PATCH/PUT should be update route.

DigitalArtisan's avatar

DigitalArtisan wrote a reply+100 XP

3mos ago

For completeness, here is the tutorial for creating a user resource in Filament 4:

Tutorial: Creating a User Resource in Filament 4

This tutorial walks you through creating a comprehensive User Resource in Filament 4 to manage users in your admin panel, including viewing, creating, editing, and deleting users with custom fields.

Overview

We'll create a User Resource that provides a full CRUD (Create, Read, Update, Delete) interface for managing users, including:

  • A table view with searchable and sortable columns
  • A form with organized sections for user information and authentication
  • Proper password handling (required on create, optional on edit)
  • Custom fields (phone, company) from your registration form
  • Navigation integration

Prerequisites

  • Laravel 12.x
  • Filament 4.x installed
  • A User model with custom fields (phone, company) already migrated
  • A Filament panel already configured

Step-by-Step Implementation

Step 1: Generate the User Resource

Use Filament's artisan command to generate a complete resource with all necessary files:

php artisan filament:make-resource User --generate

This command will create:

  • app/Filament/Resources/Users/UserResource.php - Main resource class
  • app/Filament/Resources/Users/Tables/UsersTable.php - Table configuration
  • app/Filament/Resources/Users/Schemas/UserForm.php - Form configuration
  • app/Filament/Resources/Users/Pages/ListUsers.php - List page
  • app/Filament/Resources/Users/Pages/CreateUser.php - Create page
  • app/Filament/Resources/Users/Pages/EditUser.php - Edit page

The --generate flag automatically generates the table, form, and page classes.

Step 2: Configure the Main Resource Class

Edit app/Filament/Resources/Users/UserResource.php to customize navigation and labels:

Key Points:

  • $model - Links the resource to your User model
  • $navigationLabel - Label shown in the sidebar navigation
  • $modelLabel / $pluralModelLabel - Labels used in page titles and actions
  • $navigationIcon - Icon displayed in navigation (Heroicon::OutlinedUserGroup)
  • form() - Delegates form configuration to UserForm class
  • table() - Delegates table configuration to UsersTable class
  • getPages() - Defines the routes for list, create, and edit pages

Step 3: Configure the Table View

Edit app/Filament/Resources/Users/Tables/UsersTable.php to customize the columns displayed in the list view:

Column Features:

  • searchable() - Enables search functionality for the column
  • sortable() - Allows sorting by clicking the column header
  • copyable() - Adds a copy button to quickly copy the value
  • toggleable() - Allows users to show/hide the column
  • toggleable(isToggledHiddenByDefault: true) - Column is hidden by default
  • label() - Custom label for the column header
  • dateTime() - Formats the value as a date/time

Table Actions:

  • recordActions() - Actions available for each row (Edit button)
  • toolbarActions() - Bulk actions available in the toolbar (Delete multiple)

Step 4: Configure the Form

Edit app/Filament/Resources/Users/Schemas/UserForm.php to create an organized form with sections:

Key Features:

  1. Sections: Organize form fields into logical groups

    • Section::make('Title') - Creates a collapsible section
    • ->columns(2) - Displays fields in a 2-column layout
  2. Field Validation:

    • ->required() - Makes the field mandatory
    • ->email() - Validates email format
    • ->tel() - Optimizes input for telephone numbers
    • ->maxLength(255) - Sets maximum character length
    • ->unique(ignoreRecord: true) - Ensures uniqueness, ignoring current record on edit
  3. Password Handling:

    • ->required(fn ($livewire) => ...) - Required only when creating (not editing)
    • ->dehydrated(...) - Only saves password if provided (on edit) or always (on create)
    • ->dehydrateStateUsing(...) - Automatically hashes the password before saving
    • Dynamic label changes based on create vs edit context
  4. Important Note on Namespaces:

    • Section is in Filament\Schemas\Components\Section (not Forms namespace)
    • TextInput and DateTimePicker are in Filament\Forms\Components

Step 5: Customize Page Classes (Optional)

The page classes are automatically generated and work out of the box. You can customize them if needed:

CreateUser.php:

<?php

namespace App\Filament\Resources\Users\Pages;

use App\Filament\Resources\Users\UserResource;
use Filament\Resources\Pages\CreateRecord;

class CreateUser extends CreateRecord
{
    protected static string $resource = UserResource::class;
}

EditUser.php:

<?php

namespace App\Filament\Resources\Users\Pages;

use App\Filament\Resources\Users\UserResource;
use Filament\Actions\DeleteAction;
use Filament\Resources\Pages\EditRecord;

class EditUser extends EditRecord
{
    protected static string $resource = UserResource::class;

    protected function getHeaderActions(): array
    {
        return [
            DeleteAction::make(),
        ];
    }
}

The EditUser class includes a delete action in the header, allowing you to delete users from the edit page.

Understanding the Structure

Resource Organization

Filament 4 uses a modular structure for resources:

app/Filament/Resources/Users/
├── UserResource.php          # Main resource class
├── Tables/
│   └── UsersTable.php        # Table configuration
├── Schemas/
│   └── UserForm.php          # Form configuration
└── Pages/
    ├── ListUsers.php         # List page
    ├── CreateUser.php        # Create page
    └── EditUser.php          # Edit page

This separation allows for:

  • Better code organization
  • Reusability of table and form configurations
  • Easier maintenance and testing

Auto-Discovery

Filament automatically discovers resources in app/Filament/Resources and adds them to navigation. No manual registration needed!

Customization Options

Adding More Columns to Table

Add additional columns to display more information:

TextColumn::make('phone')
    ->label('Phone')
    ->searchable()
    ->toggleable()
    ->formatStateUsing(fn ($state) => $state ?: 'N/A'),  // Show "N/A" if empty

Adding Filters

Add filters to help users find specific records:

use Filament\Tables\Filters\SelectFilter;
use Filament\Tables\Filters\Filter;

// In UsersTable.php ->filters([])
->filters([
    SelectFilter::make('email_verified_at')
        ->label('Email Status')
        ->options([
            'verified' => 'Verified',
            'unverified' => 'Unverified',
        ])
        ->query(function ($query, $state) {
            if ($state['value'] === 'verified') {
                return $query->whereNotNull('email_verified_at');
            }
            if ($state['value'] === 'unverified') {
                return $query->whereNull('email_verified_at');
            }
        }),
])

Adding Bulk Actions

Add more bulk actions for managing multiple users:

use Filament\Actions\BulkAction;

->toolbarActions([
    BulkActionGroup::make([
        BulkAction::make('verify_emails')
            ->label('Verify Selected Emails')
            ->icon('heroicon-o-check-circle')
            ->action(function ($records) {
                $records->each(function ($user) {
                    $user->markEmailAsVerified();
                });
            })
            ->requiresConfirmation(),
        DeleteBulkAction::make(),
    ]),
])

Customizing Form Sections

Add more sections or reorganize fields:

Section::make('Contact Information')
    ->schema([
        TextInput::make('phone'),
        TextInput::make('company'),
    ])
    ->collapsible(),  // Makes section collapsible

Adding Custom Actions

Add custom actions to the table or form:

// In UsersTable.php
->recordActions([
    EditAction::make(),
    Action::make('impersonate')
        ->label('Impersonate')
        ->icon('heroicon-o-user')
        ->action(function ($record) {
            // Impersonation logic
        }),
])

Customizing Navigation

Control how the resource appears in navigation:

// In UserResource.php
protected static ?int $navigationSort = 1;  // Position in sidebar

protected static ?string $navigationGroup = 'User Management';  // Group resources

protected static bool $shouldRegisterNavigation = true;  // Show/hide in nav

Testing

  1. Navigate to /admin in your browser
  2. Look for "Users" in the sidebar navigation
  3. Click it to see the list of users
  4. Test the following:
    • List View: Verify all columns display correctly
    • Search: Try searching by name, email, phone, or company
    • Sorting: Click column headers to sort
    • Create: Click "New User" and fill out the form
    • Edit: Click the edit icon on a user row
    • Delete: Delete a user from the edit page
    • Password: Verify password is required on create, optional on edit

Troubleshooting

Resource Not Appearing in Navigation

  • Clear Filament cache: php artisan filament:optimize-clear
  • Clear config cache: php artisan config:clear
  • Verify the resource is in app/Filament/Resources directory
  • Check that $shouldRegisterNavigation is not set to false

Form Fields Not Saving

  • Verify fields are in the User model's $fillable array
  • Check that database columns exist (run migrations)
  • Ensure dehydrated() is not set to false unless intentional

Password Issues

  • Password field should use ->dehydrateStateUsing() to hash the password
  • On edit, password should only be dehydrated if a value is provided
  • Use ->required(fn ($livewire) => ...) to make it conditional

Namespace Errors

  • Section is in Filament\Schemas\Components\Section
  • Form components (TextInput, DateTimePicker) are in Filament\Forms\Components
  • Table columns are in Filament\Tables\Columns

Summary

You've successfully created a comprehensive User Resource in Filament 4 with:

  • ✅ Full CRUD functionality (Create, Read, Update, Delete)
  • ✅ Organized form with sections for user information and authentication
  • ✅ Searchable and sortable table columns
  • ✅ Custom fields (phone, company) integrated
  • ✅ Smart password handling (required on create, optional on edit)
  • ✅ Automatic password hashing
  • ✅ Navigation integration
  • ✅ Bulk actions for managing multiple users
  • ✅ Professional UI with toggleable columns

The User Resource is now available in your Filament admin panel and automatically appears in the navigation sidebar. Users registered through your custom registration page will be visible and manageable through this interface.

DigitalArtisan's avatar

DigitalArtisan wrote a reply+100 XP

3mos ago

Here is a tutorial that I just created:

Tutorial: Creating a Custom Registration Page in Filament 4

This tutorial walks you through creating a custom registration page in Filament 4 with additional fields beyond the standard name, email, and password.

Overview

We'll create a custom registration page that extends Filament's built-in registration functionality to include:

  • Standard fields: Name, Email, Password, Password Confirmation
  • Custom fields: Phone Number, Company/Organization, Terms and Conditions acceptance

Prerequisites

  • Laravel 12.x
  • Filament 4.x installed
  • A Filament panel already configured

Step-by-Step Implementation

Step 1: Generate the Custom Registration Page

First, we need to create a custom registration page class. Filament doesn't have a specific command for auth pages, so we'll use the general page command and then customize it.

php artisan filament:make-page Auth/Register

This creates a basic page at app/Filament/Pages/Auth/Register.php. We'll modify it to extend Filament's base Register class.

Step 2: Customize the Registration Page

Edit app/Filament/Pages/Auth/Register.php to extend Filament's base Register class and add custom fields:

Key Points:

  • Extends Filament\Auth\Pages\Register to inherit all base functionality
  • Overrides form() to add custom fields alongside standard ones
  • getPhoneFormComponent(): Phone field with tel input type and unique validation
  • getCompanyFormComponent(): Optional company field
  • getTermsFormComponent(): Required checkbox with dehydrated(false) so it's not saved to database
  • mutateFormDataBeforeRegister(): Removes the terms field before saving (it's only for validation)
  • afterRegister(): Hook for post-registration logic (emails, role assignment, etc.)

Step 3: Enable Registration in Panel Provider

Update app/Providers/Filament/AdminPanelProvider.php to enable registration and point to your custom page:

Add the import:

use App\Filament\Pages\Auth\Register;

Update the panel configuration:

public function panel(Panel $panel): Panel
{
    return $panel
        ->default()
        ->id('admin')
        ->path('admin')
        ->login()
        ->registration(Register::class)  // Add this line
        ->colors([
            'primary' => Color::Amber,
        ])
        // ... rest of configuration
}

The ->registration(Register::class) method enables registration and tells Filament to use your custom Register page.

Step 4: Update the User Model

Add the new fields to the $fillable array in app/Models/User.php:

protected $fillable = [
    'name',
    'email',
    'password',
    'phone',      // Add this
    'company',    // Add this
];

Note: We don't add terms because it's only used for validation, not stored in the database.

Step 5: Create Database Migration

Create a migration to add the new columns to the users table:

php artisan make:migration add_phone_and_company_to_users_table --table=users

Edit the migration file:

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::table('users', function (Blueprint $table) {
            $table->string('phone', 20)->nullable()->after('email');
            $table->string('company')->nullable()->after('phone');
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::table('users', function (Blueprint $table) {
            $table->dropColumn(['phone', 'company']);
        });
    }
};

Key Points:

  • Both fields are nullable (optional)
  • Phone is limited to 20 characters
  • Company is a standard string field
  • The down() method properly reverses the migration

Step 6: Run the Migration

Execute the migration:

php artisan migrate

Understanding the Implementation

Form Components

  1. Phone Field:

    • Uses tel() input type for mobile keyboard optimization
    • Has unique() validation to prevent duplicate phone numbers
    • Maximum length of 20 characters
  2. Company Field:

    • Simple text input
    • Optional (no required() call)
    • Maximum length of 255 characters
  3. Terms Field:

    • Required checkbox using accepted() validation
    • dehydrated(false) prevents it from being saved to the database
    • Only used for validation purposes

Data Mutation

The mutateFormDataBeforeRegister() method is crucial for fields that shouldn't be saved. Since terms is only for validation, we remove it before the user is created.

Post-Registration Hooks

The afterRegister() method is called after a user is successfully registered. This is where you can:

  • Send welcome emails
  • Assign default roles
  • Create related records
  • Trigger notifications

Alternatively, you can listen to Filament's Registered event in your EventServiceProvider.

Testing

  1. Navigate to /admin/register (or your panel path + /register)
  2. Fill out the registration form with:
    • Name
    • Email (must be unique)
    • Phone (optional, but must be unique if provided)
    • Company (optional)
    • Password
    • Password confirmation
    • Terms acceptance (required)
  3. Submit the form
  4. Verify the user is created with all provided data
  5. Check that the terms field is NOT saved to the database

Customization Options

Adding More Fields

To add additional fields:

  1. Create a new component method (e.g., getAddressFormComponent())
  2. Add it to the form() method's components array
  3. Add the field to the User model's $fillable array
  4. Create a migration if it's a database field

Making Fields Required

Add ->required() to any field component:

protected function getCompanyFormComponent(): Component
{
    return TextInput::make('company')
        ->label('Company/Organization')
        ->required()  // Add this
        ->maxLength(255);
}

Custom Validation

Add validation rules to any field:

protected function getPhoneFormComponent(): Component
{
    return TextInput::make('phone')
        ->label('Phone Number')
        ->tel()
        ->maxLength(20)
        ->unique($this->getUserModel())
        ->rules(['regex:/^\+?[1-9]\d{1,14}$/']);  // E.164 format
}

Custom Post-Registration Logic

Implement the afterRegister() method with your custom logic:

protected function afterRegister(): void
{
    $user = $this->form->getRecord();
    
    // Send welcome email
    Mail::to($user->email)->send(new WelcomeMail($user));
    
    // Assign default role
    $user->assignRole('customer');
    
    // Create related profile
    $user->profile()->create([
        'phone' => $user->phone,
        'company' => $user->company,
    ]);
}

Troubleshooting

Registration Page Not Showing

  • Ensure ->registration(Register::class) is added to your panel provider
  • Clear config cache: php artisan config:clear
  • Clear route cache: php artisan route:clear

Fields Not Saving

  • Verify fields are in the User model's $fillable array
  • Check that the migration has been run
  • Ensure fields are not set to dehydrated(false) unless intentional

Validation Errors

  • Check that validation rules are correct
  • Ensure unique constraints match your database structure
  • Verify required fields have ->required() in the component

Summary

You've successfully created a custom Filament 4 registration page with:

  • ✅ Extended Filament's base Register class
  • ✅ Added custom form fields (phone, company, terms)
  • ✅ Configured the panel to use the custom registration page
  • ✅ Updated the User model to accept new fields
  • ✅ Created and ran database migrations
  • ✅ Implemented custom validation and post-registration logic

The registration page is now available at /admin/register and fully integrated with Filament's authentication system.

DigitalArtisan's avatar

DigitalArtisan wrote a reply+100 XP

4mos ago

If your using an Emulator, that is your problem. I've used emulators since the 1990s, and in reality, you need the hardware in order to get system performance.

Get the correct hardware for what the software was designed for.

DigitalArtisan's avatar

DigitalArtisan wrote a reply+100 XP

4mos ago

First off, you need to verify minimum requirements:

Windows PC, you need at least Windows 7/8/10/11 (64-bit), 2 GB or more RAM, a 2.0 GHz or faster CPU, and 500 MB of free storage.

Do you have that?

DigitalArtisan's avatar

DigitalArtisan wrote a reply+100 XP

4mos ago

Basically no, and you probably would never want a 'free' domain name, as that registrar will probably lock you into paying, and or they will keep it and resell the domain name for a fee.

A domain name is simply a human-readable address on the internet. It maps a name to an IP address (and vice versa) and does not require a hosting provider by itself.

A hosting provider, on the other hand, is what stores and serves your website’s content.

It’s generally best to keep these two separate. You can purchase a domain name from a reputable registrar for around $10–$20 per year. After that, you can choose a hosting provider (or set up your own server) and point the domain to wherever your content is hosted.

DigitalArtisan's avatar

DigitalArtisan liked a comment+100 XP

4mos ago

Yes, knowledge

DigitalArtisan's avatar

DigitalArtisan wrote a reply+100 XP

4mos ago

It completely depends on the project and the team.

Before Laravel, in the early 2000s, I used the Repository pattern extensively for enterprise projects. Nearly 25 years later, that same codebase is still in use and has been reused across multiple frameworks. At the time, I evaluated the Active Record pattern and found that it did not align with the project’s goals.

Laravel uses Eloquent by default, which largely follows the Active Record pattern and was chosen intentionally.

If the project is a large, long-living enterprise application that predates the Laravel framework, I would agree with your senior team.

DigitalArtisan's avatar

DigitalArtisan wrote a reply+100 XP

4mos ago

I created MDA to solve a real problem No you didn't. The Object Management Group® Standards Development Organization (OMG® SDO) did.

OMG members voted to establish the MDA (Model Driven Architecture) as the base architecture for our organization's standards in late 2001.

You just happened to implement a Laravel version of MDA, that's all.

DigitalArtisan's avatar

DigitalArtisan wrote a reply+100 XP

4mos ago

Or install it the way the instructions say to: https://docs.vapor.build/introduction#installing-the-vapor-cli

Either globally as you have done, or per-project.

DigitalArtisan's avatar

DigitalArtisan wrote a reply+100 XP

4mos ago

HTML with CSS to PDF never works well. Good luck if you rely on JS views.

The best for me has been as mentioned above the barryvdh/laravel-dompdf. I use Blade views, ~/resources/views/print/pdf/.... Minimal CSS, overridden from the HTML pages, pretend your in the 90s writing HTML email. You cannot do fancy things, as the DOMPDF library does not support all CSS.

I've been doing HTML to PDF for just over 15 years.

Other options are using libreoffice headless. Best is to design your PDF documents, then dynamically fill them.

Best way to test is using your web browsers Print Preview -> Print to PDF. From what I remember, Firefox was the best.

DigitalArtisan's avatar

DigitalArtisan wrote a reply+100 XP

4mos ago

You said quoate but Nginx on the host cannot directly access it. This has caused issues where assets like /logo.png work locally in Docker Desktop but fail in production unless accessed via /public/logo.png or through a Laravel route.

Sounds like a configuration problem.

What do you have containerized?

DigitalArtisan's avatar

DigitalArtisan wrote a reply+100 XP

4mos ago

with this maybe?:

 /** @var AccountTypeData $data */
    $data = $request->validated();
DigitalArtisan's avatar

DigitalArtisan wrote a reply+100 XP

4mos ago

I wouldn't say your getting it wrong, just need more information to get rid of the warning. With this:

@param  array{
	code: string, 
	name: string, 
	normal_balance_debit: bool, 
	is_active: string, description: string|null}  $data
     */
    public static function fromArray(array $data): self

I believe this is the mixed type warning you are getting. I do not use PHPStan, but I can assume you need to explicitly state what type of $data array you have, not just an array of $data.

Something like this may work for you:

/**
 * @phpstan-type AccountTypeData array{
 *     code: string,
 *     name: string,
 *     normal_balance_debit: bool,
 *     is_active: string,
 *     description: string|null
 * }
 */
final readonly class AccountTypeDTO
{
    /**
     * @param AccountTypeData $data
     */
    public static function fromArray(array $data): self
    {
DigitalArtisan's avatar

DigitalArtisan wrote a reply+100 XP

4mos ago

I have one question: Why is every class final, like every single class in your whole project?

DigitalArtisan's avatar

DigitalArtisan wrote a reply+100 XP

4mos ago

Because a newline directly after the opening <x-markdown> \n will force the markdown parser to treat the content as block-level and leave it as-is. When content is on the same line, it is treated as inline and wrapped in a <p> tag. This is by design to control how your output is processed.

DigitalArtisan's avatar

DigitalArtisan wrote a reply+100 XP

4mos ago

It is obvious now. You have:

Access denied for user 'user-db'@'localhost' 

And you have

$username = "db-user";

It's either config cached, or your not using the .env you think you are.

DigitalArtisan's avatar

DigitalArtisan wrote a reply+100 XP

4mos ago

This is a common gotcha when using Inertia.js on mobile browsers (especially Safari on iOS and some Android OEM browsers).

Your Inertia navigation didn’t create real browser history entries. The native back button sometimes closes the browser tab instead of navigating back through Inertia’s history.

More of an issue with IOS.