# Get Started with Site Mapping

A Marketing Cloud Personalization Sitemap is a configuration-driven system deployed by and executed within the Personalization module of the Salesforce Interactions SDK. The Sitemap configuration data enables other parts of the Personalization platform to register and receive events from the client context. This article provides guidance for configuring the JavaScript components of a Sitemap required to implement the Personalization web channel solution architecture. For a more comprehensive introduction to the Sitemap, see [Sitemap Overview](/docs/marketing/personalization/guide/personalization-sitemap.md).

## Sitemap Development Process

To develop a Sitemap, do the following.

1. Navigate to the site that contains the Personalization JavaScript Web SDK.
2. Launch the Personalization **Visual Editor** and log in. For guidance on installing and starting the Personalization Visual Editor, refer to the [Install the Salesforce Interactions SDK Launcher](https://help.salesforce.com/s/articleView?id=sf.mc_pers_web_campaign_interactions_sdk_launcher.htm).
   ![Launch Salesforce Interactions SDK Launcher](https://a.sfdcstatic.com/developer-website/sfdocs/personalization/media/Interaction-Studio-Launcher.png)
3. Open the Sitemap Editor by clicking **Site Map**, as highlighted in the following image.
   ![Launch the Site Map](https://a.sfdcstatic.com/developer-website/sfdocs/personalization/media/site-map-launch.png)
4. In the **Sitemap JS** tab of the Sitemap Editor, configure the JavaScript components required for your Sitemap as described in the remaining sections of this article.
   ![Sitemap Editor](https://a.sfdcstatic.com/developer-website/sfdocs/personalization/media/sitemap-editor.png)

## Sitemap JavaScript Configuration Overview

A Sitemap is built and initialized after the Personalization Web SDK is initialized. Developing a Sitemap consists of writing the JavaScript required to do the following.

1. Initializing the Web SDK
2. Configuring the Sitemap JavaScript objects
3. Initializing the Sitemap

### Sitemap JavaScript Code Structure

The following code samples depict the general JavaScript code structure for a Sitemap in each of the SDK namespaces.

```sfdocs-code {"lang":"javascript", "title": "SalesforceInteractions Namespace"}
SalesforceInteractions.init({
  // Initializes the Personalization module of the Salesforce Interactions SDK
  cookieDomain: "company.com", // Optional tracking cookie domain configuration (overrides default)
}).then(() => {
  const sitemapConfig = {
    // Sitemap configuration object
    global: {}, // Object used to contain Global site object configuration
    pageTypes: [], // Array used to contain the page type object configurations
  };

  SalesforceInteractions.initSitemap(sitemapConfig); // Initializes the Sitemap
});
```

```sfdocs-code {"lang":"javascript", "title": "Evergage Namespace"}
Evergage.init({
  // Initializes the Personalization module of the Salesforce Interactions SDK
  cookieDomain: "company.com", // Optional tracking cookie domain configuration (overrides default)
}).then(() => {
  const sitemapConfig = {
    // Sitemap configuration object
    global: {}, // Object used to contain Global site object configuration
    pageTypes: [], // Array used to contain the page type object configurations
  };

  Evergage.initSitemap(sitemapConfig); // Initializes the Sitemap
});
```

The following sections present the considerations and guidance for developing each functional component of a Sitemap. For examples of complete Sitemap configurations, see [Example Sitemaps](/docs/marketing/personalization/guide/example-sitemaps.md).

## Initialize the Personalization module of the Salesforce Interactions SDK

At the beginning of the Sitemap JavaScript, you must initialize the Personalization module of the Salesforce Interactions SDK with the following function.

```sfdocs-code {"lang":"javascript", "title": "SalesforceInteractions Namespace"}
SalesforceInteractions.init({

}).then(() => { }

---Sitemap Configuration---
```

```sfdocs-code {"lang":"javascript", "title": "Evergage Namespace"}
Evergage.init({

}).then(() => { }

---Sitemap Configuration---
```

The preceding function initializes the Personalization module of the Web SDK and allows it to begin tracking. The Web SDK starts tracking only if `SalesforceInteractions.init()` is called.

### Specify an Optional Configuration of the Web SDK

You can specify an optional configuration of the Web SDK by setting `config` object parameters for the `SalesforceInteractions.init` function. These parameters are strictly optional and aren’t required for the Sitemap to function correctly.

The following table lists the optional parameters accepted by the `config` object.

| Field Name     | Expected Value Type | Required? | Description                                                                                                                                                                                                                                                                                                                                                            |
| -------------- | ------------------- | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `trackerUrl`   | String              | No        | Overrides the default URL where the tracker sends events. The `trackerUrl` is of the format `https://<accountName>.<instance>.evergage.com`, where `accountName` is your Personalization account name and `instance` is your Personalization instance identifier. You can retrieve these details by accessing Gears from the Personalization UI and reviewing the URL. |
| `dataset`      | String              | No        | Name or identifier of your Personalization dataset                                                                                                                                                                                                                                                                                                                     |
| `cookieDomain` | String              | No        | Overrides the default domain used for setting the Personalization tracking cookie                                                                                                                                                                                                                                                                                      |

:::important
Ensure that you use the `account`, `dataset`, and `trackerUrl` parameters exclusively in your staging environment for testing purposes and seldom in your production environment. You can use these parameters to configure Sitemap operations to occur in your sandbox or learning environment without disrupting your production environment. Although there are instances where you have to use the `trackerUrl` parameter in your production environment, such instances are incredibly rare.
:::

You can use the `cookieDomain` parameter in the production environment in scenarios where you must share the tracking cookie across all possible subdomains of the default site host. For example, if the default site host is `something.company.com`, the tracking cookie is set only on that host by default. You can override this domain to share the cookie across all subdomains of `company.com` with the following configuration.

```sfdocs-code {"lang":"javascript", "title": "SalesforceInteractions Namespace"}
SalesforceInteractions.init({
  cookieDomain: "company.com",
});
```

```sfdocs-code {"lang":"javascript", "title": "Evergage Namespace"}
Evergage.init({
  cookieDomain: "company.com",
});
```

#### Optional Web SDK `config` Parameter Example

The following code samples depict an optional configuration of the Web SDK using `config` object parameters.

```sfdocs-code {"lang":"javascript", "title": "SalesforceInteractions Namespace"}
SalesforceInteractions.init({
  trackerUrl: "https://<accountName>.<instance>.evergage.com",
  cookieDomain: "staging.company.com",
});
```

```sfdocs-code {"lang":"javascript", "title": "Evergage Namespace"}
Evergage.init({
  trackerUrl: "https://<accountName>.<instance>.evergage.com",
  cookieDomain: "staging.company.com",
});
```

:::note
As mentioned in the previous section, use the `trackerUrl` parameter only in your staging or learning environment and never in your production environment. However, you can use the `cookieDomain` parameter in your production or staging environment as needed.
:::

## Configure Sitemap Objects

The `const sitemapConfig` variable is used to configure the JavaScript objects that capture site visitor actions. You can use the optional `global` object to define the user actions to be captured on every page that is matched as a page type. The pageTypes array is used to contain the configurations of one or more specific page types in a site. A Sitemap developer must configure one `global` object, and the `pageTypes` array must have a page type object configured for each page type on the site.

### Common Object Configuration Examples

A Sitemap can accept any valid JavaScript. Some JavaScript objects from the Personalization Web SDK are required to support the Personalization data collection and transmission functionality. Some are required in the `pageTypes` array configuration but not used in the `global` object.

The following table describes the JavaScript objects from the Web SDK commonly used in the Sitemap `global` and `pageTypes` configurations.

| Object          | Purpose                                                                                                                              | Usage             | Configuration                         | Namespace                |
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------ | ----------------- | ------------------------------------- | ------------------------ |
| `contentZones`  | The array of objects used to indicate areas of a page that are eligible for customization                                            | Optional          | Global object or configured page type | Both                     |
| `listeners`     | The array of `listener()` functions used to listen for user actions that trigger events to send to Personalization                   | Optional          | Global object or configured page type | Both                     |
| `isMatch`       | The function required (along with the page name) to match a specific page type                                                       | **Required**      | Configured page type only             | Both                     |
| `catalog`       | The object used to collect and send data on catalog items. Used only with the `Evergage` namespace.                                  | Optional          | Configured page type only             | `Evergage`               |
| `order`         | The object used to collect and send order data to Personalization, including line items, order IDs, total order value, and currency. | Context-dependent | Configured page type only             | `Evergage`               |
| `action`        | The property of `ActionEvent` that is used to identify user actions sent to Personalization                                          | Context-dependent | Global object or configured page type | `Evergage`               |
| `itemAction`    | The property of `ActionEvent` used to identify user actions taken on catalog items such as "Purchase" or "AddToCart".                | Context-dependent | Configured page type only             | `Evergage`               |
| `interaction`   | The property of `ActionEvent` used to identify user actions taken on catalog items such as "Purchase" or "AddToCart".                | Context-dependent | Configured page type only             | `SalesforceInteractions` |
| `onActionEvent` | The function used with `ActionEvent` that sends events to Personalization based on user actions                                      | Optional          | Global object or configured page type | Both                     |

:::important
If you're using the `Evergage` namespace within your Sitemap code, you must continue to use the `itemAction` property instead of `interaction` to identify user actions. For more information on Item Actions, refer to the [Item Actions](/docs/marketing/personalization/guide/item-actions.md) documentation.
:::

#### Resolvers

Resolvers are a set of properties provided by the Web SDK that help you return the values of page elements and attributes. You can use resolvers with some objects in the preceding table, such as `catalog`, to more easily return item information. Resolvers return functions so that they can be invoked *after* a page has been matched. When used in a page type configuration, the returned functions don’t need to be invoked to get the final value as the underlying Sitemap code handles that logic.

The following code samples show resolvers being used to return information on line items on an ecommerce site.

```sfdocs-code {"lang":"javascript", "title": "SalesforceInteractions Namespace"}
attributes: {
    name: SalesforceInteractions.resolvers.fromJsonLd("name"),
    description: SalesforceInteractions.resolvers.fromSelector(".short-description", (desc) => desc.trim()),
    url: SalesforceInteractions.resolvers.fromHref(),
    imageUrl: SalesforceInteractions.resolvers.fromSelectorAttribute(".product-carousel .carousel-item[data-slick-index='0'] img", "src", (url) =>  window.location.origin + url)
}
```

```sfdocs-code {"lang":"javascript", "title": "Evergage Namespace"}
attributes: {
    name: Evergage.resolvers.fromJsonLd("name"),
    description: Evergage.resolvers.fromSelector(".short-description"),
    url: Evergage.resolvers.fromHref(),
    imageUrl: Evergage.resolvers.fromSelectorAttribute(".product-carousel .carousel-item[data-slick-index='0'] img", "src"),
}
```

:::note
For detailed guidance on implementing resolvers and a complete list of the available resolvers, see [Sitemap Implementation Notes](/docs/marketing/personalization/guide/sitemap-implementation.md).
:::

#### Common Object Configuration Examples

The following sections describe and present example configurations of the common Sitemap objects described in the preceding table. The example configurations shown here are taken from the complete [ecommerce Example Sitemap](/docs/marketing/personalization/guide/ecommerce.md).

**Content Zones**

Content Zones are used to designate areas of a page that are potential targets for rendering personalized content from customer engagement campaigns, with each content zone targeting a single zone. This functionality is implemented using a `contentZones` array. Individual Content Zones consist of a `name` defined by you indicating the Content Zone's location on a page and a `selector` to designate the HTML element eligible for personalization. Content Zones can be defined without a `selector`, meaning no designated HTML element is associated with the Content Zone. This usage supports abandonment popups that display when a user begins to browse away from the page.
The following are examples of Content Zone configurations.

```sfdocs-code {"lang":"javascript", "title": "SalesforceInteractions Namespace"}
// Example: "Header" and "Footer" Content Zones defined in the global object configuration

contentZones: [
    { name: "global_infobar_top_of_page", selector: "header.site-header" },
    { name: "global_infobar_bottom_of_page", selector: "footer.site-footer" }
],

// Example: Content Zones defined for product recommendation rows in product detail page type

contentZones: [
    { name: "product_detail_recs_row_1", selector: ".row.recommendations div[id*='cq']:nth-of-type(1)" },
    { name: "product_detail_recs_row_2", selector: ".row.recommendations div[id*='cq']:nth-of-type(2)" },
],

// Example: "Logical" Content Zone defined with no selector for popup in the global object configuration

contentZones: [
    { name: "global_popup" }
],
```

```sfdocs-code {"lang":"javascript", "title": "Evergage Namespace"}
// Example: "Header" and "Footer" Content Zones defined in the global object configuration

contentZones: [
    { name: "global_infobar_top_of_page", selector: "header.site-header" },
    { name: "global_infobar_bottom_of_page", selector: "footer.site-footer" }
],

// Example: Content Zones defined for product recommendation rows in product detail page type

contentZones: [
    { name: "product_detail_recs_row_1", selector: ".row.recommendations div[id*='cq']:nth-of-type(1)" },
    { name: "product_detail_recs_row_2", selector: ".row.recommendations div[id*='cq']:nth-of-type(2)" },
],

// Example: "Logical" Content Zone defined with no selector for popup in the global object configuration

contentZones: [
    { name: "global_popup" }
],
```

:::note
For more information and examples of commonly used Content Zones, see [Content Zones](/docs/marketing/personalization/guide/content-zones.md).
:::

**Listeners**

Sitemap listeners are used to listen for user actions on a page, capture information from the action, and send that action and information to Personalization. Listeners could be used in the global object configuration and specific page types.

The following are examples of listeners configured in the `global` config and `product_detail` page type.

```sfdocs-code {"lang":"javascript", "title": "SalesforceInteractions Namespace"}
// Example: Listener on the global object for email sign-up that captures user email

listeners: [
    SalesforceInteractions.listener("submit", ".email-signup", () => {
        const email = SalesforceInteractions.cashDom("#dwfrm_mcsubscribe_email").val();
        if (email) {
            SalesforceInteractions.sendEvent({ interaction: { name: "Email Sign Up - Footer" }, user: { identities: { emailAddress: email } } });
        }
    }),
],

// Example: Listener on a product detail page that listens for an add-to-cart action and captures the product line item added

listeners: [
    SalesforceInteractions.listener("click", ".add-to-cart", () => {
        let lineItem = SalesforceInteractions.mcis.buildLineItemFromPageState("select.quantity-select option:checked");
        SalesforceInteractions.sendEvent({
            interaction: {
                name: SalesforceInteractions.CartInteractionName.AddToCart,
                lineItem: lineItem
            }
        });
    }),
],
```

```sfdocs-code {"lang":"javascript", "title": "Evergage Namespace"}
// Example: Listener on the global object for email sign-up that captures user email

listeners: [
    Evergage.listener("submit", ".email-signup", () => {
        const email = Evergage.cashDom("#dwfrm_mcsubscribe_email").val();
        if (email) {
            Evergage.sendEvent({action: "Email Sign Up - Footer", user: {id: email} });
        }
    }),
],

// Example: Listener on a product detail page that listens for an add-to-cart action and captures the product line item added

listeners: [
    Evergage.listener("click", ".add-to-cart", () => {
        const lineItem = Evergage.util.buildLineItemFromPageState("select[id*=quantity]");
        lineItem.sku = { _id: Evergage.cashDom(".product-detail[data-pid]").attr("data-pid") };
        Evergage.sendEvent({
            itemAction: Evergage.ItemAction.AddToCart,
            cart: {
                singleLine: {
                    Product: lineItem
                }
            }
        });
    }),
],
```

:::note
For more information on implementing Sitemap listeners, see [Sitemap Implementation Notes](/docs/marketing/personalization/guide/sitemap-implementation.md#listeners).
:::

**isMatch**

The `isMatch` property is a required component of each page type configuration in the `pageTypes` array. The `isMatch` property must declare a function that tells Personalization how to determine what type of page the user is viewing. An `isMatch` function can return a boolean, or a [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) that resolves a boolean.

The following are example configurations of the `isMatch` property.

```sfdocs-code {"lang":"javascript", "title": "SalesforceInteractions Namespace"}
// Example: isMatch property using the Web SDK cashDom function to return the "Product-Show" DOM element which
// would indicate the user is on the Product Detail ("product_detail") page type.

name: "product_detail",
isMatch: () => {
    return SalesforceInteractions.cashDom("div.page[data-action='Product-Show']").length > 0;
}

// Example: isMatch property using a regex expression to return the window href "/cart" which would indicate
// the user is on the shopping cart ("cart") page type.

name: "cart",
isMatch: () => /\/cart/.test(window.location.href),

// Example: isMatch property that returns a Promise

name: "cart",
isMatch: () => {
    return SalesforceInteractions.DisplayUtils.pageElementLoaded(".cart-empty, .cart", "html").then(() => true);
}
```

```sfdocs-code {"lang":"javascript", "title": "Evergage Namespace"}
// Example: isMatch property using the Web SDK cashDom function to return the "Product-Show" DOM element which
// would indicate the user is on the Product Detail ("product_detail") page type.

name: "product_detail",
isMatch: () => {
    return Evergage.cashDom("div.page[data-action='Product-Show']").length > 0;
}

// Example: isMatch property using a regex expression to return the window href "/cart" which would indicate
// the user is on the shopping cart ("cart") page type.

name: "cart",
isMatch: () => /\/cart/.test(window.location.href),

// Example: isMatch property that returns a Promise

name: "cart",
isMatch: () => {
    return Evergage.DisplayUtils.pageElementLoaded(".cart-empty, .cart", "html").then(() => true);
}
```

**Capture Catalog Information**

You can capture information on Catalog Objects being viewed through the Sitemap. When using the `SalesforceInteractions` namespace, this information is captured from `interaction.catalogObject` of the matching `pageType` for `ViewCatalogObject` interactions. When using the `Evergage` namespace, this information is captured from the `catalog` object of the matching `pageType`.

The `id` field (or `_id` when using the `Evergage` namespace) is required when tracking a Catalog Object. In the `SalesforceInteractions` namespace, a Promise can be returned for every field of the `catalogObject`. In the `Evergage` namespace, a Promise can be returned for the `_id`, `categories`, and `relatedCatalogObjects` fields. When returning Promises for these fields, the event is tracked only after all Promises resolve.

:::important
Catalog items and attributes must be defined and enabled through the Personalization UI before you can use them in a Sitemap. Some item attributes, like `id`, `name`, and `description`, are built into every catalog item by default. You can add additional attributes for catalog items within the Personalization UI as needed. For more information on defining catalog items and attributes, see [Create a Custom Catalog Object](https://help.salesforce.com/s/articleView?id=sf.mc_pers_catalog_object_custom.htm).
:::

The following are examples of capturing catalog information on a product detail page type.

```sfdocs-code {"lang":"javascript", "title": "SalesforceInteractions Namespace"}
interaction: {
    name: SalesforceInteractions.CatalogObjectInteractionName.ViewCatalogObject,
    catalogObject: {
        type: "Product",
        id: () => {
            return SalesforceInteractions.util.resolveWhenTrue.bind(() => {
                const productId = SalesforceInteractions.cashDom(".product-id").first().text();
                const products = getProductsFromDataLayer();
                if (products && products.length > 0) {
                    return products[0].id;
                } else if (productId) {
                    return productId;
                } else {
                    return false;
                }
            })
        },
        attributes: {
            sku: { id: SalesforceInteractions.cashDom(".product-detail[data-pid]").attr("data-pid") },
            name: SalesforceInteractions.resolvers.fromJsonLd("name"),
            description: SalesforceInteractions.resolvers.fromSelector(".short-description", (desc) => desc.trim()),
            url: SalesforceInteractions.resolvers.fromHref(),
            imageUrl: SalesforceInteractions.resolvers.fromSelectorAttribute(".product-carousel .carousel-item[data-slick-index='0'] img", "src", (url) => window.location.origin + url),
            inventoryCount: 1,
            price: SalesforceInteractions.resolvers.fromSelector(".prices .price .value", (price) => parseFloat(price.replace(/[^0-9\.]+/g, ""))),
            rating: () => {
                return SalesforceInteractions.mcis.extractFirstGroup(/([.\w]+) out of/, SalesforceInteractions.cashDom(".ratings .sr-only").text());
            },
        },
        relatedCatalogObjects: {
            Category: SalesforceInteractions.DisplayUtils.pageElementLoaded(".container .product-breadcrumb .breadcrumb a", "html").then((ele) => {
                return SalesforceInteractions.resolvers.buildCategoryId(".container .product-breadcrumb .breadcrumb a", null, null, (categoryId) => [categoryId.toUpperCase()]);
            }),
            Gender: SalesforceInteractions.DisplayUtils.pageElementLoaded(".product-breadcrumb .breadcrumb a, h1.product-name", "html").then((ele) => {
                if (SalesforceInteractions.cashDom(".product-breadcrumb .breadcrumb a").first().text().toLowerCase() === "women" ||
                    SalesforceInteractions.cashDom("h1.product-name").text().indexOf("Women") >= 0) {
                    return ["WOMEN"];
                } else if (SalesforceInteractions.cashDom(".product-breadcrumb .breadcrumb a").first().text().toLowerCase() === "men" ||
                    SalesforceInteractions.cashDom("h1.product-name").text().indexOf("Men") >= 0) {
                    return ["MEN"];
                } else {
                    return;
                }
            }),
            Color: SalesforceInteractions.DisplayUtils.pageElementLoaded(".attributes", "html").then((ele) => {
                return SalesforceInteractions.resolvers.fromSelectorAttributeMultiple(".color-value", "data-attr-value");
            }),
            Feature: SalesforceInteractions.DisplayUtils.pageElementLoaded(".features", "html").then((ele) => {
                return SalesforceInteractions.resolvers.fromSelectorMultiple(".long-description li", (features) => {
                    return features.map((feature) => {
                        return feature.trim().toUpperCase();
                    })
                })
            })
        }
    }
}
```

```sfdocs-code {"lang":"javascript", "title": "Evergage Namespace"}
catalog: {
    Product: {
        _id: () => {
            return Evergage.util.resolveWhenTrue.bind(() => {
                const productId = Evergage.cashDom(".product-id").first().text();
                const products = getProductsFromDataLayer();
                if (products && products.length > 0) {
                    return products[0].id;
                } else if (productId) {
                    return productId;
                } else {
                    return false;
                }
            })
        },
        sku: { _id: Evergage.cashDom(".product-detail[data-pid]").attr("data-pid") },
        name: Evergage.resolvers.fromJsonLd("name"),
        description: Evergage.resolvers.fromSelector(".short-description"),
        url: Evergage.resolvers.fromHref(),
        imageUrl: Evergage.resolvers.fromSelectorAttribute(".product-carousel .carousel-item[data-slick-index='0'] img", "src"),
        inventoryCount: 1,
        price: Evergage.resolvers.fromSelector(".prices .price .value"),
        rating: () => {
            return Evergage.util.extractFirstGroup(/([.\w]+) out of/, Evergage.cashDom(".ratings .sr-only").text());
        },
        categories: () => {
            return Evergage.DisplayUtils.pageElementLoaded(".container .product-breadcrumb .breadcrumb a", "html").then((ele) => {
                return Evergage.resolvers.buildCategoryId(".container .product-breadcrumb .breadcrumb a", null, null, (categoryId) => [categoryId.toUpperCase()]);
            });
        },
        relatedCatalogObjects: { // In case you're using 'dimensions' instead of 'relatedCatalogObjects', you can continue to do so as they both function the same way.
            Gender: () => {
                return Evergage.DisplayUtils.pageElementLoaded(".product-breadcrumb .breadcrumb a, h1.product-name", "html").then((ele) => {
                    if (Evergage.cashDom(".product-breadcrumb .breadcrumb a").first().text().toLowerCase() === "women" ||
                        Evergage.cashDom("h1.product-name").text().indexOf("Women") >= 0) {
                        return ["WOMEN"];
                    } else if (Evergage.cashDom(".product-breadcrumb .breadcrumb a").first().text().toLowerCase() === "men" ||
                        Evergage.cashDom("h1.product-name").text().indexOf("Men") >= 0) {
                        return ["MEN"];
                    } else {
                        return;
                    }
                });
            },
            Color: () => {
                return Evergage.DisplayUtils.pageElementLoaded(".attributes", "html").then((ele) => {
                    return Evergage.resolvers.fromSelectorAttributeMultiple(".color-value", "data-attr-value");
                });
            },
            Feature: () => {
                return Evergage.DisplayUtils.pageElementLoaded(".features", "html").then((ele) => {
                    return Evergage.resolvers.fromSelectorMultiple(".features .feature", (features) => {
                        return features.map((feature) => {
                            return feature.trim();
                        });
                    });
                });
            }
        }
    }
}
```

**Capture Cart Information**

You can capture information on the user's `cart` through the Sitemap. When using the `SalesforceInteractions` namespace, this information is captured from `interaction.lineItems` of the matching pageType for `ReplaceCart` interactions. When using the `Evergage` namespace, this information is captured from the `cart` object.

Capturing information about the user's `cart` is typically used in the shopping cart pages of ecommerce sites to collect information about line items, including the item ID (or SKU), price, and quantity. When using the `Evergage` namespace, the `_id`, `price`, and `quantity` fields of a cart object can return a Promise. When returning Promises for these fields, an event is sent only if all Promises resolve.

The following are examples of capturing cart information from a cart page type.

:::note
In the `SalesforceInteractions` namespace, you can use the `ReplaceCart` interaction to update the state of the cart in Personalization by setting its content to the provided line items while replacing the previous cart contents.
:::

```sfdocs-code {"lang":"javascript", "title": "SalesforceInteractions Namespace"}
// Example: Without SKU
interaction: {
	name: SalesforceInteractions.CartInteractionName.ReplaceCart,
	lineItems: SalesforceInteractions.DisplayUtils.pageElementLoaded(".cart-empty, .checkout-btn", "html").then(() => {
		let cartLineItems = [];
		SalesforceInteractions.cashDom(".product-info .product-details").each((index, ele) => {
			let itemQuantity = parseInt(SalesforceInteractions.cashDom(ele).find(".qty-card-quantity-count").text().trim());
			if (itemQuantity && itemQuantity > 0) {
				let lineItem = {
					catalogObjectType: "Product",
					catalogObjectId: SalesforceInteractions.cashDom(ele).find(".line-item-quantity-info").attr("data-pid").trim(),
					price: SalesforceInteractions.cashDom(ele).find(".pricing").text().trim().replace(/[^0-9\.]+/g, "") / itemQuantity,
					quantity: itemQuantity
				};
				cartLineItems.push(lineItem);
			}
		})
		return cartLineItems;
	})
}

// Example: With SKU
interaction: {
	name: SalesforceInteractions.CartInteractionName.ReplaceCart,
	lineItems: SalesforceInteractions.DisplayUtils.pageElementLoaded(".cart-empty, .checkout-btn", "html").then(() => {
		let cartLineItems = [];
		SalesforceInteractions.cashDom(".product-info .product-details").each((index, ele) => {
			let itemQuantity = parseInt(SalesforceInteractions.cashDom(ele).find(".qty-card-quantity-count").text().trim());
			if (itemQuantity && itemQuantity > 0) {
				let lineItem = {
					catalogObjectType: "Product",
					price: SalesforceInteractions.cashDom(ele).find(".pricing").text().trim().replace(/[^0-9\.]+/g, "") / itemQuantity,
					quantity: itemQuantity,
					attributes: {
						sku: {
							id: SalesforceInteractions.cashDom(ele).find(".line-item-quantity-info").attr("data-sku").trim()
						}
					}
				};
				cartLineItems.push(lineItem);
			}
		})
		return cartLineItems;
	})
}
```

:::note
In the `Evergage` namespace, you can use the `cart.complete` object to update the state of the cart in Personalization by setting its content to the provided line items while replacing previous cart contents.
:::

```sfdocs-code {"lang":"javascript", "title": "Evergage Namespace"}
cart: {
    complete: {
        Product: {
            _id: () => {
                return Evergage.DisplayUtils.pageElementLoaded(".cart-empty, .checkout-btn", "html").then((ele) => {
                    return Evergage.resolvers.fromSelectorAttributeMultiple(".product-info .product-details .line-item-quantity-info", "data-pid")
                })
            },
            price: () => {
                return Evergage.DisplayUtils.pageElementLoaded(".cart-empty, .checkout-btn", "html").then((ele) => {
                    return Evergage.resolvers.fromSelectorMultiple(".product-info .product-details .pricing");
                })
            },
            quantity: () => {
                return Evergage.DisplayUtils.pageElementLoaded('.cart-empty, .checkout-btn', "html").then((ele) => {
                    return Evergage.resolvers.fromSelectorMultiple(".product-info .product-details .qty-card-quantity-count");
                });
            }
        }
    }
}
```

**Capture Order Information**

Orders are typically used in the order confirmation pages of ecommerce sites to collect information about the line items in a transaction, including the item ID (or SKU), price, and quantity.

When using the `SalesforceInteractions` namespace, this information is captured from `interaction.order` of the matching pageType for `Purchase` interactions. When using the `Evergage` namespace, this information is captured from the `order` object.

In the `Evergage` namespace, an `order` object can return Promises for all fields. When returning Promises, an event is sent only after all Promises resolve.

:::note
- The `resolver` functions in the Personalization Web SDK are especially useful in simplifying the retrieval of this data.
- In the `Evergage` namespace, you must define an `_id` field under item type. However, if they’re properly attributed to a product, you can pass SKU values here instead of the `_id` field.
:::

The following are examples of capturing order information from an order confirmation page type.

```sfdocs-code {"lang":"javascript", "title": "SalesforceInteractions Namespace"}
// Example: Without SKU
interaction: {
	name: SalesforceInteractions.OrderInteractionName.Purchase,
	order: {
		id: SalesforceInteractions.DisplayUtils.pageElementLoaded(".order-number", "html").then((ele) => {
			return SalesforceInteractions.resolvers.fromSelector(".order-number");
		}),
		lineItems: SalesforceInteractions.DisplayUtils.pageElementLoaded(".product-line-item", "html").then(() => {
			let purchaseLineItems = [];
			SalesforceInteractions.cashDom(".product-line-item").each((index, ele) => {
				let itemQuantity = parseInt(SalesforceInteractions.cashDom(ele).find(".qty-card-quantity-count").text().trim());
				if (itemQuantity && itemQuantity > 0) {
					let lineItem = {
						catalogObjectType: "Product",
						catalogObjectId: SalesforceInteractions.cashDom(ele).find(".line-item-quantity-info").attr("data-pid").trim(),
						price: SalesforceInteractions.cashDom(ele).find(".pricing").text().trim().replace(/[^0-9\.]+/g, "") / itemQuantity,
						quantity: itemQuantity
					};
					purchaseLineItems.push(lineItem);
				}
			})
			return purchaseLineItems;
		})
	}
}

// Example: With SKU
interaction: {
	name: SalesforceInteractions.OrderInteractionName.Purchase,
	order: {
		id: SalesforceInteractions.DisplayUtils.pageElementLoaded(".order-number", "html").then((ele) => {
			return SalesforceInteractions.resolvers.fromSelector(".order-number");
		}),
		lineItems: SalesforceInteractions.DisplayUtils.pageElementLoaded(".product-line-item", "html").then(() => {
			let purchaseLineItems = [];
			SalesforceInteractions.cashDom(".product-line-item").each((index, ele) => {
				let itemQuantity = parseInt(SalesforceInteractions.cashDom(ele).find(".qty-card-quantity-count").text().trim());
				if (itemQuantity && itemQuantity > 0) {
					let lineItem = {
						catalogObjectType: "Product",
						price: SalesforceInteractions.cashDom(ele).find(".pricing").text().trim().replace(/[^0-9\.]+/g, "") / itemQuantity,
						quantity: itemQuantity,
						attributes: {
							sku: {
								id: SalesforceInteractions.cashDom(ele).find(".line-item-quantity-info").attr("data-sku").trim()
							}
						}
					};
					purchaseLineItems.push(lineItem);
				}
			})
			return purchaseLineItems;
		})
	}
}
```

```sfdocs-code {"lang":"javascript", "title": "Evergage Namespace"}
order: {
    Product: {
        orderId: () => {
            return Evergage.DisplayUtils.pageElementLoaded(".order-number", "html").then((ele) => {
                return Evergage.resolvers.fromSelector(".order-number");
            });
        },
        lineItems: {
            _id: () => {
                return Evergage.DisplayUtils.pageElementLoaded(".product-line-item line-item-quantity-info", "html").then((ele) => {
                    return Evergage.resolvers.fromSelectorAttributeMultiple(".product-line-item .line-item-quantity-info", "data-pid");
                });
            },
            price:  () => {
                return Evergage.DisplayUtils.pageElementLoaded(".product-line-item .pricing", "html").then((ele) => {
                    return Evergage.resolvers.fromSelectorAttributeMultiple(".product-line-item .pricing", "data-pid");
                });
            },
            quantity:  () => {
                return Evergage.DisplayUtils.pageElementLoaded(".product-line-item .qty-card-quantity-count", "html").then((ele) => {
                    return Evergage.resolvers.fromSelectorAttributeMultiple(".product-line-item .qty-card-quantity-count", "data-pid");
                });
            }
        }
    }
}
```

**Action**

The `action` property provides a way for Personalization to name user actions such as viewing the page or signing up for an email subscription. An `action` property is often included after the `name` in the page type specification.

The following example code sends a "Login" action to Personalization every time a user accesses the `login` page type.

:::important
The `action` property is available exclusively in the `Evergage` namespace.
:::

```javascript
{
  name: "login",
  action: "Login",
  isMatch: () => /\/login/.test(window.location.href)
}
```

**Interaction**

An `interaction` property is used to identify specific actions users take. Examples of `interaction` values include `AddToCart`, `Purchase`, and `Comment`. For a complete list of `interaction` values, refer to [Interaction Definitions](/docs/marketing/personalization/guide/interaction-definitions.md).

:::important
The `interaction` property is available exclusively in the `SalesforceInteractions` namespace. If you're using the `Evergage` namespace within your Sitemap code, you must continue to use the `itemAction` property instead of `interaction` to identify user actions. For more information on Item Actions, refer to the [Item Actions](/docs/marketing/personalization/guide/item-actions.md) documentation.
:::

The following is an example of using the `interaction` property in the `SalesforceInteractions` namespace in Sitemap code.

```javascript
// Example: Send Add-to-cart Interaction

SalesforceInteractions.sendEvent({
    interaction: {
        name: SalesforceInteractions.CartInteractionName.AddToCart,
        lineItem: lineItem
    }
});

// Example: Replace Cart Interaction on a shopping cart page

name: "cart",
isMatch: () => /\/cart/.test(window.location.href),
interaction: {
    name: SalesforceInteractions.CartInteractionName.ReplaceCart,
    lineItems: lineItems
}

// Example: Purchase Interaction on an order confirmation page

name: "order_confirmation",
isMatch: () => /\/confirmation/.test(window.location.href),
interaction: {
    name: SalesforceInteractions.OrderInteractionName.Purchase,
    order: {
        //order configuration
    }
}
```

The following is an example of using the `itemAction` property within in the `Evergage` namespace in Sitemap code.

```javascript
// Example: Send Add-to-cart item action as an event

Evergage.sendEvent({
    itemAction: Evergage.ItemAction.AddToCart
    cart: {
        singleLine: {
            Product: lineItem
        }
    }
});

// Example: View-cart item action on a shopping cart page

name: "cart",
isMatch: () => /\/cart/.test(window.location.href),
itemAction: Evergage.ItemAction.ViewCart
cart: {
    //cart config
}

// Example: Purchase item action on an order confirmation page

name: "order_confirmation",
isMatch: () => /\/confirmation/.test(window.location.href),
itemAction: Evergage.ItemAction.Purchase,
order: {
    //order configuration
}
```

**onActionEvent**

The `onActionEvent` function is used with the `ActionEvent` property to send events to Personalization based on user actions.

The following code samples depict the usage of the `onActionEvent` function in the `pageType` configuration in each of the SDK namespaces.

```sfdocs-code {"lang":"javascript", "title": "SalesforceInteractions Namespace"}
pageTypes: [
  {
    name: "search",
    isMatch: () => /\/search/.test(window.location.pathname),
    onActionEvent: (actionEvent) => {
      const searchTerm = SalesforceInteractions.cashDom(".content.searchTerm").text().trim();
      if (searchTerm) {
        actionEvent.user = actionEvent.user || {};
        actionEvent.user.attributes = actionEvent.user.attributes || {};
        actionEvent.user.attributes.lastSearchTerm = searchTerm;
      }
      return actionEvent;
    },
  },
];
```

```sfdocs-code {"lang":"javascript", "title": "Evergage Namespace"}
pageTypes: [
  {
    name: "search",
    isMatch: () => /\/search/.test(window.location.pathname),
    onActionEvent: (actionEvent) => {
      const searchTerm = Evergage.cashDom(".content.searchTerm").text().trim();
      if (searchTerm) {
        actionEvent.user = actionEvent.user || {};
        actionEvent.user.attributes = actionEvent.user.attributes || {};
        actionEvent.user.attributes.lastSearchTerm = searchTerm;
      }
      return actionEvent;
    },
  },
];
```

### Configure the Global Sitemap Object

The `global` Sitemap configuration object allows you to define what to capture on every page that is matched as a page type. The `global` Sitemap object enables you to define Content Zones, listeners, locale, and `onActionEvent` functions for every page that is matched instead of only on individually matched pages.

The following shows an example of a `global` Sitemap configuration.

```sfdocs-code {"lang":"javascript", "title": "SalesforceInteractions Namespace"}
SalesforceInteractions.init({
    cookieDomain: "northerntrailoutfitters.com"
}).then(() => {
    const sitemapConfig = {
        global: {
            contentZones: [
                {name: "global_infobar_top_of_page", selector: "header.site-header"},
                {name: "global_infobar_bottom_of_page", selector: "footer.site-footer"},
                {name: "global_popup" }
            ],
            listeners: [
                SalesforceInteractions.listener("submit", ".email-signup", () => {
                    const email = SalesforceInteractions.cashDom("#dwfrm_mcsubscribe_email").val();
                    if (email) {
                        SalesforceInteractions.sendEvent({ interaction: { name: "Email Sign Up - Footer" }, user: { identities: { emailAddress: email } } });
                    }
                }),
            ],
        },
        pageTypes: [ // ...page type configurations...]
    }

    SalesforceInteractions.initSitemap(sitemapConfig);
});
```

```sfdocs-code {"lang":"javascript", "title": "Evergage Namespace"}
Evergage.init({
    cookieDomain: "northerntrailoutfitters.com"
}).then(() => {
    const sitemapConfig = {
        global: {
            contentZones: [
                {name: "global_infobar_top_of_page", selector: "header.site-header"},
                {name: "global_infobar_bottom_of_page", selector: "footer.site-footer"},
                {name: "global_popup" }
            ],
            listeners: [
                Evergage.listener("submit", ".email-signup", () => {
                    const email = Evergage.cashDom("#dwfrm_mcsubscribe_email").val();
                    if (email) {
                        Evergage.sendEvent({action: "Email Sign Up - Footer", user: {id: email} });
                    }
                }),
            ],
        },
        pageTypes: [ // ...page type configurations...]
    }

    Evergage.initSitemap(sitemapConfig);
});
```

## Configure Page Type Array Objects

A Sitemap configuration consists of the [`global` configuration](#configure-the-global-sitemap-object), which runs for all page types, and an array of individual page type configurations. At a minimum, a page type requires configuring a `name` and an `isMatch` function. The `name` defines the name of the page type, while the `isMatch` function output is evaluated to determine if the page the user is on matches the page configuration. After `name` and `isMatch`, you can use any of the `sitemapConfig` objects mentioned in this article in a page type configuration object.

The following code sample depicts a sample page type configuration.

```javascript
{
  name: string
  isMatch: () => boolean | () => Promise<boolean>
}
```

The `isMatch` function accepts a function that returns a boolean or a Promise. The sitemap evaluates only the first page type whose `isMatch` resolves to `true`. If no page types match, no event is sent to Personalization.

The following are examples of different page type configurations in each SDK namespace.

```sfdocs-code {"lang":"javascript", "title": "SalesforceInteractions Namespace"}
SalesforceInteractions.init({
  cookieDomain: "northerntrailoutfitters.com",
}).then(() => {
  const config = {
    global: {
      onActionEvent: (actionEvent) => {
        const email = SalesforceInteractions.mcis.getValueFromNestedObject(
          "window._etmc.user_info.email",
        );
        if (email) {
          actionEvent.user = actionEvent.user || {};
          actionEvent.user.identities = actionEvent.user.identities || {};
          actionEvent.user.identities.emailAddress = email;
        }
        return actionEvent;
      },
      contentZones: [
        { name: "global_infobar_top_of_page", selector: "header.site-header" },
        { name: "global_infobar_bottom_of_page", selector: "footer.site-footer" },
      ],
      listeners: [
        SalesforceInteractions.listener("submit", ".email-signup", () => {
          const email = SalesforceInteractions.cashDom("#dwfrm_mcsubscribe_email").val();
          if (email) {
            SalesforceInteractions.sendEvent({
              interaction: { name: "Email Sign Up - Footer Test" },
              user: { identities: { emailAddress: email } },
            });
          }
        }),
      ],
    },
    pageTypes: [
      {
        name: "home",
        isMatch: () => /\/homepage/.test(window.location.href),
        interaction: {
          name: "Homepage",
        },
        contentZones: [
          { name: "home_hero", selector: ".experience-carousel-bannerCarousel" },
          {
            name: "home_sub_hero",
            selector: ".experience-carousel-bannerCarousel + .experience-component",
          },
          { name: "home_popup" },
        ],
      },
      {
        name: "login",
        action: "Login",
        isMatch: () =>
          /\/login/.test(window.location.href) &&
          window.location.hostname !== "community.northerntrailoutfitters.com",
        listeners: [
          SalesforceInteractions.listener("click", "form[name='login-form'] button", () => {
            const email = SalesforceInteractions.cashDom("#login-form-email").val();
            if (email) {
              SalesforceInteractions.sendEvent({
                action: "Logged In",
                user: { attributes: { emailAddress: email } },
              });
            }
          }),
        ],
      },
      {
        name: "cart",
        isMatch: () => /\/cart/.test(window.location.href),
        interaction: {
          name: SalesforceInteractions.CartInteractionName.ReplaceCart,
          lineItems: SalesforceInteractions.DisplayUtils.pageElementLoaded(
            ".cart-empty, .checkout-btn",
            "html",
          ).then(() => {
            let cartLineItems = [];
            SalesforceInteractions.cashDom(".product-info .product-details").each(
              (index, ele) => {
                let itemQuantity = parseInt(
                  SalesforceInteractions.cashDom(ele)
                    .find(".qty-card-quantity-count")
                    .text()
                    .trim(),
                );
                if (itemQuantity && itemQuantity > 0) {
                  let lineItem = {
                    catalogObjectType: "Product",
                    catalogObjectId: SalesforceInteractions.cashDom(ele)
                      .find(".line-item-quantity-info")
                      .attr("data-pid")
                      .trim(),
                    price:
                      SalesforceInteractions.cashDom(ele)
                        .find(".pricing")
                        .text()
                        .trim()
                        .replace(/[^0-9\.]+/g, "") / itemQuantity,
                    quantity: itemQuantity,
                  };
                  cartLineItems.push(lineItem);
                }
              },
            );
            return cartLineItems;
          }),
        },
      },
    ],
  };
  SalesforceInteractions.initSitemap(config);
});
```

```sfdocs-code {"lang":"javascript", "title": "Evergage Namespace"}
Evergage.init({
  cookieDomain: "northerntrailoutfitters.com",
}).then(() => {
  const config = {
    global: {
      onActionEvent: (actionEvent) => {
        const email = Evergage.util.getValueFromNestedObject("window._etmc.user_info.email");
        if (email) {
          actionEvent.user = actionEvent.user || {};
          actionEvent.user.attributes = actionEvent.user.attributes || {};
          actionEvent.user.attributes.emailAddress = email;
        }
        return actionEvent;
      },
      contentZones: [
        { name: "global_infobar_top_of_page", selector: "header.site-header" },
        { name: "global_infobar_bottom_of_page", selector: "footer.site-footer" },
      ],
      listeners: [
        Evergage.listener("submit", ".email-signup", () => {
          const email = Evergage.cashDom("#dwfrm_mcsubscribe_email").val();
          if (email) {
            Evergage.sendEvent({
              action: "Email Sign Up - Footer",
              user: { attributes: { emailAddress: email } },
            });
          }
        }),
      ],
    },
    pageTypes: [
      {
        name: "home",
        action: "Homepage",
        isMatch: () => /\/homepage/.test(window.location.href),
        contentZones: [
          { name: "home_hero", selector: ".experience-carousel-bannerCarousel" },
          {
            name: "home_sub_hero",
            selector: ".experience-carousel-bannerCarousel + .experience-component",
          },
          { name: "home_popup" },
        ],
      },
      {
        name: "login",
        action: "Login",
        isMatch: () =>
          /\/login/.test(window.location.href) &&
          window.location.hostname !== "community.northerntrailoutfitters.com",
        listeners: [
          Evergage.listener("click", "form[name='login-form'] button", () => {
            const email = Evergage.cashDom("#login-form-email").val();
            if (email) {
              Evergage.sendEvent({
                action: "Logged In",
                user: { attributes: { emailAddress: email } },
              });
            }
          }),
        ],
      },
      {
        name: "cart",
        isMatch: () => /\/cart/.test(window.location.href),
        itemAction: Evergage.ItemAction.ViewCart,
        catalog: {
          Product: {
            lineItems: {
              _id: () => {
                return Evergage.DisplayUtils.pageElementLoaded(
                  ".cart-empty, .checkout-btn",
                  "html",
                ).then((ele) => {
                  return Evergage.resolvers.fromSelectorAttributeMultiple(
                    ".product-info .product-details .line-item-quantity-info",
                    "data-pid",
                  );
                });
              },
              price: () => {
                return Evergage.DisplayUtils.pageElementLoaded(
                  ".cart-empty, .checkout-btn",
                  "html",
                ).then((ele) => {
                  return Evergage.resolvers.fromSelectorMultiple(
                    ".product-info .product-details .pricing",
                  );
                });
              },
              quantity: () => {
                return Evergage.DisplayUtils.pageElementLoaded(
                  ".cart-empty, .checkout-btn",
                  "html",
                ).then((ele) => {
                  return Evergage.resolvers.fromSelectorMultiple(
                    ".product-info .product-details .qty-card-quantity-count",
                  );
                });
              },
            },
          },
        },
      },
    ],
  };
  Evergage.initSitemap(config);
});
```

:::note
For additional examples of page type configurations and complete Sitemaps, see [Example Sitemaps](/docs/marketing/personalization/guide/example-sitemaps.md).
:::

## Initialize the Sitemap

A Sitemap is initialized at the end of the JavaScript code by calling `SalesforceInteractions.initSitemap(sitemapConfig)` in the `SalesforceInteractions` namespace or `Evergage.initSitemap(sitemapConfig)` in the `Evergage` namespace. Sitemap events aren’t tracked unless the sitemap is initialized by calling either of those functions *and* until a matching page type is found. If your sitemap has errors or if no matching page types are found, you can encounter errors.

The following code sample depicts the initialization of a Sitemap using `SalesforceInteractions.initSitemap(sitemapConfig)` in the `SalesforceInteractions` namespace.

```javascript
SalesforceInteractions.init({
    cookieDomain: "developer.evergage.com"
}).then(() => {

    const sitemapConfig = {
        global: {...global object configuration...},
        pageTypes: [...page type array configuration...]
    }

    SalesforceInteractions.initSitemap(sitemapConfig);
});
```

The following code sample depicts the initialization of a Sitemap using `Evergage.initSitemap(sitemapConfig)` in the `Evergage` namespace.

```javascript
Evergage.init({
    cookieDomain: "developer.evergage.com"
}).then(() => {

    const sitemapConfig = {
        global: {...global object configuration...},
        pageTypes: [...page type array configuration...]
    }

    Evergage.initSitemap(sitemapConfig);
});
```

## See Also

- [*Salesforce Help*: Install the Salesforce Interactions SDK Launcher](https://help.salesforce.com/s/articleView?id=sf.mc_pers_web_campaign_interactions_sdk_launcher.htm)
- [*Salesforce Help*: Create a Custom Catalog Object](https://help.salesforce.com/s/articleView?id=sf.mc_pers_catalog_object_custom.htm)
