---
title: "Search inputs for travel brands | Snowplow Signals"
description: "Snowplow Signals tells your search which dates and airports this traveler has tried this visit, so it ranks the next results for someone flexible on both."
source: https://signals.snowplow.io/solutions/travel/search
---

[Travel](https://signals.snowplow.io/solutions/travel) / Search inputs 

# Flight results that know what this traveler has tried. 

Signals tells your search and pricing engine which dates, airports and fares this traveler has tried this visit, so the results can rank for the dates and airports this traveler is weighing.

[Start free](https://signals.snowplow.io/start) [Talk to us](https://signals.snowplow.io/start?intent=talk) 

Higher search-to-book rate

## Turn more searches into bookings by ranking for a traveler flexible on dates and airports. 

Search-to-book moves when the results rank for what this traveler has been trying, rather than for the one search in front of them. Signals gives your search and pricing engine this session's searches, the dates and airports tried and the fares looked at, so it can rank flexible fares first, put two airports in one list, show what a traveler keeps checking, and hold a route from one visit to the next.

Four ways travel brands use it

01 Travelers who keep moving the dates Three date changes in one session, same route. Rank flexible fares first. 02 Travelers trying two airports for one city Newark, then JFK, for the same weekend. Show both in one list. 03 Travelers who keep opening the same fare Same fare opened three times, backed out each time. Put what they are checking on the row. 04 Travelers back for the fourth time on one route Fourth visit this week, same route, no booking yet. Open the form with their route in it. 

[Read the docs →](https://docs.snowplow.io/docs/signals/introduction/) 

### Rank flexible fares first when the dates keep moving

A traveler has searched Chicago to Lisbon four times in ten minutes, moving the departure date each time. Your pricing engine prices each search as it arrives, and the three dates before it reach the ranker after the new list is on the screen. Someone who moves the date three times is telling the search they are flexible on it. The next results should lead with the flexible fare and the cheapest-date view, while the traveler is still at the search form.

What Signals knows

`searches_session` 

Flight searches run this visit

`dates_tried_session` 

Separate departure dates searched this visit

`depart_dates_session` 

The departure dates searched this visit

What your search does

Puts the flexible fare and the cheapest-date view at the top once a visit reaches three departure dates for one route, and passes the dates tried in so the calendar opens on the span the traveler has been covering.

Define it · PythonUse it · TypeScript

search.tsx · 15 lines Copy Copied 

```
from snowplow_signals import (
    Attribute, Event, EventProperty, StreamAttributeGroup, domain_sessionid,
)

flight_search = Event(name="flight_search", vendor="com.example", version="1-0-0")
depart_date = EventProperty(vendor="com.example", name="flight_search", major_version=1, path="depart_date")

searches_session = Attribute(
    name="searches_session",
    type="int32",
    events=[flight_search],
    aggregation="counter",
    default_value=0,
)

dates_tried_session = Attribute(
    name="dates_tried_session",
    type="int32",
    events=[flight_search],
    aggregation="approx_count_distinct",
    property=depart_date,
)

depart_dates_session = Attribute(
    name="depart_dates_session",
    type="string_list",
    events=[flight_search],
    aggregation="unique_list",
    property=depart_date,
)

# Keyed on the visit, so the dates tried belong to this comparison and not to last month's trip.
trip_search_dates = StreamAttributeGroup(
    name="trip_search_dates",
    version=1,
    attribute_key=domain_sessionid,
    owner="you@example.com",
    attributes=[searches_session, dates_tried_session, depart_dates_session],
)
```

```
// In the code that sends a search request to your search and pricing engine
const traveler = await signals.getServiceAttributes({
  name: "trip_search_dates",
  attribute_key: "domain_sessionid",
  identifier: sessionId,
});
// { searches_session: 4, dates_tried_session: 3, depart_dates_session: ["2026-11-06", "2026-11-07", "2026-11-13"] }

if (traveler.dates_tried_session >= 3) {
  search.query({
    ...request,
    rankFirst: ["flexible_fare", "cheapest_date_view"],
    calendarSpan: traveler.depart_dates_session,
  });
}
```

### Show both airports in one result set

A traveler searched Newark to Lisbon, then JFK to Lisbon, for the same weekend. Each result set is built for one origin, so they are comparing two lists in two tabs, and the pair of airports they are weighing reaches your search after the second list has loaded. Two origins for one city in a session says the traveler will take whichever works out cheaper. One list across both airports, with the airport named on each row, saves them the second tab.

What Signals knows

`origins_session` 

The departure airports searched this visit

`destinations_session` 

The arrival airports searched this visit

`searches_session` 

Flight searches run this visit

What your search does

Runs the next search across every origin this traveler has tried for the same destination this visit, and labels each row with its airport, so the two lists become one and the cheaper airport can win on its own.

Define it · PythonUse it · TypeScript

search.tsx · 17 lines Copy Copied 

```
from snowplow_signals import (
    Attribute, Event, EventProperty, StreamAttributeGroup, domain_sessionid,
)

flight_search = Event(name="flight_search", vendor="com.example", version="1-0-0")

origins_session = Attribute(
    name="origins_session",
    type="string_list",
    events=[flight_search],
    aggregation="unique_list",
    property=EventProperty(vendor="com.example", name="flight_search", major_version=1, path="origin"),
)

destinations_session = Attribute(
    name="destinations_session",
    type="string_list",
    events=[flight_search],
    aggregation="unique_list",
    property=EventProperty(vendor="com.example", name="flight_search", major_version=1, path="destination"),
)

searches_session = Attribute(
    name="searches_session",
    type="int32",
    events=[flight_search],
    aggregation="counter",
    default_value=0,
)

trip_search_airports = StreamAttributeGroup(
    name="trip_search_airports",
    version=1,
    attribute_key=domain_sessionid,
    owner="you@example.com",
    attributes=[origins_session, destinations_session, searches_session],
)
```

```
// In the code that sends a search request to your search and pricing engine
const traveler = await signals.getServiceAttributes({
  name: "trip_search_airports",
  attribute_key: "domain_sessionid",
  identifier: sessionId,
});
// { origins_session: ["EWR", "JFK"], destinations_session: ["LIS"], searches_session: 2 }

const sameCity = traveler.origins_session.length > 1 && traveler.destinations_session.length === 1;

if (sameCity) {
  search.query({
    ...request,
    origins: traveler.origins_session,
    labelAirport: true,
  });
}
```

### Show on the result row what a traveler keeps opening the fare to check

A traveler has opened the same fare three times this session and backed out to the results each time. The fare looks right from the list, and something on the detail page sends them back, the change rules, the bag fee, the connection time. Your search rebuilds the same list each time, and the pattern of open and return reaches anything that could act on it after the traveler has gone. The third return is the moment to put what they are checking on the list itself, next to the price.

What Signals knows

`fare_views_session` 

Fares opened from the results this visit

`top_fare_session` 

The fare opened most this visit

`fare_views_by_id_session` 

How many times each fare was opened this visit

What your search does

Puts the fare rules or the total with bags on the result row once one fare has been opened and left twice this session. Which of the two it shows is your rule, since your pricing engine knows what the detail page adds to the price.

Define it · PythonUse it · TypeScript

search.tsx · 16 lines Copy Copied 

```
from snowplow_signals import (
    Attribute, Event, EventProperty, StreamAttributeGroup, domain_sessionid,
)

fare_viewed = Event(name="fare_viewed", vendor="com.example", version="1-0-0")
fare_id = EventProperty(vendor="com.example", name="fare_viewed", major_version=1, path="fare_id")

fare_views_session = Attribute(
    name="fare_views_session",
    type="int32",
    events=[fare_viewed],
    aggregation="counter",
    default_value=0,
)

top_fare_session = Attribute(
    name="top_fare_session",
    type="string",
    events=[fare_viewed],
    aggregation="most_frequent",
    property=fare_id,
)

fare_views_by_id_session = Attribute(
    name="fare_views_by_id_session",
    type="dict",
    events=[fare_viewed],
    aggregation="category_count",
    property=fare_id,
)

trip_search_fare = StreamAttributeGroup(
    name="trip_search_fare",
    version=1,
    attribute_key=domain_sessionid,
    owner="you@example.com",
    attributes=[fare_views_session, top_fare_session, fare_views_by_id_session],
)
```

```
// In the code that renders the results list
const traveler = await signals.getServiceAttributes({
  name: "trip_search_fare",
  attribute_key: "domain_sessionid",
  identifier: sessionId,
});
// { fare_views_session: 4, top_fare_session: "F-7741", fare_views_by_id_session: { "F-7741": 3, "F-7760": 1 } }

const reopened = traveler.fare_views_by_id_session[traveler.top_fare_session] ?? 0;

if (reopened >= 2) {
  results.expand({
    fare: traveler.top_fare_session,
    show: pricing.detailAdds(traveler.top_fare_session) ? "total_with_bags" : "fare_rules",
  });
}
```

### Hold the route and dates for a traveler who keeps coming back

A traveler is on their fourth visit this week for the same route, searching Boston to Denver each time and booking nothing. Each visit starts from an empty search form, and the route and dates they typed on Tuesday reach the page after they have typed them again. A fourth visit for one route is a traveler close to a decision. The search page should open with their route and the last dates they tried filled in, and the results for them one tap away.

What Signals knows

`search_visits_7d` 

Separate visits with a flight search in the last seven days

`top_origin_7d` 

The departure airport searched most in the last seven days

`top_destination_7d` 

The arrival airport searched most in the last seven days

`last_depart_date_7d` 

The departure date on their most recent search

What your search does

Fills the search form with the route this traveler searches most and the last departure date they tried once this browser has searched on three or more visits this week, and runs that search as the page opens so the results are waiting.

Define it · PythonUse it · TypeScript

search.tsx · 20 lines Copy Copied 

```
from datetime import timedelta
from snowplow_signals import (
    Attribute, AtomicProperty, Event, EventProperty, StreamAttributeGroup, domain_userid,
)

flight_search = Event(name="flight_search", vendor="com.example", version="1-0-0")

search_visits_7d = Attribute(
    name="search_visits_7d",
    type="int32",
    events=[flight_search],
    aggregation="approx_count_distinct",
    property=AtomicProperty(name="domain_sessionid"),
    period=timedelta(days=7),
)

top_origin_7d = Attribute(
    name="top_origin_7d",
    type="string",
    events=[flight_search],
    aggregation="most_frequent",
    property=EventProperty(vendor="com.example", name="flight_search", major_version=1, path="origin"),
    period=timedelta(days=7),
)

top_destination_7d = Attribute(
    name="top_destination_7d",
    type="string",
    events=[flight_search],
    aggregation="most_frequent",
    property=EventProperty(vendor="com.example", name="flight_search", major_version=1, path="destination"),
    period=timedelta(days=7),
)

last_depart_date_7d = Attribute(
    name="last_depart_date_7d",
    type="string",
    events=[flight_search],
    aggregation="last",
    property=EventProperty(vendor="com.example", name="flight_search", major_version=1, path="depart_date"),
    period=timedelta(days=7),
)

# Keyed on the browser, so a search from Tuesday is still there on Friday.
trip_search_returning = StreamAttributeGroup(
    name="trip_search_returning",
    version=1,
    attribute_key=domain_userid,
    owner="you@example.com",
    attributes=[search_visits_7d, top_origin_7d, top_destination_7d, last_depart_date_7d],
)
```

```
// In the code that renders the search page, before the traveler types anything
const traveler = await signals.getServiceAttributes({
  name: "trip_search_returning",
  attribute_key: "domain_userid",
  identifier: domainUserId,
});
// { search_visits_7d: 4, top_origin_7d: "BOS", top_destination_7d: "DEN", last_depart_date_7d: "2026-11-20" }

if (traveler.search_visits_7d >= 3) {
  searchForm.prefill({
    origin: traveler.top_origin_7d,
    destination: traveler.top_destination_7d,
    departDate: traveler.last_depart_date_7d,
  });
  search.query({
    origin: traveler.top_origin_7d,
    destination: traveler.top_destination_7d,
    departDate: traveler.last_depart_date_7d,
  });
}
```

## Try it on your own site. 

Add the SDK, define one thing you want to know about a visitor, and watch it update while you click around. The free tier runs on the real engine: no card, no sales call.

[Start free](https://signals.snowplow.io/start) [Talk to an engineer](https://signals.snowplow.io/start?intent=engineer)
