Skip to content

Mobile App Config Reference ​

The features.mobile_app key controls NativePHP Mobile frontend generation. It is a sub-key of the module config's features object.


Shape ​

json
"mobile_app": {
  "enabled": true,
  "icon":    "PackageIcon",
  "list": {
    "card": {
      "icon":           "PackageIcon",
      "titleField":     "name",
      "subtitleFields": ["code"],
      "bodyFields":     ["description"],
      "footerBadge":    "status?.name"
    }
  }
}
KeyTypeDefaultDescription
enabledbooleanfalseAlso not enforced by this package — ModuleConfigContract::isMobileAppEnabled() exists but is called from nowhere in src/ (confirmed: its only caller in the whole repo is its own unit test). Whether mobile output actually gets generated is entirely up to whether the calling code invokes the MobileApp\* generator classes at all — same caveat as mode below, just less obviously flagged.
modestring"online"Not read by this package. A consumer-side convention (e.g. SYSTEM_SHELL's make:module) for deciding whether it invokes the sync generators. See Sync Mode below.
iconstring"LayersIcon"Lucide icon name used in the mobile navigation and list card header.
list.cardobjectauto-resolvedControls the mobile list card layout — see below.

list.card Object ​

The mobile list displays records as cards. This object controls what appears in each card slot.

json
"card": {
  "icon":           "PackageIcon",
  "titleField":     "name",
  "subtitleFields": ["sku", "code"],
  "bodyFields":     ["description"],
  "footerBadge":    "status?.name"
}
KeyTypeDescription
iconstringLucide icon for the card header. Falls back to mobile_app.icon.
titleFieldstringColumn name shown as the card title. Falls back to frontend.list.primaryDisplayField then "name".
subtitleFieldsstring[]Column names shown below the title as secondary text. Auto-resolved from frontend.list.fields if omitted.
bodyFieldsstring[]Column names shown in the card body. Auto-resolved: prefers columns matching `description
footerBadgestring|nullJS accessor path for a colored badge in the card footer (e.g. "status?.name"). null to hide.

Auto-resolution Fallback Chain ​

If card is empty or partial, MobileAppConfigResolver fills gaps:

  1. icon → mobile_app.icon → "LayersIcon"
  2. titleField → frontend.list.primaryDisplayField → frontend.view.primaryDisplayField → "name"
  3. subtitleFields → first non-title, non-system, non-action column from frontend.list.fields
  4. bodyFields → first column whose name matches description|body|notes|details|bio|summary|about; empty if none match

Generated Files (when enabled: true) ​

FilePath
List pageMOBILE_APP/resources/js/src/pages/modules/{group}/{Module}/{Module}ListPage.vue
Create pageMOBILE_APP/resources/js/src/pages/modules/{group}/{Module}/{Module}CreatePage.vue
Edit pageMOBILE_APP/resources/js/src/pages/modules/{group}/{Module}/{Module}EditPage.vue
Delete pageMOBILE_APP/resources/js/src/pages/modules/{group}/{Module}/{Module}DeletePage.vue
View layoutMOBILE_APP/resources/js/src/pages/modules/{group}/{Module}/{Module}DetailsLayout.vue
View overviewMOBILE_APP/resources/js/src/pages/modules/{group}/{Module}/{Module}DetailsOverviewPage.vue
View historyMOBILE_APP/resources/js/src/pages/modules/{group}/{Module}/{Module}DetailsHistoryPage.vue
List componentMOBILE_APP/resources/js/src/pages/modules/{group}/{Module}/Components/{Module}ListComponent.vue
Create formMOBILE_APP/resources/js/src/pages/modules/{group}/{Module}/Components/{Module}CreateFormComponent.vue
Edit formMOBILE_APP/resources/js/src/pages/modules/{group}/{Module}/Components/{Module}EditFormComponent.vue
Delete formMOBILE_APP/resources/js/src/pages/modules/{group}/{Module}/Components/{Module}DeleteFormComponent.vue
RoutesMOBILE_APP/resources/js/src/pages/modules/{group}/{Module}/routes.ts
Custom feature tab (per delegation/action with hasUI: true)MOBILE_APP/resources/js/src/pages/modules/{group}/{Module}/{Module}Details{FeatureName}Page.vue (CustomFeatureTabPageGenerator)
Action modal/page (per action with hasUI: true)MOBILE_APP/resources/js/src/pages/modules/{group}/{Module}/Components/{Module}{ActionName}Modal.vue (Components\Actions\ActionModalGenerator)
modules.jsonMOBILE_APP/resources/js/src/modules.json (updated in-place)
menus.jsonMOBILE_APP/resources/js/src/menus.json (updated in-place)

Mobile Backend Files (always generated by make:mobile-modules / make:mobile-scaffold) ​

These are generated regardless of mobile_app.enabled when running a mobile scaffold command:

FilePath
ModelMOBILE_APP/app/Modules/{Group}/{Module}/{Module}Model.php
ControllerMOBILE_APP/app/Modules/{Group}/{Module}/{Module}Controller.php
RoutesMOBILE_APP/app/Modules/{Group}/{Module}/Routes/api.php
MigrationMOBILE_APP/app/Modules/{Group}/{Module}/Migrations/{date}_create_{table}_table.php
Seeder dataMOBILE_APP/app/Modules/{Group}/{Module}/Seeders/{Module}SeederData.json
List serviceMOBILE_APP/app/Modules/{Group}/{Module}/Services/{Module}ListService.php
Create serviceMOBILE_APP/app/Modules/{Group}/{Module}/Services/{Module}CreateService.php
View serviceMOBILE_APP/app/Modules/{Group}/{Module}/Services/{Module}ViewService.php
Edit serviceMOBILE_APP/app/Modules/{Group}/{Module}/Services/{Module}EditService.php
Delete serviceMOBILE_APP/app/Modules/{Group}/{Module}/Services/{Module}DeleteService.php
Delete checkMOBILE_APP/app/Modules/{Group}/{Module}/Services/{Module}DeleteCheckService.php
Activity listMOBILE_APP/app/Modules/{Group}/{Module}/Services/{Module}ActivityListService.php
Bulk actionMOBILE_APP/app/Modules/{Group}/{Module}/Services/{Module}BulkActionService.php
Sync serviceMOBILE_APP/app/Modules/{Group}/{Module}/Services/{Module}SyncService.php (offline/both only)
Sync composableMOBILE_APP/resources/js/src/pages/modules/{group}/{Module}/composables/use{Module}Sync.ts (offline/both only)
RegistryMOBILE_APP/app/Modules/registry.json (updated in-place)

Sync Mode ​

This package does not gate sync-file generation on a mode key. MobileSyncServiceGenerator and MobileSyncComposableGenerator are plain generators like any other — when a caller invokes generate() on them, they unconditionally write {Module}SyncService.php / use{Module}Sync.ts, regardless of what (if anything) is set at features.mobile_app.mode. There is no code in this package that reads a mode key to decide whether to run them; a grep for mode across src/ turns up nothing related to mobile sync gating.

The table below ("online" / "offline" / "both") describes a consumer-side convention — e.g. how SYSTEM_SHELL's make:module command chooses which mobile generators to invoke based on module.json. If you're driving this package directly (not through make:module), it is entirely your responsibility to read mode (or any other flag) from your own config and decide whether to call the sync generators at all — the engine will happily generate sync files for every mobile module you point it at, mode or no mode.

ValueSyncServiceSyncComposableUse when
"online" (default)✗✗Module data is always fetched live from the API — no local storage needed.
"offline"✅✅Module works entirely offline; data is synced to/from the device on demand.
"both"✅✅Module supports both live API access and offline sync.

A real gap if you follow "online mode → skip the sync generators" literally

MobileControllerGenerator/MobileApiRoutesGenerator don't branch on mode either — their stubs (mobile_app/backend/controller.stub, mobile_app/backend/routes.stub) unconditionallyuse/reference {Module}SyncService and wire syncPush/syncPull routes and methods for every mobile module, regardless of mode. If a consumer's own tooling follows this table's convention and skips MobileSyncServiceGenerator for a mode: "online" module, the resulting Controller.php references a {Module}SyncService class that was never generated — a Class not found the first time /sync/push or /sync/pull is actually hit. As of this writing there is no config-driven way to suppress the Controller/Routes sync wiring itself; either always run MobileSyncServiceGenerator too (safe, just generates an unused class for online-only modules), or patch the Controller/Routes stubs in a consumer-side template override.

How to set it (consumer convention, not enforced by this package) ​

Edit the module's module.json (at BACKEND/app/Project/Modules/{Group}/{Module}/module.json):

json
"features": {
  "mobile_app": {
    "enabled": true,
    "mode": "offline"
  }
}

Then re-run php artisan make:module {Group}/{Module} --force. The mode value persists across re-generations. This flow only works if the calling application's make:module command actually reads mode and conditionally invokes the sync generators — this package itself has no opinion on the key.

What the sync files do ​

{Module}SyncService.php — two API endpoints:

  • POST /api/{route-prefix}/sync/push — accepts records from the device, upserts by uuid, sets last_synced_at
  • GET /api/{route-prefix}/sync/pull?since=2026-01-01T00:00:00Z — returns records updated since a given timestamp

use{Module}Sync.ts — Vue composable that wraps both endpoints with:

  • Reactive isOnline / isSyncing / canSync state
  • push(records) and pull() methods
  • lastSyncedAt persisted in localStorage

Notes ​

  • NativePHP Mobile embeds a full Laravel app on the device. The Vue SPA calls http://localhost/api/... locally.
  • The device SQLite cannot be introspected during development. Use make:mobile-modules --blueprint=file.json (runs from SYSTEM_SHELL/BACKEND) or make:mobile-scaffold (runs from inside MOBILE_APP against the local dev SQLite).
  • All mobile backend stubs are SQLite-safe: TEXT instead of JSON columns, LIKE for text search, direct uuid()->primary() syntax, no MySQL-only expressions.

Released under the Apache-2.0 License.