> For the complete documentation index, see [llms.txt](https://docs.parcellab.com/docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.parcellab.com/docs/developers/promise/ui-plugin.md).

# Promise UI Plugin

Embed delivery estimates on product pages and checkout with the Promise UI widget. Configure IP lookup, destination controls, date display, and styling.

The Promise UI is a standalone JavaScript widget for product pages, carts, and checkout. It displays one delivery method's estimated date or range, with optional carrier, order cutoff, and destination controls.

This is an [open-source reference implementation](https://github.com/parcelLab/parcellab-embedded-ui-snippets/tree/main/packages/promise-ui). Use it directly or adapt it for your storefront.

{% hint style="info" %}
The widget calls `GET /v2/prediction`, including when you follow this guide in the v4 documentation. Its configuration options are widget options, not v4 query parameters. For a custom frontend using the v4 endpoint, see [Promise Dates](/docs/developers/promise/api.md).
{% endhint %}

## Installation

Use a Promise-enabled account with published warehouses and delivery methods. Replace `YOUR_ACCOUNT_ID` with your parcelLab account ID and set the country for your customer market. Load one bundle per page.

{% tabs %}
{% tab title="Script tag (recommended)" %}
The IIFE bundle initializes elements with `data-promise` when the page is ready. It requires `data-account-id` and `data-country`.

```html
<div
  data-promise
  data-account-id="YOUR_ACCOUNT_ID"
  data-country="DEU"
  data-ip-fetch="true"
  data-zip-picker="inline"
  data-country-picker="true"
  data-confidence="estimated"
></div>
<script src="https://cdn.parcellab.com/js/promise-ui/v1/promise.iife.js" defer></script>
```

For elements added later, call `window.DeliveryPromise.autoInit()` after inserting them.
{% endtab %}

{% tab title="JavaScript API" %}
Create the target element before calling `init()`. Omit `data-promise` when you initialize the target yourself.

```html
<div id="delivery-promise"></div>
<script src="https://cdn.parcellab.com/js/promise-ui/v1/promise.iife.js"></script>
<script>
  const widget = window.DeliveryPromise.init({
    target: '#delivery-promise',
    accountId: 'YOUR_ACCOUNT_ID',
    destinationCountry: 'DEU',
    ipFetch: true,
    zipPicker: 'inline',
    countryPicker: true,
    confidence: 'estimated'
  });
</script>
```

{% endtab %}

{% tab title="ES module" %}
The ESM bundle requires an explicit `init()` call.

```html
<div id="delivery-promise"></div>
<script type="module">
  import { init } from 'https://cdn.parcellab.com/js/promise-ui/v1/promise.esm.js';

  const widget = init({
    target: '#delivery-promise',
    accountId: 'YOUR_ACCOUNT_ID',
    destinationCountry: 'DEU',
    ipFetch: true,
    zipPicker: 'inline',
    countryPicker: true,
    confidence: 'estimated'
  });
</script>
```

{% endtab %}
{% endtabs %}

If you know the shopper's delivery address, supply `postalCode` and `destinationCountry`, or `data-postal-code` and `data-country`. The supplied postal code takes priority over saved choices and IP Fetch. Leave it empty to use a saved choice or an IP estimate.

With IP Fetch enabled and no specified IP, the browser requests the public IP from `https://api64.ipify.org`, then sends it to parcelLab. If your site uses a Content Security Policy, allow that host and `https://api.parcellab.com` in `connect-src`, and the production CDN in `script-src`.

## Live Demo

Preview data and presentation settings, then copy an HTML or JavaScript embed:

{% embed url="<https://cdn.parcellab.com/playground/promise-ui/index.html>" %}

Set your account and choose **ZIP + country**, **Country only**, or **IP Fetch + fallback country**. Leave the IP field empty for public-IP lookup, or enter a specific IPv4 or IPv6 address. Presentation presets control layout, date wording, carrier, cutoff, and destination controls.

**Sample request** shows the configured query. **Show API response** makes a separate request from the Data controls, so it can differ from a manually corrected widget preview. Enter the same ZIP and country in Data to compare them.

Copy the browser URL to share the controls. Specified IP and ZIP values are included; automatically resolved locations and manual preview corrections are not. The generated embed uses the controls. Use the production bundle URL above when you deploy it. See the [product preview guide](https://docs.parcellab.com/docs/convert/promise#preview-the-widget) for the full workflow.

## Recommended Usage

The following examples use the configured country as the fallback for IP requests. Dates, cutoff times, carriers, and locations in the screenshots are examples and depend on the response.

### Product Detail Page: Estimate with Cutoff

Show the earliest estimated delivery date and the order cutoff. Hide the carrier and location controls to keep the widget small. The widget still uses the known, saved, resolved, or fallback destination.

<figure><img src="https://1242524681-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Few6OnyRUe6InzC96WnMr%2Fuploads%2Fgit-blob-c1006431af88c69f478f298faf34016a217c55c0%2Fpromise-ui-pdp-minimal.png?alt=media" alt="Product page estimate with a calendar icon, earliest delivery date, and order cutoff. Location controls are hidden."><figcaption><p>A compact delivery estimate for a product detail page.</p></figcaption></figure>

```html
<div
  data-promise
  data-account-id="YOUR_ACCOUNT_ID"
  data-country="DEU"
  data-postal-code=""
  data-ip-fetch="true"
  data-layout="text"
  data-date-mode="from"
  data-zip-picker="none"
  data-country-picker="false"
  data-show-cutoff="always"
  data-locale="en"
  data-icon="calendar"
  data-confidence="estimated"
  data-date-format="long"
  data-show-carrier="none"
  data-require-zip="false"
  data-selection-reference-date="earliest"
  data-selection-pick="earliest"
></div>
<script src="https://cdn.parcellab.com/js/promise-ui/v1/promise.iife.js" defer></script>
```

### Checkout: Editable Location and Delivery-by Date

Show the postal code and country from the IP response and let the shopper change them. The location icon marks an approximate location based on IP data or the fallback country. If no postal code is available, the widget shows the country and **Add ZIP**. Supply the checkout address instead when it is known.

Show the selected delivery method's latest estimated arrival date with "delivery by" wording and the carrier name. Hide the cutoff during checkout.

Replace the three `SELECTED_*` values below with the configured carrier, service level, and warehouse for the selected checkout shipping option. Initialize the widget only after that option is known. Use filters that identify that method, rather than a list of available methods.

<figure><img src="https://1242524681-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Few6OnyRUe6InzC96WnMr%2Fuploads%2Fgit-blob-a9eb971d758f134b29306b26d6e3dcf04d0ce9ba%2Fpromise-ui-checkout-location.png?alt=media" alt="Checkout estimate with a delivery-by date, UPS carrier name, editable ZIP 10013 and United States, and an approximate-location icon."><figcaption><p>A checkout delivery estimate with an editable destination.</p></figcaption></figure>

```html
<div
  data-promise
  data-account-id="YOUR_ACCOUNT_ID"
  data-courier="SELECTED_COURIER"
  data-service-level="SELECTED_SERVICE_LEVEL"
  data-warehouse="SELECTED_WAREHOUSE"
  data-country="DEU"
  data-postal-code=""
  data-ip-fetch="true"
  data-layout="text"
  data-date-mode="by"
  data-zip-picker="inline"
  data-country-picker="true"
  data-show-cutoff="never"
  data-locale="en"
  data-icon="none"
  data-confidence="estimated"
  data-date-format="short"
  data-show-carrier="inline"
  data-require-zip="false"
  data-selection-reference-date="earliest"
  data-selection-pick="earliest"
></div>
<script src="https://cdn.parcellab.com/js/promise-ui/v1/promise.iife.js" defer></script>
```

The product-page example selects the method with the earliest `earliest` date. The checkout example restricts the request to the selected shipping option and displays its latest date. `data-date-mode="by"` controls the displayed date; it does not select the shipping option.

For checkout changes, initialize through the [JavaScript API](#installation) with the same settings and retain the returned `widget` instance. In your checkout's shipping-selection handler, call `update()` with all three filters together. Here, `selectedShippingOption` is your checkout's mapping to configured Promise values:

```javascript
await widget.update({
  courier: selectedShippingOption.courier,
  serviceLevel: selectedShippingOption.serviceLevel,
  warehouse: selectedShippingOption.warehouse
});
```

Changing HTML attributes alone does not update an active widget. If the selected option has no matching prediction, omit its delivery date instead of removing the filters to show another method.

Shopper changes through the ZIP or country controls skip further IP lookups. A country change clears the previous ZIP. The widget saves these manual selections for 30 days after the last change and reuses them on later visits when the page supplies no postal code. Public IP values and automatic locations are not saved in browser storage.

## Configuration

The tables list the main options. Boolean HTML attributes require the literal value `"true"` or `"false"`. Use ISO3 country codes such as `DEU` and `USA`. The account ID must be a positive integer.

### Destination and Request Options

| JavaScript option    | HTML attribute       | Default                     | Use                                                                                           |
| -------------------- | -------------------- | --------------------------- | --------------------------------------------------------------------------------------------- |
| `target`             | The element itself   | Required in JavaScript      | Selector or existing element to receive the widget.                                           |
| `accountId`          | `data-account-id`    | Required                    | Your parcelLab account ID.                                                                    |
| `destinationCountry` | `data-country`       | Required                    | Country for explicit requests and fallback for IP requests.                                   |
| `postalCode`         | `data-postal-code`   | Empty                       | Postal code. Takes priority over IP Fetch. Keep leading zeros.                                |
| `ipFetch`            | `data-ip-fetch`      | `false`                     | Estimate a location if no supplied or saved destination takes priority.                       |
| `ipAddress`          | `data-ip-address`    | Empty                       | Specific IPv4 or IPv6 literal; requires IP Fetch. Otherwise the widget fetches the public IP. |
| `courier`            | `data-courier`       | Unset                       | Filter by configured carrier.                                                                 |
| `serviceLevel`       | `data-service-level` | Unset                       | Filter by configured service level.                                                           |
| `warehouse`          | `data-warehouse`     | Unset                       | Filter by warehouse.                                                                          |
| `calibration`        | `data-calibration`   | Unset                       | `conservative`, `balanced`, or `aggressive`.                                                  |
| `tag`                | `data-tag`           | Unset                       | Target configured adjustment rules.                                                           |
| `draft`              | `data-draft`         | `false`                     | Use unpublished Promise configuration for testing.                                            |
| `apiBaseUrl`         | `data-api-base-url`  | `https://api.parcellab.com` | API origin; the widget still uses the v2 path.                                                |

### Display Options

| JavaScript option        | HTML attribute                  | Default         | Use                                                                                                         |
| ------------------------ | ------------------------------- | --------------- | ----------------------------------------------------------------------------------------------------------- |
| `layout`                 | `data-layout`                   | `text`          | `text`, `card`, or `badge`. Badge has no carrier or cutoff text.                                            |
| `dateMode`               | `data-date-mode`                | `from`          | `from`, `on`, `by`, or `range`; see date selection below.                                                   |
| `dateFormat`             | `data-date-format`              | `long`          | `long`, `longWithYear`, `short`, `shortWithYear`, or `relative`.                                            |
| `confidence`             | `data-confidence`               | `auto`          | `estimated`, `guaranteed`, or `auto`. Auto uses guaranteed wording for express methods.                     |
| `showCutoff`             | `data-show-cutoff`              | `auto`          | `auto`, `always`, `never`, or `express-only`. Only positive cutoffs are shown.                              |
| `showCarrier`            | `data-show-carrier`             | `none`          | `none` or `inline`.                                                                                         |
| `icon`                   | `data-icon`                     | `none`          | `none`, `truck`, or `calendar`. Card uses a calendar when set to `none`.                                    |
| `zipPicker`              | `data-zip-picker`               | `none`          | `none` hides the location row; `inline` lets shoppers edit ZIP; `link` uses the legacy ZIP prompt.          |
| `countryPicker`          | `data-country-picker`           | `false`         | Country selector in the visible location row. A change clears ZIP.                                          |
| `requireZip`             | `data-require-zip`              | `false`         | Require a ZIP when location controls are visible. IP lookup runs first. Ignored when `zipPicker` is `none`. |
| `maxPredictions`         | `data-max-predictions`          | `5`             | Number of API entries considered for selection.                                                             |
| `selectionReferenceDate` | `data-selection-reference-date` | `mostLikely`    | Compare `earliest`, `latest`, or `mostLikely` across methods.                                               |
| `selectionPick`          | `data-selection-pick`           | `earliest`      | Select the smallest (`earliest`) or largest (`latest`) reference date.                                      |
| `fallbackDays`           | `data-fallback-days`            | Unset           | Static business-day estimate when predictions are unavailable. HTML examples: `3` or `2-3`.                 |
| `locale`                 | `data-locale`                   | `en`            | Built-in messages: `en`, `de`, `fr`, `it`, and `es`.                                                        |
| `messages`               | `data-messages`                 | Locale defaults | Partial message overrides; use a JSON object in HTML.                                                       |
| `theme`                  | `data-theme`                    | Widget defaults | CSS token overrides; use a JSON object in HTML.                                                             |
| `className`              | `data-class-name`               | Unset           | Extra class on the widget root.                                                                             |

`confidence` changes the wording. It does not change the prediction or establish a service guarantee. Use `estimated` unless your offered service supports a guarantee. The [source reference](https://github.com/parcelLab/parcellab-embedded-ui-snippets/blob/main/packages/promise-ui/README.md#configuration) also lists advanced JavaScript-only options.

### Select a Method, Then Select Its Displayed Date

First, the widget considers the first `maxPredictions` results and selects one method using `selectionReferenceDate` and `selectionPick`. The request filters limit which methods can appear. At checkout, filter to the shipping option the shopper selected.

Then `dateMode` chooses which date from that method to display:

| Mode    | v2 response field in the selected entry       | Display                             |
| ------- | --------------------------------------------- | ----------------------------------- |
| `from`  | `prediction.earliest`                         | Earliest estimated arrival.         |
| `on`    | `prediction.mostLikely`                       | Most likely arrival.                |
| `by`    | `prediction.latest`                           | Delivery-by date.                   |
| `range` | `prediction.earliest` and `prediction.latest` | Date range; one date if both match. |

For example, `selectionReferenceDate: 'latest'` with `selectionPick: 'earliest'` selects the method with the earliest upper date. `dateMode: 'by'` then displays that upper date. Changing `dateMode` alone does not change the selected method.

## Destination Priority and Persistence

On initialization, the widget uses this order:

1. A postal code supplied by the page, with its configured country.
2. An unexpired shopper choice saved in browser storage, including a country-only choice.
3. IP Fetch, if enabled, using the specified IP or the browser's public IP.
4. The configured country, if IP Fetch is off or public-IP lookup fails.

For an IP request, `destinationCountry` becomes `fallbackCountry`. If the IP location has matching methods, it wins even in another country. Otherwise, the API tries the fallback country without the IP ZIP or region. An empty generated prediction does not trigger another fallback. See the [v2 destination contract](/docs/developers/v2/promise/promise-dates.md#use-an-ip-address).

The widget reads `location.country` and `location.zip` for the automatic location display. ZIP values of `null` or `"0"` leave the editor empty. The approximate-location marker covers both IP lookup and country fallback; the response does not distinguish them. Use the checkout address when it becomes available.

Changes through the ZIP and country controls are saved for 30 days after the last change in the same browser and origin. Reloading does not extend expiry. The storage key is `pl-promise-zip`, shared by widgets on that origin rather than separated by account. Public IP values and automatic locations are not saved. If storage is blocked, the current choice still works but cannot be reused on later visits. Calls to `update()` do not write these saved choices.

## Widget Instance API

| Method                   | Behavior                                                                                   |
| ------------------------ | ------------------------------------------------------------------------------------------ |
| `widget.update(options)` | Merge options, cancel the current request, and render again. Returns a promise.            |
| `widget.refresh()`       | Render with the current destination. Can reuse a cached response. Returns a promise.       |
| `widget.destroy()`       | Cancel the request and remove the rendered widget. Saved choices and shared caches remain. |

`init()` returns immediately and starts loading. Calling it again for the same target updates that instance. To use a different target, create another instance.

When checkout supplies an address, update both fields together:

```javascript
await widget.update({
  destinationCountry: 'FRA',
  postalCode: '75001'
});
```

For a country-only selection, clear the old postal code:

```javascript
await widget.update({ destinationCountry: 'FRA', postalCode: '' });
```

A supplied country clears the automatic location and skips IP Fetch. Presentation-only changes keep it. To return to IP lookup for the current page:

```javascript
await widget.update({ postalCode: '', ipFetch: true, ipAddress: '' });
```

This does not delete a saved manual choice, which can return on reload.

### Caching and Order Persistence

Successful responses, including empty results, are cached in memory for the loaded module and shared by its widgets. The cache has no expiry. `refresh()` does not bypass a matching cached result. The widget does not poll automatically or count down the displayed cutoff. Reloading the page starts a new module cache.

The widget does not save the displayed promise on an order. Its public API also does not expose the selected prediction or response request ID. For checkout persistence, use a custom API integration or extend the reference implementation to retain the response used for display. Save that exact date or range, selected method, confirmed destination, and request ID when the order is placed. See [the send/read/save flow](/docs/developers/promise/api.md#integration-flow-send-read-and-save) and [the order field mapping](https://docs.parcellab.com/docs/convert/promise#fields-to-read-and-write).

## Loading, Missing Data, and Errors

| State                                                  | Display                                                                                                                           |
| ------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------- |
| Loading                                                | Loading message and enabled location controls.                                                                                    |
| ZIP required but unavailable                           | ZIP prompt without a date, after IP lookup if enabled.                                                                            |
| Matching predictions                                   | One selected method's date or range.                                                                                              |
| Empty result or HTTP `404`                             | Delivery estimate unavailable, unless `fallbackDays` is configured. An unresolved IP can first retry with the configured country. |
| Network failure, other HTTP error, or `success: false` | Error message, unless `fallbackDays` is configured.                                                                               |
| Static fallback                                        | The host-defined business-day estimate, without carrier or cutoff. It does not replace the ZIP-required prompt.                   |

Enabled location controls remain available for correction. A country fallback supplies a destination; `fallbackDays` supplies a static estimate. A static estimate is not an API prediction.

## Styling and Localization

The widget renders in the light DOM and injects its default styles once per page. It inherits your page typography. Use `theme` to change supported colors and radius, or target the `.pl-promise` root and its element classes with CSS.

Add these options to your initialization configuration:

```javascript
const displayOptions = {
  locale: 'de',
  messages: { deliverTo: 'Lieferung nach' },
  theme: {
    accentColor: '#3D3AD3',
    textColor: '#222222',
    cardBackgroundColor: '#f6f6f6',
    radius: '12px'
  }
};
```

For example, override root tokens on a widget mounted in `#delivery-promise`:

```css
#delivery-promise .pl-promise {
  --plp-accent: #3D3AD3;
  --plp-text: #222222;
  --plp-radius: 12px;
}
```

Useful element classes include `.pl-promise__summary-date`, `.pl-promise__deliver-to`, `.pl-promise__cutoff-line`, `.pl-promise__card`, and `.pl-promise__badge`. See the [full styling reference](https://github.com/parcelLab/parcellab-embedded-ui-snippets/blob/main/packages/promise-ui/README.md#styling) for supported tokens and classes.

Messages are built in for English, German, French, Italian, and Spanish. Other locales use English UI messages. Dates and country names use the supplied locale through the browser's `Intl` APIs. ZIP editing, country selection, and the approximate-location marker have accessible labels.

## Source Code

* [Promise UI source and complete reference](https://github.com/parcelLab/parcellab-embedded-ui-snippets/tree/main/packages/promise-ui)
* [Promise UI playground](https://cdn.parcellab.com/playground/promise-ui/index.html)
* [Promise product and integration flow](https://docs.parcellab.com/docs/convert/promise)

The reference implementation uses the MIT license.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.parcellab.com/docs/developers/promise/ui-plugin.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
