> ## Documentation Index
> Fetch the complete documentation index at: https://docs.napps.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Product Detail Handler Extension

The Product Detail Handler Extension lets you customize behavior on the product detail page.

Use this extension when you want to format PDP data or control product actions through callbacks.

You can use it to:

* Change product information before it is shown.
* Add, remove, or replace product tags.
* Change the displayed title, vendor, SKU, stamp image, or availability.
* React when the selected product or variant changes.
* React when the user adds a product to the cart.
* React when the user clicks product, quick add, collection, vendor, or external navigation actions.
* Cancel supported default actions by returning `false`.

## Handler Shape

The extension is based on the `ProductDetailHandler` type from `@napps/component-extension`.

```ts theme={null}
import type { ProductDetailHandler } from "@napps/component-extension";

export const handler: ProductDetailHandler = {
  onFormat(draft) {
    draft.setTitle("Custom title");
  },

  onAddedToCart(event) {
    if (event.data.quantity <= 0) {
      return false;
    }

    return true;
  }
};
```

Callbacks can be synchronous or asynchronous. If the callback is async, the app will only wait up to 3 seconds before assuming the callback failed.

```ts theme={null}
type ProductDetailHandlerEventArgReturnType =
  | void
  | boolean
  | Promise<void>
  | Promise<boolean>;
```

When a callback receives an event, you can return `false` to cancel the default action when that action supports cancellation.

## Formatting PDP Data

Use `onFormat` to change product information before it is displayed.

```ts theme={null}
onFormat?: (draft: ProductDetailInfoState) => void | Promise<void>;
```

The `draft` object lets you edit the visible PDP data:

```ts theme={null}
interface ProductDetailInfoState {
  setTitle: (title: string) => void;
  setVendor: (vendor: string | null | undefined) => void;
  setSku: (sku: string | null | undefined) => void;
  setStampImage: (imageURL: string | null | undefined) => void;
  setAvailability: (availability: boolean) => void;
  clearLowStockInfo: () => void;
  getTags: () => ProductTag[];
  addTag: (tag: ProductTag) => void;
  removeTag: (tag: ProductTag) => void;
  replaceTags: (tags: ProductTag[]) => void;
  clearTags: () => void;
  removeTagByType: (type: string) => void;
}
```

The same draft exposes read-only gallery media and lets you attach tags to individual images and videos.

```ts theme={null}
interface ProductMediaItem {
  id: string;
  alt: string;
  contentType: string;
  previewImageUrl: string | null;
  type: "image" | "video" | "external_video" | "model3d";
}

interface MediaTag {
  id: string;
  text?: string | null;
  textColor?: string | null;
  backgroundColor?: string | null;
  iconUrl?: string | null;
  iconColor?: string | null;
}

interface ProductDetailInfoState {
  mediaItems: ProductMediaItem[];
  getMediaTags(mediaID: string): MediaTag[];
  addMediaTag(mediaID: string, tag: MediaTag): void;
  setMediaTags(mediaID: string, tags: MediaTag[]): void;
  removeMediaTag(mediaID: string, tagID: string): void;
  clearMediaTags(mediaID: string): void;
  clearAllMediaTags(): void;
}
```

Media cannot be reordered, hidden, or added. `addMediaTag` replaces an existing tag with the same ID on the same media item. A tag must contain text, an icon URL, or both. `onFormat` runs again when the selected variant or option changes, and the entire formatting hook shares the three-second timeout.

Example:

```ts theme={null}
import { ProductTag, type ProductDetailHandler } from "@napps/component-extension";

export const handler: ProductDetailHandler = {
  onFormat(draft) {
    draft.setVendor("Custom vendor");
    draft.setAvailability(true);
    draft.removeTagByType("old-label");

    draft.addTag(
      new ProductTag("promo", "Promo", "#ffffff", "#0f766e")
    );
  }
};
```

## Action Callbacks

The handler can react to PDP actions through optional callbacks.

