Skip to main content

Escaping the Metadata: Advanced Frontend Overrides

Framework M is designed with a "No-Cliff" philosophy. While 90% of your application can be driven by high-level metadata decorator definitions, there are mission-critical scenarios or complex UX workflows where standard forms and lists are insufficient.

This guide explains how to take full control of a DocType's interface using file-based overrides. For general discovery details and structure, see Frontend DX.


1. Naming Conventions​

Override files must use the exact PascalCase name of your DocType.

  • DocType: UserProfile -> File: src/overrides/UserProfileForm.tsx
  • DocType: SalesInvoice -> File: src/overrides/SalesInvoiceList.tsx

2. The frm Controller​

The most powerful tool for custom overrides is the frm object. When your override is mounted, it receives a standardized controller that provides reactive access to the document and system actions.

Basic Usage​

import { StandardFormViewProps } from "../pages/FormView";

export default function MyCustomForm({ frm, meta }: StandardFormViewProps) {
return (
<div>
<h1>Customizing {meta.name}</h1>
<p>Current Status: {frm.doc.docstatus}</p>

<button onClick={() => frm.save()} disabled={frm.isSaving}>
{frm.isSaving ? "Saving..." : "Flash Save"}
</button>
</div>
);
}

Frm API Reference​

PropertyTypeDescription
docanyThe current reactive document state (draft).
setDoc`(datafn)`
save() => PromiseTriggers the useCreate or useUpdate mutation. Redirection is handled automatically.
cancel() => voidSilently discards changes and routes back to the list view.
refetch() => PromiseForcibly refreshes data from the server (e.g., after a workflow action).
isEditingbooleantrue if we are editing an existing record, false for "New".
isSavingbooleanReactive flag for mutation "in-flight" status.
isLockedbooleantrue if the document is Submitted or Cancelled.
errorsstring[]Reactive list of validation or system errors.
workflowanyThe current workflow state (if a workflow is defined for this DocType).

3. Pattern: The "Progressive Enrichment" Override​

You don't always have to build from scratch. You can wrap the StandardFormView to add sidebars, headers, or custom banners while keeping the central AutoForm alive.

HOC Pattern​

import { StandardFormView } from "../pages/FormView";

export default function EnrichmentOverride(props: StandardFormViewProps) {
const { frm } = props;

return (
<div style={{ padding: 20, border: '5px solid gold' }}>
<header>
<h2>Advanced Editor Layer</h2>
<p>This layout wraps the standard form.</p>
</header>

{/* Mounting the standard view inside your custom layout */}
<StandardFormView {...props} />

<footer>
<button onClick={() => frm.save()}>Custom Save Button</button>
</footer>
</div>
);
}

4. Pattern: The "Total Control" Override​

For specialized views (like an Order Entry screen with a custom POS-style grid), you can ignore the standard views entirely and build a pure React interface using @framework-m/ui components.

import { XStack, YStack, Button, Input } from "@framework-m/ui";

export default function OrderEntryForm({ frm }: StandardFormViewProps) {
const calculateTotal = () => {
return frm.doc.items?.reduce((sum, item) => sum + item.amount, 0) || 0;
};

return (
<YStack gap="$md">
<Input
value={frm.doc.customer}
onChangeText={(val) => frm.setDoc({ customer: val })}
/>

<Display value={calculateTotal()} />

<Button onPress={() => frm.save()}>Complete Order</Button>
</YStack>
);
}

5. When to Escape?​

  • Complex Inter-field Logic: When changing Field A needs to trigger a remote recalculation of a hidden Sub-table.
  • Custom Visuals: When you need a Canvas, a Map, or a specialized Chart integrated into the form.
  • Read Models: When the data should be fetched from a high-performance Read Model SQL view rather than the default CRUD API.
  • Mission Critical UX: When the standard vertical stacking of AutoForm doesn't provide the speed of entry required by the user.