# Products

Products are the sellable catalog items that you expose to guests. A product combines a unit type, rate configuration, target inventory goals, and optional services into one offer that can be used in your booking flows.

Products sit under [Product Groups](/catalog/product-groups.md). Product groups define shared commercial controls, while products represent the individual offers within those groups.

## Key Concepts

### Product composition

A product combines:

* A **unit type** (`unitType`)
* A **product group** (`productGroup`)
* A **rate** (`rate`) with interval and rate calendars
* **Target inventory settings** (`targetUnitCount`, `targetOccupancyPercentage`)
* Optional and included **services**

### Product types

`CatalogProductProductTypeEnum` defines the product category:

| Value          | Description                                   |
| -------------- | --------------------------------------------- |
| `MULTI_FAMILY` | Product configured for multi-family use cases |
| `SHORT_STAY`   | Product configured for short stay use cases   |
| `STUDENT`      | Product configured for student use cases      |

### Rate intervals

`CatalogRateIntervalEnum` controls the pricing interval:

| Value     | Description     |
| --------- | --------------- |
| `MONTHLY` | Monthly pricing |
| `NIGHTLY` | Nightly pricing |
| `WEEKLY`  | Weekly pricing  |

## GraphQL API

### Types

#### `Product`

| Field                       | Type                        | Description                                                        |
| --------------------------- | --------------------------- | ------------------------------------------------------------------ |
| `id`                        | `ID!`                       | Product identifier                                                 |
| `targetUnitCount`           | `Int!`                      | Target number of units for this product                            |
| `targetOccupancyPercentage` | `Float!`                    | Target occupancy percentage for this product                       |
| `productGroup`              | `ProductGroup!`             | Product group the product belongs to                               |
| `productGroupId`            | `ID!`                       | Product group identifier, deprecated in favor of `productGroup.id` |
| `unitType`                  | `UnitType`                  | Unit type linked to the product                                    |
| `rate`                      | `ProductRate`               | Rate linked to the product                                         |
| `services`                  | `ProductServiceConnection!` | Services currently selected for the product                        |
| `eligibleServices`          | `ServiceConnection!`        | Services that can be added to the product                          |
| `allocatedUnits`            | `AllocatedUnitConnection!`  | Units allocated to the product                                     |

#### `ProductRate`

| Field       | Type                       | Description                                    |
| ----------- | -------------------------- | ---------------------------------------------- |
| `id`        | `ID!`                      | Product rate identifier                        |
| `interval`  | `CatalogRateIntervalEnum!` | Rate interval (`MONTHLY`, `NIGHTLY`, `WEEKLY`) |
| `productId` | `ID`                       | Related product identifier                     |

#### `ProductService`

| Field             | Type                                 | Description                                 |
| ----------------- | ------------------------------------ | ------------------------------------------- |
| `id`              | `ID!`                                | Product service identifier                  |
| `service`         | `Service!`                           | Referenced service                          |
| `serviceOffering` | `CatalogProductServiceOfferingEnum!` | Whether the service is included or optional |
| `createdAt`       | `ISO8601DateTime!`                   | Creation timestamp                          |
| `updatedAt`       | `ISO8601DateTime!`                   | Last update timestamp                       |

### Queries

#### Get a product

```graphql
query Product($id: ID!, $first: Int, $after: String) {
  catalog {
    product(id: $id) {
      id
      targetUnitCount
      targetOccupancyPercentage
      productGroup {
        id
        internalReference
      }
      unitType {
        id
        code
      }
      rate {
        id
        interval
        calendars(first: $first, after: $after) {
          nodes {
            id
            startDate
            endDate
            amountCents
            currency
          }
          totalCount
          pageInfo {
            hasNextPage
            endCursor
          }
        }
      }
      services(first: $first, after: $after) {
        nodes {
          id
          serviceOffering
          service {
            id
            name
            amountCents
            amountCurrency
            applicationUnit
          }
        }
        totalCount
      }
      eligibleServices(first: $first, after: $after) {
        nodes {
          id
          name
          amountCents
          amountCurrency
          applicationUnit
        }
        totalCount
      }
    }
  }
}
```

