Skip to main content

Resources

A resource is the Qore definition for one Eloquent model.

Resources extend QoreResource<TModel> and describe how that model appears in Qore: CRUD pages, fields, permissions, actions, relation tables, global search, logbook entries and URLs.

Use a resource when a model needs the standard application surface. A working resource normally has five app pieces:

  • the resource class;
  • registration in AppServiceProvider;
  • seeded permissions;
  • a model policy;
  • a menu item.

Create A Resource Class

Create the resource in app/Resources. This example assumes an App\Models\Product model with name and price columns.

<?php

namespace App\Resources;

use App\Models\Product;
use Illuminate\Database\Eloquent\Model;
use Qore\Next\System\Field\Field;
use Qore\Next\System\Fields\CreatedAtField;
use Qore\Next\System\Fields\NumberField;
use Qore\Next\System\Fields\TextField;
use Qore\Next\System\Fields\UpdatedAtField;
use Qore\Next\System\Resource\QoreResource;

/**
* @extends QoreResource<Product>
*/
class ProductResource extends QoreResource
{
/**
* @return class-string<Product>
*/
public function modelClass(): string
{
return Product::class;
}

public function labelSingular(): string
{
return __('common.products.singular');
}

public function labelPlural(): string
{
return __('common.products.plural');
}

/**
* @param Product $model
*/
public function modelTitle(Model $model): string
{
return $model->name;
}

public function icon(): string
{
return 'package';
}

/**
* @return Field[]
*/
public function fields(): array
{
return [
TextField::make('name', __('common.name'))
->setRules(fn () => ['required', 'string', 'max:255']),

NumberField::make('price', __('common.price'))
->setRules(fn () => ['required', 'numeric', 'min:0'])
->setPrecision(2),

CreatedAtField::make(),
UpdatedAtField::make(),
];
}
}

ProductResource becomes the resource name products. Qore uses that name for default URLs and permission names, such as view any products.

At minimum a resource defines:

  • modelClass()
  • labelSingular()
  • labelPlural()
  • icon()
  • fields()

Register The Resource

Register resources in AppServiceProvider::register() so Qore can expose their routes, globals, relation options and menu metadata.

use App\Resources\ProductResource;

use function Qore\Next\System\Helpers\qore;

public function register(): void
{
qore()
->registerResource(new UserResource)
->registerResource(new ProductResource);
}

Registering the resource does not grant access by itself. Permissions and policies still decide what the user may do.

Retrieve A Registered Resource

Use getResource() when the resource may be absent and getResourceOrFail() when it must be registered:

$resource = qore()->getResource('products');
$resource = qore()->getResource(Product::class);
$resource = qore()->getResource($product);

$resource = qore()->getResourceOrFail($product);

Both methods accept a resource name, a registered model class or an Eloquent model instance. Model instances are resolved through their morph class. getResource() returns null when no resource is registered; getResourceOrFail() throws a RuntimeException.

Seed Permissions

Add CRUDA permissions for the resource in database/seeders/PermissionSeeder.php.

use Illuminate\Database\Seeder;
use Qore\Next\System\Models\Permission;

use function Qore\Next\System\Helpers\admin_role;
use function Qore\Next\System\Helpers\qore;

class PermissionSeeder extends Seeder
{
public function run(): void
{
qore()->permissions()->cruda('products');

admin_role()->givePermissions(Permission::query()->select('id')->get());
}
}

cruda('products') creates the default resource permissions:

  • view any products
  • view products
  • create products
  • update products
  • delete products

Use the same resource name everywhere unless the resource overrides its permission methods. See Permissions for custom permission names.

Create The Policy

Create a policy for the model in app/Policies/ProductPolicy.php.

namespace App\Policies;

use App\Models\Product;
use App\Models\User;

class ProductPolicy
{
public function viewAny(User $user): bool
{
return $user->hasPermissionToViewAny('products');
}

public function view(User $user, Product $model): bool
{
return $user->hasPermissionToView('products', $model);
}

public function create(User $user): bool
{
return $user->hasPermissionToCreate('products');
}

public function update(User $user, Product $model): bool
{
return $user->hasPermissionToUpdate('products', $model);
}

public function delete(User $user, Product $model): bool
{
return $user->hasPermissionToDelete('products', $model);
}
}

Laravel auto-discovers policies that follow the normal App\Models\Product and App\Policies\ProductPolicy naming convention. Register the policy manually only when the class lives outside that convention:

Gate::policy(Product::class, ProductPolicy::class);

Add It To The Menu

Add the resource to the application menu, usually in app/Menu/AppMenu.php.

use function Qore\Next\System\Helpers\qore;

$this->addResourceMenuItem(resource: qore()->getResourceOrFail('products'));

addResourceMenuItem() uses the resource label, icon, index URL and getViewAnyPermissions() automatically. If the user cannot viewAny, the frontend hides the menu item from the globals payload.

Resource Checklist

When a resource does not show up, check these in order:

  1. The resource is registered in AppServiceProvider::register().
  2. The resource name used in permissions matches ProductResource -> products.
  3. The permission seeder has run and the current role has the permissions.
  4. The policy returns true for the action you are testing.
  5. The menu includes the resource or a custom menu item that points to it.

Common Overrides

Most resources start with the required methods and override only what changes the user experience:

  • modelTitle(Model $model): string for readable titles.
  • modelDescription(Model $model): ?string for optional context in relation options and global search.
  • indexQuery(): Builder for default scoping and eager loading.
  • optionsQuery(): Builder for relation select options, attach-existing choices and global search queries. It defaults to indexQuery().
  • getSelectOption(Model $model): SelectOption for relation option labels and descriptions. Keep the option value as the model key.
  • indexTabs(): TableTab[] for index table tabs.
  • indexLayout(): Node, createLayout(): Node, detailLayout(Model $model): Node and editLayout(Model $model): Node for custom page layouts.
  • formFieldLayout(array $fields): ?Node for a shared create/edit form field layout while keeping Qore's default create and edit pages.
  • actions(): ResourceAction[] for bulk and detail actions.
  • alerts(): ResourceAlert[] for detail-page warnings or notices.
  • tags(Model $model): TagNode[] for tags next to the detail-page actions.
  • getGlobalSearchFields(): Field[] and globalSearchNode(Model $model): Node for search behaviour.

getOptions(SelectMetadata $metadata) invokes getOptionsCallback() and returns normalized SelectOption values. The callback may return any iterable of SelectOption, SelectOptionable or Eloquent model values. Models that do not implement SelectOptionable are resolved through their registered resource.

See the focused pages for the details: