DirkScripts Logo

Login With FiveM

Login

âš›ī¸ Dirk CFX React
â€ē

Script Config (React)

Script Config (React)

This page covers the React/NUI side of the script config system. For the Lua side (registration, watchers, DB persistence), see dirk_lib → scriptConfig.


Overview

The script config system lets you build a full admin configuration panel for any FiveM resource with:

  • Live settings that apply without server restart
  • Undo / redo with full edit history
  • Version conflict detection (optimistic concurrency)
  • Change audit log with admin attribution
  • JSON editor for bulk/advanced edits
  • Reset to defaults with confirmation

The architecture flows like this:

schema.json + defaults.json
        ↓
dirk_lib server (registerScriptConfig → MySQL)
        ↕ NUI callbacks (get / update / history / reset)
dirk-cfx-react (createScriptConfig → useForm → SettingsPanel)
        ↕ React component sections
Admin UI in-game

Step-by-Step Setup

1. Define Your Schema

Create schema.json in your resource root. This is a JSON Schema that defines every setting, its type, and its default value.

json
{
  "type": "object",
  "properties": {
    "basic": {
      "type": "object",
      "properties": {
        "debug": { "type": "boolean", "default": false },
        "interactionType": { "type": "string", "default": "target" },
        "maxPlayers": { "type": "number", "default": 32 }
      }
    },
    "rewards": {
      "type": "array",
      "x-arrayKey": "name",
      "items": {
        "type": "object",
        "properties": {
          "name": { "type": "string" },
          "label": { "type": "string" },
          "amount": { "type": "number", "default": 1 }
        }
      }
    }
  }
}

Schema extensions:

ExtensionPurpose
x-serverOnlyNever sent to client — server-only secrets
x-renamedFromAuto-migrate old key path to new key path
x-arrayKeyMerge arrays by identity field instead of index

â„šī¸ defaults.json is optional — if provided, it supplies the initial default values. Otherwise defaults are derived from the "default" fields in schema.json.

2. Register on the Lua Side

See dirk_lib → scriptConfig for full details. The minimal setup:

src/server/scriptConfig.lua

lua
CreateThread(function()
  lib.scriptConfig(schema, function(src)
    return IsPlayerAceAllowed(src, 'admin')
  end)
end)

src/client/scriptConfig.lua

lua
local _ = lib.scriptConfig  -- triggers lazy-load + KVP cache

3. Create the TypeScript Config Store

In your resource's web/src/stores/, create a file that defines your settings types, Zod schema, and calls createScriptConfig:

tsx
import { z } from "zod";
import { createScriptConfig } from "dirk-cfx-react";

// ── Zod Schema ────────────────────────────────────────────────

const BasicSchema = z.object({
  debug: z.boolean(),
  interactionType: z.string(),
  maxPlayers: z.number(),
});

const RewardSchema = z.object({
  name: z.string(),
  label: z.string(),
  amount: z.number(),
});

export const ScriptConfigSchema = z.object({
  basic: BasicSchema,
  rewards: z.array(RewardSchema),
});

export type Settings = z.infer<typeof ScriptConfigSchema>;

// ── Defaults ──────────────────────────────────────────────────

export const defaultScriptConfig: Settings = {
  basic: {
    debug: false,
    interactionType: "target",
    maxPlayers: 32,
  },
  rewards: [],
};

// ── Store ─────────────────────────────────────────────────────

export const {
  store: useScriptConfig,
  updateSettings,
  getHistory,
  useScriptConfigHooks,
} = createScriptConfig<Settings>(defaultScriptConfig);

4. Build the Admin Panel

Create web/src/components/Admin/main.tsx with your nav items and section routing:

tsx
import { useNuiEvent, SettingsPanel, type NavItem } from "dirk-cfx-react";
import { Settings, Sword } from "lucide-react";
import { useState } from "react";
import { useScriptConfigHooks, defaultScriptConfig, ScriptConfigSchema } from "@/stores/useScriptConfig";
import { BasicSection } from "./BasicSection";
import { RewardsSection } from "./RewardsSection";

const NAV_ITEMS: NavItem[] = [
  { id: "basic", icon: Settings, label: "Basic" },
  { id: "rewards", icon: Sword, label: "Rewards" },
];

export function AdminPanel() {
  const [open, setOpen] = useState(false);
  useScriptConfigHooks();

  useNuiEvent("OPEN_ADMIN_SECTION", () => setOpen(true));

  return (
    <SettingsPanel
      navItems={NAV_ITEMS}
      title="My Resource"
      subtitle="Admin Settings"
      open={open}
      onClose={() => setOpen(false)}
      defaultScriptConfig={defaultScriptConfig}
      schema={ScriptConfigSchema}
      resetConfirmText="my_resource"
    >
      {(activeTab) => (
        <>
          {activeTab === "basic" && <BasicSection />}
          {activeTab === "rewards" && <RewardsSection />}
        </>
      )}
    </SettingsPanel>
  );
}

5. Build Section Components

Each admin section uses useFormField and useFormActions from dirk-cfx-react to bind to the form state managed by SettingsPanel:

tsx
import { useFormField, useFormActions, AdminPageTitle, InputContainer } from "dirk-cfx-react";
import { Settings } from "lucide-react";
import { Flex, NumberInput, Switch, Select, useMantineTheme } from "@mantine/core";
import type { Settings as SettingsType } from "@/stores/useScriptConfig";

