> ## 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.

# App Extensions examples

These examples show the smallest useful implementation for each supported app-extension
contract. Copy the relevant pattern into the file generated by `napps create-extension`, then
adapt the business rule to your shop.

All examples use `@napps/component-extension` types. App extensions run business logic and edit
native-provided data; use [`@napps/nodes`](/js-ui/react/introduction) when you need to render
custom React UI.

## Product listing formatter

Add a tag to discounted products and hide products from a restricted vendor.

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

export function formatProducts(items: ProductListingBaseDataEditor[]) {
  for (const item of items) {
    if (item.product.vendor === "Restricted Vendor") {
      item.setVisible(false);
      continue;
    }

    if (item.product.discountPercentage > 0) {
      item.addTag(
        new ProductTag(
          "discount",
          `${item.product.discountPercentage}% off`,
          "#ffffff",
          "#b42318"
        )
      );
    }
  }
}
```

The formatter can be synchronous or asynchronous. Keep it idempotent because the same product can
be formatted more than once. Async work must finish within the extension timeout.

`ProductTag` is provided by the extension runtime as a global constructor.

## Collection filters

Apply a default order and preselect a value only when the filter exists.

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

export function prepareCollectionProductsFilters(
  context: CollectionProductsFiltersContext
) {
  if (context.trigger !== "Initial") {
    return;
  }

  context.filters.setOrder("BestSelling");

  const color = context.filters.available.findByLabel("Color");
  if (color?.hasValue("black")) {
    context.filters.addValueByLabel("Color", "black");
  }
}
```

`Initial` runs before the collection query. `UserFiltersChanged` runs after the customer changes
filters and before the updated query. Filter methods return `false` when the requested filter or
value is unavailable.

## Product detail handler

Format the PDP and block add-to-cart when a rule fails validation.

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

export const productDetailHandler: ProductDetailHandler = {
  onFormat(draft) {
    draft.setVendor("Example shop");
    draft.removeTagByType("legacy-label");
    draft.addTag(
      new ProductTag("editor-pick", "Editor's pick", "#ffffff", "#175cd3")
    );

    for (const media of draft.mediaItems) {
      if (media.type === "video") {
        draft.addMediaTag(media.id, {
          id: "video-label",
          text: "Video",
          textColor: "#ffffff",
          backgroundColor: "#101828"
        });
      }
    }
  },

  onBeforeProductAddToCart(event) {
    if (event.data.quantity < 1 || event.data.variant.stock <= 0) {
      return false;
    }

    return true;
  },

  onAddedToCart(event) {
    console.log("Added variant", event.data.productVariant.id);
    return true;
  }
};
```

The PDP handler receives `event.data` payloads. `onBeforeProductAddToCart` uses
`{ product, variant, quantity }`; variant-carrying selection and completion events use
`{ product, productVariant }`. Return `false` from cancellable callbacks to stop the native action.

## Cart operations

Return a cart operation when a qualifying product is present. Native can invoke this extension more
than once, so make the rule converge instead of adding the same mutation forever.

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

export function onCartChanged(cart: Cart) {
  const qualifyingItem = cart.lineItems.find(
    (line) => line.product?.vendor === "Example shop"
  );

  if (!qualifyingItem || qualifyingItem.quantity >= 2) {
    return [];
  }

  return [
    CartOperations.updateLineItem(
      qualifyingItem.id,
      undefined,
      undefined,
      2
    )
  ];
}
```

Return `CartOperation[]` or a promise of that array. The native runner applies the operations and
may run the extension again with the updated cart. Keep each pass deterministic and return an empty
array when no change is needed.

`CartOperations` is provided by the extension runtime as a global factory object.

## Combining services with an extension

Services can be used inside an extension when the surface provides them. Handle nullable results
from product lookups and inspect GraphQL errors after a resolved request.

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

export async function loadBadge(productID: string) {
  const product = await NAPPS.productService.getProductByID(productID);
  if (!product) {
    return null;
  }

  const fields = await product.getMetaFields(["custom.badge"]);
  return fields.custom?.badge ?? null;
}
```

See the individual [extension contracts](/typescript-reference/component-extensions) for the
complete callback types, timeout behavior, and mutation methods.
