Custom Events Marketplace
Publish your registered events to a shared marketplace so other tenants can fork the schema into their own org. Pro+.
The client.events.custom.marketplace namespace lets PRO+ tenants share event schemas across organisations. A publisher exposes a registered event as a listing; another PRO+ tenant forks the listing — the platform copies the schema into the fork's org as a fresh registration the fork tenant fully owns. Forks are independent thereafter (no upstream propagation). Available in TypeScript, Python, Go, and Terraform.
| SDK | Install | Namespace |
|---|---|---|
| TypeScript | npm install @caldera/platformxe-sdk@^1.3.0 | client.events.custom.marketplace |
| Python | pip install platformxe>=1.2.0 | client.events.custom.marketplace |
| Go | go get github.com/calderax/platformxe-go@v1.2.0 | client.Events.Custom.Marketplace |
| Terraform | source = "calderax/platformxe" ≥ 1.2.0 | platformxe_marketplace_listing |
Plan tiers
| Capability | Free | Basic | Pro | Enterprise |
|---|---|---|---|---|
| Browse listings | ✅ | ✅ | ✅ | ✅ |
| Publish your own | — | — | ✅ | ✅ |
| Fork a listing | — | — | ✅ | ✅ |
| Unpublish / republish | n/a | n/a | ✅ (owner) | ✅ (owner) |
What forking does
When you fork a listing:
- The platform creates a new
customEventRegistrationsrow in your org with the listing's schema copied verbatim. - The fork shows up as a normal registered event — you can emit against it, archive it, bump versions, all independently.
- A
marketplace_forksrow records source attribution (publisher org id + source canonical name) so audit logs preserve provenance. - The publisher's
fork_countincrements — that's the only thing the publisher sees.
Forks do NOT auto-update. When a publisher pushes a new version of an event they previously listed, your fork is unaffected. To pick up the new shape, fork the new listing.
Quick start — browse + fork
// Browse
const { listings } = await client.events.custom.marketplace.list({
search: 'property',
limit: 10,
});
// Look at one
const detail = await client.events.custom.marketplace.get(listings[0].id);
console.log(detail.payloadSchema);
// Fork it into your org under your own naming
const fork = await client.events.custom.marketplace.fork(listings[0].id, {
namespace: 'lettings', // must be on your org's allowlist
name: 'property.favorited', // your local name
// version defaults to '1.0.0' — initial version of the forked event
// status defaults to 'draft' — fork doesn't auto-publish
});
console.log(fork.forkCanonicalName); // 'TENANT_CUSTOM:org_yours:lettings.property.favorited@1.0.0'
console.log(fork.sourceCanonicalName); // attribution to the publisher
listings = client.events.custom.marketplace.list(search="property", limit=10)
detail = client.events.custom.marketplace.get(listings["data"]["listings"][0]["id"])
fork = client.events.custom.marketplace.fork(
listings["data"]["listings"][0]["id"],
namespace="lettings",
name="property.favorited",
)
out, _ := client.Events.Custom.Marketplace.List(platformxe.ListMarketplaceParams{
Search: ptr("property"),
Limit: ptr(10),
})
detail, _ := client.Events.Custom.Marketplace.Get(out.Listings[0].ID)
fork, _ := client.Events.Custom.Marketplace.Fork(out.Listings[0].ID, platformxe.ForkMarketplaceInput{
Namespace: "lettings",
Name: "property.favorited",
})
Publish
// You've already registered this event (Phase 9A) and bumped it to status='published'.
const listing = await client.events.custom.marketplace.publish({
registrationId: 'cer_…',
title: 'Property favorited',
description: 'Fired when a customer favorites a property in a lettings agent UI.',
tags: ['lettings', 'crm', 'favorites'],
});
listing.id; // 'mkl_…'
listing.sourceCanonicalName; // 'TENANT_CUSTOM:your-org:lettings.property.favorited@1.0.0'
listing.status; // 'published'
resource "platformxe_marketplace_listing" "property_favorited" {
registration_id = platformxe_custom_event.property_favorited.id
title = "Property favorited"
description = "Fired when a customer favorites a property in a lettings agent UI."
tags = ["lettings", "crm", "favorites"]
}
Validation:
- Source registration must be owned by the publishing org and
status = 'published'. - The platform-seeded generic event (
platformxe.generic) cannot be listed. - Listings are pinned per
(orgId, namespace, name, version). Re-publishing the same triple returns409 listing_already_exists— bump the source event's version to publish a new shape.
Unpublish + republish
// Hide the listing — existing forks are unaffected.
await client.events.custom.marketplace.unpublish('mkl_…');
// Re-activate later if you change your mind.
await client.events.custom.marketplace.republish('mkl_…');
Archived listings (status = 'archived') cannot be republished — create a new listing for the next version instead.
Browse
const result = await client.events.custom.marketplace.list({
namespace: 'lettings', // optional namespace filter
status: 'published', // default; pass 'unpublished' to widen
search: 'property', // free-text on title + description
limit: 25, // [1..100]
offset: 0,
});
result.listings; // MarketplaceListingSummary[]
result.total; // total count (un-paginated)
Sort order is published_at DESC, id ASC.
Reference — wire shapes
PublishMarketplaceRequest:
{
registrationId: string; // source registration to publish
title: string; // min 3 chars
description?: string;
tags?: string[];
}
MarketplaceListingSummary (returned from list/publish/republish):
{
id: string; // 'mkl_…'
publisherOrganizationId: string;
namespace: string;
name: string;
version: string;
sourceCanonicalName: string; // TENANT_CUSTOM:pub-org:ns.name@v
title: string;
description: string | null;
tags: string[];
status: 'published' | 'unpublished' | 'archived';
forkCount: number;
publishedBy: string;
publishedAt: string;
unpublishedAt: string | null;
createdAt: string;
updatedAt: string;
}
MarketplaceListingDetail extends summary with payloadSchema: object and payloadExample: object | null.
ForkMarketplaceRequest:
{
namespace: string; // must be on fork org's allowlist + match /^[a-z][a-z0-9_-]{1,30}$/
name: string; // matches /^[a-z][a-z0-9_.-]{1,60}$/
version?: string; // defaults to "1.0.0" — initial version of the forked event
description?: string; // optional override; falls back to listing description
status?: 'draft' | 'published'; // default 'draft'
}
ForkMarketplaceResponse:
{
forkId: string; // 'mkf_…' (the marketplace_forks row)
forkRegistrationId: string; // 'cer_…' (the customEventRegistrations row in your org)
forkCanonicalName: string; // TENANT_CUSTOM:your-org:ns.name@v
sourceCanonicalName: string; // attribution
}
Reference — error codes
| Code | Status | Cause |
|---|---|---|
MARKETPLACE_PUBLISH_NOT_AVAILABLE | 402 | Free / Basic plan |
MARKETPLACE_FORK_NOT_AVAILABLE | 402 | Free / Basic plan |
invalid_title | 400 | < 3 chars |
registration_not_found | 404 | Source registration not owned by publisher org |
registration_not_published | 409 | Source must be status='published' |
registration_is_generic | 400 | The platform-seeded platformxe.generic event cannot be listed |
listing_already_exists | 409 | Same (orgId, namespace, name, version) already listed — bump source version |
listing_not_found | 404 | Unpublish / republish / fork against unknown listing (or non-owner for mutations) |
already_unpublished | 409 | Unpublishing a listing already in unpublished/archived |
already_published | 409 | Republishing a listing already published |
archived_listing | 409 | Archived listings cannot be republished |
listing_not_published | 409 | Cannot fork a listing that is not currently published |
invalid_namespace / invalid_name | 400 | Fork target name fails the regex |
namespace_not_allowed | 400 | Fork namespace not on the fork org's allowlist |
fork_already_exists | 409 | A registration already exists at the fork's (namespace, name, version) |
Lifecycle — what fires when
Every state transition emits a typed event on the bus (@caldera/events@^1.24.0). Subscribers (workflow triggers, webhook subscriptions, federation push, audit log) use the bus catalog. The 5 marketplace events:
MARKETPLACE_LISTING_PUBLISHED— listing row createdMARKETPLACE_LISTING_UNPUBLISHED— publisher hides the listingMARKETPLACE_LISTING_REPUBLISHED— re-activatedMARKETPLACE_LISTING_FORKED— a fork row inserted + fork registration createdMARKETPLACE_LISTING_FORK_FAILED— fork attempted but blocked by validation/quota/allowlist