@momentco-ai/moment-sdk
v0.4.0
Published
Embeddable calendar sync widget for external team websites
Readme
Moment SDK
Embeddable calendar sync widget for partner websites. Fans click a trigger on your site, pick Google Calendar or Outlook, and subscribe to games, lists, or full team schedules — without leaving your page.
Zero runtime dependencies. Works via a single <script> tag or npm import.
What ships on npm
| Path | Format | Use case |
| --------------------- | ----------------------- | --------------------------------------------------------------------- |
| dist/moment-sdk.js | IIFE (UMD-style) | <script> tag — exposes MomentSdk and createMomentButton globals |
| dist/moment-sdk.mjs | ESM | Bundler / import |
| dist/types/ | TypeScript declarations | Type-safe integrations |
| examples/ | Static HTML + CLI | Interactive demos (npx moment-sdk-examples) |
Pin a version in production (see Installation).
Installation
Script tag (recommended for partner sites)
Load from a public CDN and pin the version:
<script src="https://cdn.jsdelivr.net/npm/@momentco-ai/[email protected]"></script>Alternatives:
<!-- unpkg -->
<script src="https://unpkg.com/@momentco-ai/[email protected]"></script>npm / bundler
npm install @momentco-ai/moment-sdk
# or
pnpm add @momentco-ai/moment-sdkimport { MomentSdk, createMomentButton } from '@momentco-ai/moment-sdk';
const sdk = new MomentSdk({
onSuccess: (result) => console.log('Synced!', result),
});Quick start
Copy this into any page. Replace your-team-slug and your-event-slug with values provided by Moment.
<!-- 1. Trigger button -->
<button
class="moment-sync-trigger"
data-moment-team-slug="your-team-slug"
data-moment-slug="your-event-slug"
data-moment-trigger-type="moment"
>
Add to Calendar
</button>
<!-- 2. Load SDK (pin version in production) -->
<script src="https://cdn.jsdelivr.net/npm/@momentco-ai/[email protected]"></script>
<script>
const sdk = new MomentSdk({
onSuccess: (result) => console.log('Synced!', result),
onError: (result) => console.error('Failed', result),
onClose: (result) => console.log('Closed', result),
});
</script>Clicking the button opens a modal where the fan selects Google Calendar or Outlook and completes OAuth in a popup. Your callbacks fire when sync succeeds, fails, or the modal closes.
Trigger types
Set data-moment-trigger-type (or triggerType in open() / createMomentButton()):
| Type | Required attributes | Subscribes to |
| ------------------ | ------------------------------------------------------ | ----------------------------------------------------------------- |
| moment (default) | data-moment-team-slug + data-moment-slug | One or more specific games/events |
| list | data-moment-team-slug + data-moment-list-slug | A curated list of moments |
| team | data-moment-team-slug | All published moments for the brand/team |
| brand-playlist | data-moment-team-slug | The brand's default playlist (dynamic schedule thread) |
| playlist | data-moment-team-slug + data-moment-playlist-slugs | One of several playlists (picker when >1 slug) |
| schedule | data-moment-team-slug | Full schedule experience — user picks a mix of playlists + events |
For team, brand-playlist, and schedule, omit moment/list/ID and source-resolution attributes — only data-moment-team-slug is required.
Data attributes
Place these on any element matching the trigger selector (default: .moment-sync-trigger):
| Attribute | Required | Description |
| ------------------------------- | -------------- | ------------------------------------------------------------------------------- |
| data-moment-team-slug | Yes | Team/brand slug provided by Moment |
| data-moment-slug | For moment | Brand-level moment slug (resolved server-side) |
| data-moment-list-slug | For list | List slug |
| data-moment-trigger-type | No | moment (default), list, team, brand-playlist, playlist, or schedule |
| data-moment-playlist-slugs | For playlist | Comma-separated playlist slugs (picker shown when more than one) |
| data-moment-ids | No | Comma-separated moment IDs (fallback if slugs don't resolve) |
| data-moment-game-id | No | Alias for data-moment-ids |
| data-moment-calendar | No | Pre-select provider: google or outlook |
| data-api-source-id | No | Sync-subscription source ID (pairs with external id attributes) |
| data-external-event-id | No | Exact external id (event_id:showing_id) resolved against source |
| data-external-event-id-prefix | No | Upstream event id only — user picks a showing in the embed modal |
When data-api-source-id is set with either data-external-event-id (one showing) or data-external-event-id-prefix (all showings for an event), Live resolves moments server-side — useful when your CMS emits native event IDs instead of Moment slugs.
Init options
| Option | Type | Default | Description |
| ----------------- | ------------------- | ---------------------- | ---------------------------------------------------- |
| triggerSelector | string | .moment-sync-trigger | CSS selector for trigger elements |
| onSuccess | (payload) => void | — | Called on successful calendar sync |
| onError | (payload) => void | — | Called on sync failure |
| onClose | (payload) => void | — | Called when modal closes (success, error, or cancel) |
| onAnalytics | (event) => void | — | Called for every analytics event |
Programmatic API
Open the modal without data attributes:
const sdk = new MomentSdk({
/* callbacks */
});
// Single moment
sdk.open({
teamSlug: 'your-team-slug',
momentSlug: 'your-event-slug',
triggerType: 'moment',
});
// Source-based resolution (CMS event ID — single showing)
sdk.open({
teamSlug: 'your-team-slug',
triggerType: 'moment',
apiSourceId: 'your-sync-source-id',
externalEventId: '1420:2706',
});
// Source-based resolution (event with multiple showings — user picks date)
sdk.open({
teamSlug: 'your-team-slug',
triggerType: 'moment',
apiSourceId: 'your-sync-source-id',
externalEventIdPrefix: '1420',
});
// Follow full team schedule
sdk.open({
teamSlug: 'your-team-slug',
triggerType: 'team',
});
// Follow default playlist
sdk.open({
teamSlug: 'your-team-slug',
triggerType: 'brand-playlist',
});
sdk.close(); // Close modal
sdk.rebind(); // Re-bind triggers after dynamic DOM updates
sdk.destroy(); // Tear down instancecreateMomentButton()
Build a trigger button in JavaScript:
const btn = createMomentButton({
teamSlug: 'your-team-slug',
momentSlug: 'your-event-slug',
triggerType: 'moment',
label: 'Add to Calendar',
className: 'my-calendar-btn', // optional — partner CSS
});
document.getElementById('container').appendChild(btn);The button gets the .moment-sync-trigger class and the correct data-moment-* attributes. Style it with your own CSS or rely on the SDK's minimal default button styles.
Styling
- Trigger buttons — fully themeable on your page. The SDK injects minimal defaults via
:where(.moment-sync-trigger)so your classes win. - Modal UI — hosted in a secure iframe on Moment Live; not styled by your page CSS.
<style>
.my-calendar-btn {
font-family: Inter, system-ui, sans-serif;
font-weight: 600;
border-radius: 9999px;
padding: 10px 18px;
background: #1d4ed8;
color: #fff;
border: none;
cursor: pointer;
}
</style>Or use plain HTML triggers with your own classes:
<button
class="my-calendar-btn moment-sync-trigger"
data-moment-team-slug="your-team-slug"
data-moment-slug="your-event-slug"
data-moment-trigger-type="moment"
>
Add This Game to My Calendar
</button>Analytics events
The onAnalytics callback receives:
| Event | When |
| --------------------- | ----------------------------------- |
| embed.init | SDK initialized |
| embed.trigger.click | User clicked a trigger |
| embed.open | Modal opened |
| oauth.start | OAuth popup requested |
| oauth.popup.blocked | Browser blocked the popup |
| oauth.popup.closed | User closed popup before completing |
| subscribe.success | Calendar sync succeeded |
| subscribe.error | Calendar sync failed |
| embed.close | Modal closed |
Result payload
onSuccess, onError, and onClose receive:
{
"source": "moment-live-embed",
"type": "moment.embed.result",
"result": "success",
"provider": "google",
"triggerType": "moment",
"momentIds": ["..."],
"message": "optional details"
}result is one of success, error, cancelled, or partial (schedule trigger, when only some resources synced). provider is google, outlook, or apple when applicable. triggerType echoes the trigger (moment, list, team, brand-playlist, playlist, or schedule). For schedule, an optional counts: { requested, succeeded, failed } reports the aggregate outcome.
Integration patterns
Single game / event page
<button
class="moment-sync-trigger"
data-moment-team-slug="your-team-slug"
data-moment-slug="your-moment-slug"
data-moment-trigger-type="moment"
>
Sync to Calendar
</button>CMS with native event IDs
When Moment provides a sync-subscription source ID for your feed:
<button
class="moment-sync-trigger"
data-moment-team-slug="your-team-slug"
data-moment-trigger-type="moment"
data-api-source-id="your-source-id"
data-external-event-id="0022400123"
>
Sync to Calendar
</button>Follow the full schedule
<button
class="moment-sync-trigger"
data-moment-team-slug="your-team-slug"
data-moment-trigger-type="team"
>
Sync Full Schedule
</button>Dynamic buttons (SPA / React / Vue)
After rendering new trigger elements, call sdk.rebind() so click handlers attach to the new DOM nodes.
Examples playground
After installing the package, run the bundled demos (no repo clone required):
npx moment-sdk-examplesOpens http://localhost:5051 with moment, collection, and brand-schedule triggers wired to the sdk-demo fixture brand.
Repo maintainers: pnpm examples runs the same server from a local clone.
See examples/README.md for routes and fixture slugs.
Security
OAuth tokens never touch your site — authorization happens on Moment Live's backend. The SDK validates postMessage origin on every iframe and popup message.
See SECURITY.md for the full security model and vulnerability reporting.
Changelog
See CHANGELOG.md for version history and migration notes.
Support
- Email: [email protected]
License
MIT — Moment Co.
