Demo

Setting

How plugins contribute UI to the global settings page with AppRegistry.setting.add, and read/update settings via base-frontend hooks.

Overview

The shell owns the Settings hub. Plugins inject panels during register() with AppRegistry.setting.add, and use @quan-erp/base-frontend hooks to load and persist key/value settings (platform keys or plugin-owned keys).

  • Register UI with AppRegistry.setting.add({ pluginName, element })
  • Load with useSettingQuery({ mode: 'private' | 'public' })
  • Persist with useUpdateSettingQuery()
  • Prefer existing SettingKeys before inventing new ones

1. Register setting UI

In frontend/src/index.tsx, call AppRegistry.setting.add inside PluginModule.register. Pass pluginName (metadata.name) and a React element for your panel.

  • SettingComponents shape: { pluginName: string, element: ReactElement }
  • pluginName must equal module.metadata.json name
  • Keep the panel self-contained — load/save inside the component, not in register()
TSXfrontend/src/index.tsx
1import type { AppRegistryState, PluginModule } from "@quan-erp/shared-types"; 2import metadata from "../module.metadata.json" with { type: "json" }; 3import { MyPluginSettings } from "./page/setting/my-plugin-settings"; 4 5const Plugin: PluginModule = { 6 register(AppRegistry: AppRegistryState) { 7 AppRegistry.setting.add({ 8 pluginName: metadata.name, 9 element: <MyPluginSettings />, 10 }); 11 }, 12}; 13 14export default Plugin;

2. Build the settings panel

Put panel UI under frontend/src/page/setting/ (or similar). Use base-frontend setting hooks and shared-ui form controls. Gate sensitive controls with the same APIs you use elsewhere (Protected / requiredApis).

TSXfrontend/src/page/setting/my-plugin-settings.tsx
1import { useSettingContext, useSettingQuery } from "@quan-erp/base-frontend"; 2 3export function MyPluginSettings() { 4 const { data: settings, isLoading } = useSettingQuery({ mode: "private" }); 5 const { onUpdateSetting, editedSetting } = useSettingContext(); 6 7 if (isLoading) return null; 8 9 return ( 10 <div className="flex flex-col gap-4 p-4"> 11 <h3>My plugin</h3> 12 {/* Bind controls to settings / editedSetting; 13 call onUpdateSetting(key, value, datatype) on change */} 14 </div> 15 ); 16}

3. Settings hooks

Import hooks from @quan-erp/base-frontend.

  • useSettingQuery({ mode: 'private' | 'public' }, option?) — load setting map
  • useUpdateSettingQuery() — persist settings (wrapper uses { many: true })
  • useSettingStore — client cache; note double-call pattern useSettingStore()()
  • useSettingContext — onUpdateSetting(key, value, datatype, userId?, isPublic?), editedSetting
  • useIsContainInBottomNavBar(route) — whether a route is in mobile bottom-nav config
TSXread + update
1import { 2 useSettingQuery, 3 useUpdateSettingQuery, 4} from "@quan-erp/base-frontend"; 5 6const { data = {} } = useSettingQuery({ mode: "private" }); 7const updateSetting = useUpdateSettingQuery(); 8 9// Prefer useSettingContext().onUpdateSetting inside the Settings hub. 10// For standalone flows: 11await updateSetting.mutateAsync({ /* SettingMapValue */ });

4. Setting keys

Platform SettingKeys include LOCALE, THEME, DATE_FORMAT, TIMEZONE, BUSINESS_NAME, PRIMARY_COLOR, and more. Reuse them when your plugin should follow shell preferences. For plugin-specific config, use a clear namespaced key and a matching SettingDataType.

  • Private vs public mode — pick the mode that matches who should read the value
  • Do not invent duplicates of LOCALE / THEME / TIMEZONE
  • Align datatype with what onUpdateSetting / backend SettingService expect

Critical rules

  • Register only in register() via AppRegistry.setting.add
  • pluginName must match metadata.name
  • element is a ReactElement (JSX), not a component type
  • Use base-frontend setting hooks — do not bypass with ad-hoc axios to settings unless following platform APIs
  • Protect privileged controls with the same requiredApis pattern as pages

Checklist

  • Create page/setting panel component
  • AppRegistry.setting.add({ pluginName, element }) in register()
  • Wire useSettingQuery / useSettingContext (or useUpdateSettingQuery)
  • Reuse platform keys when applicable
  • Verify panel appears on Settings after quan-erp watch and install

Common failures

  • Panel missing — not registered, wrong pluginName, or plugin not installed
  • element type error — passed a component function instead of <Component />
  • Settings not loading — wrong mode (private vs public) or hooks used outside settings providers
  • Values not saving — onUpdateSetting / mutate not called, or datatype mismatch
  • Duplicate platform keys — overwriting LOCALE/THEME unexpectedly from a plugin panel