Skip to content

Resources

A resource connects a model with a form and a table. From it Anvil derives the list, the create, detail and edit pages, their routes, the navigation entry, the permissions, the global search and – if enabled – the API. A step by step example: Your first resource.

namespace App\Resources;
use Anvil\Collection\Collection;
use Anvil\Collection\Columns\TextColumn;
use Anvil\Resource\Resource;
use Anvil\Schema\Fields\TextInput;
use Anvil\Schema\Form;
use App\Models\InvoiceModel;
final class InvoiceResource extends Resource
{
protected string $model = InvoiceModel::class;
protected string $recordTitleAttribute = 'number';
protected string $navigationIcon = 'receipt';
protected ?string $navigationGroup = 'Sales';
protected array $globalSearch = ['number'];
public function form(Form $form): Form
{
return $form->schema([
TextInput::make('number')->required()->maxLength(30),
]);
}
public function collection(Collection $collection): Collection
{
return $collection->columns([
TextColumn::make('number')->sortable()->searchable(),
]);
}
}
Terminal window
# New table: migration, model, entity and resource
php spark make:anvil-model Invoice --fields "number!:string:30,amount:decimal:12.2,due_on:date" --resource
# Existing table with a uuid column: model, entity and resource
php spark make:anvil-resource Invoice --table invoices

The generated form and columns are a starting point, see Commands. Then register the resource in the resources() of a panel:

public function resources(): array
{
return [InvoiceResource::class];
}

The model must extend Anvil\Data\Model, which brings UUIDs, tenant isolation and auditing, see Data rules.

Topic Members Details
Model, names, navigation $model, $slug, $label, $recordTitleAttribute, $navigationIcon, … Properties
Form of create, edit and detail page form() Forms, Schemas
List collection() Tables
Who may do what canView(), canUpdate(), …, policies Permissions
Loading and saving scopeQuery(), formValues(), afterSave(), … Loading and saving
Detail page relations(), $attachments, detailComponents(), history Detail page
Related records relations() Related records
Actions actions(), bulkActions() Actions
Search in the topbar $globalSearch Global search
Changes audit log History
API, workflows, exports $api, $workflows, $exportable API, Workflows, Exports

For a resource with the slug invoices in the panel admin:

Route Page Permission
/admin/invoices list invoices.view
/admin/invoices/create create form invoices.create
/admin/invoices/{uuid} detail page: read-only form, relations, components, attachments, history invoices.view
/admin/invoices/{uuid}/edit edit form invoices.update

URLs contain the UUID of the record, never its internal id. A record outside the tenant or scopeQuery() answers 404, as if it did not exist. All changes (save, delete, actions) are sent to the component endpoint of the panel with a token that names the record; the token is encrypted and authenticated by the server, so a client cannot redirect a request to another record.

Build URLs with $resource->url('index' | 'create' | 'view' | 'edit', $uuid) or path() for same-origin redirects.

A resource is instantiated per panel ($resource->panel()) and builds its URLs from it. Permissions are named by the slug, so a role with invoices.view may list invoices in every panel that registers a resource with that slug. See Several panels.