Ui5Card
Introduction
The Ui5Card is a UI5 artifact that wraps a sap.ui.integration Integration Card — a compact, embeddable element such as a KPI, list, or object preview. Each card is a small PHP class declaring its identity and wiring a data provider, backed by a Blade-rendered manifest that lives in the card's own self-contained folder (alongside an optional i18n bundle).
A card is read-only — it visualizes data, never mutates state — and it can be consumed two ways: served standalone at its own endpoint, or embedded in a Ui5Dashboard. Both paths render the same manifest.
Conceptual Overview
The two render paths
A single card class serves both of these without change:
- Standalone —
GET card/{ns}@{ver}/manifest.jsonreturns the Blade-rendered manifest (the provider's array is available to the template as$data). The UI5 Integration Card framework fetches and renders it. - Embedded in a dashboard — the dashboard's emitter builds the same manifest URL (with bound slot values appended as query params) and hands it to the card widget via
getCard(). The widget fetches the manifest from the standalone endpoint above.
Structure — a self-contained card folder
Each card owns a self-contained folder — a canonical sap.ui.integration card package:
resources/ui5/cards/{slug}/
├── manifest.json.blade.php # the manifest, Blade-rendered to JSON
└── i18n/ # optional translation bundle
├── i18n.properties
└── i18n_de.properties # locale variants, as neededThe .blade.php suffix keeps editors treating the template as Blade; the URL-served resource is still manifest.json. Cards are sandboxed: a card resolves its {i18n>…} bindings against its own bundle (served card-relative at card/{ns}@{ver}/i18n/), not the host app's i18n model — so each card carries its translations with it.
How to Generate
php artisan ui5:card Finance/Revenue \
--title="Revenue Overview" \
--description="Displays key revenue metrics for the current quarter"This creates the card class, its provider, and the self-contained card folder (manifest + i18n stub).
Options
| Option | Default | Description |
|---|---|---|
name (argument) | (required) | Format: {App}/{Card}, e.g. Sales/Overview — without the Card suffix |
--title | Card Title | Card title (an i18n key in the manifest) |
--description | Card Description | Card description |
--seed[=<name>] | (bare) | Scaffold the manifest from a named seed (see Seeding) |
The PHP and JS namespaces are derived from the target app's module — there are no namespace-prefix options.
⚠️ Pass the bare name — the generator adds the
Cardsuffix.ui5:card Finance/Revenueproduces the classRevenueCard, the providerRevenueProvider, and the folder slugcards/revenue/(the name insnake_case). PassingFinance/RevenueCardis legal but gives youRevenueCardCardandcards/revenue_card/, which is almost never what you meant.
Output
Given php artisan ui5:card Finance/Revenue:
ui5/
└── Finance/
├── src/
│ └── Cards/
│ ├── RevenueCard.php
│ └── Provider/
│ └── RevenueProvider.php
└── resources/
└── ui5/
└── cards/
└── revenue/
├── manifest.json.blade.php
└── i18n/
└── i18n.propertiesOn success the command prints the exact registration directives — the module method (getCards()) and the owning Dashboard Group's getChildNamespaces(), each with the literal line to add.
Seeding
By default the card's manifest.json.blade.php is a bare, header-only Object card ("content": { "groups": [] }) and the Provider returns no data — a valid card that boots but shows nothing.
To scaffold a populated starting point:
php artisan ui5:card Finance/Kpi --seed=kpi
php artisan ui5:card Finance/Kpi --seed # pick from the catalogA card seed is different from a tile/chart seed: it swaps only the manifest.json, populating it with static visualization data that deliberately does not correspond to the generated DataProvider. Its job is "see a populated shape instantly" — live data-binding to the provider is your act to build; the scaffolded provider stays bare on purpose. An unknown seed name fails with the list of available seeds. The card seed catalog ships kpi, top-deals and account-snapshot; it is open content.
Artifact Roles
RevenueCard.php
Implements Ui5CardInterface. Identity is declared as class constants — the getters are inherited from AbstractUi5Card:
class RevenueCard extends AbstractUi5Card
{
public const string NAMESPACE = 'com.acme.finance.cards.revenue';
public const string VERSION = '1.0.0';
public const string TITLE = 'Revenue Overview';
public const string DESCRIPTION = 'Displays key revenue metrics for the current quarter';
public function getProvider(): string
{
return RevenueProvider::class;
}
}getProvider() declares the provider by class-string; the framework resolves it through the container, so the provider's constructor dependencies are autowired (see the provider below). Never write return app(...) or return new ... here — a declaration method declares, it does not build. Override getCard() only to set dashboard-grid properties (see Dashboard embedding).
RevenueProvider.php
Supplies the data the manifest template renders: provide() returns an array, and the Blade template receives it as $data. It implements DataProviderInterface. Follow the injection recipe: services go in the constructor (autowired), the context arrives as a provide() parameter. Slot values do not: the card path runs no slot pipeline.
class RevenueProvider extends AbstractConfigurable implements DataProviderInterface
{
public function __construct(private readonly RevenueRepository $revenue) {}
public function provide(): array
{
return ['title' => 'Revenue', 'value' => $this->revenue->thisQuarter(), 'unit' => 'EUR'];
}
}manifest.json.blade.php
The Integration Card manifest, rendered to JSON per request. Use Blade freely; the output must be valid JSON. Bind user-visible strings to the card's own i18n bundle with {i18n>KEY} — never the double-brace form, which collides with Blade's echo syntax. Provider data is available as $data.
{
"_version": "1.15.0",
"sap.app": {
"id": "{{ $card->getNamespace() }}",
"type": "card",
"applicationVersion": { "version": "{{ $card->getVersion() }}" }
},
"sap.card": {
"type": "Object",
"header": { "title": "{i18n>title}", "subTitle": "{i18n>subTitle}" },
"content": { "item": { "title": "{title}", "number": "{value}", "unit": "{unit}" } }
}
}i18n/i18n.properties
The card's translation bundle. Add locale variants alongside it (i18n_de.properties, i18n_en_US.properties); Core derives sap.app/i18n.supportedLocales from whichever files are present and serves them card-relative.
title=Revenue
subTitle=Current quarterModule Integration
Register cards in your module's getCards(), passing the module instance to each:
public function getCards(): array
{
return [
new Cards\RevenueCard($this),
];
}This enables discovery, routing, and (when embedded) dashboard composition. The manifest folder is resolved from the card's slug.
Dashboard embedding
To embed a card in a Ui5Dashboard, reference it from a group's getChildNamespaces() by its ::NAMESPACE constant. Override getCard() to set grid placement (and any other sap.ui.integration.widgets.Card property exceptmanifest, which the framework injects):
public function getCard(): Card
{
return new Card(
layoutData: new GridContainerItemLayoutData(columns: 3, rows: 5),
);
}Best Practices
- Keep heavy data logic in the provider; inject services via its constructor.
- Translate every user-visible string with
{i18n>KEY}— keep the keys in the card's owni18n/bundle. - Prefer standard UI5 card types (
Object,List,Table,Analytical). - A card is read-only — for state changes, use a Ui5Action.
Related Links
- SAP UI5 Integration Cards Explorer
- Ui5Dashboard — composing cards into a dashboard
- Slots — how bound values reach a card's provider
- Settings — how an artifact declares configuration its provider reads