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

# Product and market status

> How product status and market status decide whether a product is public in product search / GraphQL and available to merchandising.

export const StatusExplorer = () => {
  const statuses = ['active', 'inactive', 'draft', 'incomplete', 'archived'];
  const statusLabels = {
    active: 'Active',
    inactive: 'Inactive',
    draft: 'Draft',
    incomplete: 'Incomplete',
    archived: 'Archived'
  };
  const merchandisingLabels = {
    searchable: 'Searchable',
    discontinued: 'Discontinued',
    typically_not_synced: 'Typically not synced',
    confirm: 'Confirm for this path'
  };
  const whyWhenProductBlocks = {
    draft: 'Upcoming, not ready to publish. In general, product status keeps it hidden on every surface, even if market status is active.',
    incomplete: 'Missing required data. In general, product status keeps it hidden on every surface, even if market status is active.',
    inactive: 'Turned off. Product status is the global lifecycle, so this surface is typically not public.',
    archived: 'Ended. Archived is a product lifecycle status, so the product is typically not publishable. Confirm how each merchandising integration treats it. Do not assume it matches inactive on every path.'
  };
  const whyWhenProductIsActive = {
    active: 'For the shared contract, the product is publishable and this surface is active, so it is public.',
    inactive: 'The product is publishable, but this surface is turned off, so it is typically unavailable here. Nosto typically gets discontinued / not searchable.',
    archived: 'Archived can appear as a market choice in Enad. Market and channel status is typically active, inactive, or incomplete. Confirm this path before you rely on it.',
    draft: 'This surface is upcoming and not ready to publish.',
    incomplete: 'This surface is missing required data.'
  };
  const toneClasses = {
    yes: 'bg-emerald-50 text-emerald-800 ring-1 ring-inset ring-emerald-200 dark:bg-emerald-950/40 dark:text-emerald-200 dark:ring-emerald-900',
    no: 'bg-zinc-100 text-zinc-700 ring-1 ring-inset ring-zinc-200 dark:bg-zinc-800 dark:text-zinc-200 dark:ring-zinc-700',
    searchable: 'bg-emerald-50 text-emerald-800 ring-1 ring-inset ring-emerald-200 dark:bg-emerald-950/40 dark:text-emerald-200 dark:ring-emerald-900',
    discontinued: 'bg-amber-50 text-amber-900 ring-1 ring-inset ring-amber-200 dark:bg-amber-950/40 dark:text-amber-200 dark:ring-amber-900',
    typically_not_synced: 'bg-zinc-100 text-zinc-700 ring-1 ring-inset ring-zinc-200 dark:bg-zinc-800 dark:text-zinc-200 dark:ring-zinc-700',
    confirm: 'bg-amber-50 text-amber-900 ring-1 ring-inset ring-amber-200 dark:bg-amber-950/40 dark:text-amber-200 dark:ring-amber-900'
  };
  const [productStatus, setProductStatus] = useState('active');
  const [marketStatus, setMarketStatus] = useState('active');
  const [isDark, setIsDark] = useState(false);
  useEffect(() => {
    const root = document.documentElement;
    const sync = () => {
      setIsDark(root.classList.contains('dark') || root.getAttribute('data-theme') === 'dark' || root.getAttribute('data-mode') === 'dark');
    };
    sync();
    const observer = new MutationObserver(sync);
    observer.observe(root, {
      attributes: true,
      attributeFilter: ['class', 'data-theme', 'data-mode']
    });
    return () => observer.disconnect();
  }, []);
  const isPublic = productStatus === 'active' && marketStatus === 'active';
  let merchandising = 'confirm';
  if (productStatus === 'active' && marketStatus === 'active') {
    merchandising = 'searchable';
  } else if (productStatus === 'active' && marketStatus === 'inactive') {
    merchandising = 'discontinued';
  } else if (productStatus === 'draft' || productStatus === 'incomplete') {
    merchandising = 'typically_not_synced';
  }
  const why = productStatus === 'active' ? whyWhenProductIsActive[marketStatus] : whyWhenProductBlocks[productStatus];
  const publicTone = isPublic ? 'yes' : 'no';
  const selectStyle = {
    width: '100%',
    borderRadius: '8px',
    border: isDark ? '1px solid #3f3f46' : '1px solid #e4e4e7',
    backgroundColor: isDark ? '#18181b' : '#ffffff',
    color: isDark ? '#fafafa' : '#09090b',
    colorScheme: isDark ? 'dark' : 'light',
    padding: '8px 12px',
    fontSize: '14px',
    outline: 'none'
  };
  const optionStyle = {
    backgroundColor: isDark ? '#18181b' : '#ffffff',
    color: isDark ? '#fafafa' : '#09090b'
  };
  return <div className="not-prose rounded-2xl border border-zinc-200 bg-zinc-50 dark:border-zinc-800 dark:bg-zinc-900/50" style={{
    display: 'flex',
    flexDirection: 'column',
    gap: '16px',
    marginTop: '24px',
    marginBottom: '24px',
    padding: '20px',
    position: 'relative'
  }}>
      <div style={{
    display: 'flex',
    flexDirection: 'column',
    gap: '4px'
  }}>
        <div className="text-sm font-semibold text-zinc-950 dark:text-white">
          Try a combination
        </div>
        <div className="text-sm text-zinc-600 dark:text-zinc-400">
          Outcomes are for one store group + market. Only{' '}
          <span className="font-medium text-zinc-800 dark:text-zinc-200">
            active
          </span>{' '}
          +{' '}
          <span className="font-medium text-zinc-800 dark:text-zinc-200">
            active
          </span>{' '}
          is public.
        </div>
      </div>

      <div style={{
    display: 'grid',
    gridTemplateColumns: 'repeat(2, minmax(0, 1fr))',
    gap: '12px'
  }}>
        <label htmlFor="product-status" style={{
    display: 'flex',
    flexDirection: 'column',
    gap: '6px',
    minWidth: 0
  }}>
          <span className="text-sm font-medium text-zinc-700 dark:text-zinc-200">
            Product status
          </span>
          <select id="product-status" value={productStatus} onChange={event => setProductStatus(event.target.value)} style={selectStyle}>
            {statuses.map(status => <option key={status} value={status} style={optionStyle}>
                {statusLabels[status]}
              </option>)}
          </select>
        </label>

        <label htmlFor="market-status" style={{
    display: 'flex',
    flexDirection: 'column',
    gap: '6px',
    minWidth: 0
  }}>
          <span className="text-sm font-medium text-zinc-700 dark:text-zinc-200">
            Market status
          </span>
          <select id="market-status" value={marketStatus} onChange={event => setMarketStatus(event.target.value)} style={selectStyle}>
            {statuses.map(status => <option key={status} value={status} style={optionStyle}>
                {statusLabels[status]}
              </option>)}
          </select>
        </label>
      </div>

      <div style={{
    display: 'grid',
    gridTemplateColumns: 'repeat(2, minmax(0, 1fr))',
    gap: '12px'
  }}>
        <div className="rounded-xl border border-zinc-200 bg-white p-4 dark:border-zinc-800 dark:bg-zinc-950">
          <div className="text-xs font-medium uppercase tracking-wide text-zinc-500 dark:text-zinc-400">
            Public in product search / GraphQL
          </div>
          <div className={`mt-2 inline-flex rounded-full px-2.5 py-1 text-sm font-medium ${toneClasses[publicTone]}`}>
            {isPublic ? 'Yes' : 'No'}
          </div>
        </div>

        <div className="rounded-xl border border-zinc-200 bg-white p-4 dark:border-zinc-800 dark:bg-zinc-950">
          <div className="text-xs font-medium uppercase tracking-wide text-zinc-500 dark:text-zinc-400">
            Merchandising
          </div>
          <div className={`mt-2 inline-flex rounded-full px-2.5 py-1 text-sm font-medium ${toneClasses[merchandising]}`}>
            {merchandisingLabels[merchandising]}
          </div>
        </div>
      </div>

      <div className="rounded-xl border border-zinc-200 bg-white px-4 py-3 text-sm leading-6 text-zinc-700 dark:border-zinc-800 dark:bg-zinc-950 dark:text-zinc-300">
        <span className="font-medium text-zinc-950 dark:text-white">Why. </span>
        {why}
      </div>
    </div>;
};

