# The canonical stay URL

> One stay query format across every Kismet site — the StayUrlParam enum, parse/serialize helpers, useStayQuery, site-param registration, and the two gates that make compliance required.




Every Kismet site carries the stay a guest is shopping for in the URL under ONE
set of key names, so a search bar, a results page, and a property page agree
with each other across reloads, shared links, and the move to a property page.
The `stay-query` contract makes this a MUST: keys come from the
`StayUrlParam` enum (or a registered site param), and reads and writes go
through the SDK helpers only.

## The canonical keys

`StayUrlParam` is the single enum. Never spell these keys as string literals —
compare the enum member, or derive the key lists from it.

| Key | Meaning | Default | Written |
|---|---|---|---|
| `checkIn`, `checkOut` | ISO days; both or neither, `checkOut > checkIn` | omitted | data |
| `guests` | integer 1–16 | 2 (omitted) | data |
| `beds`, `sleeps` | minimums; 0 means any | 0 (omitted) | data |
| `minPrice`, `maxPrice` | whole currency units | omitted | data |
| `currency` | ISO-4217-style code; only with a price bound | `USD` | data |
| `sort` | `recommended · price-asc · price-desc · name-asc · bedrooms · sleeps` | `recommended` | data |
| `pets`, `type`, `group`, `tags`, `prioritize` | optional filters | omitted | data |
| `sheet` | slug of the open quick view | omitted | UI-only |
| `layout` | `grid · map` | `grid` | UI-only |

Two write modes, one shared list: data keys change results, so `useStayQuery`
writes them with `router.replace({ scroll: false })`. UI-only keys (`sheet`,
`layout`) are written shallow with `history.replaceState` and never trigger a
server re-read of results. `STAY_DATA_PARAM_KEYS`, `STAY_UI_PARAM_KEYS`, and
`ALL_STAY_PARAM_KEYS` all derive from the enum.

## Read and write through the helpers

```ts
import { parseStayQuery, stayQueryToSearchParams } from '@kismet-tech/sdk';

const stayQuery = parseStayQuery(searchParams); // invalid values fall back silently
const href = `/properties?${stayQueryToSearchParams(stayQuery).toString()}`;
```

A param at its default is omitted; garbage is dropped, never thrown. Use
`countStayNights(stayQuery)` and `describeActiveStaySettings(stayQuery)` for
nights and the active-settings line.

## The one hook (React and Next)

```tsx
const [stayQuery, updateStayQuery] = useStayQuery({ router, isKnownSheetSlug });

updateStayQuery({ checkIn: '2026-10-12', checkOut: '2026-10-15' }); // router.replace, scroll: false
updateStayQuery({ sheet: 'fern-ridge-cabin' });                     // shallow write, no re-read
updateStayQuery({ sheet: undefined });                              // close: removed
```

Sheet rules: written shallow, removed on close, read after hydration, an
unknown slug resolves to closed (`isKnownSheetSlug`), and preserved when data
keys change. A Next App Router adapter — `createNextStayQueryController` from
`@kismet-tech/sdk/next` — takes `useRouter()` / `usePathname()` /
`useSearchParams()` outputs as-is and follows the same two write modes.

## Site-specific params (L4)

A site-specific param must be namespaced `x-<site>-<name>` and registered;
anything else is an invented key. Registration goes into the same registry the
parser, serializer, build gate, and `kismet check` read. Unprefixed keys and
built-in collisions are refused.

```ts
export const acmeView = defineStayParam({
  key: 'x-acme-view',
  mode: 'ui',
  parse: (raw) => (raw === 'list' || raw === 'grid' ? raw : undefined),
  default: 'grid',
});
```

## Compliance is required, not advised

Two gates back the contract's MUSTs and MUST NOTs:

- `content/stay-params` (runtime, `npx kismet check`) — fails on any rendered
  link carrying an invented stay key (`?minBedrooms=2`, `?check_in=…`,
  `?price_min=…`).
- `recipes/stay-query-source` (static, `npx kismet recipes check --app-dir .`,
  the starters' `check:recipes`) — fails when a file that declares the
  `stay-query` contract touches `searchParams` / `URLSearchParams` /
  `router.push|replace` outside the SDK helpers, or uses a non-canonical,
  unregistered key.

Where to go next: [What is a contract](https://developers.kismet.travel/sdk/contracts.md) for the requirement
levels, and [Recipes and implementation tests](https://developers.kismet.travel/sdk/recipes.md) for declaring
implementations.
