This guide explains how to integrate the Classy Embedded SDK into Single Page Applications built with React, Vue, Angular, or other SPA frameworks.
Add the ?manual-init=true query parameter to the SDK script URL:
<!-- In your index.html -->
<script async src="https://embed.classy.org/api/checkout/sdk/js/YOUR_ORG_ID?manual-init=true"></script>
Render the donation-grid mount before calling eg.init(). The SDK discovers
inline grids from a classy attribute containing the campaign ID. Its
id must match an inline-donation-grid component ID in that
campaign's Studio configuration.
<!-- Render this before calling eg.init() -->
<div id="YOUR_STUDIO_COMPONENT_ID" classy="YOUR_CAMPAIGN_ID"></div>
// The SDK discovers campaigns from the DOM; no campaignId is passed to init()
await window.eg.init();
// Then open modal for any campaign on the page
window.eg.modal.open('YOUR_CAMPAIGN_ID');Call eg.destroy() when leaving the donation page:
window.eg.destroy();All methods are available on window.eg:
| Method | Returns | Description |
|---|---|---|
eg.init(config?) |
Promise<SDKInstance> |
Initialize the SDK and resolve with an SDK instance when initialization is complete. |
eg.destroy() |
void |
Clean up the SDK, remove event listeners, restore DOM elements. |
eg.isInitialized() |
boolean |
Check if the SDK is currently initialized. |
eg.modal |
ModalSDK |
Modal API for opening/closing donation modal (available after init). |
eg.sendAnalyticsEvent(name, data) |
void |
Send custom analytics events (available after init). |
import { useEffect } from 'react';
// campaignId and componentId are used for the DOM mount, not eg.init()
function DonatePage({ campaignId, componentId }) {
useEffect(() => {
let mounted = true;
const initSDK = async () => {
if (window.eg?.init && mounted) {
// React has rendered the mount before this effect runs
await window.eg.init();
}
};
initSDK();
// Cleanup on unmount
return () => {
mounted = false;
window.eg?.destroy();
};
}, [campaignId, componentId]);
return (
<div>
<h1>Make a Donation</h1>
{/* componentId must match the Studio inline-donation-grid component ID */}
<div id={componentId} {...{ classy: campaignId }}></div>
</div>
);
}<script setup>
import { onMounted, onUnmounted } from 'vue';
// campaignId and componentId are used for the DOM mount, not eg.init()
const props = defineProps(['campaignId', 'componentId']);
onMounted(async () => {
if (window.eg?.init) {
// Vue has rendered the mount before onMounted runs
await window.eg.init();
}
});
onUnmounted(() => {
window.eg?.destroy();
});
</script>
<template>
<div>
<h1>Make a Donation</h1>
<!-- componentId must match the Studio inline-donation-grid component ID -->
<div :id="componentId" :classy="campaignId"></div>
</div>
</template>import { Component, Input, OnInit, OnDestroy } from '@angular/core';
declare global {
interface Window { eg: any; }
}
@Component({
selector: 'app-donate',
// componentId must match the Studio inline-donation-grid component ID
template: `
<div>
<h1>Make a Donation</h1>
<div [id]="componentId" [attr.classy]="campaignId"></div>
</div>
`
})
export class DonateComponent implements OnInit, OnDestroy {
// campaignId and componentId are used for the DOM mount, not eg.init()
@Input() campaignId: string = '';
@Input() componentId: string = '';
async ngOnInit() {
if (window.eg?.init) {
// Angular has rendered the mount before ngOnInit runs
await window.eg.init();
}
}
ngOnDestroy() {
window.eg?.destroy();
}
}'use client';
import { useEffect } from 'react';
// campaignId and componentId are used for the DOM mount, not eg.init()
export default function DonatePage({ campaignId, componentId }) {
useEffect(() => {
let mounted = true;
const initSDK = async () => {
// Wait for SDK to load
const waitForSDK = () => new Promise((resolve) => {
if (window.eg?.init) return resolve(window.eg);
const interval = setInterval(() => {
if (window.eg?.init) {
clearInterval(interval);
resolve(window.eg);
}
}, 100);
});
const eg = await waitForSDK();
if (mounted) {
// The client component has rendered the mount before this effect runs
await eg.init();
}
};
initSDK();
return () => {
mounted = false;
window.eg?.destroy();
};
}, [campaignId, componentId]);
return (
<div>
<h1>Make a Donation</h1>
{/* componentId must match the Studio inline-donation-grid component ID */}
<div id={componentId} {...{ classy: campaignId }}></div>
</div>
);
}'use client' directive since the SDK interacts with the DOM and window object.
classy, not data-classy-campaign, for the campaign ID. The element's id must exactly match an inline-donation-grid component ID from that campaign's Studio configuration.
eg.destroy() when leaving the donation pageawait when calling eg.init() - it returns a Promiseid + classy mount before calling eg.init()eg.init() multiple times without calling eg.destroy() first?manual-init=true to the SDK script URLdata-classy-campaign; the crawler does not discover itMake sure the mount exists before eg.init() runs, uses classy with the campaign ID, and has an id matching the campaign's Studio inline-grid component. Also call eg.destroy() when leaving the page. The SDK restores the original DOM element on destroy, which allows it to be reinitialized on the next visit.
Ensure the SDK is fully initialized before the user can interact with the donation form. Use the Promise returned by init() to know when it's ready.
The SDK script loads asynchronously. Wait for it to load before calling init(). See the Next.js example for a pattern to wait for the SDK.