Open Closed

Help with adding new settings to the UI admin console #10700


User avatar
0
Raff created

Hi,

I'm having trouble working out how to add new settings to the react UI, the AI Agent isn't working as it keeps going into an endless searching mode which just sits there and is burning credits like crazy. I've created new settings for SendGrid emailing, from the information I found that the admin console is supposed to automatically identify the settings and render them, but I couldn't get this to work.

Here's what I have so far:

public static class SendGridSettings { private const string Prefix = "SendGrid";

public const string Enabled = Prefix + ".Enabled";
public const string FromName = Prefix + ".FromName";
public const string FromAddress = Prefix + ".FromAddress";
public const string ApiKey = Prefix + ".ApiKey";

}

public class SendGridSettingDefinitionProvider : SettingDefinitionProvider { public override void Define(ISettingDefinitionContext context) { context.Add( new SettingDefinition(SendGridSettings.Enabled, defaultValue: false.ToString(), isVisibleToClients: true), new SettingDefinition(SendGridSettings.FromName, defaultValue: "<my email sender name>", isVisibleToClients: true), new SettingDefinition(SendGridSettings.FromAddress, defaultValue: "<my email sender>", isVisibleToClients: true), new SettingDefinition(SendGridSettings.ApiKey, isEncrypted: true, isVisibleToClients: true) ); } }

public static class SendGridPermissions { public const string GroupName = "SettingManagement";

public const string SendGrid = GroupName + ".SendGrid";

}

public class SendGridPermissionDefinitionProvider : PermissionDefinitionProvider { public override void Define(IPermissionDefinitionContext context) { var settingManagementGroup = context.GetGroupOrNull("SettingManagement"); if (settingManagementGroup == null) { settingManagementGroup = context.AddGroup("SettingManagement", L("Permission:SettingManagement")); }

    settingManagementGroup.AddPermission(SendGridPermissions.SendGrid, L("Permission:SendGrid"));
}

private static LocalizableString L(string name)
{
    return LocalizableString.Create&lt;MyResource&gt;(name);
}

}

This didn't create a new tab in the admin console so i tried creating a pSendGridSettings.tsx in react/src/pages/settings but I couldn't get this to work with the correct imports as I couldn't find any documentation on how to do this.

What is the best way to add new settings to the admin console?

Regards Raff

Markdown supported.
Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)