A product in Enad has two status layers. They work together, but they are not the same thing.

<CardGroup cols={2}>
  <Card title="Product status" icon="box">
    The **global lifecycle**. In general, `draft` and `incomplete` keep the
    product hidden on every surface, even if market status is `active`.
  </Card>

  <Card title="Market status" icon="map">
    Availability for **one store group + market** (one surface). When the
    product is otherwise publishable, this can still turn that surface off.
  </Card>
</CardGroup>

The same product can be live in B2B Sweden and off in B2C Finland. Each [store group + market](/docs/catalog/store-groups-and-markets) pair has its own market status.

For the shared contract, a product is public in [product search / GraphQL](/docs/product-search) when it is **publishable globally** and **market status for that surface is `active`**. In general, the same combination also controls **merchandising** (search and recommendations). Integration-specific behavior may differ. See [Nosto](#nosto) for the mapping we document today.

## What each status means

| Status       | Meaning                                                                                                                                                                            |
| ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `active`     | Publishable.                                                                                                                                                                       |
| `inactive`   | Turned off. On a market, this typically makes an otherwise publishable product unavailable on that surface.                                                                        |
| `draft`      | Upcoming, not ready to publish. On the product, this typically keeps it hidden.                                                                                                    |
| `incomplete` | Missing required data. On the product, this typically keeps it hidden.                                                                                                             |
| `archived`   | Ended. Used as a **product** lifecycle status. Treat `archived` on a market as something to confirm; market and channel status is typically `active`, `inactive`, or `incomplete`. |

The explorer lists the statuses you can see on a product in Enad, including `archived` on the market axis.

## Try a combination

Pick a product status and a market status to see the intended outcome for **one** surface.

<StatusExplorer />

## Variant status

Variants have their own status as well. In general they follow the same logic as the product: a global lifecycle, plus availability per store group + market where that is supported.

Once product status and market status are clear, variant status can typically be read the same way. Integration-specific behavior may differ.

## Storefront presentation

Enad exposes the status. The storefront decides how that status is presented.

This is most relevant for `archived`. The shared contract above covers whether the product is public in product search / GraphQL and merchandising. The storefront can still choose the visitor experience, for example:

* a 404
* an old-link or "no longer available" page
* hiding the product from listings while keeping a direct URL

Product search / GraphQL and merchandising integrations do not have to match that storefront experience.

## Merchandising integrations

The explorer shows the shared publication idea. Some integrations add their own states on top. This list will grow as more merchandising tools are connected.

### Nosto

Nosto keeps its own catalog status per surface. For the shared contract, this is the mapping that is robust enough to publish:

| Combination                          | What Nosto typically gets                                  |
| ------------------------------------ | ---------------------------------------------------------- |
| Product `active` + market `active`   | Synced and **searchable**                                  |
| Product `active` + market `inactive` | **Discontinued** / not searchable                          |
| Product `draft` or `incomplete`      | Not public, and typically **not synced** as active content |

`discontinued` is integration-specific to Nosto: when the product stays `active` and the **market** is `inactive`, Nosto typically keeps the product but drops it out of search.

Other combinations (including product `inactive`, product `archived`, and market `archived`) should be confirmed per implementation. Do not assume every non-`active` product status means "not synced", or that `archived` always maps like `inactive`.
