Custom component
CustomComponent places a Vue component you wrote — a real SFC registered by a frontend layer — on a page whose other blocks stay declarative. Reach for it when no block of the catalog can draw a part of the screen, and only for that part: everything else on the page stays a DMS block described on the backend.
If only a form field needs a custom editor, use a custom input instead of replacing the block. The retention input walkthrough shows adjustment, validation and confirmation inside a standard form.
When to use it
Build the one missing block as a custom component — a flow canvas, a seat map, a bespoke chart — and keep the toolbar, the filters, the table and the stats around it as DMS blocks.
Build the whole screen as one custom component. The roles editor then sees a single line for the whole page, period selectors and watch actions no longer reach its parts, and every later change needs a frontend release.
Usage
CustomComponent(componentName) returns a ComponentBuilder, so it composes like any block: attach it as a static field, set its props with .options(), nest it in a layout. Always give it .meta().
import { PageController, RegisterPage, pagesCategory } from "@antelopejs/interface-dms/page";
import { CustomComponent } from "@antelopejs/interface-dms/base/custom";
import { Grid, GridRow } from "@antelopejs/interface-dms/base/grid";
import { KpiCard } from "@antelopejs/interface-dms/base/kpi-card";
import { PeriodSelector } from "@antelopejs/interface-dms/base/period-selector";
@RegisterPage()
export class RoutesPage extends PageController("routes", {
displayName: "$api.routes.title",
icon: "i-ph-graph",
category: pagesCategory,
}) {
static period = PeriodSelector({ id: "api" });
static kpis = Grid().child(
"row",
GridRow()
.child("calls", KpiCard({ title: "$api.routes.calls", fetchUrl: "/api/routes/calls", periodScope: "api" }))
.child("errors", KpiCard({ title: "$api.routes.errors", fetchUrl: "/api/routes/errors", periodScope: "api", invert: true })),
);
static graph = CustomComponent("ApiRouteGraph")
.options({ fetchUrl: "/api/routes/graph", periodScope: "api" })
.meta({
name: "$api.routes.graph.name",
description: "$api.routes.graph.description",
icon: "i-ph-graph",
});
}
Your SFC receives the .options() object as props, along with the page and route context the frontend gives every component.
.meta() — how the block appears in roles
.meta({ name, description?, icon? }) sets the metadata the roles editor shows for the block's permission. Without it, the permission is titled with the raw component name (ApiRouteGraph), with no icon and no explanation, in every language.
| Field | Purpose |
|---|---|
name | Title of the block's permission. Use a $ i18n key. |
description | One line under the title in the roles editor. Use a $ i18n key. |
icon | Icon of the permission line, e.g. i-ph-graph. |
Add the keys to every locale file of your frontend layer. Every DMS block factory sets this metadata itself; a custom component is the one block where you must.
The name must resolve
The string you pass must match the full name your frontend layer registered: its componentPrefix followed by the name passed to sdk.registerComponent(name, component). Resolution is normalized — a leading lazy and every non-alphanumeric character are stripped and the rest is lowercased — so ApiRouteGraph and api-route-graph are the same lookup. The full rule is in Frontend layer.
<div> with no error. A blank custom pane is almost always this mismatch: check the name your layer registers first.Permissions and data
A custom component is a position on the page like any other, with its own permission under the page. Authorization for any data your Vue fetches is enforced by the routes it calls: use useApiData to read and $authFetch to write (Authenticated requests). To follow the page's period, live state or refreshes, use the frontend composables (usePageLiveRefresh, refreshPageBlocks) rather than timers of your own.