# Page, Offers & Add-Ons

The `useCampaign` hook is the primary way to access **page-level data** in your components. A Page in Limio acts as a container for offers and optional add-ons, and can store custom attributes that drive content, logic, or display behaviour.

```typescript
import { useCampaign } from "@limio/sdk"

const { campaign, offers, addOns, groupValues, tag } = useCampaign()
```

**Returns:**

| Field         | Type             | Description                                           |
| ------------- | ---------------- | ----------------------------------------------------- |
| `campaign`    | `CampaignInfo`   | Page metadata — name, path, and custom attributes     |
| `offers`      | `ElasticOffer[]` | Offers attached to this page                          |
| `addOns`      | `ElasticAddOn[]` | Add-ons attached to this page                         |
| `groupValues` | `OfferGroup[]`   | Group definitions configured at the offer level       |
| `tag`         | `string`         | The tag the user entered the page with (for tracking) |

***

## Displaying offers

The most common pattern is to destructure `offers` from `useCampaign` and render a card for each one. Offers contain pricing, display attributes, product references, and attachments.

```tsx
import React from "react"
import { useCampaign, useBasket } from "@limio/sdk"

const PricingPage = () => {
  const { offers = [] } = useCampaign()
  const { addOfferToBasket } = useBasket()

  return (
    <section>
      {offers.map((offer) => {
        const { display_name__limio, display_price__limio, cta_text__limio } =
          offer.data.attributes

        return (
          <div key={offer.id}>
            <h2>{display_name__limio}</h2>
            <p>{display_price__limio}</p>
            <button onClick={() => addOfferToBasket(offer)}>
              {cta_text__limio || "Subscribe"}
            </button>
          </div>
        )
      })}
    </section>
  )
}
```

<details>

<summary>Example offer object</summary>

```javascript
{
  "id": "a1b2c3d4e5f6...",
  "name": "Leemeeo Premium Monthly",
  "path": "/offers/Leemeeo Pricing/Leemeeo Premium Monthly",
  "parent_path": "/offers/Leemeeo Pricing",
  "type": "item",
  "data": {
    "name": "Leemeeo Premium Monthly",
    "tags": ["/tags/premium"],
    "attributes": {
      "price__limio": [
        {
          "name": "charge_1",
          "label": "Monthly",
          "value": "29.00",
          "currencyCode": "USD",
          "type": "recurring",
          "trigger": "order_date",
          "repeat_interval": 1,
          "repeat_interval_type": "months",
          "repeat_count": 1
        }
      ],
      "display_name__limio": "Leemeeo Premium",
      "display_price__limio": "$29/month",
      "detailed_display_price__limio": "Pay $29.00 a month, cancel anytime.",
      "offer_features__limio": "<ul><li>Unlimited projects</li><li>Priority support</li><li>Advanced analytics</li></ul>",
      "cta_text__limio": "Start Free Trial",
      "group__limio": "monthly",
      "payment_types__limio": ["zuora_card"],
      "checkout__limio": { "checkout_type": "standard" },
      "autoRenew__limio": true
    },
    "attachments": [
      {
        "path": "/assets/Images/Premium",
        "name": "Premium Badge",
        "type": "image/png",
        "url": "/public/premium-badge.png"
      }
    ],
    "products": [
      {
        "name": "Leemeeo Premium",
        "path": "/products/Leemeeo Premium",
        "attributes": {
          "product_code__limio": "SKU-LMEO-001"
        },
        "type": "item"
      }
    ]
  }
}
```

</details>

***

## Grouping offers

Offers can be grouped by their `group__limio` attribute (e.g. "monthly" vs "yearly"). The `groupOffers` utility organises offers into labelled groups for tabbed or sectioned layouts.

```typescript
import { groupOffers } from "@limio/sdk"

const grouped = groupOffers(offers, groupLabels)
```

**Parameters:**

| Param         | Type             | Description                                                         |
| ------------- | ---------------- | ------------------------------------------------------------------- |
| `offers`      | `ElasticOffer[]` | The offers array from `useCampaign`                                 |
| `groupLabels` | `Group[]`        | Group definitions — typically from component props or `groupValues` |