export function BasicSection() {
  const theme = useMantineTheme();
  const color = theme.colors[theme.primaryColor][theme.primaryShade as number];
  const basic = useFormField<SettingsType, "basic">("basic");
  const { setValue } = useFormActions<SettingsType>();

  return (
    <Flex direction="column" gap="sm" p="sm">
      <AdminPageTitle title="Basic" icon={Settings} color={color} />

      <InputContainer title="Debug Mode" description="Enable debug commands and logging">
        <Switch
          checked={basic.debug}
          onChange={(e) => setValue("basic.debug", e.currentTarget.checked)}
        />
      </InputContainer>

      <InputContainer title="Interaction Type" description="How players interact with objects">
        <Select
          value={basic.interactionType}
          onChange={(v) => setValue("basic.interactionType", v)}
          data={["target", "interact", "textui"]}
        />
      </InputContainer>

      <InputContainer title="Max Players" description="Maximum concurrent players">
        <NumberInput
          value={basic.maxPlayers}
          onChange={(v) => setValue("basic.maxPlayers", v)}
          min={1}
          max={256}
        />
      </InputContainer>
    </Flex>
  );
}

API Reference

createScriptConfig<T>(defaultValue)

Factory function that creates a Zustand store and NUI integration for your settings type. Call once at module level.

tsx
const { store, updateSettings, getHistory, useScriptConfigHooks } =
  createScriptConfig<MySettings>(defaultScriptConfig);

Returns:

PropertyTypeDescription
storeStoreApi<T>Zustand store — use as a hook for reactive reads
updateSettings(data: Partial<T>) => Promise<NuiResponse<T>>Send partial update to server via NUI
getHistory(params?) => Promise<HistoryResponse>Fetch paginated change history
useScriptConfigHooks() => voidCall inside your admin component to register NUI listeners

SettingsPanel

The main admin UI shell. Renders a full-screen modal with sidebar navigation, undo/redo, save/discard, history, JSON editor, and reset.

tsx
<SettingsPanel
  navItems={NAV_ITEMS}
  title="My Resource"
  open={isOpen}
  defaultScriptConfig={defaults}
  schema={zodSchema}
>
  {(activeTab) => <>{/* render active section */}</>}
</SettingsPanel>
PropTypeRequiredDescription
navItemsNavItem[]yesSidebar navigation items { id, icon, label }
titlestringyesPanel title
subtitlestringnoPanel subtitle
openbooleanyesWhether the panel is visible
onClose() => voidnoClose handler (defaults to fetchNui("CLOSE_ADMIN_SECTION"))
children(activeTab: string) => ReactNodeyesRender function receiving the active tab ID
defaultScriptConfigTyesDefault settings object for reset
schema{ safeParse }noZod schema for JSON editor validation
resetConfirmTextstringnoText user must type to confirm reset
widthstringnoPanel width (default: "70vh")
heightstringnoPanel height (default: "85vh")

Built-in sidebar features:

  • Undo / Redo — Step through edit history
  • Save — Calls updateSettings with all pending changes, shows unsaved count badge
  • History — Paginated audit log modal with search/filter
  • Discard — Revert to last saved state
  • Manual Edit (JSON) — Full JSON editor with schema validation
  • Reset Defaults — Wipe to defaults with confirmation modal

useForm

Advanced form state hook with deep nesting, undo/redo, and Zod validation. Used internally by SettingsPanel but can be used standalone.

tsx
const form = useForm<MyFormData>({
  initialValues: { ... },
  validate: zodSchema,
});

Key methods:

MethodDescription
setValue(path, value)Set a nested value (e.g. "basic.debug")
getValue(path)Get a nested value
reset()Reset to initial values
reinitialize(newValues)Reset with new initial values
undo()Undo last change
redo()Redo undone change
validate()Run validation, returns { success, errors }
getInputProps(path)Returns { value, onChange, error } for Mantine inputs
isDirty()Whether any values have changed
getChangedFields()Array of paths that have been modified

Context Hooks

These hooks access the form state within FormProvider (set up by SettingsPanel):

HookReturnsDescription
useFormField(path)T[path]Reactive value at a nested path
useFormFields(...paths)[T[p1], T[p2], ...]Reactive values at multiple paths
useFormActions(){ setValue, reset, ... }All form mutation methods
useFormError(path)string | undefinedValidation error for a field
useFormErrors()Record<string, string>All current validation errors

NUI Callback Reference

The settings system communicates with dirk_lib through these NUI callbacks (handled automatically):

CallbackDirectionPurpose
NUI_READYUI → LuaSignals that the NUI is loaded and ready
GET_SETTINGSUI → LuaFetches server config (currency, colours, etc.)
GET_LOCALESUI → LuaFetches locale strings
GET_ITEMSUI → LuaFetches inventory item definitions
OPEN_ADMIN_SECTIONLua → UIOpens the admin panel
CLOSE_ADMIN_SECTIONUI → LuaCloses the admin panel
UPDATE_SCRIPT_SETTINGSUI → LuaSends settings update with expected version
GET_SCRIPT_SETTINGS_HISTORYUI → LuaFetches paginated change history

â„šī¸ All NUI callbacks are registered automatically by dirk_lib's scriptConfig module. You don't need to set them up manually.


Version Conflict Handling

When two admins edit settings simultaneously:

  1. Admin A opens the panel → receives settings + client_version hash
  2. Admin B saves a change → server's version hash updates
  3. Admin A tries to save → sends their expectedVersion
  4. Server detects mismatch → returns success: false with latestData
  5. UI automatically merges the latest data back into the form
  6. Admin A can review and re-save

This prevents silent overwrites and ensures all changes are tracked.

Copyright Š 2026 DirkScripts.

Not affiliated with or endorsed by Rockstar North, Take-Two Interactive, or any other rights holders. FiveM is a copyright and registered trademark of Take-Two Interactive Software, Inc.
Our checkout system is provided by Tebex Limited, who manage payment processing, product delivery, and billing support. Prices shown in currencies other than GBP are approximate conversions updated daily. All purchases are processed in GBP, so the final amount charged may vary depending on your bank or payment provider’s exchange rate.