diff --git a/docs/api/router/RouteOptionsType.md b/docs/api/router/RouteOptionsType.md
index 2154c2bae5c..7dd13cd7cdc 100644
--- a/docs/api/router/RouteOptionsType.md
+++ b/docs/api/router/RouteOptionsType.md
@@ -31,6 +31,7 @@ The `RouteOptions` type is used to describe the options that can be used when cr
- Type: `(rawSearchParams: unknown) => TSearchSchema`
- Optional
- A function that will be called when this route is matched and passed the raw search params from the current location and return valid parsed search params. If this function throws, the route will be put into an error state and the error will be thrown during render. If this function does not throw, its return value will be used as the route's search params and the return type will be inferred into the rest of the router.
+- Optionally, the parameter type can be tagged with the `SearchSchemaInput` type like this: `(searchParams: TSearchSchemaInput & SearchSchemaInput) => TSearchSchema`. If this tag is present, `TSearchSchemaInput` will be used to type the `search` property of `` and `navigate()` **instead of** `TSearchSchema`. The difference between `TSearchSchemaInput` and `TSearchSchema` can be useful, for example, to express optional search parameters.
#### `parseParams`
diff --git a/docs/api/router/SearchParamOptionsType.md b/docs/api/router/SearchParamOptionsType.md
index 8bcc7a5cb8c..cefe40b3f0a 100644
--- a/docs/api/router/SearchParamOptionsType.md
+++ b/docs/api/router/SearchParamOptionsType.md
@@ -10,7 +10,7 @@ The `SearchParamOptions` type is used to describe how search params can be provi
type SearchParamOptions = {
search?:
| true
- | Record
+ | TToSearch
| ((prev: TFromSearch) => TToSearch)
}
```
diff --git a/docs/api/router/SearchSchemaInputType.md b/docs/api/router/SearchSchemaInputType.md
new file mode 100644
index 00000000000..1cc1b01a09c
--- /dev/null
+++ b/docs/api/router/SearchSchemaInputType.md
@@ -0,0 +1,7 @@
+---
+id: SearchSchemaInputType
+title: `SearchSchemaInput` type
+---
+
+
+The `SearchSchemaInput` type is used to tag the input type of a `validateSearch` method to signalize to TanStack router that its parameter type `TSearchSchemaInput` shall be used to type the search param of `` and `navigate()`.
\ No newline at end of file
diff --git a/examples/react/basic-default-search-params/.gitignore b/examples/react/basic-default-search-params/.gitignore
new file mode 100644
index 00000000000..d451ff16c10
--- /dev/null
+++ b/examples/react/basic-default-search-params/.gitignore
@@ -0,0 +1,5 @@
+node_modules
+.DS_Store
+dist
+dist-ssr
+*.local
diff --git a/examples/react/basic-default-search-params/README.md b/examples/react/basic-default-search-params/README.md
new file mode 100644
index 00000000000..115199d292c
--- /dev/null
+++ b/examples/react/basic-default-search-params/README.md
@@ -0,0 +1,6 @@
+# Example
+
+To run this example:
+
+- `npm install` or `yarn`
+- `npm start` or `yarn start`
diff --git a/examples/react/basic-default-search-params/index.html b/examples/react/basic-default-search-params/index.html
new file mode 100644
index 00000000000..2e8ce205fc4
--- /dev/null
+++ b/examples/react/basic-default-search-params/index.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+ Vite App
+
+
+
+
+
+
+
diff --git a/examples/react/basic-default-search-params/package.json b/examples/react/basic-default-search-params/package.json
new file mode 100644
index 00000000000..6ad42014237
--- /dev/null
+++ b/examples/react/basic-default-search-params/package.json
@@ -0,0 +1,25 @@
+{
+ "name": "tanstack-router-react-example-basic-default-search-params",
+ "version": "0.0.0",
+ "private": true,
+ "scripts": {
+ "dev": "vite --port=3001",
+ "build": "vite build",
+ "serve": "vite preview",
+ "start": "vite"
+ },
+ "dependencies": {
+ "@tanstack/react-query": "^5.4.3",
+ "@tanstack/react-router": "1.0.8",
+ "@tanstack/router-devtools": "1.0.8",
+ "@vitejs/plugin-react": "^1.1.3",
+ "axios": "^1.1.3",
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0",
+ "vite": "^2.8.6"
+ },
+ "devDependencies": {
+ "@types/react": "^18.2.41",
+ "@types/react-dom": "^18.2.17"
+ }
+}
diff --git a/examples/react/basic-default-search-params/src/main.tsx b/examples/react/basic-default-search-params/src/main.tsx
new file mode 100644
index 00000000000..b79a6398301
--- /dev/null
+++ b/examples/react/basic-default-search-params/src/main.tsx
@@ -0,0 +1,224 @@
+import React from 'react'
+import ReactDOM from 'react-dom/client'
+import {
+ Outlet,
+ RouterProvider,
+ Link,
+ Route,
+ ErrorComponent,
+ Router,
+ RootRoute,
+ ErrorRouteProps,
+ NotFoundRoute,
+ SearchSchemaInput,
+} from '@tanstack/react-router'
+import { TanStackRouterDevtools } from '@tanstack/router-devtools'
+import axios from 'axios'
+import { z } from 'zod'
+
+type PostType = {
+ id: number
+ title: string
+ body: string
+}
+
+const fetchPosts = async () => {
+ console.log('Fetching posts...')
+ await new Promise((r) => setTimeout(r, 300))
+ return axios
+ .get('https://jsonplaceholder.typicode.com/posts')
+ .then((r) => r.data.slice(0, 10))
+}
+
+const fetchPost = async (postId: number) => {
+ console.log(`Fetching post with id ${postId}...`)
+ await new Promise((r) => setTimeout(r, 300))
+ const post = await axios
+ .get(`https://jsonplaceholder.typicode.com/posts/${postId}`)
+ .catch((err) => {
+ if (err.response.status === 404) {
+ throw new NotFoundError(`Post with id "${postId}" not found!`)
+ }
+ throw err
+ })
+ .then((r) => r.data)
+
+ return post
+}
+
+const rootRoute = new RootRoute({
+ component: RootComponent,
+})
+
+function RootComponent() {
+ return (
+