# Private Offer Field Mapping

Define how data from your CRM (Salesforce or HubSpot) populates the fields in a cloud marketplace private offer.

---

## Overview

[Private Offer Field Mapping](https://console.suger.io/settings?tab=private_offer) controls how the final legal and financial terms of a deal — such as contract value, payment terms, and EULAs — are transmitted from your CRM to AWS, Azure, GCP, or Snowflake when a private offer is created. While co-sell mapping focuses on deal registration, private offer mapping ensures the offer's terms are populated correctly and offer creation does not fail.

## Set Up Private Offer Settings

### Step 1: Access Mapping Settings

1. Log in to the **Suger Console**.
2. Navigate to [**Settings** > **Private Offer**](https://console.suger.io/settings?tab=private_offer).

   ![Private Offer settings tab in Suger Console](images/configure-private-offer-field-mappings-1.png)

3. Locate the section for your CRM (Salesforce or HubSpot).
4. Click **+ New Config** in that section.
5. In the **Create Private Offer Config Info** modal:
   - **Cloud Partner** — Select the hyperscaler (AWS, Azure, GCP, or Snowflake) you are configuring.
   - **Field Mappings** — Map individual fields between the offer and your CRM (detailed in the next step).
   - **Scripts** — (Optional) Provide custom scripts to fill fields that cannot be handled by standard mapping.

     ![Create Private Offer Config Info modal with field mappings and scripts](images/configure-private-offer-field-mappings-2.png)

6. Click **Save**.

### Step 2: Define Outbound Mappings (CRM → Offer)

Cloud partners have rigid data requirements, so you must map your internal CRM fields to the corresponding marketplace schema. Suger supports these types of field mappings:

- **Static Values (Default Value)** — Use for fields that are mandatory but identical for every offer, such as your standard **Support Terms** or a specific **Seller Note**.
- **Direct Mapping** — Link fields 1:1.
  - _Salesforce example_: Map `Opportunity.Amount` → `Offer.Amount`.
  - _HubSpot example_: Map `Deal.Amount` → `Offer.Total_Value`.
- **Expression Mode (Advanced)** — Use logic for data transformations.
  - _Example_: Calculate a custom discount: `Opportunity.Amount * (1 - Opportunity.Discount_Pct__c)`.
  - _Example_: Concatenate a unique offer name: `Account.Name + " - Private Offer - " + Today()`.

#### Key Fields to Map

Ensure these high-impact fields are correctly mapped to prevent offer creation failures.

**AWS private offer**

| Field Name | Source Type | Operational Purpose |
| --- | --- | --- |
| Salesforce Opportunity ID | Salesforce Field (`Id`) | Links the private offer to the correct CRM record for tracking. |
| Product ID | Salesforce Field | Maps the Suger product to the specific AWS listing ID. |
| Offer Name | Expression | Defines the offer title. Example: `{{ .Name }} - {{ .Account.Name }}`. |
| Buyer AWS Account ID | Salesforce Field | Required for AWS to authorize the specific buyer to view the offer. |
| Contacts | Field / Manual | Defines who receives notification emails when the offer is live. |
| EULA Type | Salesforce Field | Specifies the legal terms (Standard AWS Terms vs. Custom EULA). |
| Offer Expiry Date | Salesforce Field | The "Accept By" date (format: `YYYY-MM-DD`). |
| Start / End Date | Salesforce Field | Defines the contract term duration. |

**Azure private offer**

Azure mappings focus on identifying the correct billing account and ensuring offer dates align with Microsoft's UTC requirements.

| Field Name | Source Type | Operational Purpose |
| --- | --- | --- |
| Salesforce Opportunity Id | Salesforce Field | Identifies the CRM record for cross-platform tracking. |
| Product Id | Salesforce Field | Links the offer to the specific Azure Marketplace listing. |
| Offer Name | Expression | Custom title for the offer (e.g., `{{ .Name }}`). |
| Buyer Azure Billing Account ID | Salesforce Field | Authorizes the specific Azure account to accept the offer. |
| Prepared By Email | Salesforce Field | Identifies the person responsible for offer creation. |
| Offer Expiry Date | Salesforce Field | The acceptance deadline (format: `YYYY-MM-DD`). |
| Offer End Date | Salesforce Field | The official termination date of the contract. |
| Start Date | Salesforce Field | The service commencement date (default: acceptance date). |

**GCP private offer**

GCP requires more granular contact information than other partners to keep both the buyer and seller teams informed throughout the procurement lifecycle.

:::tip
For **GCP Billing IDs**, add a validation rule in Salesforce so the string follows the hex format `XXXXXX-XXXXXX-XXXXXX`. If this ID is off by a single digit, the GCP API call fails with a vague "Validation Error."
:::

| Field Name | Source Type | Operational Purpose |
| --- | --- | --- |
| Buyer GCP Billing Account ID | Salesforce Field | Must follow the `000000-000000-000000` format. |
| Buyer Organization Name | Salesforce Field | The legal entity name of the buyer being notified. |
| Buyer Contact Email | Salesforce Field | The specific buyer address notified by GCP to accept the offer. |
| Sales Contact Email | Salesforce Field | Your team's contact for GCP-led communications. |
| Term Length in Months | Salesforce Field | Determines the contract duration (range: 1 to 60 months). |
| Offer Expiry Date | Salesforce Field | The UTC date before which the offer must be accepted. |

**Snowflake private offer**

Snowflake mappings introduce the **Offer Display Name**, designed for customer-facing clarity within the Snowflake Marketplace UI.

| Field Name | Source Type | Operational Purpose |
| --- | --- | --- |
| Offer Display Name | Salesforce Field | The customer-facing title shown in the Snowflake Marketplace. |
| Offer Expiry Date | Salesforce Field | The date the offer becomes invalid if not accepted. |
| Start Date | Salesforce Field | When the Snowflake service entitlements begin. |
| Offer End Date | Salesforce Field | The final day of the service term. |

**Offer names are cleaned for the target marketplace.** When a field mapping fills an offer name, Suger replaces each character that marketplace doesn't accept with a space, then collapses repeated spaces and trims the ends.

| Marketplace | Characters replaced with a space |
| --- | --- |
| AWS | `;` `"` `'` `<` `>` `\` |
| Azure | Everything except `A`–`Z`, `a`–`z`, `0`–`9`, spaces, `_`, and `-` |
| Snowflake | Everything except `A`–`Z`, `a`–`z`, `0`–`9`, spaces, and `_` |
| GCP | Nothing |

### Step 3: Set Listing Filters (Optional)

On the main **Private Offer** settings page, you can enable a **List Products Filter**. This lets you write a custom script that restricts which marketplace products your sales team can choose from when creating an offer, based on specific customer needs.

![List Products Filter script on the Private Offer settings page](images/configure-private-offer-field-mappings-3.png)

#### Test the filter

Click **Test**, next to **Save**, to run the script in the editor — saved or not — against your organization's first 1,000 products. Nothing is saved.

- If the script depends on the CRM record an offer is created from, enter a **Salesforce Opportunity ID**, **Salesforce Quote ID**, **HubSpot Deal ID**, or **Dynamics 365 Opportunity ID**, then click **Run again**.
- The result lists the products the script selects from those 1,000. The test never falls back to listing everything: *The script matched no products* means it selected none — when a rep creates an offer, a script that sets no `$target.productIds` is ignored and every product is listed, while IDs that match no product list nothing.
- **Script error** shows the JavaScript error when the script throws or sets `$target.productIds` to anything other than an array of product ID strings.
- Only organization Admins can run the test; anyone else sees **Not allowed**. A script longer than 12,000 bytes once URL-encoded is too large to test here — save it and try it on a real offer.
- The offer form pages through all your products, so a script that reasons about the whole product set can behave differently there.

### Step 4: Choose a EULA Document Source (Salesforce, Optional)

When a seller creates a private offer in the [Suger Salesforce app](/salesforce-app/salesforce-app-offer/#attach-your-own-eula-files), the EULA section lets them attach PDFs from the Opportunity's or the Quote's attachments. If your signed agreements are stored on a different record — for example, an `Agreement__c` record that each Quote looks up to — select that lookup here, and sellers can attach PDFs from the related record's Salesforce Files too.

1. On the [**Private Offer**](https://console.suger.io/settings?tab=private_offer) settings page, go to the **Salesforce** section.
2. Confirm **Source Object Type**. The lookup list comes from this object, and sellers see the new button only when they launch an offer from a record of this object. It defaults to `Opportunity`, and it is also the object your field mappings read from.
3. In **EULA Document Source**, select the lookup field that points to the record holding the files. Each option shows the field label, its relationship name, and the object it points to — for example, `Agreement (Agreement__r) → Agreement__c` — and **Related object** confirms the target once you select one.
4. Click **Save** in the **Salesforce** section.

```d2
direction: right
console: "Suger Console\nEULA Document Source" { style.stroke-dash: 4 }
launch: "Record the offer is launched from\n(Source Object Type, e.g. Quote)"
related: "Related record\n(e.g. Agreement)"
files: "Salesforce Files\non the related record"
offer: "EULA on the\nprivate offer"
console -> launch: "names one lookup on" { style.stroke-dash: 4 }
launch -> related: "lookup"
related -> files
files -> offer: "seller picks PDFs"
```

Sellers who launch an offer from a record of that object then see a button named after the lookup — **Select from Agreement files**, for example — in the offer form's EULA section, alongside the Opportunity or Quote attachment buttons available there. It lists the Salesforce Files on the related record, and the seller chooses which PDFs to attach — nothing is attached automatically.

- **One direct lookup.** Only lookup fields on the Source Object Type that point to a single object are listed; lookups to User and polymorphic lookups are left out. If the object has none, the selector says *No supported lookup fields on this object.* A chain such as Opportunity → primary Quote → Agreement is not supported — configure a direct lookup on the object sellers launch from.
- **Changing Source Object Type clears the selection**, so pick a lookup on the new object before you save.
- **Refresh after changing fields in Salesforce.** The refresh button beside the selector reloads the object's fields.
- **The seller's own Salesforce access applies.** If the lookup is empty on the record, or the seller can't see the related record, the button is disabled and shows *No related record found.*
- **PDFs only, within the form's limit** — 20 MB per file on AWS offer forms, 3.5 MB on Azure and GCP offer forms.
- **To turn it off,** select **None** and save. EULAs already attached to offers are not changed.

### Step 5: Tag Products by Product Line (Optional, BETA)

**Product Tagging** records the product line each product belongs to. With **Contact routing** on, Suger files the buyer contacts on a product's offers under its product line — the Salesforce app groups an account's [Buyer Contacts](/salesforce-app/salesforce-app-page-components/#buyer-contacts) that way — and the Products page shows each product's [tag](/console/products/#product-tags).

```d2
direction: right
product: "Product with\na product line"
offers: "Its private offers"
contacts: "Buyer contacts\nnamed on those offers"
sfdc: "Salesforce app:\nBuyer Contacts grouped\nby product line"
product -> offers
offers -> contacts: "Contact routing on"
contacts -> sfdc
```

1. On the [**Private Offer**](https://console.suger.io/settings?tab=private_offer) settings page, go to **Product Tagging**.
2. Click **New Config** and choose the cloud: **AWS**, **AZURE**, or **GCP**. That cloud's tagging dialog opens.
3. Tag each product: click **+ Tag** on its row, then pick an existing tag or type a new one. To tag several products at once, select them — or **Select all visible** — and use **Add tag to selection**. **Search by product name or ID** and **Untagged only** narrow the list. Click **Save**.
4. Turn on **Contact routing**, then click **Save** in the Product Tagging section.

The **Coverage** column tracks each cloud — for example, *12/40 tagged · 3 tags* — and the pencil icon reopens its tagging dialog.

- **One product line per product**, up to 100 characters. Casing counts: *Security* and *security* are two product lines.
- **Only buyer contacts are filed.** Your own sales reps, your partners' contacts, and anyone at your organization's email domain are left out.
- **Turning on Contact routing** also files the contacts of products tagged while it was off. Turning it off hides the product lines and stops filing contacts.
- **Deleting a cloud's configuration** (trash icon) first removes the tags from each of that cloud's tagged products.
- **Roles.** Tagging products needs the Editor or Admin role — for anyone else the tagging dialog is read-only. Creating or deleting a configuration and saving **Contact routing** need the Admin role.

## Troubleshooting

| Issue | Cause | Resolution |
| --- | --- | --- |
| "Invalid ID" on creation | The CRM field contains a typo or an incorrectly formatted account ID. | Copy-paste the ID directly from the AWS/Azure portal into the CRM record and refresh the creation modal. |
| Pricing logic error | Mathematical errors or missing parenthesis in Expression Mode. | Use the **Test** button in the mapping modal to validate your script before saving. |
| Missing required fields | The mapping points to a CRM field that is currently blank on that record. | Populate the data in your CRM, or add a Default Value in the configuration to act as a fallback. |
| Widget says "Integration Missing" | The CRM integration is disconnected or the API key is invalid. | Verify connection status in **Settings** > **Integrations** and ensure your API client is active. |
| "Field Not Found" error | A CRM field used in a mapping was deleted or renamed. | Update the corresponding **Private Offer Field Mappings** in Suger to reflect the new field names. |
| "Configured lookup … is unavailable" under **EULA Document Source**, and the Salesforce section's **Save** is disabled | The saved lookup field no longer appears in the object's field list — for example, it was deleted or renamed in Salesforce. | Click the refresh button beside the selector, then select another lookup — or **None** — and save. |
| No **Select from … files** button on the Salesforce offer form | No EULA Document Source is saved, or the offer was launched from a record that isn't the **Source Object Type**. | Save a lookup in [Step 4](#step-4-choose-a-eula-document-source-salesforce-optional), and launch the offer from a record of that object. |

## FAQ

**Can I manage contract renewals and upsells from HubSpot or Salesforce?**

Yes. Suger supports [Agreement-Based Offers (ABO)](/console/private-offers/) natively within the Suger widget, allowing sales teams to modify active entitlements directly from the deal record.

**What happens if I try to edit an offer after it has been sent?**

You cannot edit an ongoing offer once it is in a `Pending_Acceptance` state. The best practice is to cancel the existing offer and create a new one with the corrected terms.

**Can I map different pricing models (Flat Fee vs. Consumption) through field mapping?**

Yes. Use **Expression Mode** to dynamically set the pricing model based on a CRM field. For example, if your HubSpot deal includes a custom property for "Billing Type," you can write an expression that instructs Suger to select "Usage-Based" or "Contract" pricing on the marketplace offer automatically.

**Does Suger support mapping for multi-year installment plans?**

Yes. If your CRM (like Salesforce) uses a related list for payment schedules, you can map these to the marketplace's installment fields. This ensures the buyer sees the exact payment dates and amounts in their marketplace console that were agreed upon in your CRM.

**How does Suger handle EULA (End User License Agreement) mapping?**

You have two options:

1. **Static** — Set a **Default Value** with a URL to your standard EULA.
2. **Dynamic** — Map a CRM field (e.g., `Custom_EULA_Link__c`) to the marketplace legal terms field. This is ideal when you negotiate custom legal terms for specific enterprise deals.

To let sellers attach signed PDFs stored on a related Salesforce record instead — an Agreement that a Quote looks up to, for example — set a **EULA Document Source** ([Step 4](#step-4-choose-a-eula-document-source-salesforce-optional)).

**If I change the "Amount" in my CRM, will an already-sent private offer update automatically?**

No. Once a private offer is created and sent to a buyer, its terms are locked by the cloud provider (AWS/Azure/GCP). To change the amount, cancel the existing offer in the Suger Console, update the CRM record, and generate a new offer.

**What is the "Scripts" section in the private offer configuration used for?**

The **Scripts** section allows advanced automation beyond simple 1:1 mapping. You can write custom JavaScript to perform complex lookups or conditional logic — such as applying specific tax codes or partner incentives — before the offer object is finalized.

**Can I restrict which products my team can map to?**

Yes. Use the **List Products Filter** script on the main Private Offer settings page to filter the marketplace catalog available to your sales reps, ensuring they only create offers for approved products or SKUs.

**How do I ensure the "Buyer Identifier" is always accurate?**

Make the **Buyer Account ID** (AWS Account ID or Azure Tenant ID) a required field in your CRM. In Suger, you can use **Expression Mode** to validate the length of the string (e.g., ensuring it is exactly 12 digits for AWS) before allowing the offer to be created.

**Does Suger track who modified the mapping logic?**

Yes. Every configuration has a **History** button showing the previous version of the mapping, the new version, the user who made the change, and the timestamp — useful for troubleshooting pricing mismatches after a logic update.