```json
{
  "id": "product-id",
  "first": 20
}
```

#### List products in a product group

```graphql
query ProductGroupProducts($id: ID!, $first: Int, $after: String) {
  catalog {
    productGroup(id: $id) {
      id
      internalReference
      products(first: $first, after: $after) {
        nodes {
          id
          targetUnitCount
          targetOccupancyPercentage
          unitType {
            id
            code
          }
          rate {
            id
            interval
          }
        }
        totalCount
        pageInfo {
          hasNextPage
          endCursor
        }
      }
    }
  }
}
```

```json
{
  "id": "product-group-id",
  "first": 20
}
```

### Mutations

#### Create a product

```graphql
mutation CreateProduct($input: CreateProductMutationInput!) {
  catalog {
    product {
      create(input: $input) {
        id
      }
    }
  }
}
```

```json
{
  "input": {
    "product": {
      "name": "Standard Studio Product",
      "productGroupId": "product-group-id",
      "productType": "STUDENT",
      "targetUnitCount": 20,
      "targetOccupancyPercentage": 95,
      "unitTypeId": "unit-type-id",
      "rate": {
        "amountCents": 125000,
        "currency": "GBP",
        "interval": "WEEKLY"
      }
    }
  }
}
```

Required fields for `product` in `CreateProductMutationInput`:

| Field             | Type                             |
| ----------------- | -------------------------------- |
| `name`            | `String!`                        |
| `productGroupId`  | `ID!`                            |
| `productType`     | `CatalogProductProductTypeEnum!` |
| `targetUnitCount` | `Int!`                           |
| `unitTypeId`      | `ID!`                            |
| `rate`            | `CreateRateInput!`               |

#### Update a product

```graphql
mutation UpdateProduct($input: UpdateProductMutationInput!) {
  catalog {
    product {
      update(input: $input) {
        id
      }
    }
  }
}
```

```json
{
  "input": {
    "productId": "product-id",
    "product": {
      "targetUnitCount": 24,
      "targetOccupancyPercentage": 97.5
    }
  }
}
```

Required fields for `UpdateProductMutationInput`:

| Field       | Type                  |
| ----------- | --------------------- |
| `productId` | `ID!`                 |
| `product`   | `ProductUpdateInput!` |

#### Delete a product

```graphql
mutation DeleteProduct($input: DeleteProductMutationInput!) {
  catalog {
    product {
      delete(input: $input) {
        id
      }
    }
  }
}
```

```json
{
  "input": {
    "productId": "product-id"
  }
}
```

Required fields for `DeleteProductMutationInput`:

| Field       | Type  |
| ----------- | ----- |
| `productId` | `ID!` |

> **Tip:** The Catalog product namespace also supports service management (`addServices`, `removeService`, `updateService`) and unit allocation (`allocateUnits`, `unallocateUnits`), plus rate updates through `catalog.rate.update`.

## MCP Tools

If you use MCP, these tools are relevant:

* [get\_product](/mcp/tools/catalog/products/get_product.md)
* [get\_product\_group](/mcp/tools/catalog/product-groups/get_product_group.md)

See [Tools](/mcp/tools.md) for setup and full tool coverage.

## Booking date bounds

Products do not own contract date boundaries directly — they inherit them from their parent product group. The `contractEarliestStartDate`, `contractLatestStartDate`, `contractEarliestEndDate`, and `contractLatestEndDate` fields on `ProductGroup` control the date range a booking made against any product in the group can occupy.

In particular, `contractEarliestStartDate` and `contractLatestEndDate` are the outer bounds, while `contractLatestStartDate` and `contractEarliestEndDate` allow a student to start late or end early within those bounds. See [Product Groups](/catalog/product-groups.md#contract-windows) on the Product Group doc for full details.

## Relationships

* A product belongs to a [Product Groups](/catalog/product-groups.md)
* A product references a [Unit Types](/spaces-and-inventory/unit-types.md)
* A product includes rate information that works with [Pricing](/pricing/pricing.md)
* Product details are used as part of the [Flow](/ecommerce/flow.md) booking flow


---

# 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.lavanda.app/catalog/products.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.
