Examples
Copy-paste patterns based on the current runtime surface. These examples use AbstractModule, SdkContext, and the public @meteorack/modules-sdk-ui barrel where applicable.
Settings Helpers
phpAbstractModule already mixes in HasSettings, so you can read and write module-scoped settings directly.
Best For
- Persisted module preferences such as API keys, intervals, and toggles.
- Settings screens that need a simple save/read loop.
- Boot-time feature flags loaded inside onSdkReady().
You Will Use
Related Docs
<?php
use Meteorack\Sdk\Core\SdkContext;
use Meteorack\Sdk\RuntimeWp\AbstractModule;
class MyModule extends AbstractModule
{
public function getSlug(): string { return 'my-module'; }
public function getName(): string { return 'My Module'; }
public function getVersion(): string { return '1.0.0'; }
public function onSdkReady(SdkContext $ctx): void
{
parent::onSdkReady($ctx);
$this->setSetting('api_key', 'sk_live_...');
$perPage = $this->getSetting('per_page', 25);
$ctx->logger()->info('my_module.settings_loaded', [
'per_page' => $perPage,
]);
}
}
return new MyModule();Cache Remote Data
phpPair CacheServiceInterface with HttpServiceInterface through SdkContext to avoid repeated remote requests.
Best For
- Dashboard widgets backed by external APIs.
- Rate-limited endpoints that should not be hit on every request.
- Expensive transforms that can be safely reused for a short TTL.
You Will Use
Related Docs
public function refreshStats(SdkContext $ctx): array
{
$cached = $ctx->cache()?->get('dashboard_stats');
if (is_array($cached)) {
return $cached;
}
$stats = $ctx->http()?->get('https://api.example.com/stats') ?? [];
$ctx->cache()?->set('dashboard_stats', $stats, 300);
return $stats;
}Emit and Listen to Events
phpUse EventsServiceInterface for in-process pub/sub between modules.
Best For
- Cross-module reactions without direct coupling.
- Fire-and-forget notifications after orders, syncs, or lifecycle transitions.
- Keeping handlers independently testable from the original action.
Related Docs
public function onSdkReady(SdkContext $ctx): void
{
parent::onSdkReady($ctx);
$ctx->events()?->on('payment.completed', [$this, 'handlePayment']);
}
public function createOrder(SdkContext $ctx, int $orderId): void
{
$ctx->events()?->emit('order.created', [
'order_id' => $orderId,
]);
}
public function handlePayment(array $payload): void
{
$this->setSetting('last_paid_order', $payload['order_id'] ?? null);
}React Admin Page
tsxThe scaffolded admin page uses the public @meteorack/modules-sdk-ui barrel for common layout and UI primitives.
Best For
- Module dashboards and settings pages.
- Admin entrypoints registered through module.json.
- Pages that need shared Meteorack shell spacing and action patterns.
You Will Use
import React from 'react';
import { AppShell, PageHeader, Card, Button } from '@meteorack/modules-sdk-ui';
export function MainPage() {
return (
<AppShell>
<PageHeader
title="Dashboard"
description="Module overview and controls"
actions={<Button variant="primary">Save</Button>}
/>
<Card>
<Button variant="secondary">Run action</Button>
</Card>
</AppShell>
);
}Realtime Project Events
tsMint a public realtime token, join the `project-events` alias, and publish an application event from TypeScript.
Best For
- Live dashboards and collaborative project views.
- Server-triggered notifications that should fan out to connected clients.
- Private-beta integrations that need Meteorack Realtime without raw transport details.
You Will Use
Related Docs
import { Socket } from "phoenix";
const apiBase = "https://api.meteorack.com";
const projectId = "11111111-1111-1111-1111-111111111111";
const subscribeKey = process.env.METEORACK_SUBSCRIBE_KEY!;
const publishKey = process.env.METEORACK_PUBLISH_KEY!;
async function mintRealtimeToken() {
const response = await fetch(
`${apiBase}/api/v1/cloud/projects/${projectId}/realtime/token?channels=project-events`,
{
headers: {
Authorization: `Bearer ${subscribeKey}`,
},
},
);
if (!response.ok) {
throw new Error(`Token request failed with ${response.status}`);
}
return (await response.json()).data as {
token: string;
ws_url: string;
};
}
async function publishInventoryUpdate(eventId: string) {
const response = await fetch(
`${apiBase}/api/v1/cloud/projects/${projectId}/realtime/publish`,
{
method: "POST",
headers: {
Authorization: `Bearer ${publishKey}`,
"Content-Type": "application/json",
"Idempotency-Key": eventId,
},
body: JSON.stringify({
channel: "project-events",
event: "inventory.updated",
data: {
sku: "sku_1",
available: 12,
},
}),
},
);
if (!response.ok) {
throw new Error(`Publish failed with ${response.status}`);
}
const result = await response.json();
if (
response.headers.get("Idempotency-Key") !== eventId ||
result.data?.event_id !== eventId
) {
throw new Error("Publish identity mismatch");
}
}
async function main() {
const realtime = await mintRealtimeToken();
const socket = new Socket(realtime.ws_url, {
params: { token: realtime.token },
});
socket.connect();
const channel = socket.channel("project-events");
channel.on("inventory.updated", (payload) => {
console.log("inventory.updated", payload);
});
const joinResult = await channel.join();
if (joinResult !== "ok") {
throw new Error(`Join failed with ${joinResult}`);
}
const eventId = crypto.randomUUID();
await publishInventoryUpdate(eventId);
}
void main();Error Boundary
tsxUse ErrorBoundary to isolate broken widgets so one rendering failure does not take down the page.
Best For
- Risky widgets fed by third-party or malformed payloads.
- Gradual rollouts where one section should fail locally instead of globally.
- Pages that mix stable shell chrome with experimental content blocks.
You Will Use
Related Docs
import { ErrorBoundary, Card } from '@meteorack/modules-sdk-ui';
function RiskyWidget() {
throw new Error('Malformed payload');
}
export function SafeWidget() {
return (
<ErrorBoundary fallback={<Card>Widget failed to render.</Card>}>
<RiskyWidget />
</ErrorBoundary>
);
}Scheduled Tasks
phpUse the cron helpers already mixed into AbstractModule to schedule recurring or one-off jobs.
Best For
- Recurring sync jobs that should not run inline with page loads.
- Delayed cleanup work after activation or imports.
- Background retries where duplicate scheduling must be avoided.
Related Docs
public function onSdkReady(SdkContext $ctx): void
{
parent::onSdkReady($ctx);
$this->scheduleCron('my_module_sync', 'hourly');
$this->scheduleOnce('my_module_cleanup', time() + 3600);
add_action('my_module_sync', [$this, 'sync']);
add_action('my_module_cleanup', [$this, 'cleanup']);
}
public function sync(): void
{
$this->getSdkContext()->logger()->info('my_module.sync', []);
}
public function cleanup(): void
{
$this->getSdkContext()->logger()->info('my_module.cleanup', []);
}Need the surrounding context? The guides walk through the same patterns end to end.