upload-stuff

Type safety

One router definition types both the server handlers and the client hook — and the compiler keeps them in sync.

upload-stuff has a single source of truth: the file router you define on the server. The server handlers (middleware, onUploadComplete) and the client hook (useUploadStuff) all read their types from it, so the two ends cannot drift — change the router and every call site updates or fails to compile.

Every block on this page is type-checked at build time, so the hovers below are the real inferred types, not hand-written annotations.

One definition, both ends

Hover the // ^? lines — those types are inferred, not declared. .middleware()'s return is exactly the middlewareData that .onUploadComplete() receives:

const  = ({ : true, : ["image/*"], : "4MB" })
  .(({  }) => ({ : . }))
  .(({ middlewareData }) => {
middlewareData: {
    uploadedBy: string;
}
return { : . }; }) .();

.input() works the same way: the schema's parsed output is what every later step receives. Here the schema transforms a comma-separated string, so input.tags is a string[]:

const  = ({ : true, : ["image/*"], : "4MB" })
  .(.({ : .().(() => .(",")) }))
  .(({ input }) => {
input: {
    tags: string[];
}
return { : . }; }) .();

On the client, onClientUploadComplete receives the onUploadComplete return as res.serverData — no annotation anywhere:

const  = <>({
  : "https://example.com",
});
const {  } = ();

(() => ., {
  : () => {
    .(.serverData);
serverData: {
    ownerId: string;
}
}, });

The (r) => r.avatar selector is more than a string: because the route name is a real property, Go to Definition on it jumps straight to the route's server definition.

What the compiler rejects

Two examples: .middleware() returned { uploadedBy }, so reading middlewareData.role fails — and the selector is type-checked against the router, so the misspelled r.avatr is rejected:

const  = {
  : ({ : true, : ["image/*"], : "4MB" })
    .(({  }) => ({ : . }))
    .(({  }) => {
      return { : .role };
Property 'role' does not exist on type '{ uploadedBy: string; }'.
}) .(), }; const = <typeof >({ : "https://example.com", }); const { } = (); (() => .avatr);
Property 'avatr' does not exist on type 'RouteRegistry<{ avatar: BuiltFileRoute<{ _routeConfig: RouteConfig; _input: { in: UnsetMarker; out: UnsetMarker; }; _fields: UnsetMarker; _fieldsDeclaration: Record<...>; _ctx: Context; _middlewareData: { ...; }; _completeFnData: { ...; }; }>; }>'. Did you mean 'avatar'?

One ordering rule: each builder method reads the current type, so write the chain as .input().middleware().fields().onUploadComplete().build().fields() after .middleware() is what lets its resolver see middlewareData, and .onUploadComplete() sees everything before it.

Next steps

On this page