Skip to main content

Wildcard Routes

Wildcard routes let you create catch-all routes that match any number of path segments after a certain point in the URL. This powerful feature is perfect for building admin panels, file systems, documentation sites, proxies, and more.

What Are Wildcard Routes?​

Think of wildcard routes as a "catch everything else" pattern. While static routes match exact paths and dynamic routes match single segments, wildcard routes capture all remaining segments in the URL path.

Common Use Cases:

  • OAuth/Authentication: Handle multiple authentication providers in one route (like BetterAuth does with /auth/[...] for Google, GitHub, Facebook, etc.)
  • Multi-tenant Applications: Serve tenant-specific content with dynamic subpaths like /tenants/[tenantId]/[...]
  • Blog/CMS Systems: Manage deeply nested content categories like /blog/tech/web-dev/react/2024/hooks-guide
  • E-commerce Catalogs: Handle complex product category hierarchies like /products/electronics/computers/laptops/gaming
  • Internationalization (i18n): Serve localized content with paths like /[locale]/docs/[...] for /en/docs/getting-started/installation
  • Microservices Gateway: Route API requests to different microservices based on path patterns

Basic Usage​

Creating a wildcard route is simple: use the [...] folder name in your route structure.

Creating a Wildcard Route​

Here's a basic example:

Folder Structure:

api/
admin/
[...]/
route.ts

