Documentation Index
Fetch the complete documentation index at: https://developers.tally.so/llms.txt
Use this file to discover all available pages before exploring further.
Every Tally widget emits the same set of events. The transport changes depending on the
integration method:
- Embeds and popups send events as
postMessage payloads. Listen for them on the
message event on window.
- Code injection (running inside the form via your custom domain) receives them as
CustomEvents dispatched directly on window.
Add your event listeners to the page hosting the embed or popup — not to the form itself. For
code injection, add them to the form via your custom domain settings.
Fires when the form is rendered. Because embeds and popups are lazy-loaded, you receive
this event each time the form is actually shown.
interface LoadedPayload {
formId: string;
}
window.addEventListener('message', (e) => {
if (e?.data?.includes('Tally.FormLoaded')) {
const payload = JSON.parse(e.data).payload as LoadedPayload;
// ...
}
});
Tally.FormPageView
Fires every time the respondent navigates to a page of the form — handy for multi-page
forms.
interface PageViewPayload {
formId: string;
page: number;
}
// For embeds and popups
window.addEventListener('message', (e) => {
if (e?.data?.includes('Tally.FormPageView')) {
const payload = JSON.parse(e.data).payload as PageViewPayload;
// ...
}
});
// For code injection via a custom domain
window.addEventListener('Tally.FormPageView', (e) => {
const payload = e.detail as PageViewPayload;
// ...
});
Fires when the form is submitted. The payload contains the submission metadata and
the full set of answers.
interface SubmissionPayload {
id: string; // submission ID
respondentId: string;
formId: string;
formName: string;
createdAt: Date; // submission date
fields: Array<{
id: string;
title: string;
type:
| 'INPUT_TEXT'
| 'INPUT_NUMBER'
| 'INPUT_EMAIL'
| 'INPUT_PHONE_NUMBER'
| 'INPUT_LINK'
| 'INPUT_DATE'
| 'INPUT_TIME'
| 'TEXTAREA'
| 'MULTIPLE_CHOICE'
| 'DROPDOWN'
| 'CHECKBOXES'
| 'LINEAR_SCALE'
| 'FILE_UPLOAD'
| 'HIDDEN_FIELDS'
| 'CALCULATED_FIELDS'
| 'RATING'
| 'MULTI_SELECT'
| 'MATRIX'
| 'RANKING'
| 'SIGNATURE'
| 'PAYMENT';
answer: { value: any; raw: any };
}>;
}
// For embeds and popups
window.addEventListener('message', (e) => {
if (e?.data?.includes('Tally.FormSubmitted')) {
const payload = JSON.parse(e.data).payload as SubmissionPayload;
// ...
}
});
// For code injection via a custom domain
window.addEventListener('Tally.FormSubmitted', (e) => {
const payload = e.detail as SubmissionPayload;
// ...
});
Fires only for popups, when the popup is closed.
interface PopupClosedPayload {
formId: string;
}
window.addEventListener('message', (e) => {
if (e?.data?.includes('Tally.PopupClosed')) {
const payload = JSON.parse(e.data).payload as PopupClosedPayload;
// ...
}
});
Popups also accept lifecycle callbacks (onOpen, onClose, onPageView, onSubmit) directly on
the Tally.openPopup() options argument. See Popups for the full reference.