daniel-de-wit/nova-single-record-resource
Laravel Nova package that turns a resource into a single-record resource, so you can build singleton pages like settings, profiles, or site options. It hides index and create views and links directly to one editable record.
daniel-de-wit/nova-single-record-resource is a small trait package for Nova that turns a normal resource into a "singleton" resource: the navigation item links straight to the one-and-only record instead of an index table, and the index is effectively disabled. It is aimed at models like Site Settings, About Page, Company Profile, or any table that should only ever contain a single row.
Minimal steps:
composer require daniel-de-wit/nova-single-record-resource
// app/Models/Setting.php — plain Eloquent model, one row expected
// app/Nova/Setting.php
use DanielDeWit\NovaSingleRecordResource\SingleRecordResource;
use Laravel\Nova\Resource;
class Setting extends Resource
{
use SingleRecordResource;
public static $model = \App\Models\Setting::class;
public static function singleRecordId(): int
{
return 1; // id of the singleton row
}
}
Register the resource as usual in NovaServiceProvider::resources() — no other wiring is required.
Look first at:
singleRecordId() against the version you install; the API is small but the last release was Feb 2021).src/SingleRecordResource.php in vendor/daniel-de-wit/nova-single-record-resource — it is a short file; read it before customising so you know which Nova methods it overrides (indexQuery, authorizedToViewAny, navigation URL, etc.).viewAny on the policy, so that is the first place to check if the link does not appear.First use case: a Setting model backing a "Site Settings" page where an admin edits a single row of fields (site name, logo, contact email). Install the package, apply the trait, seed row id = 1, and the sidebar item becomes a one-click shortcut to the edit form.
Seed the singleton row eagerly. The trait assumes the record exists. Create it in a seeder or in the model's boot():
// database/seeders/DatabaseSeeder.php
Setting::firstOrCreate(['id' => 1], [
'site_name' => config('app.name'),
]);
Without this, the nav link points at a non-existent record and Nova will 404 when clicked.
Keep the model dumb and the resource rich. The singleton pattern works best when the model is a plain Eloquent model and all editing logic lives in the Nova resource fields. Treat the trait as a navigation/query concern only.
Hide the index affordances. Because there is only ever one record, disable creation and deletion on that resource to keep the UI honest:
public static function authorizedToCreate(Request $request): bool
{
return false;
}
public function authorizedToDelete(Request $request): bool
{
return false;
}
Also suppress the "Create" button in the detail toolbar via $this->canCreate if needed.
Policy must allow viewAny. Nova decides whether to render the nav item by calling authorizedToViewAny() on the resource, which delegates to the policy's viewAny. Even though the index is not shown, return true from viewAny for roles that should see the settings link:
public function viewAny(User $user): bool
{
return $user->isAdmin();
}
public function view(User $user, Setting $setting): bool
{
return $user->isAdmin();
}
Per-tenant / per-context singletons. If you need a different record per context (e.g., one settings row per team), compute singleRecordId() from the request rather than hardcoding 1:
public static function singleRecordId(): int
{
return Setting::forTenant(request()->user()->tenant_id)->value('id');
}
Be aware this runs on every nav render — keep the lookup cached or memoised if your nav is heavy.
Group and order like any other resource. The trait does not change grouping; public static $group = 'Configuration'; and $sort still work, so a single-record resource sits naturally next to your other navigation groups.
Pair with Laravel\Nova\Fields\* and resolveUsing sparingly. Since there is one record, fields with per-row complexity (repeaters, HasMany relationships) usually add more surface than they save. Prefer flat fields and, if you need structured data, store it in a JSON column.
Integration tip — testing. Because the trait is just a URL/query override, you can cover it with a small Nova HTTP test: assert a GET /nova-api/settings/1 returns 200 for an admin and 403/404 for a non-admin, and that the navigation JSON contains a link to /resources/settings/1.
It is old and lightly maintained (last release Feb 2021, 18 stars). It targets Nova 3-era APIs. Verify against your Nova version before relying on it in production; if it fails to boot or the nav link renders incorrectly after a Nova upgrade, the package is a single short trait — copy it into app/Nova/Concerns/ and maintain it yourself rather than waiting on upstream.
Missing record = dead link. The most common failure mode is forgetting to seed the singleton row. The link appears (policy allows it) but 404s. Guard with a firstOrCreate seeder in your deploy pipeline.
viewAny still governs visibility. Teams often set viewAny to false because "there is no index", and then complain the nav item is missing. Set it to true for the roles that should see the link.
The index route still exists. The trait hides/redirects, but the underlying /nova/resources/settings route is not physically removed. If you care about users bypassing the nav, gate it in the policy or redirect in a middleware/RouteServiceProvider.
Clearing Nova caches. After installing or changing the trait, run php artisan nova:cache (or artisan optimize:clear) if the nav still shows the old index-style link — Nova caches resource metadata.
Debugging a missing nav item: check in this order — (1) resource registered in NovaServiceProvider::resources(), (2) $displayInNavigation not false, (3) policy viewAny returns true, (4) singleRecordId() returns a valid id, (5) php artisan nova:cache.
Method names are version-sensitive. The exact method the trait requires (e.g., singleRecordId()) is defined by the README for the tagged release. If you install a fork or a newer tag, confirm the method name; getting it wrong typically surfaces as an abstract-method error on the resource class rather than a quiet failure.
Extension points are limited by design. The trait overrides navigation URL and index query; it does not give you hooks. If you need custom behaviour (e.g., "settings for a specific locale"), do not try to bend the trait — override indexQuery and static::url() on the resource yourself, or wrap a small base resource class around it.
Prefer a dedicated settings package if requirements grow. For a single row with a handful of fields this package is exactly right; once you need versioned settings, per-tenant overrides, or public config caching, move to a settings package (spatie/laravel-settings, bagisto-style config) and keep Nova as the front-end only.
How can I help you explore Laravel packages today?