Skip to content

Examples

Generate a Resource

Generate a Resource, expose its App Menu destination, and inspect the resulting list, form, detail, and Inspector surfaces.

Example type Recipe

Overview

Resource example

The first App recipe follows the whole path from App generation to tenant installation. This recipe instead focuses on what changes on screen around nexia-apps:make-package-resource workshop Note. Follow the Note App Menu destination into the generated list, create, detail, edit, and Inspector states.

Final result

AreaWhat appearsWhere to confirm it
App MenuOperations → Note inside the Workshop AppThe Workshop App Menu in the tenant Shell
Work surfacesList, create, detail, and edit routes plus a list-side Inspector/apps/workshop/notes
Baseline datawsp_notes records with name, status, created, and updated valuesThe screen and tenant database
API and permissionsFive read, create, update, and delete routes with workshop.note.* permissionsPackage routes and the Resource Module
App sourceModel, status enum, Policy, Controller, migration, Resource Module, Filament Resource, frontend surfaces, locales, and a Feature testGenerator output under packages/workshop/

The generator only prepares App source. The menu and screens become visible after host activation, tenant installation, user permission assignment, and reload of the running frontend.

How Core processes the Resource Module

NoteModule is the integration spine among the generated files. It extends the SDK AbstractResourceModule and explicitly implements the required capability interfaces.

Code example
PHP
final class NoteModule extends AbstractResourceModule implements
    NavigationContribution,
    PermissionContribution,
    ResourceAuthorizationContribution,
    ResourceCatalogContribution,
    ShellResourceContribution

AbstractResourceModule fixes the appDescriptors() implementation that publishes the public ResourceDescriptor from resourceModuleDefinition(). Generated traits derive navigation, CRUD permissions, destination actions, and Shell contracts from one Resource key, while NoteController and NotePolicy still handle model access and write authorization.

WorkshopAppManifest registers Contribution/ as a discovery root
  → NexiaContributionRegistry finds NoteModule for each implemented interface
  ├─ ResourceCatalogRegistry: workshop.note ↔ Note model, owning App, authorization contract
  ├─ AppDescriptorCatalog: public field and search schema
  ├─ NavigationContributionRegistry: Operations → Note destination
  ├─ PermissionCatalog: workshop.note.{read,create,update,delete}
  └─ Shell resource composition: list/show/form/inspector components and routes
       → Core Shell hosts the surfaces after App-installation and actor-permission checks

The generator coordinates several files because the backend model, Policy and routes, Core catalogs, and frontend surfaces must all share the same workshop.note identity. It is not merely a convenient block of files to copy; that shared contract is what keeps list, detail, edit, and Inspector behavior consistent.

Person fields start with Party

All three Resource Module scaffold paths include the same commented recipe for a person field:

Generated ResourceRecipe owner
App Package Resourcestubs/package-resource/resource-module.stub
Host standard Resourcestubs/resource/resource-module.stub
Host document Resourcestubs/resource-document/resource-module.stub

The recipe is commented because a generic Resource does not necessarily own a person field. When the domain does, uncomment it in the generated Module, rename the field for the business meaning, and keep Party as the selected identity:

Code example
PHP
'author_party_public_id' => [
    'type' => 'resource_reference',
    'accepted_resource_keys' => ['directory.party'],
    'selector_purpose' => 'workshop.reference-options',
    'selector_permissions' => ['workshop.note.create'],
    'party_selection' => [
        'types' => ['person'],
        // Add only when this field requires an active login membership:
        // 'eligibility' => 'active_legal_entity_member',
    ],
],

The optional eligibility constraint narrows which Parties may be selected; it does not turn membership into permission. Add the matching migration column, model/API handling, validation, and form/display fields as one change. For the full Party-first choice between Party, Legal Entity, Operating Unit, and an owner-domain exact employment binding, see Resource References.

Before you run it

  • Core's app, vite, postgres, and redis services are running.
  • Core runs with APP_ENV=local; this recipe uses the local-only development installer.
  • The packages/workshop/ App Package exists and its routes carry the app.installed:workshop guard.
  • To observe a complete first generation, use a Workshop App baseline where Note does not exist. The generator reports existing targets as SKIP instead of overwriting them.
  • You have a practice tenant ID and an access administrator who can assign the generated Workshop App permissions.

List the tenant IDs:

Code example
Shell
task artisan -- tenants:list --no-ansi

1. Preview and generate the Resource

commands.sh first reports the plan with --dry-run, then runs the same command without the preview flag.

Code example
Shell
sh docs/developers/examples/resource/commands.sh

The dry-run reports 20 new Resource files plus App Manifest, route, and locale edits as WOULD without writing them. The real run reports new files as CREATE, package merge points as UPDATE, and ends with:

Package resource scaffolded.

nexia-apps:make-package-app already generates the source for the App Launcher entry and Overview screen. This Resource command specifically adds the Operations → Note destination and the Note list, create, detail, edit, and Inspector screens.

Neither generator changes the browser immediately because each command only writes source. Their results become visible after host activation, tenant installation, and frontend reload:

GeneratorSource created immediatelyWhat appears in the browser after exposure
nexia-apps:make-package-appWorkshop App definition and Overview screenWorkshop in the App Launcher and Overview in its App Menu
nexia-apps:make-package-resourceNote Resource and work screensOperations → Note plus list, create, detail, edit, and Inspector screens

The representative generated paths are:

