Getting Started
Quick Start
Get started with Surf in under 20 lines
Quick Start#
A complete Surf server with commands. Pick your framework:
import { createSurf } from '@surfjs/core'import { createSurfRouteHandler } from '@surfjs/next' const surf = await createSurf({ name: 'My Store', commands: { search: { description: 'Search products', params: { query: { type: 'string', required: true }, limit: { type: 'number', default: 10 }, }, run: async ({ query, limit }) => { return db.products.search(query, { limit }) }, }, },}) export const { GET, POST } = createSurfRouteHandler(surf)Now any AI agent can discover and use your commands:
import { SurfClient } from '@surfjs/client' const client = await SurfClient.discover('http://localhost:3000') // List available commandsconsole.log(client.commands)// => { search: { description: 'Search products', params: {...} } } // Execute a commandconst result = await client.execute('search', { query: 'laptop' })// => { results: [...], total: 42 }Optional: Add a SurfBadge#
If your app has a frontend, you can add a visual badge that helps AI vision models discover your Surf commands:
import { SurfBadge } from '@surfjs/react' <SurfBadge endpoint="https://mystore.com" name="My Store" commands={[{ name: 'search', description: 'Search products' }]}/>The badge is optional — discovery works via /.well-known/surf.json without it. See the @surfjs/react docs for details.
For Browser-Based Agents#
When your app includes SurfProvider or SurfBadge, window.surf is automatically available. But window.surf isn't just an HTTP wrapper — it's a local runtime that can execute commands directly in the browser.
Server commands (default)
Without any client-side setup, window.surf proxies all commands to your server:
// Agent in browserconst result = await window.surf.execute('search', { query: 'laptop' })// => Proxied to server → HTTP POST → responseLocal commands (with useSurfCommands)
Register local handlers to make commands execute in the browser — no server round-trip:
import { useSurfCommands } from '@surfjs/react' function App() { useSurfCommands({ 'search': { mode: 'local', run: ({ query }) => { const results = searchIndex.search(query) return { ok: true, result: results } } } }) return <MyApp />}Now agents can interact instantly:
// Agent in browser — local handler runs, no networkawait window.surf.execute('search', { query: 'laptop' })// => { ok: true, result: [...] } — instantThis is the primary way browser-based agents interact with Surf. See Architecture & Execution Models for the three execution strategies and when to use each.
Framework-Agnostic (@surfjs/web)
Not using React? @surfjs/web is the core browser runtime — use it with any framework or plain JavaScript:
import { initSurf, registerCommand } from '@surfjs/web' // Initialize window.surfinitSurf({ endpoint: 'https://myapp.com' }) // Register local command handlersregisterCommand('search', { mode: 'local', run: ({ query }) => { const results = searchIndex.search(query) return { ok: true, result: results } }}) // Now agents can use window.surf.execute('search', { query: 'laptop' })@surfjs/react's useSurfCommands is a thin wrapper around registerCommand that handles React lifecycle (cleanup on unmount, re-registration on change). The runtime is the same.