coringawc/filament-single-record-resource
Implements the single-record resource pattern for Filament. Replace index lists with a per-user “one record” resource (Profile, Settings, Wallet, Subscription). Traits handle routing, navigation, auth fallbacks, record resolution, and nested breadcrumbs.
Install the package:
composer require coringawc/filament-single-record-resource
Apply HasSingleRecordResource to your Resource:
use CoringaWc\FilamentSingleRecordResource\Traits\HasSingleRecordResource;
class MyWalletResource extends Resource
{
use HasSingleRecordResource;
public static function getPages(): array
{
return [
'view' => ViewMyWallet::route('/'),
'edit' => EditMyWallet::route('/edit'),
];
}
}
Add HasSingleRecord to your pages:
use CoringaWc\FilamentSingleRecordResource\Traits\HasSingleRecord;
class ViewMyWallet extends ViewRecord
{
use HasSingleRecord;
protected static string $resource = MyWalletResource::class;
}
Implement a "My Profile" resource:
UserProfileResource with HasSingleRecordResource.HasSingleRecord in ViewUserProfile and EditUserProfile.User model has a hasOne relationship to UserProfile (or similar).Resource with HasSingleRecordResource.HasSingleRecord in ViewRecord/EditRecord pages.belongsTo(User) relationship on the single-record model.resolveSingleRecordBuilder for query modifications:
public static function resolveSingleRecordBuilder(Builder $query): Builder
{
return $query->where('active', true);
}
resolveSingleRecord for complex logic (e.g., firstOrCreate):
public static function resolveSingleRecord(): ?Model
{
return auth()->user()->wallet()->firstOrCreate();
}
HasSingleRecordResource.HasSingleRecord in pages./wallets/companies/products → /companies/products).view() is allowed on the resolved record, even if viewAny() is denied.public function viewAny(User $user): bool
{
return false; // Deny index, but allow view() on user's record
}
SingleRecordResolvableResource for static analysis:
class MyWalletResource extends Resource implements SingleRecordResolvableResource
{
use HasSingleRecordResource;
}
Missing Relationships:
belongsTo(User) relationship, resolution fails silently. Fix: Explicitly define the relationship or override resolveSingleRecord.Nested Resource Confusion:
HasSingleRecordResource if they’re not part of the single-record chain. Fix: Only apply it to root single-record resources.Authorization Overrides:
view() on the resolved record can block access. Fix: Ensure policies grant view() for user-specific records.URL Routing Issues:
HasSingleRecord in all nested pages and enforce strict query scoping.Check Resolution:
Override resolveSingleRecord() temporarily to log the resolved record:
public static function resolveSingleRecord(): ?Model
{
$record = parent::resolveSingleRecord();
\Log::info('Resolved record:', ['record' => $record]);
return $record;
}
Verify Authorization:
Test viewAny() and view() separately to isolate permission issues:
// In a policy test:
$this->assertFalse($policy->viewAny($user));
$this->assertTrue($policy->view($user, $record));
Clear Cache:
After adding HasSingleRecordResource, run:
php artisan optimize:clear
Custom Resolvers:
Extend resolveSingleRecord() for tenant-aware or multi-user scenarios.
Dynamic Pages: Dynamically register pages based on resolved record attributes:
public static function getPages(): array
{
$pages = ['view' => ViewMyWallet::route('/')];
if ($record->hasPremiumFeatures) {
$pages['premium'] = PremiumSettings::route('/premium');
}
return $pages;
}
Fallback Logic:
Override getIndexUrl() or getNavigationGroup() to customize sidebar behavior:
public static function getIndexUrl(): string
{
return static::getResourceUrl('view');
}
DB_DATABASE is set in workbench containers to avoid SQLite connection issues (fixed in v1.1.1).resolveSingleRecordBuilder to avoid N+1 queries:
return $query->with('transactions');
How can I help you explore Laravel packages today?