2 Answer(s)
  • User Avatar
    0
    AI-Bot created
    Support Team ABP Agent
    • In ABP React UI, the Setting Management page does not automatically create a new tab/group just because you added SettingDefinitions. Defining settings only makes them available to the backend (and to the client if isVisibleToClients: true), but the admin UI tabs are contributed explicitly by the UI package/module.

    • The recommended/standard way to add a new “tab” (group) in the Administration → Settings (Setting Management) UI is:

      1. Define your settings (you already did with SendGridSettingDefinitionProvider).
      2. Create an application service that reads/writes those settings (typically using ISettingProvider + ISettingManager).
      3. Add a custom UI group to the Setting Management page:
        • For MVC/Blazor/Angular there are documented “custom setting group” contribution mechanisms in the Setting Management module docs.
        • For React, there isn’t an equivalent “auto-discover my setting definitions and render them as a tab” mechanism in the same way; you typically create your own page/component and wire it into the admin menu, or extend the setting-management UI in the way that UI package exposes (if it does for your version). If you were trying to drop a pSendGridSettings.tsx under src/pages/settings, that won’t work unless your React app’s routing/menu is configured to load it.
    • Backend part (what you should keep / adjust):

      • Keep isVisibleToClients: true for non-secret values.
      • Keep isEncrypted: true for ApiKey (good).
      • Don’t expect this to create an admin tab by itself; it only makes the setting available.
    • Practical approach for React (works reliably):

      1. Create an app service like:
    public class SendGridSettingsAppService : ApplicationService
         {
             private readonly ISettingProvider _settingProvider;
             private readonly ISettingManager _settingManager;
    
             public SendGridSettingsAppService(ISettingProvider settingProvider, ISettingManager settingManager)
             {
                 _settingProvider = settingProvider;
                 _settingManager = settingManager;
             }
    
             public async Task<SendGridSettingsDto> GetAsync()
             {
                 return new SendGridSettingsDto
                 {
                     Enabled = await _settingProvider.GetAsync<bool>(SendGridSettings.Enabled),
                     FromName = await _settingProvider.GetOrNullAsync(SendGridSettings.FromName),
                     FromAddress = await _settingProvider.GetOrNullAsync(SendGridSettings.FromAddress),
                     ApiKey = await _settingProvider.GetOrNullAsync(SendGridSettings.ApiKey)
                 };
             }
    
             public async Task UpdateAsync(SendGridSettingsDto input)
             {
                 await _settingManager.SetGlobalAsync(SendGridSettings.Enabled, input.Enabled.ToString());
                 await _settingManager.SetGlobalAsync(SendGridSettings.FromName, input.FromName);
                 await _settingManager.SetGlobalAsync(SendGridSettings.FromAddress, input.FromAddress);
                 await _settingManager.SetGlobalAsync(SendGridSettings.ApiKey, input.ApiKey);
             }
         }
    
     (Then generate the React service proxies and build a custom admin page that calls `GetAsync/UpdateAsync`.)
    
    1. Add a new admin menu item in React to navigate to your “SendGrid Settings” page (instead of trying to make it appear automatically under the Setting Management tabs).
    • If you specifically want it inside the built-in Setting Management UI: follow the Setting Management module’s “custom setting group” approach shown in the docs (note: the docs show this clearly for MVC/Blazor/Angular; React doesn’t have the same “drop-in contributor” story documented there).

    Sources (validated):

    • https://abp.io/docs/10.4/modules/setting-management
    • https://abp.io/community/articles/mb2w01fe

    This response was generated by AI. If it didn’t solve your problem, feel free to post a new answer — our support team will jump in to help you soon.

    Markdown supported.
    Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)
  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    Hi,

    There's no auto-discover mechanism that turns SettingDefinitions into admin UI tabs. SettingDefinitionProvider only registers the setting on the backend (and exposes it to clients when isVisibleToClients: true); it doesn't render anything by itself.

    On the React UI specifically, the Admin Console at /admin-console is shipped as a prebuilt SPA inside the Volo.Abp.AdminConsole NuGet package and is meant to stay in sync with ABP updates. Its tabs (Account / Emailing / Feature management / Identity management / SaaS management) are fixed; there's no contributor API to inject a custom tab. See the docs:

    • https://abp.io/docs/latest/framework/ui/react/admin-console
    • https://abp.io/docs/latest/framework/ui/react/customization

    The recommended approach is to keep your SettingDefinitionProvider / PermissionDefinitionProvider exactly as you have them, add a small AppService to read/write the values, and build the page in the main React app (the react/ folder in your solution — that's the code you own and can freely customize).

    Step 1 — Add an AppService in *.Application (and a DTO in *.Application.Contracts):

    // *.Application.Contracts/SendGrid/SendGridSettingsDto.cs
    public class SendGridSettingsDto
    {
        public bool Enabled { get; set; }
        public string? FromName { get; set; }
        public string? FromAddress { get; set; }
        public string? ApiKey { get; set; }
    }
    
    // *.Application.Contracts/SendGrid/ISendGridSettingsAppService.cs
    public interface ISendGridSettingsAppService : IApplicationService
    {
        Task<SendGridSettingsDto> GetAsync();
        Task UpdateAsync(SendGridSettingsDto input);
    }
    
    // *.Application/SendGrid/SendGridSettingsAppService.cs
    [Authorize(SendGridPermissions.SendGrid)]
    public class SendGridSettingsAppService : YourProjectNameAppService, ISendGridSettingsAppService
    {
        private readonly ISettingManager _settingManager;
    
        public SendGridSettingsAppService(ISettingManager settingManager)
        {
            _settingManager = settingManager;
        }
    
        public async Task<SendGridSettingsDto> GetAsync()
        {
            return new SendGridSettingsDto
            {
                Enabled = bool.Parse(await SettingProvider.GetOrNullAsync(SendGridSettings.Enabled) ?? "false"),
                FromName = await SettingProvider.GetOrNullAsync(SendGridSettings.FromName),
                FromAddress = await SettingProvider.GetOrNullAsync(SendGridSettings.FromAddress),
                ApiKey = await SettingProvider.GetOrNullAsync(SendGridSettings.ApiKey),
            };
        }
    
        public async Task UpdateAsync(SendGridSettingsDto input)
        {
            await _settingManager.SetGlobalAsync(SendGridSettings.Enabled, input.Enabled.ToString());
            await _settingManager.SetGlobalAsync(SendGridSettings.FromName, input.FromName ?? string.Empty);
            await _settingManager.SetGlobalAsync(SendGridSettings.FromAddress, input.FromAddress ?? string.Empty);
            await _settingManager.SetGlobalAsync(SendGridSettings.ApiKey, input.ApiKey ?? string.Empty);
        }
    }
    

    ABP's conventional controllers will expose this as GET / PUT /api/app/send-grid-settings automatically — no extra routing needed. isEncrypted: true on the ApiKey setting is handled transparently by ISettingManager, so you get plain text in the DTO and an encrypted value in the database.

    Step 2 — Add an API client in react/src/lib/api/sendgrid.ts:

    import { api } from './axios'
    
    export interface SendGridSettingsDto {
      enabled: boolean
      fromName: string | null
      fromAddress: string | null
      apiKey: string | null
    }
    
    export const sendGridApi = {
      get: () => api.get<SendGridSettingsDto>('/app/send-grid-settings').then((r) => r.data),
      update: (input: SendGridSettingsDto) => api.put('/app/send-grid-settings', input),
    }
    

    Note the path is /app/send-grid-settings, not /api/app/send-grid-settings — the axios baseURL already includes /api.

    Step 3 — Add the page in react/src/pages/sendgrid/SendGridSettingsPage.tsx:

    import { useEffect, useState } from 'react'
    import { toast } from 'sonner'
    import { Button } from '@/components/ui/button'
    import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
    import { Checkbox } from '@/components/ui/checkbox'
    import { Input } from '@/components/ui/input'
    import { Label } from '@/components/ui/label'
    import { sendGridApi } from '@/lib/api/sendgrid'
    
    export function SendGridSettingsPage() {
      const [form, setForm] = useState({ enabled: false, fromName: '', fromAddress: '', apiKey: '' })
      const [loading, setLoading] = useState(true)
      const [saving, setSaving] = useState(false)
    
      useEffect(() => {
        sendGridApi.get()
          .then((d) => setForm({
            enabled: d.enabled,
            fromName: d.fromName ?? '',
            fromAddress: d.fromAddress ?? '',
            apiKey: d.apiKey ?? '',
          }))
          .catch(() => toast.error('Failed to load SendGrid settings'))
          .finally(() => setLoading(false))
      }, [])
    
      async function onSave() {
        setSaving(true)
        try {
          await sendGridApi.update(form)
          toast.success('SendGrid settings saved')
        } catch {
          toast.error('Failed to save SendGrid settings')
        } finally {
          setSaving(false)
        }
      }
    
      return (
        <div className="space-y-6">
          <h1 className="text-3xl font-bold tracking-tight">SendGrid Settings</h1>
          <Card>
            <CardHeader><CardTitle>Configuration</CardTitle></CardHeader>
            <CardContent className="space-y-4">
              <div className="flex items-center gap-2">
                <Checkbox id="enabled" checked={form.enabled} disabled={loading}
                  onCheckedChange={(v) => setForm({ ...form, enabled: v === true })} />
                <Label htmlFor="enabled">Enabled</Label>
              </div>
              <div className="space-y-2">
                <Label htmlFor="fromName">From Name</Label>
                <Input id="fromName" value={form.fromName} disabled={loading}
                  onChange={(e) => setForm({ ...form, fromName: e.target.value })} />
              </div>
              <div className="space-y-2">
                <Label htmlFor="fromAddress">From Address</Label>
                <Input id="fromAddress" type="email" value={form.fromAddress} disabled={loading}
                  onChange={(e) => setForm({ ...form, fromAddress: e.target.value })} />
              </div>
              <div className="space-y-2">
                <Label htmlFor="apiKey">API Key</Label>
                <Input id="apiKey" type="password" value={form.apiKey} disabled={loading}
                  onChange={(e) => setForm({ ...form, apiKey: e.target.value })} />
              </div>
              <Button onClick={onSave} disabled={loading || saving}>
                {saving ? 'Saving...' : 'Save'}
              </Button>
            </CardContent>
          </Card>
        </div>
      )
    }
    

    Step 4 — Register the route in react/src/routes/router.tsx:

    import { SendGridSettingsPage } from '@/pages/sendgrid/SendGridSettingsPage'
    
    const sendGridSettingsRoute = createRoute({
      getParentRoute: () => rootRoute,
      path: '/sendgrid-settings',
      component: SendGridSettingsPage,
      beforeLoad: createPermissionGuard('SettingManagement.SendGrid'),
    })
    
    const routeTree = rootRoute.addChildren([
      indexRoute,
      forbiddenRoute,
      accountRoute,
      identityRoute,
      sendGridSettingsRoute,
    ])
    

    Step 5 — Add a sidebar menu item in react/src/lib/routing/route-config.ts:

    import { Mail } from 'lucide-react'
    
    // inside routeConfig:
    {
      path: '/sendgrid-settings',
      nameKey: 'Menu:SendGridSettings',
      icon: Mail,
      order: 7,
      requiredPolicy: 'SettingManagement.SendGrid',
    },
    

    Then add "Menu:SendGridSettings": "SendGrid Settings" to react/src/locales/en.json (and the other locale files you support).

    That's it — the user will see a SendGrid Settings entry in the sidebar (gated by your SettingManagement.SendGrid permission), the page calls your AppService, and ISettingManager handles persistence including the encrypted API key.

    Here's how it looks in a fresh 10.4.0 React tiered template I built to confirm the flow end to end:

    Thanks

    Markdown supported.
    Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)
Boost Your Development
ABP Live Training
Packages
See Trainings
Mastering ABP Framework Book
The Official Guide
Mastering
ABP Framework
Learn More
Mastering ABP Framework Book
Made with ❤️ on ABP v10.8.0-preview. Updated on September 09, 2026, 11:56
1
ABP Assistant
🔐 You need to be logged in to use the chatbot. Please log in first.