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

# Troubleshooting — Web

> Solutions for common Primer Checkout Web issues

## Quick diagnosis

| Symptom                         | Likely Cause                              | Solution                                          |
| ------------------------------- | ----------------------------------------- | ------------------------------------------------- |
| Components don't render         | `loadPrimer()` not called                 | Call `loadPrimer()` before using components       |
| `customElements is not defined` | Code running on server                    | Load SDK only on client side                      |
| `window is not defined`         | Code running on server                    | Add `typeof window !== 'undefined'` check         |
| Two card forms appear           | Using both custom form and payment method | Choose one approach                               |
| Card inputs don't work          | Inputs outside `<primer-card-form>`       | Wrap inputs in card form component                |
| Options not applied (React)     | New object reference each render          | Use stable references with `useMemo` or constants |
| Styles not applying             | Trying to style shadow DOM                | Use CSS variables instead                         |

## Server-side rendering errors

### Error: "customElements is not defined"

**Cause:** Primer code is running on the server where Web Components API doesn't exist.

**Solution:** Ensure `loadPrimer()` is called only in client-side lifecycle methods.

<Tabs>
  <Tab title="Next.js / React">
    ```javascript theme={"dark"}
    import { useEffect } from 'react';
    import { loadPrimer } from '@primer-io/primer-js';

    function MyCheckoutComponent() {
      useEffect(() => {
        if (typeof window !== 'undefined') {
          loadPrimer();
        }
      }, []);

      return (
        <primer-checkout client-token="your-client-token">
          {/* Checkout content */}
        </primer-checkout>
      );
    }
    ```
  </Tab>

  <Tab title="Nuxt 3">
    ```javascript theme={"dark"}
    <script setup>
    import { onMounted } from 'vue';

    onMounted(async () => {
      if (import.meta.client) {
        const { loadPrimer } = await import('@primer-io/primer-js');
        loadPrimer();
      }
    });
    </script>
    ```
  </Tab>

  <Tab title="SvelteKit">
    ```javascript theme={"dark"}
    <script>
      import { onMount } from 'svelte';
      import { browser } from '$app/environment';

      onMount(async () => {
        if (browser) {
          const { loadPrimer } = await import('@primer-io/primer-js');
          loadPrimer();
        }
      });
    </script>
    ```
  </Tab>
</Tabs>

### Error: "window is not defined"

**Cause:** Code is accessing browser globals during server-side rendering.

**Solution:** Add environment checks before accessing browser APIs:

```javascript theme={"dark"}
if (typeof window !== 'undefined') {
  // Browser-only code here
}
```

## Card form issues

### Duplicate card forms

**Cause:** Using both `<primer-card-form>` and `<primer-payment-method type="PAYMENT_CARD">` in the same layout.

**Why this happens:** `<primer-payment-method type="PAYMENT_CARD">` internally creates its own `<primer-card-form>`. When you also add a custom card form, you end up with two card forms on the page.

**Solution:** Choose one approach:

```html theme={"dark"}
<!-- Option A: Use custom card form only -->
<div slot="payments">
  <primer-card-form>
    <div slot="card-form-content">
      <!-- Your custom layout -->
    </div>
  </primer-card-form>
  <!-- Other payment methods, but NOT PAYMENT_CARD -->
  <primer-payment-method type="PAYPAL"></primer-payment-method>
</div>

<!-- Option B: Use payment method component only -->
<div slot="payments">
  <!-- Let the component handle the card form -->
  <primer-payment-method type="PAYMENT_CARD"></primer-payment-method>
  <primer-payment-method type="PAYPAL"></primer-payment-method>
</div>
```

### Card inputs not working

**Cause:** Card input components placed outside `<primer-card-form>`.

**Solution:** All card inputs must be descendants of the card form:

```html theme={"dark"}
<!-- WRONG: Inputs outside primer-card-form -->
<primer-card-form></primer-card-form>
<primer-input-card-number></primer-input-card-number>

<!-- CORRECT: Inputs inside primer-card-form -->
<primer-card-form>
  <div slot="card-form-content">
    <primer-input-card-number></primer-input-card-number>
    <primer-input-card-expiry></primer-input-card-expiry>
    <primer-input-cvv></primer-input-cvv>
  </div>
</primer-card-form>
```

### Dynamic rendering creates duplicates

**Cause:** When dynamically rendering payment methods, `PAYMENT_CARD` is included while also using a custom card form.

**Solution:** Filter out `PAYMENT_CARD` when using a custom card form:

```javascript theme={"dark"}
checkout.addEventListener('primer:methods-update', (event) => {
  const availableMethods = event.detail
    .toArray()
    // Filter out PAYMENT_CARD if you're using a custom card form
    .filter((method) => method.type !== 'PAYMENT_CARD');

  availableMethods.forEach((method) => {
    const element = document.createElement('primer-payment-method');
    element.setAttribute('type', method.type);
    container.appendChild(element);
  });
});
```

## React-specific issues

### Options not being applied

**Cause:** Creating new object references on every render forces unnecessary comparisons.

<Info title="Deep Comparison">
  The Primer SDK implements deep comparison for the `options` property. This means unstable references won't cause re-initialization, but they still add comparison overhead on every render.
