[data-reveal]{opacity:1!important;transform:none!important}
Custom component
Components

Custom component

Build only the block the catalog is missing — a Vue component of your own, declared on the backend with its name, icon and description so roles can grant it.

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.

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().

src/pages/routes.ts
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.

FieldPurpose
nameTitle of the block's permission. Use a $ i18n key.
descriptionOne line under the title in the roles editor. Use a $ i18n key.
iconIcon 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.

If the backend name and the registered name disagree after normalization, the slot renders a blank <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.

Next steps

Best practices

The rules for blocks versus custom components.

Frontend layer

Register the Vue component.

Auth & permissions

How block permissions are derived.