Custom Views
Clarifies how to choose between metadata-driven custom widgets and bespoke route-level pages.
Overview
In older discussions, "custom view" is used for two different patterns:
- Metadata-driven custom UI blocks inside form/list layouts.
- Fully bespoke route pages with independent layout/navigation.
Use the correct implementation path below.
Decision Guide
Use Custom Widget (metadata-driven form-view block)
Choose this when UI is rendered inside an existing form-view layout node with type: "custom" and should remain inside the generated Solid form flow.
- Location:
solid-ui/src/<module-name>/admin-layout/<model-name>/extension-components/ - Registration:
<module-name>.ui-module.tsviaextensionComponents - Metadata wiring: layout JSON
type: "custom",attrs.widget: "<registered-name>"
Use this path for:
- form-view custom widgets rendered through
type: "custom" - form buttons
- list buttons
- row actions
- field widgets
- kanban card widgets
Use Custom Page (bespoke routing)
Choose this when you need a dedicated page shell, route group, or custom navigation unrelated to metadata widgets.
- Location:
solid-ui/src/<module-name>/custom-layout/<layout-reference>/... - Route wiring: register route trees in
<module-name>.ui-module.tsunderroutes.extraRoutes - App wiring: keep
solid-ui/src/AppRoutes.tsxthin and let it consumesolidUiModuleRuntime.routes - Do not place full custom pages under
solid-ui/src/<module-name>/admin-layout/...
See Bespoke Frontend UI for the full route-level pattern.
API Convention for Both Paths
For frontend API calls, Solid supports both of these patterns:
- Direct Solid HTTP helpers from
@solidxai/core-ui - Module-owned Redux / RTK Query integration under
solid-ui/src/<module-name>/redux/
Option A: Solid HTTP Helpers
Use:
solidGet,solidPost,solidPut,solidPatch,solidDelete,solidAxios
Guidelines:
- Pass endpoint paths like
/resource(no hardcoded/apiprefix). - Prefer context data (
params,formik.values, route params) instead of hardcoded IDs. - Handle loading, success, and error explicitly.
- For filter-heavy requests, use
qs.stringify(..., { encodeValuesOnly: true })or Axiosparams.
Option B: Redux / RTK Query
If the consuming app prefers store-backed integration, place RTK Query APIs under:
solid-ui/src/<module-name>/redux/Then register reducers and middleware in <module-name>.ui-module.ts.
Choose this when you want:
- shared API state
- caching and invalidation
- generated hooks
- a more structured app-wide API layer
See Redux Module Integration for the full pattern.
Example: Metadata Custom Widget
import { solidGet } from "@solidxai/core-ui";
import { useEffect, useState } from "react";
import type { SolidFormWidgetProps } from "@solidxai/core-ui";
export function BookSimilarTitles({ formData }: SolidFormWidgetProps) {
const [rows, setRows] = useState<any[]>([]);
const [loading, setLoading] = useState(false);
useEffect(() => {
const title = formData?.title;
if (!title) return;
const run = async () => {
setLoading(true);
try {
const query = encodeURIComponent(String(title));
const resp = await solidGet(`/books/similar?title=${query}`);
setRows(resp?.data?.data?.records || []);
} finally {
setLoading(false);
}
};
run();
}, [formData?.title]);
if (loading) return <div>Loading...</div>;
return <div>Found: {rows.length}</div>;
}Registration:
import { ExtensionComponentTypes, type SolidUiModule } from "@solidxai/core-ui";
import { BookSimilarTitles } from "./admin-layout/book/extension-components/BookSimilarTitles";
const libraryUiModule = {
name: "library",
extensionComponents: [
{
name: "BookSimilarTitles",
component: BookSimilarTitles,
type: ExtensionComponentTypes.formWidget,
},
],
} satisfies SolidUiModule;
export default libraryUiModule;Layout usage:
{
"type": "custom",
"attrs": {
"name": "book-similar-widget",
"widget": "BookSimilarTitles"
}
}