Server functions let you define server-only logic that can be called from anywhere in your application - loaders, components, hooks, or other server functions. They run on the server but can be invoked from client code seamlessly.
import { createServerFn } from '@tanstack/react-start'
export const getServerTime = createServerFn().handler(async () => {
// This runs only on the server
return new Date().toISOString()
})
// Call from anywhere - components, loaders, hooks, etc.
const time = await getServerTime()
import { createServerFn } from '@tanstack/react-start'
export const getServerTime = createServerFn().handler(async () => {
// This runs only on the server
return new Date().toISOString()
})
// Call from anywhere - components, loaders, hooks, etc.
const time = await getServerTime()
Server functions provide server capabilities (database access, environment variables, file system) while maintaining type safety across the network boundary.
Server functions are created with createServerFn() and can specify HTTP method:
import { createServerFn } from '@tanstack/react-start'
// GET request (default)
export const getData = createServerFn().handler(async () => {
return { message: 'Hello from server!' }
})
// POST request
export const saveData = createServerFn({ method: 'POST' }).handler(async () => {
// Server-only logic
return { success: true }
})
import { createServerFn } from '@tanstack/react-start'
// GET request (default)
export const getData = createServerFn().handler(async () => {
return { message: 'Hello from server!' }
})
// POST request
export const saveData = createServerFn({ method: 'POST' }).handler(async () => {
// Server-only logic
return { success: true }
})
Call server functions from:
// In a route loader
export const Route = createFileRoute('/posts')({
loader: () => getPosts(),
})
// In a component
function PostList() {
const getPosts = useServerFn(getServerPosts)
const { data } = useQuery({
queryKey: ['posts'],
queryFn: () => getPosts(),
})
}
// In a route loader
export const Route = createFileRoute('/posts')({
loader: () => getPosts(),
})
// In a component
function PostList() {
const getPosts = useServerFn(getServerPosts)
const { data } = useQuery({
queryKey: ['posts'],
queryFn: () => getPosts(),
})
}
Server functions accept a single data parameter. Since they cross the network boundary, validation ensures type safety and runtime correctness.
import { createServerFn } from '@tanstack/react-start'
export const greetUser = createServerFn({ method: 'GET' })
.inputValidator((data: { name: string }) => data)
.handler(async ({ data }) => {
return `Hello, ${data.name}!`
})
await greetUser({ data: { name: 'John' } })
import { createServerFn } from '@tanstack/react-start'
export const greetUser = createServerFn({ method: 'GET' })
.inputValidator((data: { name: string }) => data)
.handler(async ({ data }) => {
return `Hello, ${data.name}!`
})
await greetUser({ data: { name: 'John' } })
For robust validation, use schema libraries like Zod:
import { createServerFn } from '@tanstack/react-start'
import { z } from 'zod'
const UserSchema = z.object({
name: z.string().min(1),
age: z.number().min(0),
})
export const createUser = createServerFn({ method: 'POST' })
.inputValidator(UserSchema)
.handler(async ({ data }) => {
// data is fully typed and validated
return `Created user: ${data.name}, age ${data.age}`
})
import { createServerFn } from '@tanstack/react-start'
import { z } from 'zod'
const UserSchema = z.object({
name: z.string().min(1),
age: z.number().min(0),
})
export const createUser = createServerFn({ method: 'POST' })
.inputValidator(UserSchema)
.handler(async ({ data }) => {
// data is fully typed and validated
return `Created user: ${data.name}, age ${data.age}`
})
Handle form submissions with FormData:
export const submitForm = createServerFn({ method: 'POST' })
.inputValidator((data) => {
if (!(data instanceof FormData)) {
throw new Error('Expected FormData')
}
return {
name: data.get('name')?.toString() || '',
email: data.get('email')?.toString() || '',
}
})
.handler(async ({ data }) => {
// Process form data
return { success: true }
})
export const submitForm = createServerFn({ method: 'POST' })
.inputValidator((data) => {
if (!(data instanceof FormData)) {
throw new Error('Expected FormData')
}
return {
name: data.get('name')?.toString() || '',
email: data.get('email')?.toString() || '',
}
})
.handler(async ({ data }) => {
// Process form data
return { success: true }
})
Server functions can throw errors, redirects, and not-found responses that are handled automatically when called from route lifecycles or components using useServerFn().
import { createServerFn } from '@tanstack/react-start'
export const riskyFunction = createServerFn().handler(async () => {
if (Math.random() > 0.5) {
throw new Error('Something went wrong!')
}
return { success: true }
})
// Errors are serialized to the client
try {
await riskyFunction()
} catch (error) {
console.log(error.message) // "Something went wrong!"
}
import { createServerFn } from '@tanstack/react-start'
export const riskyFunction = createServerFn().handler(async () => {
if (Math.random() > 0.5) {
throw new Error('Something went wrong!')
}
return { success: true }
})
// Errors are serialized to the client
try {
await riskyFunction()
} catch (error) {
console.log(error.message) // "Something went wrong!"
}
Use redirects for authentication, navigation, etc:
import { createServerFn } from '@tanstack/react-start'
import { redirect } from '@tanstack/react-router'
export const requireAuth = createServerFn().handler(async () => {
const user = await getCurrentUser()
if (!user) {
throw redirect({ to: '/login' })
}
return user
})
import { createServerFn } from '@tanstack/react-start'
import { redirect } from '@tanstack/react-router'
export const requireAuth = createServerFn().handler(async () => {
const user = await getCurrentUser()
if (!user) {
throw redirect({ to: '/login' })
}
return user
})
Throw not-found errors for missing resources:
import { createServerFn } from '@tanstack/react-start'
import { notFound } from '@tanstack/react-router'
export const getPost = createServerFn()
.inputValidator((data: { id: string }) => data)
.handler(async ({ data }) => {
const post = await db.findPost(data.id)
if (!post) {
throw notFound()
}
return post
})
import { createServerFn } from '@tanstack/react-start'
import { notFound } from '@tanstack/react-router'
export const getPost = createServerFn()
.inputValidator((data: { id: string }) => data)
.handler(async ({ data }) => {
const post = await db.findPost(data.id)
if (!post) {
throw notFound()
}
return post
})
For more advanced server function patterns and features, see these dedicated guides:
Access request headers, cookies, and response customization:
Return Response objects for streaming, binary data, or custom content types.
Use server functions without JavaScript by leveraging the .url property with HTML forms.
Compose server functions with middleware for authentication, logging, and shared logic. See the Middleware guide.
Cache server function results at build time for static generation. See Static Server Functions.
Handle request cancellation with AbortSignal for long-running operations.
Note: Server functions use a compilation process that extracts server code from client bundles while maintaining seamless calling patterns. On the client, calls become fetch requests to the server.
Your weekly dose of JavaScript news. Delivered every Monday to over 100,000 devs, for free.
Your weekly dose of JavaScript news. Delivered every Monday to over 100,000 devs, for free.