Add transaction filtering to your marketplace inbox page
New filtering options in the Sharetribe APIs make it possible to filter and sort transactions. In this article, we share some examples on how you can use existing filtering components and combine them with these new transaction filtering parameters to add filtering capabilities to your inbox page.
Jul 30, 2025

In early 2025, our team at Sharetribe released more options for filtering transactions in Marketplace API and Integration API through the transactions/query endpoints.
Marketplace API and Integration API now have these new parameters
- hasBooking
- hasStockReservation
- hasPayin
- hasMessage
- bookingStates
- stockReservationStates
- bookingStart
- bookingEnd
Integration API also introduced parameters that were earlier only available in Marketplace API:
- lastTransitions
- processNames
Both Marketplace API and Integration API also support filtering listings on transaction extended data, i.e. protected data and metadata, as well as sorting transactions on
- lastTransitionedAt
- lastMessageAt
- bookingStart
- bookingEnd
This opens up a lot of new use cases that you can implement in your custom developed marketplace. On the Integration API side, these new parameters allow you to create more elaborate analytics and integrations. On the Marketplace API side, you can allow your customers and providers to filter and sort their transactions.
In the Sharetribe Web Template, the main page where a user interacts with all of their transactions is the inbox page. In this article, we share some examples on how you can use the existing filtering components, already used by default on the search pages, and combine them with these new transaction filtering parameters to add filtering capabilities to your inbox page.
The implementation in this article is built on top of Sharetribe Web Template v8.6.0, so if you are working on a later version, you may need to make some adjustments.
Inbox page filtering logic
In this section, I will describe the general logic of setting up inbox page filtering, from search parameter handling to creating local configurations.
Search parameter handling
The search page filtering behavior uses a pattern where each filtering action navigates the user to the same search page, but with new query parameters. When adding filtering to the inbox page, you can follow this same pattern.
This means that every filtering action also causes a new data load for the page. In practice, you would handle this so that in the InboxPage.duck.js loadData function, you get the search parameters from the search param, and pass them to the API call.
... dispatch(fetchOrdersOrSalesRequest()); const { page = 1, ...otherSearchParams } = parse(search); const apiQueryParams = { only: onlyFilter, lastTransitions: getAllTransitionsForEveryProcess(), ...otherSearchParams, include: [ 'listing', 'provider', ...
In the InboxPage component, whenever a user selects new filtering parameters, you use history.push() to trigger navigation with the new query parameters. History is available with the useHistory() hook. You will also need to have the route configuration available for some of the existing navigation helpers, and you can get that with the useRouteConfiguration() hook.'
const history = useHistory(); const routeConfiguration = useRouteConfiguration();
You can then get the current page search parameters from the page location, and save them in page state. Location is available with the useLocation() hook.
const location = useLocation(); const [currentFilters, setCurrentFilters] = useState(); if (currentFilters !== location.search) { setCurrentFilters(location.search); }
Once you have the current page search parameters, you can use them as initial values for your filter components, and combine them with any new filter values with a subsequent history.push().
At its simplest, the filter update function could look something like this:
const updateFiltering = (values) => { const currentSearch = parse(currentFilters); history.push( createResourceLocatorString( 'InboxPage', routeConfiguration, { tab }, { ...currentSearch, ...values } ) ); };
You can use the parse helper from util/urlHelpers.js to convert the search string to an object with the search values as attributes.
/** * currentFilters: "?bookingStates=pending&hasBooking=true" * currentSearch: { * "bookingStates": "pending", * "hasBooking": true * } */
The createResourceLocatorString helper constructs the query path based on the inbox tab, the current search, and the new values.
Configurations
The search page uses the listing field and listing search configurations created in Console or in the template’s local config files. Currently, there are no such configurations for transactions, so you will need to create your own local transaction search configuration. To use existing filter components, it makes sense to adhere to a similar structure:
- Add key, label, and schemaType – use the SCHEMA_TYPE_… -schemas where possible
- If the field you want to enable has options, add an array of options with the attributes option and label
The following examples will include some search configuration examples as well. You can create the transaction search configuration in the InboxPage component or in a separate file.
To implement the configurations, the search page has a FilterComponent element that takes a single field config as a prop and then returns the corresponding filter. To replicate the same idea, you can create an InboxFilterComponent that has a similar flow, and you can add different filter implementations into the InboxFilterComponent depending on which use cases you want to enable. You can then map your local filter configuration to an array of these filter components.

Implementing individual filters
In this section, we will look at a few examples of implementing individual filter types that will use the logic described above.
Add a boolean filter
When you add a boolean filter, such as hasBooking or hasMessage, you can use the SelectSingleFilter component with a search configuration that has options ‘true’ and ‘false’. That way, you enable the users to only search with one of the options, as well as clear the search completely
const filters = [{ key: 'hasBooking', label: 'Transactions with bookings', schemaType: SCHEMA_TYPE_ENUM, options: [ { option: 'true', label: 'True' }, { option: 'false', label: 'False' } ], }];
The SelectSingleFilter component clears the previous option by default, so you don’t end up searching with both true and false values.
The simplest version of the InboxFilterComponent would then contain only the SelectSingleFilter:
const InboxFiltersComponent = props => { const { config, onHandleChangedValueFn, ...rest } = props; const { key, schemaType, label } = config; switch (schemaType) { case SCHEMA_TYPE_ENUM: { const { options } = config; return ( <SelectSingleFilter id={key} name={key} label={label} options={options} onSubmit={onHandleChangedValueFn} queryParamNames={[key]} {...rest} /> ); } default: { return null; } } };
Note that since the initial values from the current filters are passed as booleans and not strings, the SelectSingleFilter doesn’t highlight the selected option by default. To add the highlighting to the filter, you will need to add a conversion from boolean to string values when passing props.initialValues to the SelectSingleFilter component.
Once you have set up the functionality of your filters, you can style the array of individual InboxFilterComponent elements to the main Inbox page. Here, the filters array contains the individual transaction search configurations.
... footer={<FooterContainer />} > <div className={css.filterRow}> {filters.map(f => ( <InboxFiltersComponent config={f} initialValues={parse(currentFilters)} onHandleChangedValueFn={updateFiltering} className={css.filter} /> ))} </div> {fetchOrdersOrSalesError ? ( <p className={css.error}> ...

Alternatively, you can pass them to the side bar as a part of the sideNav prop to LayoutSideNavigation. This way, you can have a similar filtering experience to the grid-layout search page.
sideNav={ <> <H2 as="h1" className={css.title}> <FormattedMessage id="InboxPage.title" /> </H2> <TabNav rootClassName={css.tabs} tabRootClassName={css.tab} tabs={tabs} />{' '} <div className={css.filterRow}> {filters.map(f => ( <InboxFiltersComponent config={f} initialValues={parse(currentFilters)} onHandleChangedValueFn={updateFiltering} className={css.filter} /> ))} </div> </> }

Add a filter with multiple options
When you add a filter for stockReservationStates and bookingStates, where you can query with multiple options, you can use the SelectMultipleFilter.
In your filter definition, you’ll need to define the available options. Both stock reservations and bookings have the same five possible states: pending, proposed, accepted, declined, and cancelled.
{ key: 'bookingStates', label: 'Booking states', schemaType: SCHEMA_TYPE_MULTI_ENUM, options: [ { option: 'pending', label: 'Pending' }, { option: 'proposed', label: 'Proposed' }, { option: 'accepted', label: 'Accepted' }, { option: 'declined', label: 'Declined' }, { option: 'cancelled', label: 'Cancelled' }, ], },
You can then add the SelectMultipleFilter to the InboxFiltersComponent switch case for the SCHEMA_TYPE_MULTI_ENUM case.
case SCHEMA_TYPE_MULTI_ENUM: { const { options } = config; return ( <SelectMultipleFilter options={options} label={label} id={key} name={key} onSubmit={onHandleChangedValueFn} schemaType={schemaType} queryParamNames={[key]} {...rest} /> ); } case SCHEMA_TYPE_MULTI_ENUM: { const { options } = config; return ( <SelectMultipleFilter options={options} label={label} id={key} name={key} onSubmit={onHandleChangedValueFn} schemaType={schemaType} queryParamNames={[key]} {...rest} /> ); }
Add a dates filter
Adding date filtering is a bit more involved, but you can still achieve it with the existing filtering components.
The configuration for a date based search is simple, because no options need to be configured. However, you do need to configure a filter for both the booking start and booking end dates, because they can be filtered independently.
{ key: 'bookingStart', label: 'Booking dates start', schemaType: 'dates', }, { key: 'bookingEnd', label: 'Booking dates end', schemaType: 'dates', },
In the InboxFilterComponent, you can again create a new case for ‘dates’ schemaType, and return a BookingDateRangeFilter with the necessary props.
case 'dates': { return ( <BookingDateRangeFilter label={label} id={key} name={key} queryParamNames={[key]} onSubmit={onHandleChangedValueFn} liveEdit {...rest} /> ) }
The next step is to convert the dates parameters to ISO 8601 format, complete with hours and minutes instead of just days, because that’s the format the API expects.
The helper file src/util/dates.js contains a lot of different helper functions for modifying dates, so it already imports the moment.js library that has functions for managing dates and times. If you create a new helper function in dates.js and export it from there, you don’t need to add extra imports for moment.js elsewhere in the template. For example:
// src/util/dates.js export const getISOString = (date, timeZone = null) => timeZone ? moment(date) .tz(timeZone) .toISOString() : moment(date).toISOString();
Now, you can import this helper in InboxPage.duck.js and use it to format the incoming dates parameters to an API-friendly format. For example:
const parseDates = (dateParam, paramKey) => { const [start, end] = dateParam.split(','); const { timeZone } = Intl.DateTimeFormat().resolvedOptions(); return { [paramKey]: `${getISOString(start, timeZone)},${getISOString(end, timeZone)}`, }; }; ... export const loadData = (params, search) => (dispatch, getState, sdk) => { ... const { page = 1, bookingStart: bookingStartRaw, bookingEnd: bookingEndRaw, ...otherSearchParams } = parse(search); // If the parameter exists, return the function output, otherwise return an empty object const bookingStartMaybe = bookingStartRaw ? parseDates(bookingStartRaw, 'bookingStart') : {}; const bookingEndMaybe = bookingEndRaw ? parseDates(bookingEndRaw, 'bookingEnd') : {}; const apiQueryParams = { only: onlyFilter, lastTransitions: getAllTransitionsForEveryProcess(), ...bookingStartMaybe, ...bookingEndMaybe, ...otherSearchParams, include: [ ...
Conclusion
Inbox filtering can be a useful tool on your marketplace, especially if you have power users who have a lot of transactions. Users can filter to only view paid transactions, or only bookings that are starting within the next month, for example.
In this article, I shared an overview of how you can implement inbox filtering in Sharetribe Web Template using the existing filter components already available in the codebase. If you implement some or all of these use cases, let us know by sending a message to hello at sharetribe.com, or through your Console chat widget!