Each `Group` has `{ id: string, label: string, thumbnail?: string }`.

**Returns** an array of `GroupInfo` objects:

| Field       | Type             | Description                                             |
| ----------- | ---------------- | ------------------------------------------------------- |
| `groupId`   | `string`         | The group identifier (matches `group__limio` on offers) |
| `id`        | `string`         | Same as `groupId`                                       |
| `label`     | `string`         | Display label for the group                             |
| `offers`    | `ElasticOffer[]` | Offers belonging to this group                          |
| `thumbnail` | `string`         | Optional thumbnail URL for the group                    |

Offers without a `group__limio` attribute are placed in an "Other" group.

**Example — tabbed pricing page with monthly/yearly toggle:**

```tsx
import React, { useMemo, useState } from "react"
import { useCampaign, groupOffers, useBasket } from "@limio/sdk"

const GroupedPricingPage = ({ groupLabels }) => {
  const { offers } = useCampaign()
  const { addOfferToBasket } = useBasket()

  const offerGroups = useMemo(
    () => groupOffers(offers, groupLabels),
    [offers, groupLabels]
  )

  const [activeGroup, setActiveGroup] = useState(offerGroups[0]?.groupId)

  const currentGroup = offerGroups.find((g) => g.groupId === activeGroup)

  return (
    <section>
      {/* Group tabs */}
      <div className="billing-toggle">
        {offerGroups.map((group) => (
          <button
            key={group.groupId}
            className={group.groupId === activeGroup ? "active" : ""}
            onClick={() => setActiveGroup(group.groupId)}
          >
            {group.label}
          </button>
        ))}
      </div>

      {/* Offer cards for selected group */}
      <div className="offer-cards">
        {currentGroup?.offers.map((offer) => (
          <div key={offer.id} className="offer-card">
            <h3>{offer.data.attributes.display_name__limio}</h3>
            <p>{offer.data.attributes.display_price__limio}</p>
            <button onClick={() => addOfferToBasket(offer)}>
              {offer.data.attributes.cta_text__limio || "Subscribe"}
            </button>
          </div>
        ))}
      </div>
    </section>
  )
}
```

<details>

<summary>Example <code>groupLabels</code> and output</summary>

**Input `groupLabels` (typically passed as a component prop):**

```javascript
[
  { id: "monthly", label: "Monthly", thumbnail: "" },
  { id: "yearly", label: "Yearly", thumbnail: "" }
]
```

**Output from `groupOffers`:**

```javascript
[
  {
    groupId: "monthly",
    id: "monthly",
    label: "Monthly",
    offers: [
      // Leemeeo Premium Monthly ($29/mo)
      // Leemeeo Starter Monthly ($9/mo)
    ],
    thumbnail: ""
  },
  {
    groupId: "yearly",
    id: "yearly",
    label: "Yearly",
    offers: [
      // Leemeeo Premium Yearly ($290/yr)
    ],
    thumbnail: ""
  }
]
```

</details>

***

## Add-ons

Add-ons are optional products attached to a page — typically upsells or extras that complement a base offer. Destructure `addOns` from `useCampaign` to access them. Add-ons share the same structure as offers, with a `record_type` of `"add_on"` and their own `price__limio` charges.

```tsx
import React from "react"
import { useCampaign, useBasket } from "@limio/sdk"

const AddOnList = () => {
  const { offers = [], addOns = [] } = useCampaign()
  const { addOfferToBasket } = useBasket()

  return (
    <section>
      <h2>Optional extras</h2>
      {addOns.map((addOn) => (
        <div key={addOn.id}>
          <span>{addOn.data.attributes.display_name__limio}</span>
          <span>{addOn.data.attributes.description__limio}</span>
          <button onClick={() => addOfferToBasket(addOn)}>
            Add
          </button>
        </div>
      ))}
    </section>
  )
}
```

<details>

<summary>Example add-on object</summary>