```ts theme={null}
interface ProductDetailHandler {
  onDestroyed?: () => void | Promise<void>;
  onFormat?: (draft: ProductDetailInfoState) => void | Promise<void>;
  onVariantSelected?: () => void;
  onProductSelected?: (
    event: ProductDetailEventArgs<ProductSelectedEventArgs>
  ) => ProductDetailHandlerEventArgReturnType;
  onAddedToCart?: (
    event: ProductDetailEventArgs<AddedToCartEventArgs>
  ) => ProductDetailHandlerEventArgReturnType;
  onProductClicked?: (
    event: ProductDetailEventArgs<ProductClickedEventArgs>
  ) => ProductDetailHandlerEventArgReturnType;
  onProductQuickAddClicked?: (
    event: ProductDetailEventArgs<ProductClickedEventArgs>
  ) => ProductDetailHandlerEventArgReturnType;
  onExternalNavigationRequestClicked?: (
    event: ProductDetailEventArgs<ExternalNavigationRequestEventArgs>
  ) => ProductDetailHandlerEventArgReturnType;
  onCollectionClicked?: (
    event: ProductDetailEventArgs<CollectionClickedEventArgs>
  ) => ProductDetailHandlerEventArgReturnType;
  onVendorClicked?: (
    event: ProductDetailEventArgs<VendorClickedEventArgs>
  ) => ProductDetailHandlerEventArgReturnType;
}
```

### onDestroyed

Runs when the handler is destroyed. Use it to clean up work you started inside the handler.

### onFormat

Runs when PDP display data can be formatted. Use it to change title, vendor, SKU, availability, stamp image, low stock information, or tags.

### onVariantSelected

Runs when a variant is selected.

### onProductSelected

Runs when a product is selected.

The event data contains:

```ts theme={null}
interface ProductSelectedEventArgs {
  product: Product;
  variant: ProductVariant | undefined;
}
```

### onAddedToCart

Runs when a product is added to the cart.

The event data contains:

```ts theme={null}
interface AddedToCartEventArgs {
  product: Product;
  variant: ProductVariant;
  quantity: number;
}
```

### onProductClicked

Runs when a product is clicked.

The event data contains:

```ts theme={null}
interface ProductClickedEventArgs {
  product: Product;
}
```

### onProductQuickAddClicked

Runs when the quick add action is clicked for a product.

The event data contains the clicked product.

### onExternalNavigationRequestClicked

Runs when an external navigation action is clicked.

The event data contains:

```ts theme={null}
interface ExternalNavigationRequestEventArgs {
  title: string;
  target: string;
}
```

### onCollectionClicked

Runs when a collection action is clicked.

The event data contains:

```ts theme={null}
interface CollectionClickedEventArgs {
  collection: Collection;
  title: string;
}
```

### onVendorClicked

Runs when a vendor action is clicked.

The event data contains:

```ts theme={null}
interface VendorClickedEventArgs {
  vendor: string;
  title: string;
}
```

## Event Object

Callbacks that receive an event use this shape:

```ts theme={null}
interface ProductDetailEventArgs<T> {
  data: T;
}
```

Use `event.data` to read the action data.

Example:

```ts theme={null}
import type { ProductDetailHandler } from "@napps/component-extension";

export const handler: ProductDetailHandler = {
  onExternalNavigationRequestClicked(event) {
    if (!event.data.target.startsWith("https://")) {
      return false;
    }

    return true;
  }
};
```

## Product Detail Context

Product detail handlers receive a context while they are created.

```ts theme={null}
interface ProductDetailHandlerContext {
  productID: string;
  product?: Product;
  selectedProductVariant?: ProductVariant;
  handlingType: ProductDetailHandlingType;
  requestVariantSelection: () => Promise<ProductVariant | null>;
  selectVariant: (variantID: string) => boolean;
  selectOption: (type: string, value: string) => void;
}
```

The context lets you:

* Read the current product ID.
* Read the current product, when available.
* Read the selected variant, when available.
* Check how the product detail flow is being used.
* Ask the user to select a variant.
* Select a variant by ID.
* Select an option by type and value.

The handling type can be:

```ts theme={null}
enum ProductDetailHandlingType {
  PDP = "PDP",
  QuickAdd = "QuickAdd",
  Select = "Select"
}
```

* `PDP` - Normal Product Detail Page
* `QuickAdd` - Quick Add Sheet/Dialog
* `Select` - Similar to Quick Add but with the purpose to select a variant

## Creation Arguments

When a handler is created, these arguments can be provided:

```ts theme={null}
interface CreateProductDetailHandlerArgs {
  identifier: string;
  packageName: string;
  productID: string;
  settings: {
    [key: string]: any;
  };
}
```

Use `settings` for configuration passed to the handler.

## Best Practices

* Keep callbacks fast.
* Use `onFormat` only for display formatting.
* Use action callbacks for behavior.
* Return `false` only when you want to stop the default action.
* Check optional values before using them.
