Adding a custom control
Step-by-step to wire a new custom_control modal
Backend row action
Ensure the control table row action includes:
{
"action_type": "custom_control",
"onSuccess": "OpenModalForm",
"method": "POST",
"action": { "api": "/api/your/feature/..." }
}Prefetch response should contain fields your form needs (payload, ids, labels).
Create the component
Add under:
./ApiTables/table-modals/custom-controls/YourFeatureForm.tsxPattern:
'use client';
import useUtilsProvider from '@/app/ApiTables/table-providers/useUtilsProvider';
import { useDispatch } from 'react-redux';
import { _triggerOutscopeTableRefresher } from '@/store/slices/appSlice';
export default function YourFeatureForm({ action, closeModal }: { action: any; closeModal: () => void }) {
const { rowActionsPostHandler, triggerTableReload } = useUtilsProvider();
const dispatch = useDispatch();
async function onSubmit(data: FormData) {
await rowActionsPostHandler(
action.method,
action.url?.api?.replace('/api', ''),
{ ...data },
action
);
// Choose one:
triggerTableReload();
// dispatch(_triggerOutscopeTableRefresher());
closeModal();
}
return (/* form UI */);
}Match save/reload behavior to sibling features on the same page.
Wire in ./ApiTables/table-modals/ApiTablesModals.tsx
- Import your component
- Add a conditional inside
./ApiTables/table-modals/ApiTablesModals.tsxcustomControlActionpopup:
{customControlAction?.url?.api?.includes('your-feature-path') && (
<YourFeatureForm action={customControlAction} closeModal={handleCloseModal} />
)}Use a unique URL fragment to avoid collisions. Prefer api path over web when both exist.
Test checklist
- Click row action → loader → modal opens with prefilled data
- Submit success closes modal and refreshes table
- Close modal clears
customControlAction(no ghost popup) - Error paths show toast via
errorHandling - Works in RTL / locale if form uses translations
Bulk-only custom modals
Bulk flows do not use custom_control the same way. Example: create_withdrawal_bulk uses selectedBulkAction and a separate Popup block — copy AddToCreditBulkModal pattern.
Anti-patterns
- Do not add controls only in feature pages — they will not open without
ApiTablesModalswiring - Do not rely on auto-discovery by
action_keyalone (unless you extend the router) - Avoid overlapping URL
includespatterns