```javascript
{
  "path": "/add_ons/leemeeo-priority-support",
  "name": "Leemeeo Priority Support",
  "id": "b83ed9f688d6...",
  "type": "item",
  "data": {
    "name": "Leemeeo Priority Support",
    "record_type": "add_on",
    "attributes": {
      "display_name__limio": "Priority Support",
      "description__limio": "24/7 priority support with a dedicated account manager.",
      "price__limio": [
        {
          "name": "charge_1",
          "label": "Monthly",
          "value": "15.00",
          "currencyCode": "USD",
          "type": "recurring",
          "trigger": "subscription_start",
          "repeat_interval": 1,
          "repeat_interval_type": "month"
        }
      ],
      "label__limio": ["add-on"],
      "compatible_products": ["premium"]
    },
    "products": [
      {
        "path": "/products/Priority Support",
        "attributes": {
          "display_name__limio": "Priority Support",
          "product_code__limio": "SKU-LMEO-PS"
        }
      }
    ]
  }
}
```

</details>

***

## Page attributes

Access custom attributes configured on the page itself through `campaign.attributes`. These drive content, layout logic, or checkout behaviour.

```tsx
import React from "react"
import { useCampaign } from "@limio/sdk"

const PageHeader = () => {
  const { campaign } = useCampaign()

  return (
    <section>
      <h1>{campaign.name}</h1>
      {campaign.attributes.meta_title__limio && (
        <title>{campaign.attributes.meta_title__limio}</title>
      )}
    </section>
  )
}
```

<details>

<summary>Example campaign object</summary>

```javascript
{
  "name": "Leemeeo Pricing",
  "path": "/offers/Leemeeo Pricing",
  "attributes": {
    "meta_title__limio": "Leemeeo — Choose Your Plan",
    "push_to_checkout__limio": true
  }
}
```

</details>

***

## Offer utility functions

The SDK exports additional utilities for working with offers. These are most commonly used in subscription management views rather than acquisition pages.

### `checkActiveOffers()`

Filters an array of offers to return only those that are currently active based on their start and end dates.

```typescript
import { checkActiveOffers } from "@limio/sdk"

const activeOffers = checkActiveOffers(subscription.offers)
const allCurrentAndFuture = checkActiveOffers(subscription.offers, true)
```

| Param           | Type             | Default | Description                 |
| --------------- | ---------------- | ------- | --------------------------- |
| `offers`        | `ElasticOffer[]` | `[]`    | Offers to filter            |
| `includeFuture` | `boolean`        | `false` | Include future-dated offers |

### `useOfferInfo()`

Extracts and normalises common offer attributes into a typed object — useful for rendering offer cards with consistent logic.

```typescript
import { useOfferInfo } from "@limio/sdk"

const {
  displayName,
  hasRecurringCharge,
  isAutoRenew,
  allowMultibuy,
  isDelivery,
  offerImage,
  isGift
} = useOfferInfo(offer)
```

**Returns:**

| Field                | Type         | Description                                    |
| -------------------- | ------------ | ---------------------------------------------- |
| `displayName`        | `string`     | Product display name or group label            |
| `hasRecurringCharge` | `boolean`    | Whether the offer has a recurring price charge |
| `isAutoRenew`        | `boolean`    | Whether auto-renewal is enabled                |
| `allowMultibuy`      | `boolean`    | Whether quantity selection is allowed          |
| `isDelivery`         | `boolean`    | Whether the offer includes physical delivery   |
| `offerImage`         | `Attachment` | First image attachment on the offer            |
| `isGift`             | `boolean`    | Whether this is a gift redemption offer        |
| `usesExternalPrice`  | `boolean`    | Whether pricing is fetched externally          |
| `productNames`       | `string[]`   | Names of products included in the offer        |
| `offerDescription`   | `string`     | MMA description text                           |

***

## See also

* [Basket (Cart)](https://github.com/innovate42/innovate42-service-template/blob/docs/docs/_external/spaces/developer-docs/limio-sdk/basket.md) — Adding offers to the basket and checking out
* [Subscription Update Checkout](/developers/limio-sdk/subscription-update-checkout.md) — Building plan change flows
* [Pricing](https://github.com/innovate42/innovate42-service-template/blob/docs/docs/_external/spaces/developer-docs/limio-sdk/pricing.md) — Working with price objects and formatting


---

# Agent Instructions: Querying This Documentation

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

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.limio.com/developers/limio-sdk/page.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
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.