</Info>

**Solution:** Use stable references:

```javascript theme={"dark"}
// SUBOPTIMAL: New object every render
function CheckoutPage() {
  return <primer-checkout options={{ locale: 'en-GB' }} />;
}

// OPTIMAL: Stable reference
const SDK_OPTIONS = { locale: 'en-GB' };

function CheckoutPage() {
  return <primer-checkout options={SDK_OPTIONS} />;
}

// OPTIMAL: Use useMemo for dynamic options
function CheckoutPage({ userLocale }) {
  const options = useMemo(() => ({
    locale: userLocale,
  }), [userLocale]);

  return <primer-checkout options={options} />;
}
```

### Calling methods before ready

**Cause:** Calling SDK methods before the checkout is initialized.

**Solution:** Wait for the `primer:ready` event:

```javascript theme={"dark"}
// WRONG: May fail if checkout isn't ready
const checkout = document.querySelector('primer-checkout');
const primerJS = checkout.primerJS; // May be undefined
primerJS.setCardholderName('John Doe'); // Error!

// CORRECT: Wait for the ready event
checkout.addEventListener('primer:ready', (event) => {
  const primerJS = event.detail;
  primerJS.setCardholderName('John Doe'); // Works correctly
});
```

### React 18 vs React 19 property assignment

**Cause:** React 18 converts object props to `[object Object]` strings for web components.

**Solution:** Use the appropriate pattern for your React version:

<Tabs>
  <Tab title="React 18">
    ```javascript theme={"dark"}
    const SDK_OPTIONS = { locale: 'en-GB' };

    function CheckoutPage() {
      const checkoutRef = useRef(null);

      useEffect(() => {
        if (checkoutRef.current) {
          checkoutRef.current.options = SDK_OPTIONS;
        }
      }, []);

      return <primer-checkout ref={checkoutRef} client-token={token} />;
    }
    ```
  </Tab>

  <Tab title="React 19">
    ```javascript theme={"dark"}
    const SDK_OPTIONS = { locale: 'en-GB' };

    function CheckoutPage() {
      return <primer-checkout client-token={token} options={SDK_OPTIONS} />;
    }
    ```
  </Tab>
</Tabs>

## Styling issues

### CSS not applying to components

**Cause:** Trying to style internal elements with CSS selectors. Shadow DOM prevents external CSS from reaching internal elements.

**Solution:** Use CSS variables instead:

```css theme={"dark"}
/* WRONG: Won't work with Shadow DOM */
primer-checkout input {
  border-color: blue;
}

/* CORRECT: Use CSS variables */
primer-checkout {
  --primer-color-border-input-default: blue;
}
```

## Validation vs payment errors

Understanding the difference helps with proper error handling:

| Error Type            | When It Occurs                                        | How It's Handled                                                    |
| --------------------- | ----------------------------------------------------- | ------------------------------------------------------------------- |
| **Validation errors** | During input (invalid format, missing fields)         | Handled automatically by input components; prevents form submission |
| **Payment failures**  | After form submission (declined card, network issues) | Requires explicit handling with error container or custom code      |

<Warning>
  Don't confuse these two error types. Validation errors prevent form submission and are shown inline. Payment failures occur after the form is submitted and require explicit handling.
</Warning>

## Debugging tips

### Log all Primer events

```javascript theme={"dark"}
const primerEvents = [
  'primer:ready',
  'primer:state-change',
  'primer:methods-update',
  'primer:payment-success',
  'primer:payment-failure',
  'primer:card-error',
  'primer:card-network-change',
];

primerEvents.forEach((eventName) => {
  document.addEventListener(eventName, (event) => {
    console.log(`[${eventName}]`, event.detail);
  });
});
```

### Verify component registration

```javascript theme={"dark"}
// Should return a constructor, not undefined
console.log(customElements.get('primer-checkout'));
```

### Check available payment methods

```javascript theme={"dark"}
checkout.addEventListener('primer:methods-update', (event) => {
  const methods = event.detail.toArray();
  console.table(methods.map((m) => ({ type: m.type, id: m.id })));
});
```

## Getting help

When contacting Primer support, include:

1. The `diagnosticsId` from any error callbacks
2. Your browser, OS version, and framework version
3. Steps to reproduce the issue

```javascript theme={"dark"}
primer.onPaymentFailure = ({ error }) => {
  console.error('Payment failed:', {
    code: error.code,
    message: error.message,
    diagnosticsId: error.diagnosticsId,
  });
};
```

## See also

<CardGroup cols={2}>
  <Card title="SSR guide" icon="server" href="/checkout/primer-checkout/frameworks/ssr-guide">
    Server-side rendering patterns
  </Card>

  <Card title="React integration" icon="react" href="/checkout/primer-checkout/frameworks/react-integration-guide">
    React-specific guidance
  </Card>

  <Card title="Events guide" icon="bolt" href="/checkout/primer-checkout/configuration/events">
    Event handling patterns
  </Card>

  <Card title="Build a custom card form" icon="credit-card" href="/checkout/primer-checkout/guides-and-recipes/build-custom-card-form">
    Card form tutorial
  </Card>
</CardGroup>
