Validate query parameters in a page route

Use Page queryParams to validate, coerce, and default query-string state.

Query parameters often drive list views and filters. Defended routing lets the page reject invalid values before content renders, while accepted parameters arrive in $queryParams with the constrained types.

<App scrollWholePage="false">
  <Pages fallbackPath="/tickets/help">
    <Page url="/">
      <VStack gap="$space-3">
        <Text variant="strong">Ticket links</Text>
        <HStack>
          <Button
            label="Open active tickets"
            onClick="navigate('/tickets?status=open&page=2')"
          />
          <Button
            label="Open invalid status"
            onClick="navigate('/tickets?status=archived&page=2')"
          />
          <Button
            label="Open invalid page"
            onClick="navigate('/tickets?status=open&page=0')"
          />
        </HStack>
      </VStack>
    </Page>

    <Page
      url="/tickets"
      queryParams="status:enum(open,closed)?,page:int(min=1)?">
      <VStack gap="$space-2">
        <Text variant="strong">Tickets accepted</Text>
        <Text>Status: {$queryParams.status ?? 'open'}</Text>
        <Text>Page: {$queryParams.page ?? 1}</Text>
        <Text>Page type: {typeof ($queryParams.page ?? 1)}</Text>
        <Button label="Back home" onClick="navigate('/')" />
      </VStack>
    </Page>

    <Page url="/tickets/help">
      <VStack gap="$space-2">
        <Text variant="strong">Ticket URL rejected</Text>
        <Text>Use status open or closed, and page 1 or higher.</Text>
        <Button label="Back home" onClick="navigate('/')" />
      </VStack>
    </Page>
  </Pages>
</App>
Validate ticket list query parameters
<App scrollWholePage="false">
  <Pages fallbackPath="/tickets/help">
    <Page url="/">
      <VStack gap="$space-3">
        <Text variant="strong">Ticket links</Text>
        <HStack>
          <Button
            label="Open active tickets"
            onClick="navigate('/tickets?status=open&page=2')"
          />
          <Button
            label="Open invalid status"
            onClick="navigate('/tickets?status=archived&page=2')"
          />
          <Button
            label="Open invalid page"
            onClick="navigate('/tickets?status=open&page=0')"
          />
        </HStack>
      </VStack>
    </Page>

    <Page
      url="/tickets"
      queryParams="status:enum(open,closed)?,page:int(min=1)?">
      <VStack gap="$space-2">
        <Text variant="strong">Tickets accepted</Text>
        <Text>Status: {$queryParams.status ?? 'open'}</Text>
        <Text>Page: {$queryParams.page ?? 1}</Text>
        <Text>Page type: {typeof ($queryParams.page ?? 1)}</Text>
        <Button label="Back home" onClick="navigate('/')" />
      </VStack>
    </Page>

    <Page url="/tickets/help">
      <VStack gap="$space-2">
        <Text variant="strong">Ticket URL rejected</Text>
        <Text>Use status open or closed, and page 1 or higher.</Text>
        <Button label="Back home" onClick="navigate('/')" />
      </VStack>
    </Page>
  </Pages>
</App>

Key points

Constrain only public URL state: Put filter, sort, and pagination contracts in queryParams, where they are enforced for direct links, buttons, and pasted URLs.

Mark optional values with ?: status:enum(open,closed)? and page:int(min=1)? allow the page to provide defaults when parameters are absent.

Use typed values after validation: $queryParams.page is a number when present, so pagination logic can use it without ad hoc parsing.

Reject bad combinations early: Invalid query values redirect to fallbackPath and emit a constraint-rejected navigation diagnostic.


See also