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(), ]); }}Creating a resource
Section titled “Creating a resource”# New table: migration, model, entity and resourcephp 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 resourcephp spark make:anvil-resource Invoice --table invoicesThe 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.
What a resource defines
Section titled “What a resource defines”| 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 |
Pages and routes
Section titled “Pages and routes”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.
The same resource in several panels
Section titled “The same resource in several panels”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.