Catalog reference
Every StoreOS plugin — ID, provider key, credential fields, and validation rules.
The catalog package
The plugin catalog is static data, published as @storeos/plugins and imported by both the console and storeos.dev. It is the single source of truth for names, logos, install copy, and which plugins are installable.
import {
PLUGIN_CATEGORIES,
INSTALLABLE_PLUGIN_IDS,
listAllPlugins,
listInstallablePlugins,
getPluginById,
getPluginBySlug,
getPluginCategory,
pluginSlug,
pluginLogoUrl,
catalogCopyAsParagraphs,
} from "@storeos/plugins";
listAllPlugins().length; // 23
listInstallablePlugins().length; // 20| Export | Returns |
|---|---|
PLUGIN_CATEGORIES | PluginCategory[] — the full tree, in display order |
INSTALLABLE_PLUGIN_IDS | The 20 IDs the API accepts for configure/uninstall |
listAllPlugins() | Flat PluginCatalogItem[] across all categories |
listInstallablePlugins() | Items with a provider and no comingSoon |
getPluginById(id) | One item, or null |
getPluginBySlug(slug) | Matches slug or id, or null |
getPluginCategory(item) | The category containing that item |
pluginSlug(item) | item.slug ?? item.id — the /plugins/{slug} segment |
pluginLogoUrl(item, base?) | Logo path, optionally prefixed with a CDN origin |
catalogCopyAsParagraphs(value) | Normalizes string | string[] | undefined to string[] |
PluginCatalogItem
type PluginCatalogItem = {
id: PluginId;
slug?: string; // URL segment; defaults to id
name: string;
description: string;
tagline?: string;
installationGuide?: string | string[];
faq?: string | string[]; // alternating question, answer
authorName?: string;
authorUrl?: string;
logo: string; // public path, leading slash
comingSoon?: boolean; // preview only; Install disabled
provider?: PluginProvider; // backend key when connectable today
};An item is installable when it has a provider and is not comingSoon. comingSoon items still render a detail page so merchants can preview what is landing.
Delivery
| Plugin | ID | Provider | Status |
|---|---|---|---|
| Pathao | pathao | pathao | Installable |
| Steadfast | steadfast | steadfast | Installable |
| RedX | redx | — | Coming soon |
Pathao — plugins.courier.pathao
| Field | Type | Notes |
|---|---|---|
store_id | number | Pathao merchant store ID |
client_id | string | From the Pathao merchant dashboard |
client_secret | string | |
username | string | Merchant account login |
password | string | |
webhook_secret | string | Paste the StoreOS webhook URL into Pathao |
Steadfast — plugins.courier.steadfast
| Field | Type | Notes |
|---|---|---|
api_key | string | Steadfast merchant panel |
secret_key | string | |
webhook_token | string | Required for delivery status sync |
Both couriers can be checked live with plugin__validateCourier, and both need a webhook URL pasted into the provider's dashboard.
Payments
| Plugin | ID | Provider | Status |
|---|---|---|---|
| bKash | bkash | bkash | Installable |
| SSLCOMMERZ | sslcommerz | sslcommerz | Installable |
Payment plugins are the only ones with two credential sets plus a mode:
{
live?: { /* credentials */ },
sandbox?: { /* credentials */ },
activeMode?: "live" | "sandbox",
}activeMode decides which set is used at checkout, so a merchant can keep sandbox keys in place after going live.
bKash credentials — appKey, appSecret, username, password, secretKey
SSLCOMMERZ credentials — storeId, storePassword
Configuration completeness is reported by plugin__paymentProviderConfigurationStatus, which returns configured plus the list of missingFields from the active credential set.
Notifications
| Plugin | ID | Provider | Channel |
|---|---|---|---|
| Waland | waland | waland | |
| BulkSMSBD | bulksmsbd | bulksmsbd | SMS |
| SMTP | smtp | smtp |
| Plugin | Fields |
|---|---|
waland | apiKey, sessionId |
bulksmsbd | apiKey, senderId |
smtp | host, port, username, password, senderFrom |
Delivery can be exercised end to end with plugin__testNotificationDeliveries on channel WHATSAPP, SMS, or EMAIL.
Analytics
| Plugin | ID | Path | Field | Validation |
|---|---|---|---|---|
| Google Tag Manager | google-tag-manager | plugins.analytics.gtm | OAuth | See Google OAuth |
| Google Analytics | google-analytics | plugins.analytics.ga | OAuth | See Google OAuth |
| Hotjar | hotjar | plugins.analytics.hotjar | siteId | 5–12 digits |
| Meta Pixel | meta-pixel | plugins.analytics.metaPixel | pixelId | 5–20 digits |
| Microsoft Clarity | microsoft-clarity | plugins.analytics.clarity | projectId | 8–16 alphanumerics |
| Lucky Orange | lucky-orange | plugins.analytics.luckyOrange | siteId | 6–40 of a-z0-9- |
| ShareChat Pixel | sharechat-pixel | plugins.analytics.shareChatPixel | pixelId | 4–64 chars, no < or > |
| Facebook domain verification | facebook-domain-verification | plugins.seo.facebookDomainVerification | content | Meta token, see below |
| TikTok Pixel | tiktok-pixel | — | — | Coming soon |
Validation runs server-side in plugin__configure. A value that fails its pattern is rejected as a missing payload rather than silently stored — so a typo'd pixel ID fails loudly at configure time, not weeks later in your analytics.
Marketing
| Plugin | ID | Path | Field | Validation |
|---|---|---|---|---|
| Popupsmart | popupsmart | plugins.marketing.popupsmart | accountId | 3–12 digits |
| Google Search Console | google-search-console | plugins.seo.searchConsole | metaContent | Meta token, see below |
| SEO catalog | seo-catalog | — | — | Coming soon |
Meta verification tokens
Google Search Console and Facebook domain verification both accept either the bare token or the full tag pasted from the provider:
<meta name="google-site-verification" content="AbC123_token-value" />The content="…" value is extracted, then required to be 8–200 characters of A-Za-z0-9_-. Anything containing < or > after extraction is rejected.
Support
| Plugin | ID | Path | Fields | Validation |
|---|---|---|---|---|
| Intercom | intercom | plugins.support.intercom | appId | 6–20 alphanumerics |
| Tawk.to | tawk-to | plugins.support.tawkTo | propertyId, widgetId | Property: 16–32 hex. Widget: 1–20 alphanumerics |
| WhatsApp chat | whatsapp-chat | plugins.support.whatsappChat | phoneNumber, prefilledMessage? | Non-digits stripped, then 8–15 digits |
whatsapp-chat normalizes before storing: +880 1711-000000 is saved as 8801711000000. An empty prefilledMessage is stored as null, not an empty string.
Rendering the catalog yourself
The catalog is plain data, so a marketplace page is a map over it:
import { PLUGIN_CATEGORIES, pluginSlug, pluginLogoUrl } from "@storeos/plugins";
export function Marketplace({ cdn }: { cdn?: string }) {
return PLUGIN_CATEGORIES.map((category) => (
<section key={category.id}>
<h2>{category.title}</h2>
<p>{category.description}</p>
{category.items.map((item) => (
<a key={item.id} href={`/plugins/${pluginSlug(item)}`}>
<img src={pluginLogoUrl(item, cdn)} alt="" />
<h3>{item.name}</h3>
<p>{item.tagline ?? item.description}</p>
{item.comingSoon ? <span>Coming soon</span> : null}
</a>
))}
</section>
));
}installationGuide and faq are authored as string[]. FAQ entries alternate question, answer, question, answer — pair them up before rendering:
import { catalogCopyAsParagraphs, getPluginById } from "@storeos/plugins";
const plugin = getPluginById("popupsmart");
const faq = catalogCopyAsParagraphs(plugin?.faq);
const pairs = faq.reduce<Array<[string, string]>>((acc, line, i) => {
if (i % 2 === 0) acc.push([line, faq[i + 1] ?? ""]);
return acc;
}, []);