Building your app

Extending the student profile

An embedded app can add content to okta-web's student-profile page straight from code — no portal forms. Write a Livewire component in a fixed folder, annotate it with a single attribute, and the platform auto-discovers and registers it.

Embedded apps only. External apps surface their data over HTTP/webhooks, not through this registry.

Folder layout (shipped in the boilerplate)

Modules/<App>/app/StudentProfile/
├── Panels/    ← full cards (Panel)
├── Stats/     ← header-strip numbers (Stat)
└── Actions/   ← header buttons (Action)

Any annotated component under these folders is discovered and registered automatically at boot — there is no registration code to write.

Declaration: an attribute on the class

namespace Modules\MyApp\app\StudentProfile\Panels;

use App\Support\StudentProfile\Attributes\StudentPanel;
use App\Support\StudentProfile\StudentProfileZone;
use Livewire\Component;

#[StudentPanel(
    title: 'Attendance summary',
    permission: 'my_app.records.view',
    order: 50,
    zone: StudentProfileZone::Main,
)]
class AttendanceSummary extends Component
{
    public string $studentHashid;   // the only identifier passed — never a numeric id

    public function render()
    {
        return view('my-app::student-profile.attendance-summary');
    }
}
  • key and component are derived automatically from the class.
  • Only title/label + permission are required; the rest is optional.

Mandatory rules

  • Use studentHashid only — never a numeric id.
  • Every contribution declares a permission in the <feature>.<resource>.<action> shape.
  • This surface is embedded-only.

The four types (attribute parameters)

1) #[StudentPanel] — a full card

Param Required Description
title ✓ Card title
permission ✓ View permission
order — Order (default 100, lower first)
zone — StudentProfileZone::Main (wide column) or ::Sidebar
icon — Icon
description — Short description
key — Override the derived key

2) #[StudentStat] — a header-strip number

Param Required Description
label ✓ Label
permission ✓ View permission
order — Order
icon — Icon
color — primary/success/warning/danger/…

The component itself is the live widget that renders the value.

3) #[StudentAction] — a header button

Param Required Description
label ✓ Button text
permission ✓ View permission
url or event ✓ Open a URL or dispatch an event
params — Event params (with event only)
order — Order
variant — primary/secondary/danger/…
icon — Icon

url takes precedence over event. The event is broadcast via Alpine $dispatch and caught by Livewire #[On] or wire-elements (openModal).

4) Zone

  • Main — the wide centre column.
  • Sidebar — the side column.

The unified block chrome (<x-profile-block> / <x-profile-stat>)

The student profile is a unified block dashboard: every card renders inside platform-owned chrome that shows its source (your app) with its accent colour, a sync-status pill, last-synced time, and an "open in app" link. To make your card blend into the board, wrap your panel content in <x-profile-block> (instead of a bare <x-card>):

{{-- my-app::student-profile.attendance-summary --}}
<x-profile-block
    :title="__('my-app::profile.attendance')"
    :source="\App\Support\StudentProfile\BlockSource::resolve('my-app')"
    status="live"                    {{-- live | syncing | offline | error --}}
    :last-synced-at="$syncedAt"
    :open-in-app-href="route('store.show', 'my-app')"
    :open-in-app-label="__('my-app::profile.open')">

    {{-- your content: chart / table / list — tenant data via App\Services\PartnerApi\* only --}}

    <x-slot:actions>…</x-slot:actions>   {{-- optional: ⋮ menu --}}
</x-profile-block>
  • BlockSource::resolve('<module-slug>') gives you a stable source identity (name / accent / icon) — don't invent a colour per card. (Or pass source-label / accent / icon directly.)
  • Status is dynamic: pass status and last-synced-at at render time from your real sync state. status="offline" shows an offline state — use <x-slot:offline> for the fallback (link-to-connect) content.
  • Stat tiles (#[StudentStat]) use <x-profile-stat> (:label + :value + :source) so they appear in the stats row attributed to your app.
  • The platform owns the chrome; you supply content + status only. The "data sources" bar and the source filter are derived automatically from your registered blocks (no extra config).

How it shows in the portal

The partner portal does not run your code, so its Student Profile tab is read-only: it lists the contributions okta-web discovered after a sandbox install, and the published manifest's studentProfile block is derived from them. You write the code — everything else is automatic.

Your component receives studentHashid only: <livewire:my-app::student-profile-summary :studentHashid="$studentHashid" />.


View the whole guide on one page · ↧ Raw Markdown