packages/workshop/
  src/Enums/NoteStatus.php
  src/Models/Note.php
  src/Policies/NotePolicy.php
  src/Http/Controllers/NoteController.php
  src/Contribution/Resources/NoteModule.php
  database/migrations/tenant/*_create_wsp_notes_table.php
  resources/js/resources/notes/
    surface/Note{List,Show,Form}Surface.tsx
    section/Note{List,Show,Form}Section.tsx
    inspector/NoteInspector.tsx
  resources/lang/{en,ko,zh}.json
  tests/Feature/NoteAuthorizationTest.php

2. Expose the screen to a tenant

Set TENANT to the practice tenant ID. These commands synchronize the new translations and permissions, then migrate, initialize, and install the App for the selected local tenant before reloading the long-running processes.

Code example
Shell
TENANT=abc123def456

task artisan -- nexia-runtime:clear-translation-catalog-cache --no-ansi
task artisan -- nexia-apps:activate-package-app workshop
task artisan -- nexia-apps:install-dev-app workshop --tenant="$TENANT" --with-prerequisites
task app:reload
docker compose restart vite
docker compose up --wait --no-deps vite

Comparing an activated and installed App baseline with the state after exposing the Note Resource, the App Menu changes as follows:

Before                     After
Workshop                   Workshop
└─ Overview                ├─ Overview
                           └─ Operations
                              └─ Note

Choosing Note opens the list at /apps/workshop/notes. Before a record is created, the page shows search, filtering, refresh, the New Note action, and the empty state.

Activation registers permission definitions but does not grant them to a user. In Access Management, put workshop.note.read, create, and update in an App Role and assign it to the user who will verify the screen. This recipe uses --record-owner=legal_entity. The list is page-local: it can target one or more authorized Legal Entities with legal_entity_public_ids[]. Creating a Note requires its own explicit, single legal_entity_public_id; do not select a global Legal Entity in the Shell.

3. Verify in the browser

Reload the practice tenant and follow this path:

  1. Open Other apps → Workshop from the App Launcher.
  2. Choose Operations → Note from the App Menu.
  3. From the empty list, select New Note and save Installation check note.
  4. Select the new row to open its right-side Inspector, then open its detail and edit screens.

The Workshop App Menu showing Note under Operations beside the Installation check note detail screen

This is the actual result after completing this stage. On the left, the App Menu shows Operations → Note, added by the generator. The main pane shows the generated detail screen with ID, name, status, created and updated timestamps, plus the permission-controlled Edit action.

Each action moves the work area into a distinct state:

ActionScreen changeWhat to confirm
Choose Operations → NoteThe empty list opensSearch, status filter, and New Note action
Choose New NoteThe create form opens in a work tabRequired name input plus save and cancel actions
Save Installation check noteThe detail screen opensName, Draft status, created time, and updated time
Select the row in the listThe right-side Inspector opensThe same basic information plus view and edit actions
Choose EditThe edit form opens in a work tabExisting name and unsaved field changes

The generated list already provides:

  • name search, status filtering, allowed-column sorting, and pagination;
  • name and status columns with permission-aware view and edit actions;
  • a basic-information Inspector and a separate detail work tab;
  • create and edit forms with field-error feedback; and
  • Draft, Active, and Archived status labels.

The delete route and UI are also scaffolded, but the generated Policy's delete() method always returns false. The delete action should remain unavailable until the App defines its retention and audit rules.

Verify from the terminal

Confirm that the host discovers the package contributions and routes:

Code example
Shell
task artisan -- nexia-apps:doctor-package-app workshop
task artisan -- route:list --path=workshop/notes

The doctor must contain no FAIL row. The route list must contain canonical /api/workshop/notes collection and member routes, together with legacy Legal Entity compatibility routes.

Common errors

SymptomCauseFixConfirm
Package app not found at packages/workshop. Run nexia-apps:make-package-app first.The Workshop package from the prerequisite recipe does not existComplete Create the first App to create packages/workshopThe Resource dry run lists the expected files as WOULD
Generation succeeds but Operations → Note is absentApp activation, development installation, or process refresh is incompleteApply all activation, development-installation, PHP, and Vite refresh commands from step 2 to the same tenant/apps/workshop/notes opens and Note appears in the App Menu
The list opens but New Note or Edit is absentThe user lacks workshop.note.create or workshop.note.updateAdd the required permissions to an App Role and assign it with an Access Grant in the current Legal EntityRefreshing shows only the actions allowed by those permissions
Delete is absentThe generated NotePolicy::delete() denies deletion by defaultDefine retention and audit rules before implementing the App Policy and UI togetherDelete appears only for a user allowed by the finished permission and Policy

What to add next

The generated result is an executable work-screen baseline, not a finished note domain. Extend the coordinated surfaces according to the product requirements:

CapabilitySource to change
Body, author, category, and other domain fieldsMigration, model, Controller validation, information schema, and form
Real lifecycle states and transitionsNoteStatus, explicit App commands, and Policy
Role-specific read, create, and update scopeResource Module permissions and NotePolicy
Menu group, icon, and orderNoteModule::$navigation
CSV and XLSX export or importModel ExportImportable and transferColumns()

The list, detail, form, and Inspector under resources/js/resources/notes/ share one Resource contract. When adding a field, review the API serialization and every display surface instead of changing only one screen.

Included files

Code example
Shell
#!/usr/bin/env sh
set -eu

# --record-owner accepts only `tenant` or `legal_entity`.
# Preview first; the command reports its package-local output without writing.
task artisan -- nexia-apps:make-package-resource workshop Note \
  --record-owner=legal_entity \
  --label-ko=노트 \
  --label-ko-plural=노트 \
  --label-zh=笔记 \
  --label-zh-plural=笔记 \
  --dry-run

# Run the same command without --dry-run only after reviewing the preview.
task artisan -- nexia-apps:make-package-resource workshop Note \
  --record-owner=legal_entity \
  --label-ko=노트 \
  --label-ko-plural=노트 \
  --label-zh=笔记 \
  --label-zh-plural=笔记