This structure creates a wildcard route at /api/admin/* that matches any path starting with /api/admin/.

Route Handler:

api/admin/[...]/route.ts
import type { BurgerRequest } from "burger-api";

export async function GET(req: BurgerRequest) {
// wildcardParams contains all segments after /admin/
const wildcardParams = req.wildcardParams || [];

return Response.json({
message: "Admin wildcard route",
path: wildcardParams.join("/"),
segments: wildcardParams,
});
}

Example Requests:

GET /api/admin/settings
→ wildcardParams = ["settings"]

GET /api/admin/settings/profile
→ wildcardParams = ["settings", "profile"]

GET /api/admin/users/123/permissions/edit
→ wildcardParams = ["users", "123", "permissions", "edit"]

Accessing Wildcard Parameters​

The req.wildcardParams property gives you access to all captured path segments as an array of strings.

api/files/[...]/route.ts
import type { BurgerRequest } from "burger-api";

export async function GET(req: BurgerRequest) {
const segments = req.wildcardParams || [];

// Build the file path from segments
const filePath = segments.join("/");

// Check if path is provided
if (segments.length === 0) {
return Response.json(
{
error: "No file path specified",
},
{ status: 400 }
);
}

return Response.json({
message: `Fetching file: ${filePath}`,
directory: segments.slice(0, -1).join("/"),
filename: segments[segments.length - 1],
});
}

Accessing the endpoint:

GET /api/files/documents/2024/report.pdf
→ wildcardParams = ["documents", "2024", "report.pdf"]
→ filePath = "documents/2024/report.pdf"
→ directory = "documents/2024"
→ filename = "report.pdf"

Route Priority​

Understanding how BurgerAPI matches routes is important when combining different route types.

Route Matching Order

BurgerAPI uses a smart routing system (a trie) that matches routes in this order:

  1. Static Routes - Exact path matches (e.g., /admin/settings)
  2. Dynamic Routes - Single segment patterns (e.g., /admin/[section])
  3. Wildcard Routes - Catch-all patterns (e.g., /admin/[...])

More specific routes always win! This prevents ambiguity and gives you precise control.

Priority Examples​

Let's say you have these routes defined:

api/
admin/
settings/
route.ts → /api/admin/settings (Static)
[section]/
route.ts → /api/admin/[section] (Dynamic)
[...]/
route.ts → /api/admin/[...] (Wildcard)

Here's what gets matched:

Request URLMatched RouteWhy
/api/admin/settingsStatic /api/admin/settingsStatic routes matched first
/api/admin/usersDynamic /api/admin/[section]No static match, dynamic matched
/api/admin/users/listWildcard /api/admin/[...]Multiple segments, wildcard catches all
/api/admin/reports/2024/q3Wildcard /api/admin/[...]Multiple segments, only wildcard matches

Advanced Usage​

Wildcard routes become even more powerful when combined with dynamic routes.

Wildcard Inside Dynamic Routes​

You can nest wildcard routes within dynamic routes to create flexible APIs:

Folder Structure:

api/
users/
[userId]/
[...]/
route.ts

This creates a pattern like /api/users/{userId}/* where you can access both the user ID and the remaining path.

Route Handler:

api/users/[userId]/[...]/route.ts
import type { BurgerRequest } from "burger-api";
import { z } from "zod";

// Validate the userId parameter
export const schema = {
get: {
params: z.object({
userId: z.string().min(1),
}),
},
};

export async function GET(req: BurgerRequest) {
// Access validated userId from params
const { userId } = req.validated.params;

// Access the remaining path segments
const wildcardParams = req.wildcardParams || [];

return Response.json({
userId,
userPath: wildcardParams.join("/"),
segments: wildcardParams,
message: `Accessing user ${userId}'s resource: ${wildcardParams.join("/")}`,
});
}

Example Requests:

GET /api/users/123/posts
→ params.userId = "123"
→ wildcardParams = ["posts"]

GET /api/users/123/posts/456/comments
→ params.userId = "123"
→ wildcardParams = ["posts", "456", "comments"]

GET /api/users/abc/files/documents/resume.pdf
→ params.userId = "abc"
→ wildcardParams = ["files", "documents", "resume.pdf"]

Real-World Examples​

Example 1: OAuth Authentication Handler (Like BetterAuth)​

Handle multiple OAuth providers with a single wildcard route:

api/auth/[...]/route.ts
import type { BurgerRequest } from "burger-api";

// OAuth provider configurations
const providers = {
"google/callback": { name: "Google", redirectUrl: "/dashboard" },
"github/callback": { name: "GitHub", redirectUrl: "/dashboard" },
"facebook/callback": { name: "Facebook", redirectUrl: "/dashboard" },
"twitter/callback": { name: "Twitter", redirectUrl: "/dashboard" },
};

export async function GET(req: BurgerRequest) {
const segments = req.wildcardParams || [];
const authPath = segments.join("/");

// Check if it's a known auth route
const provider = providers[authPath];

if (!provider) {
return Response.json(
{
error: "Unknown authentication provider",
path: authPath,
},
{ status: 404 }
);
}

// Handle OAuth callback
const url = new URL(req.url);
const code = url.searchParams.get("code");

if (!code) {
return Response.json(
{ error: "Missing authorization code" },
{ status: 400 }
);
}

// Process OAuth callback (simplified)
return Response.json({
success: true,
provider: provider.name,
message: `Successfully authenticated with ${provider.name}`,
redirectUrl: provider.redirectUrl,
});
}

Example 2: Multi-tenant Application​

Build a multi-tenant SaaS API where each tenant has dynamic nested resources:

api/tenants/[tenantId]/[...]/route.ts
import type { BurgerRequest } from "burger-api";
import { z } from "zod";

export const schema = {
get: {
params: z.object({
tenantId: z.string().uuid(),
}),
},
};

export async function GET(req: BurgerRequest) {
const { tenantId } = req.validated.params;
const resourcePath = req.wildcardParams || [];

// Handle different tenant resources
const [resource, ...rest] = resourcePath;

switch (resource) {
case "users":
return Response.json({
tenant: tenantId,
resource: "users",
subPath: rest,
data: { message: "Fetching tenant users" },
});

case "settings":
return Response.json({
tenant: tenantId,
resource: "settings",
subPath: rest,
data: { message: "Fetching tenant settings" },
});

case "analytics":
return Response.json({
tenant: tenantId,
resource: "analytics",
subPath: rest,
data: { message: "Fetching tenant analytics" },
});

default:
return Response.json(
{
error: "Unknown tenant resource",
tenant: tenantId,
path: resourcePath.join("/"),
},
{ status: 404 }
);
}
}

Example 3: Blog/CMS with Nested Categories​

Handle deeply nested blog categories and posts:

api/blog/[...]/route.ts
import type { BurgerRequest } from "burger-api";

export async function GET(req: BurgerRequest) {
const segments = req.wildcardParams || [];

// Parse the path: /blog/tech/web-dev/react/2024/hooks-guide
if (segments.length === 0) {
// Return all top-level categories
return Response.json({
type: "categories",
data: ["tech", "business", "lifestyle"],
});
}

// Check if last segment looks like a post slug
const lastSegment = segments[segments.length - 1];
const isPost =
lastSegment.includes("-") ||
!segments[segments.length - 1].match(/^\d{4}$/);

if (isPost) {
// This is a blog post
return Response.json({
type: "post",
slug: lastSegment,
category: segments.slice(0, -1).join("/"),
breadcrumbs: segments,
// In real app: fetch post from database
data: {
title: lastSegment.replace(/-/g, " "),
category: segments.slice(0, -1),
content: "Post content here...",
},
});
} else {
// This is a category
return Response.json({
type: "category",
path: segments.join("/"),
breadcrumbs: segments,
// In real app: fetch posts in this category
posts: ["getting-started", "advanced-techniques", "best-practices"],
});
}
}

Example 4: E-commerce Product Categories​

Handle complex product category hierarchies:

api/products/[...]/route.ts
import type { BurgerRequest } from "burger-api";

export async function GET(req: BurgerRequest) {
const categoryPath = req.wildcardParams || [];

// Example: /products/electronics/computers/laptops/gaming
const category = categoryPath[categoryPath.length - 1];
const parentCategories = categoryPath.slice(0, -1);

// Get query parameters for filtering
const url = new URL(req.url);
const minPrice = url.searchParams.get("min_price");
const maxPrice = url.searchParams.get("max_price");
const brand = url.searchParams.get("brand");

return Response.json({
category: category,
fullPath: categoryPath.join(" > "),
breadcrumbs: categoryPath,
filters: {
minPrice: minPrice ? parseFloat(minPrice) : null,
maxPrice: maxPrice ? parseFloat(maxPrice) : null,
brand: brand,
},
// In real app: fetch products from database
products: [
{ id: 1, name: "Gaming Laptop Pro", price: 1299 },
{ id: 2, name: "Ultra Gaming Laptop", price: 1599 },
],
});
}

Example 5: Internationalized Documentation​

Serve localized documentation with language prefixes:

api/[locale]/docs/[...]/route.ts
import type { BurgerRequest } from "burger-api";
import { z } from "zod";

const supportedLocales = ["en", "es", "fr", "de", "ja", "zh"];

export const schema = {
get: {
params: z.object({
locale: z.enum(supportedLocales as [string, ...string[]]),
}),
},
};

export async function GET(req: BurgerRequest) {
const { locale } = req.validated.params;
const docPath = req.wildcardParams || [];

if (docPath.length === 0) {
// Return documentation home for this locale
return Response.json({
locale,
message: `Welcome to documentation (${locale})`,
sections: ["getting-started", "api-reference", "guides"],
});
}

// Fetch localized documentation
const fullPath = docPath.join("/");

return Response.json({
locale,
path: fullPath,
breadcrumbs: docPath,
// In real app: fetch from CMS or markdown files
content: {
title: `${fullPath} (${locale})`,
body: `Localized content for ${fullPath} in ${locale}`,
},
});
}

Example 6: Microservices API Gateway​

Route requests to different microservices based on path:

api/gateway/[...]/route.ts
import type { BurgerRequest } from "burger-api";

// Microservice routing map
const services = {
users: "http://users-service:3001",
orders: "http://orders-service:3002",
payments: "http://payments-service:3003",
inventory: "http://inventory-service:3004",
};

export async function GET(req: BurgerRequest) {
const segments = req.wildcardParams || [];
const [service, ...path] = segments;

const serviceUrl = services[service];

if (!serviceUrl) {
return Response.json(
{
error: "Unknown service",
service,
availableServices: Object.keys(services),
},
{ status: 404 }
);
}

// Forward request to microservice
const targetUrl = `${serviceUrl}/${path.join("/")}`;
const url = new URL(req.url);

try {
const response = await fetch(`${targetUrl}${url.search}`, {
method: req.method,
headers: req.headers,
});

const data = await response.json();

return Response.json({
service,
path: path.join("/"),
data,
});
} catch (error) {
return Response.json(
{
error: "Service unavailable",
service,
message: error.message,
},
{ status: 503 }
);
}
}

API Reference​

wildcardParams Property​

Type: string[]

Description: An array containing all path segments captured by the wildcard route. Each segment is a string representing one part of the URL path after the wildcard point.

Usage:

const wildcardParams = req.wildcardParams || [];

Example: For the URL /api/admin/settings/users/permissions:

// In api/admin/[...]/route.ts
wildcardParams = ["settings", "users", "permissions"];

Important Notes​

Limitations

When working with wildcard routes, keep these limitations in mind:

  1. No Mixing in Same Directory: You cannot have both dynamic [id] and wildcard [...] folders in the same directory level. Choose one pattern per level.

  2. Lowest Priority: Wildcard routes are always matched last. If a static or dynamic route exists that matches the path, it will be chosen first.

  3. Single Wildcard Per Branch: Each route branch should have only one wildcard route. Having multiple wildcards in the same path can lead to unpredictable behavior.

  4. All Segments Captured: The wildcard captures all remaining segments as an array. There's no partial wildcard matching.

Best Practices
  • Always check for empty arrays: Use req.wildcardParams || [] to handle cases where no segments are provided
  • Validate segments manually: Wildcard parameters are not validated by Zod schemas. Add your own validation logic to ensure the captured segments are what you expect
  • Use meaningful responses: Return clear error messages when paths don't match expected patterns
  • Document your paths: If building a complex API, document which paths are valid for your wildcard routes
  • Combine with dynamic routes: You can use Zod validation for dynamic route params (like [userId]) while handling wildcards manually

Summary​

Wildcard routes give you the flexibility to handle complex, nested URL structures with ease:

  • Use [...] folder syntax to create wildcard routes
  • Access captured segments via req.wildcardParams
  • Combine with dynamic routes for powerful patterns
  • Remember: Static > Dynamic > Wildcard in matching priority
  • Perfect for admin panels, file systems, and proxy APIs

Ready to build something amazing? Check out the other routing documentation to learn more: