Documentation
Feedback
Guides
Storefront Development

Storefront Development
FastStore
Use case: Implementing custom newsletter analytics events
This guide walks you through the process of implementing custom analytics events in FastStore projects. The example in this guide explains how to track user subscriptions by implementing two custom analytics events for a custom newsletter section:
  • Submit newsletter: Triggered when a user clicks Subscribe.
  • Submit newsletter success: Triggered upon successful subscription.
{"base64":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAACCAIAAADwyuo0AAAACXBIWXMAAAPoAAAD6AG1e1JrAAAAI0lEQVR4nGNYvPvBw///vcMy+QQlGAJLVzeueiep48LGygkAqiIKhsD1y8oAAAAASUVORK5CYII=","img":{"src":"https://vtexhelp.vtexassets.com/assets/docs/src/analytics-custom-newsletter-firing-event___45d3f6e3f8586ef7fbbb389128bb4460.gif","width":1358,"height":607,"type":"gif"}}
For a complete implementation example, check the playground.store repository, which provides more in-depth details.

Before you begin

To follow this guide, you should be familiar with the following topics:

Instructions

Creating a custom newsletter section

Create a new section to receive the two new custom events:
  1. Open your store repository. If you don't have it, create a folder named components in the src directory.
  2. In components, create the sections folder.
  3. In the sections folder, create the CustomNewsletter.tsx file and add the following code. Below is an explanation of the code.
CustomNewsletter.tsx

_174
import type {
_174
ToastProps,
_174
NewsletterAddendumProps as UINewsletterAddendumProps,
_174
NewsletterHeaderProps as UINewsletterHeaderProps,
_174
NewsletterProps as UINewsletterProps,
_174
} from "@faststore/ui";
_174
import { useUI } from "@faststore/ui";
_174
import styles from "./section.module.scss";
_174
_174
import {
_174
Button,
_174
Icon,
_174
InputField,
_174
Newsletter,
_174
NewsletterAddendum,
_174
NewsletterContent,
_174
NewsletterForm,
_174
NewsletterHeader,
_174
} from "@faststore/ui";
_174
_174
import { Section } from "@faststore/core";
_174
import { useNewsletter_unstable as useNewsletter } from "@faststore/core/experimental";
_174
import { type FormEvent, useRef } from "react";
_174
import { sendAnalyticsEvent, useAnalyticsEvent } from "@faststore/sdk";
_174
_174
interface ArbitraryEvent {
_174
name: string;
_174
params: string;
_174
}
_174
_174
type SubscribeMessage = {
_174
icon: string;
_174
title: string;
_174
message: string;
_174
};
_174
_174
export interface NewsletterProps {
_174
title: UINewsletterHeaderProps["title"];
_174
card?: UINewsletterProps["card"];
_174
colorVariant?: UINewsletterProps["colorVariant"];
_174
description?: UINewsletterHeaderProps["description"];
_174
privacyPolicy?: UINewsletterAddendumProps["addendum"];
_174
emailInputLabel?: string;
_174
displayNameInput?: boolean;
_174
icon: {
_174
alt: string;
_174
icon: string;
_174
};
_174
nameInputLabel?: string;
_174
subscribeButtonLabel?: string;
_174
subscribeButtonLoadingLabel?: string;
_174
toastSubscribe?: SubscribeMessage;
_174
toastSubscribeError?: SubscribeMessage;
_174
}
_174
_174
export const AnalyticsHandler = () => {
_174
useAnalyticsEvent((event: ArbitraryEvent) => {
_174
console.log("Received event", event);
_174
});
_174
_174
/* ... */
_174
_174
return null;
_174
};
_174
_174
function CustomNewsletter({
_174
icon: { icon, alt: iconAlt },
_174
title,
_174
description,
_174
privacyPolicy,
_174
emailInputLabel,
_174
displayNameInput,
_174
nameInputLabel,
_174
subscribeButtonLabel,
_174
subscribeButtonLoadingLabel,
_174
card,
_174
toastSubscribe,
_174
toastSubscribeError,
_174
colorVariant,
_174
...otherProps
_174
}: NewsletterProps) {
_174
const { pushToast } = useUI();
_174
const { subscribeUser, loading } = useNewsletter();
_174
_174
const formRef = useRef<HTMLFormElement>(null);
_174
const nameInputRef = useRef<HTMLInputElement>(null);
_174
const emailInputRef = useRef<HTMLInputElement>(null);
_174
_174
const onSubmit = async (event: FormEvent) => {
_174
event.preventDefault();
_174
_174
try {
_174
// send event start
_174
sendAnalyticsEvent<ArbitraryEvent>({
_174
name: "Submit Newsletter",
_174
params: "test",
_174
});
_174
_174
const data = await subscribeUser({
_174
data: {
_174
name: nameInputRef.current?.value ?? "",
_174
email: emailInputRef.current?.value ?? "",
_174
},
_174
});
_174
_174
if (data?.subscribeToNewsletter?.id) {
_174
// send event start
_174
sendAnalyticsEvent<ArbitraryEvent>({
_174
name: "Submit newsletter success",
_174
params: "test",
_174
});
_174
_174
pushToast({
_174
...toastSubscribe,
_174
status: "INFO",
_174
icon: (
_174
<Icon width={30} height={30} name={toastSubscribe?.icon ?? ""} />
_174
),
_174
} as ToastProps);
_174
_174
formRef?.current?.reset();
_174
}
_174
} catch (error) {
_174
pushToast({
_174
...toastSubscribeError,
_174
status: "ERROR",
_174
icon: (
_174
<Icon width={30} height={30} name={toastSubscribeError?.icon ?? ""} />
_174
),
_174
} as ToastProps);
_174
}
_174
};
_174
_174
return (
_174
<Section className={`${styles.section} section-newsletter layout__section`}>
_174
<AnalyticsHandler />
_174
<Newsletter card={card ?? false} colorVariant={colorVariant}>
_174
<NewsletterForm ref={formRef} onSubmit={onSubmit}>
_174
<NewsletterHeader
_174
title={title}
_174
description={description}
_174
icon={
_174
<Icon width={32} height={32} name={icon} aria-label={iconAlt} />
_174
}
_174
/>
_174
_174
<NewsletterContent>
_174
{displayNameInput && (
_174
<InputField
_174
id="newsletter-name"
_174
required
_174
label={nameInputLabel ?? "Name"}
_174
inputRef={nameInputRef}
_174
/>
_174
)}
_174
<InputField
_174
id="newsletter-email"
_174
type="email"
_174
required
_174
label={emailInputLabel ?? "Email"}
_174
inputRef={emailInputRef}
_174
/>
_174
<NewsletterAddendum addendum={privacyPolicy} />
_174
<Button variant="secondary" inverse type="submit">
_174
{loading ? subscribeButtonLoadingLabel : subscribeButtonLabel}
_174
</Button>
_174
</NewsletterContent>
_174
</NewsletterForm>
_174
</Newsletter>
_174
</Section>
_174
);
_174
}
_174
_174
export default CustomNewsletter;

NewsletterProps

Defines the customizable properties of the newsletter.
  • title: Heading of the newsletter.
  • description: Subtitle or description text.
  • emailInputLabel: Label for the email input field.
  • subscribeButtonLabel: Text for the submit button.
  • toastSubscribe: Success pop-up settings.
  • toastSubscribeError: Error pop-up settings.
  • privacyPolicy: Legal disclaimer text.
  • card: Defines whether the newsletter should display in a card-style container.
  • colorVariant: Styling theme (example: "primary", "secondary").
CustomNewsletter.tsx

_174
import type {
_174
ToastProps,
_174
NewsletterAddendumProps as UINewsletterAddendumProps,
_174
NewsletterHeaderProps as UINewsletterHeaderProps,
_174
NewsletterProps as UINewsletterProps,
_174
} from "@faststore/ui";
_174
import { useUI } from "@faststore/ui";
_174
import styles from "./section.module.scss";
_174
_174
import {
_174
Button,
_174
Icon,
_174
InputField,
_174
Newsletter,
_174
NewsletterAddendum,
_174
NewsletterContent,
_174
NewsletterForm,
_174
NewsletterHeader,
_174
} from "@faststore/ui";
_174
_174
import { Section } from "@faststore/core";
_174
import { useNewsletter_unstable as useNewsletter } from "@faststore/core/experimental";
_174
import { type FormEvent, useRef } from "react";
_174
import { sendAnalyticsEvent, useAnalyticsEvent } from "@faststore/sdk";
_174
_174
interface ArbitraryEvent {
_174
name: string;
_174
params: string;
_174
}
_174
_174
type SubscribeMessage = {
_174
icon: string;
_174
title: string;
_174
message: string;
_174
};
_174
_174
export interface NewsletterProps {
_174
title: UINewsletterHeaderProps["title"];
_174
card?: UINewsletterProps["card"];
_174
colorVariant?: UINewsletterProps["colorVariant"];
_174
description?: UINewsletterHeaderProps["description"];
_174
privacyPolicy?: UINewsletterAddendumProps["addendum"];
_174
emailInputLabel?: string;
_174
displayNameInput?: boolean;
_174
icon: {
_174
alt: string;
_174
icon: string;
_174
};
_174
nameInputLabel?: string;
_174
subscribeButtonLabel?: string;
_174
subscribeButtonLoadingLabel?: string;
_174
toastSubscribe?: SubscribeMessage;
_174
toastSubscribeError?: SubscribeMessage;
_174
}
_174
_174
export const AnalyticsHandler = () => {
_174
useAnalyticsEvent((event: ArbitraryEvent) => {
_174
console.log("Received event", event);
_174
});
_174
_174
/* ... */
_174
_174
return null;
_174
};
_174
_174
function CustomNewsletter({
_174
icon: { icon, alt: iconAlt },
_174
title,
_174
description,
_174
privacyPolicy,
_174
emailInputLabel,
_174
displayNameInput,
_174
nameInputLabel,
_174
subscribeButtonLabel,
_174
subscribeButtonLoadingLabel,
_174
card,
_174
toastSubscribe,
_174
toastSubscribeError,
_174
colorVariant,
_174
...otherProps
_174
}: NewsletterProps) {
_174
const { pushToast } = useUI();
_174
const { subscribeUser, loading } = useNewsletter();
_174
_174
const formRef = useRef<HTMLFormElement>(null);
_174
const nameInputRef = useRef<HTMLInputElement>(null);
_174
const emailInputRef = useRef<HTMLInputElement>(null);
_174
_174
const onSubmit = async (event: FormEvent) => {
_174
event.preventDefault();
_174
_174
try {
_174
// send event start
_174
sendAnalyticsEvent<ArbitraryEvent>({
_174
name: "Submit Newsletter",
_174
params: "test",
_174
});
_174
_174
const data = await subscribeUser({
_174
data: {
_174
name: nameInputRef.current?.value ?? "",
_174
email: emailInputRef.current?.value ?? "",
_174
},
_174
});
_174
_174
if (data?.subscribeToNewsletter?.id) {
_174
// send event start
_174
sendAnalyticsEvent<ArbitraryEvent>({
_174
name: "Submit newsletter success",
_174
params: "test",
_174
});
_174
_174
pushToast({
_174
...toastSubscribe,
_174
status: "INFO",
_174
icon: (
_174
<Icon width={30} height={30} name={toastSubscribe?.icon ?? ""} />
_174
),
_174
} as ToastProps);
_174
_174
formRef?.current?.reset();
_174
}
_174
} catch (error) {
_174
pushToast({
_174
...toastSubscribeError,
_174
status: "ERROR",
_174
icon: (
_174
<Icon width={30} height={30} name={toastSubscribeError?.icon ?? ""} />
_174
),
_174
} as ToastProps);
_174
}
_174
};
_174
_174
return (
_174
<Section className={`${styles.section} section-newsletter layout__section`}>
_174
<AnalyticsHandler />
_174
<Newsletter card={card ?? false} colorVariant={colorVariant}>
_174
<NewsletterForm ref={formRef} onSubmit={onSubmit}>
_174
<NewsletterHeader
_174
title={title}
_174
description={description}
_174
icon={
_174
<Icon width={32} height={32} name={icon} aria-label={iconAlt} />
_174
}
_174
/>
_174
_174
<NewsletterContent>
_174
{displayNameInput && (
_174
<InputField
_174
id="newsletter-name"
_174
required
_174
label={nameInputLabel ?? "Name"}
_174
inputRef={nameInputRef}
_174
/>
_174
)}
_174
<InputField
_174
id="newsletter-email"
_174
type="email"
_174
required
_174
label={emailInputLabel ?? "Email"}
_174
inputRef={emailInputRef}
_174
/>
_174
<NewsletterAddendum addendum={privacyPolicy} />
_174
<Button variant="secondary" inverse type="submit">
_174
{loading ? subscribeButtonLoadingLabel : subscribeButtonLabel}
_174
</Button>
_174
</NewsletterContent>
_174
</NewsletterForm>
_174
</Newsletter>
_174
</Section>
_174
);
_174
}
_174
_174
export default CustomNewsletter;

onSubmit function

Handles the newsletter subscription process.
  • event.preventDefault(): Prevents default form submission.
    • sendAnalyticsEvent: Tracks the submission attempt.
      • The onSubmit function calls the subscribeUser() with email and name.
        • If successful:
          • Shows a success toast (pushToast).
          • Resets the form (formRef.current?.reset()).
        • If the call fails, it shows an error toast.
    • pushToast: Displays temporary success/error messages.
    • sendAnalyticsEvent: Tracks user interactions.
      • Submit Newsletter event: Tracks the form submission attempt when the user clicks the Subscribe button.
      • Submit newsletter success event: Tracks if the subscription was successful.
    • useAnalyticsEvent hook: Tracks and handles custom analytics events.
CustomNewsletter.tsx

_174
import type {
_174
ToastProps,
_174
NewsletterAddendumProps as UINewsletterAddendumProps,
_174
NewsletterHeaderProps as UINewsletterHeaderProps,
_174
NewsletterProps as UINewsletterProps,
_174
} from "@faststore/ui";
_174
import { useUI } from "@faststore/ui";
_174
import styles from "./section.module.scss";
_174
_174
import {
_174
Button,
_174
Icon,
_174
InputField,
_174
Newsletter,
_174
NewsletterAddendum,
_174
NewsletterContent,
_174
NewsletterForm,
_174
NewsletterHeader,
_174
} from "@faststore/ui";
_174
_174
import { Section } from "@faststore/core";
_174
import { useNewsletter_unstable as useNewsletter } from "@faststore/core/experimental";
_174
import { type FormEvent, useRef } from "react";
_174
import { sendAnalyticsEvent, useAnalyticsEvent } from "@faststore/sdk";
_174
_174
interface ArbitraryEvent {
_174
name: string;
_174
params: string;
_174
}
_174
_174
type SubscribeMessage = {
_174
icon: string;
_174
title: string;
_174
message: string;
_174
};
_174
_174
export interface NewsletterProps {
_174
title: UINewsletterHeaderProps["title"];
_174
card?: UINewsletterProps["card"];
_174
colorVariant?: UINewsletterProps["colorVariant"];
_174
description?: UINewsletterHeaderProps["description"];
_174
privacyPolicy?: UINewsletterAddendumProps["addendum"];
_174
emailInputLabel?: string;
_174
displayNameInput?: boolean;
_174
icon: {
_174
alt: string;
_174
icon: string;
_174
};
_174
nameInputLabel?: string;
_174
subscribeButtonLabel?: string;
_174
subscribeButtonLoadingLabel?: string;
_174
toastSubscribe?: SubscribeMessage;
_174
toastSubscribeError?: SubscribeMessage;
_174
}
_174
_174
export const AnalyticsHandler = () => {
_174
useAnalyticsEvent((event: ArbitraryEvent) => {
_174
console.log("Received event", event);
_174
});
_174
_174
/* ... */
_174
_174
return null;
_174
};
_174
_174
function CustomNewsletter({
_174
icon: { icon, alt: iconAlt },
_174
title,
_174
description,
_174
privacyPolicy,
_174
emailInputLabel,
_174
displayNameInput,
_174
nameInputLabel,
_174
subscribeButtonLabel,
_174
subscribeButtonLoadingLabel,
_174
card,
_174
toastSubscribe,
_174
toastSubscribeError,
_174
colorVariant,
_174
...otherProps
_174
}: NewsletterProps) {
_174
const { pushToast } = useUI();
_174
const { subscribeUser, loading } = useNewsletter();
_174
_174
const formRef = useRef<HTMLFormElement>(null);
_174
const nameInputRef = useRef<HTMLInputElement>(null);
_174
const emailInputRef = useRef<HTMLInputElement>(null);
_174
_174
const onSubmit = async (event: FormEvent) => {
_174
event.preventDefault();
_174
_174
try {
_174
// send event start
_174
sendAnalyticsEvent<ArbitraryEvent>({
_174
name: "Submit Newsletter",
_174
params: "test",
_174
});
_174
_174
const data = await subscribeUser({
_174
data: {
_174
name: nameInputRef.current?.value ?? "",
_174
email: emailInputRef.current?.value ?? "",
_174
},
_174
});
_174
_174
if (data?.subscribeToNewsletter?.id) {
_174
// send event start
_174
sendAnalyticsEvent<ArbitraryEvent>({
_174
name: "Submit newsletter success",
_174
params: "test",
_174
});
_174
_174
pushToast({
_174
...toastSubscribe,
_174
status: "INFO",
_174
icon: (
_174
<Icon width={30} height={30} name={toastSubscribe?.icon ?? ""} />
_174
),
_174
} as ToastProps);
_174
_174
formRef?.current?.reset();
_174
}
_174
} catch (error) {
_174
pushToast({
_174
...toastSubscribeError,
_174
status: "ERROR",
_174
icon: (
_174
<Icon width={30} height={30} name={toastSubscribeError?.icon ?? ""} />
_174
),
_174
} as ToastProps);
_174
}
_174
};
_174
_174
return (
_174
<Section className={`${styles.section} section-newsletter layout__section`}>
_174
<AnalyticsHandler />
_174
<Newsletter card={card ?? false} colorVariant={colorVariant}>
_174
<NewsletterForm ref={formRef} onSubmit={onSubmit}>
_174
<NewsletterHeader
_174
title={title}
_174
description={description}
_174
icon={
_174
<Icon width={32} height={32} name={icon} aria-label={iconAlt} />
_174
}
_174
/>
_174
_174
<NewsletterContent>
_174
{displayNameInput && (
_174
<InputField
_174
id="newsletter-name"
_174
required
_174
label={nameInputLabel ?? "Name"}
_174
inputRef={nameInputRef}
_174
/>
_174
)}
_174
<InputField
_174
id="newsletter-email"
_174
type="email"
_174
required
_174
label={emailInputLabel ?? "Email"}
_174
inputRef={emailInputRef}
_174
/>
_174
<NewsletterAddendum addendum={privacyPolicy} />
_174
<Button variant="secondary" inverse type="submit">
_174
{loading ? subscribeButtonLoadingLabel : subscribeButtonLabel}
_174
</Button>
_174
</NewsletterContent>
_174
</NewsletterForm>
_174
</Newsletter>
_174
</Section>
_174
);
_174
}
_174
_174
export default CustomNewsletter;

Creating the section index file

To make the Newsletter section available in the project, create a file named index.tsx in the components folder and add the following:
CustomNewsletter.tsx
index.tsx

_10
import CustomNewsletter from "./sections/CustomNewsletter/CustomNewsletter";
_10
_10
const sections = {
_10
CustomNewsletter,
_10
};
_10
_10
export default sections;

Styling the new section

To add styles to this new section, create a section.module.scss file and add the following:
CustomNewsletter.tsx
index.tsx
section.module.scss

_10
@layer components {
_10
.section {
_10
@import "@faststore/ui/src/components/atoms/Button/styles.scss";
_10
@import "@faststore/ui/src/components/atoms/Icon/styles.scss";
_10
@import "@faststore/ui/src/components/atoms/Input/styles.scss";
_10
@import "@faststore/ui/src/components/atoms/Link/styles.scss";
_10
@import "@faststore/ui/src/components/molecules/InputField/styles.scss";
_10
@import "@faststore/ui/src/components/organisms/Newsletter/styles.scss";
_10
}
_10
}

Updating the CMS folder

  1. In the root directory of your storefront project, create a folder named cms if it doesn't exist.
  2. In cms, create the faststore folder.
  3. In the cms/faststore folder, create the sections.json file.
  4. In the sections.json file, add the new section you want to display in Headless CMS. The schema below defines how Headless CMS renders a section:
CustomNewsletter.tsx
index.tsx
section.module.scss
sections.json

_136
[
_136
{
_136
"name": "CustomNewsletter",
_136
"requiredScopes": [],
_136
"schema": {
_136
"title": "Custom Newsletter",
_136
"description": "Allow users to subscribe to your updates",
_136
"type": "object",
_136
"required": ["title"],
_136
"properties": {
_136
"icon": {
_136
"title": "Icon",
_136
"type": "object",
_136
"properties": {
_136
"icon": {
_136
"title": "Icon",
_136
"type": "string",
_136
"enumNames": ["Envelope"],
_136
"enum": ["Envelope"],
_136
"default": "Envelope"
_136
},
_136
"alt": {
_136
"type": "string",
_136
"title": "Alternative Label",
_136
"default": "Envelope"
_136
}
_136
}
_136
},
_136
"title": {
_136
"title": "Title",
_136
"type": "string",
_136
"default": "Get News and Special Offers!"
_136
},
_136
"description": {
_136
"title": "Description",
_136
"type": "string",
_136
"default": "Receive our news and promotions in advance"
_136
},
_136
"privacyPolicy": {
_136
"title": "Privacy Policy Disclaimer",
_136
"type": "string",
_136
"widget": {
_136
"ui:widget": "draftjs-rich-text"
_136
}
_136
},
_136
"emailInputLabel": {
_136
"title": "Email input label",
_136
"type": "string",
_136
"default": "Your Email"
_136
},
_136
"displayNameInput": {
_136
"title": "Request name?",
_136
"type": "boolean",
_136
"default": true
_136
},
_136
"nameInputLabel": {
_136
"title": "Name input label",
_136
"type": "string",
_136
"default": "Your Name"
_136
},
_136
"subscribeButtonLabel": {
_136
"title": "Subscribe button label",
_136
"type": "string",
_136
"default": "Subscribe"
_136
},
_136
"subscribeButtonLoadingLabel": {
_136
"title": "Subscribe button loading label",
_136
"type": "string",
_136
"default": "Loading..."
_136
},
_136
"card": {
_136
"title": "Newsletter should be in card format?",
_136
"type": "boolean",
_136
"default": false
_136
},
_136
"colorVariant": {
_136
"title": "Color variant",
_136
"type": "string",
_136
"enumNames": ["Main", "Light", "Accent"],
_136
"enum": ["main", "light", "accent"],
_136
"default": "main"
_136
},
_136
"toastSubscribe": {
_136
"title": "Toast Subscribe",
_136
"type": "object",
_136
"properties": {
_136
"title": {
_136
"title": "Title",
_136
"description": "Message Title",
_136
"type": "string",
_136
"default": "Hooray!"
_136
},
_136
"message": {
_136
"title": "Message",
_136
"description": "Message",
_136
"type": "string",
_136
"default": "Thank for your subscription."
_136
},
_136
"icon": {
_136
"title": "Icon",
_136
"type": "string",
_136
"enumNames": ["CircleWavyCheck"],
_136
"enum": ["CircleWavyCheck"],
_136
"default": "CircleWavyCheck"
_136
}
_136
}
_136
},
_136
"toastSubscribeError": {
_136
"title": "Toast Subscribe Error",
_136
"type": "object",
_136
"properties": {
_136
"title": {
_136
"title": "Title",
_136
"description": "Message Title",
_136
"type": "string",
_136
"default": "Oops."
_136
},
_136
"message": {
_136
"title": "Message",
_136
"description": "Message",
_136
"type": "string",
_136
"default": "Something went wrong. Please Try again."
_136
},
_136
"icon": {
_136
"title": "Icon",
_136
"type": "string",
_136
"enumNames": ["CircleWavyWarning"],
_136
"enum": ["CircleWavyWarning"],
_136
"default": "CircleWavyWarning"
_136
}
_136
}
_136
}
_136
}
_136
}
_136
}
_136
]

Creating a custom newsletter section

Create a new section to receive the two new custom events:
  1. Open your store repository. If you don't have it, create a folder named components in the src directory.
  2. In components, create the sections folder.
  3. In the sections folder, create the CustomNewsletter.tsx file and add the following code. Below is an explanation of the code.

NewsletterProps

Defines the customizable properties of the newsletter.
  • title: Heading of the newsletter.
  • description: Subtitle or description text.
  • emailInputLabel: Label for the email input field.
  • subscribeButtonLabel: Text for the submit button.
  • toastSubscribe: Success pop-up settings.
  • toastSubscribeError: Error pop-up settings.
  • privacyPolicy: Legal disclaimer text.
  • card: Defines whether the newsletter should display in a card-style container.
  • colorVariant: Styling theme (example: "primary", "secondary").

onSubmit function

Handles the newsletter subscription process.
  • event.preventDefault(): Prevents default form submission.
    • sendAnalyticsEvent: Tracks the submission attempt.
      • The onSubmit function calls the subscribeUser() with email and name.
        • If successful:
          • Shows a success toast (pushToast).
          • Resets the form (formRef.current?.reset()).
        • If the call fails, it shows an error toast.
    • pushToast: Displays temporary success/error messages.
    • sendAnalyticsEvent: Tracks user interactions.
      • Submit Newsletter event: Tracks the form submission attempt when the user clicks the Subscribe button.
      • Submit newsletter success event: Tracks if the subscription was successful.
    • useAnalyticsEvent hook: Tracks and handles custom analytics events.

Creating the section index file

To make the Newsletter section available in the project, create a file named index.tsx in the components folder and add the following:

Styling the new section

To add styles to this new section, create a section.module.scss file and add the following:

Updating the CMS folder

  1. In the root directory of your storefront project, create a folder named cms if it doesn't exist.
  2. In cms, create the faststore folder.
  3. In the cms/faststore folder, create the sections.json file.
  4. In the sections.json file, add the new section you want to display in Headless CMS. The schema below defines how Headless CMS renders a section:
CustomNewsletter.tsx

_174
import type {
_174
ToastProps,
_174
NewsletterAddendumProps as UINewsletterAddendumProps,
_174
NewsletterHeaderProps as UINewsletterHeaderProps,
_174
NewsletterProps as UINewsletterProps,
_174
} from "@faststore/ui";
_174
import { useUI } from "@faststore/ui";
_174
import styles from "./section.module.scss";
_174
_174
import {
_174
Button,
_174
Icon,
_174
InputField,
_174
Newsletter,
_174
NewsletterAddendum,
_174
NewsletterContent,
_174
NewsletterForm,
_174
NewsletterHeader,
_174
} from "@faststore/ui";
_174
_174
import { Section } from "@faststore/core";
_174
import { useNewsletter_unstable as useNewsletter } from "@faststore/core/experimental";
_174
import { type FormEvent, useRef } from "react";
_174
import { sendAnalyticsEvent, useAnalyticsEvent } from "@faststore/sdk";
_174
_174
interface ArbitraryEvent {
_174
name: string;
_174
params: string;
_174
}
_174
_174
type SubscribeMessage = {
_174
icon: string;
_174
title: string;
_174
message: string;
_174
};
_174
_174
export interface NewsletterProps {
_174
title: UINewsletterHeaderProps["title"];
_174
card?: UINewsletterProps["card"];
_174
colorVariant?: UINewsletterProps["colorVariant"];
_174
description?: UINewsletterHeaderProps["description"];
_174
privacyPolicy?: UINewsletterAddendumProps["addendum"];
_174
emailInputLabel?: string;
_174
displayNameInput?: boolean;
_174
icon: {
_174
alt: string;
_174
icon: string;
_174
};
_174
nameInputLabel?: string;
_174
subscribeButtonLabel?: string;
_174
subscribeButtonLoadingLabel?: string;
_174
toastSubscribe?: SubscribeMessage;
_174
toastSubscribeError?: SubscribeMessage;
_174
}
_174
_174
export const AnalyticsHandler = () => {
_174
useAnalyticsEvent((event: ArbitraryEvent) => {
_174
console.log("Received event", event);
_174
});
_174
_174
/* ... */
_174
_174
return null;
_174
};
_174
_174
function CustomNewsletter({
_174
icon: { icon, alt: iconAlt },
_174
title,
_174
description,
_174
privacyPolicy,
_174
emailInputLabel,
_174
displayNameInput,
_174
nameInputLabel,
_174
subscribeButtonLabel,
_174
subscribeButtonLoadingLabel,
_174
card,
_174
toastSubscribe,
_174
toastSubscribeError,
_174
colorVariant,
_174
...otherProps
_174
}: NewsletterProps) {
_174
const { pushToast } = useUI();
_174
const { subscribeUser, loading } = useNewsletter();
_174
_174
const formRef = useRef<HTMLFormElement>(null);
_174
const nameInputRef = useRef<HTMLInputElement>(null);
_174
const emailInputRef = useRef<HTMLInputElement>(null);
_174
_174
const onSubmit = async (event: FormEvent) => {
_174
event.preventDefault();
_174
_174
try {
_174
// send event start
_174
sendAnalyticsEvent<ArbitraryEvent>({
_174
name: "Submit Newsletter",
_174
params: "test",
_174
});
_174
_174
const data = await subscribeUser({
_174
data: {
_174
name: nameInputRef.current?.value ?? "",
_174
email: emailInputRef.current?.value ?? "",
_174
},
_174
});
_174
_174
if (data?.subscribeToNewsletter?.id) {
_174
// send event start
_174
sendAnalyticsEvent<ArbitraryEvent>({
_174
name: "Submit newsletter success",
_174
params: "test",
_174
});
_174
_174
pushToast({
_174
...toastSubscribe,
_174
status: "INFO",
_174
icon: (
_174
<Icon width={30} height={30} name={toastSubscribe?.icon ?? ""} />
_174
),
_174
} as ToastProps);
_174
_174
formRef?.current?.reset();
_174
}
_174
} catch (error) {
_174
pushToast({
_174
...toastSubscribeError,
_174
status: "ERROR",
_174
icon: (
_174
<Icon width={30} height={30} name={toastSubscribeError?.icon ?? ""} />
_174
),
_174
} as ToastProps);
_174
}
_174
};
_174
_174
return (
_174
<Section className={`${styles.section} section-newsletter layout__section`}>
_174
<AnalyticsHandler />
_174
<Newsletter card={card ?? false} colorVariant={colorVariant}>
_174
<NewsletterForm ref={formRef} onSubmit={onSubmit}>
_174
<NewsletterHeader
_174
title={title}
_174
description={description}
_174
icon={
_174
<Icon width={32} height={32} name={icon} aria-label={iconAlt} />
_174
}
_174
/>
_174
_174
<NewsletterContent>
_174
{displayNameInput && (
_174
<InputField
_174
id="newsletter-name"
_174
required
_174
label={nameInputLabel ?? "Name"}
_174
inputRef={nameInputRef}
_174
/>
_174
)}
_174
<InputField
_174
id="newsletter-email"
_174
type="email"
_174
required
_174
label={emailInputLabel ?? "Email"}
_174
inputRef={emailInputRef}
_174
/>
_174
<NewsletterAddendum addendum={privacyPolicy} />
_174
<Button variant="secondary" inverse type="submit">
_174
{loading ? subscribeButtonLoadingLabel : subscribeButtonLabel}
_174
</Button>
_174
</NewsletterContent>
_174
</NewsletterForm>
_174
</Newsletter>
_174
</Section>
_174
);
_174
}
_174
_174
export default CustomNewsletter;

Syncing the new section with Headless CMS

  1. In the terminal, log in to your VTEX account by running vtex login {accountName}.
    Replace {accountName} with the name of your account. For example, vtex login store.
  2. Run yarn cms-sync to sync the new section you created with CMS.
  3. Go to the VTEX Admin and navigate to Storefront > Headless CMS.
  4. Click the Home Content Type.
  5. In the Sections tab, click +, search for the new Custom Newsletter section, and add it to your page.
    {"base64":"  ","img":{"width":1358,"height":551,"type":"gif","mime":"image/gif","wUnits":"px","hUnits":"px","length":2814641,"url":"https://vtexhelp.vtexassets.com/assets/docs/src/analytics-custom-newsletter-section___96abd10dc72c4f09c15f71a9e42651d7.gif"}}
  6. Delete the Newsletter default section.
  7. Click Save.
  8. Click Preview to preview the new section before publishing it to the live store.

Results

  1. After opening the preview browser tab, open the browser's Developer Tools and go to the Console tab.
  2. Go to the newsletter section on the store page and complete Your Name and Your Email fields.
  3. Click Subscribe. You will notice in the Console tab that the two custom events were successfully triggered.
    {"base64":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAACCAIAAADwyuo0AAAACXBIWXMAAAPoAAAD6AG1e1JrAAAAI0lEQVR4nGNYvPvBw///vcMy+QQlGAJLVzeueiep48LGygkAqiIKhsD1y8oAAAAASUVORK5CYII=","img":{"src":"https://vtexhelp.vtexassets.com/assets/docs/src/analytics-custom-newsletter-firing-event___45d3f6e3f8586ef7fbbb389128bb4460.gif","width":1358,"height":607,"type":"gif"}}
Contributors
1
Photo of the contributor
Was this helpful?
Yes
No
Suggest Edits (GitHub)
Contributors
1
Photo of the contributor
On this page