Migrate from v6 to v7
Version 7 of UploadThing is a big release backed by all new infrastructure along with a nearly full rewrite of the internals.
- We've reduced the client-side bundle size by 35%. This is in addition to the
70% reduction in a recent minor release.
uploadthing/clientnow loads just over 25kB of JavaScript. - Resumable uploads are now supported
- We've re-architected the backend to require less round-trips to the UploadThing server, take advantage of modern web advancements, and removed all polling logic. All of these changes results in a much faster upload experience.
- We've begun work on a self-hosted version of UploadThing (more info coming soon)
In this guide, we'll walk you through the steps to migrate from v6 to v7 and list all the breaking changes and the new features.
UPLOADTHING_SECRET is now UPLOADTHING_TOKEN
In prior versions of Uploadthing, presigned urls were generated on our servers, and fetched from your backend using authenticated API calls. With v7, we have moved the generation of presigned urls to your server, meaning you can there is no need for the extra API call.
With this change, however, your server needs to know more about your app in
order to generate presigned URLs; such as the app id and region. We didn't want
to require additional environment variables, so we've decided to merge the
required information into a single UPLOADTHING_TOKEN environment variable.
The token is a base64 encoded JSON object that contains information such as your
app id, the app region as well as the API key. The token is available in the
UploadThing Dashboard ↗ under API Keys and
selecting the V7 tab:

If you previously passed in the API key as a configuration object, the keys in the `config object have changed to reflect the new token structure.
import { createRouteHandler } from "uploadthing/server";
createRouteHandler({
router,
config: {
uploadthingSecret: "",
uploadthingAppId: "",
token: "",
},
});
createRouteHandler from uploadthing/server now returns a single handler
Previously, createRouteHandler from uploadthing/server returned separate
named functions for GET and POST methods. The routing logic has been moved
to the internals, meaning we now return a single handler for all methods.
import { createRouteHandler } from "uploadthing/server";
import { uploadRouter } from "~/uploadthing/router";
const { GET, POST } = createRouteHandler({ router });
const handler = createRouteHandler({ router });
If your framework expects named exports for each method, you can re-export the handler as such:
import { createRouteHandler } from "uploadthing/server";
import { uploadRouter } from "~/uploadthing/router";
const handler = createRouteHandler({ router, config });
export { handler as GET, handler as POST };
The Next.js adapter still returns named exports for GET and POST methods.
import { createRouteHandler } from "uploadthing/next";
import { uploadRouter } from "~/uploadthing/router";
export const { GET, POST } = createRouteHandler({ router });
genUploader now returns an object
Along with the introduction of resumable uploads, we've
introduced a new createUpload function that allows you to create a new upload.
The genUploader function now returns an object with two functions,
uploadFiles and createUpload. To migrate:
import { genUploader } from 'uploadthing/client'
import type { UploadRouter } from '~/uploadthing/router'
const uploadFiles = genUploader<UploadRouter>()
const { uploadFiles, createUpload } = genUploader<UploadRouter>()
Log levels has changed
Internally, we've changed the logging from using Consola to `effect/Logger. In
alignment with that change, the log level naming has changed.
import { createRouteHandler } from "uploadthing/server";
import { uploadRouter } from "~/uploadthing/router";
const handler = createRouteHandler({
router,
config: {
logLevel: "error" | "warn" | "info" | "debug" | "trace",
logLevel: "Fatal" | "Error" | "Warning" | "Info" | "Debug" | "Trace",
},
});
The same change applies to
UTApi's constructor options.
skipPolling has been moved
In v6 we introduced the ability to return data from your server-side
onUploadComplete callback that would be sent along to the client-side
onClientUploadComplete callback. This required the client to wait for extra
round-trips as your server callback ran and eventually finished, sent back the
data to the uploadthing server which then would respond with the data to the
client. If you got "unlucky" with the polling, this could take a long time.
Additionally, we found that this feature was not used by many people,
essentially just delaying the client-side callback for no benefit in the
majority of cases.
In 6.5, we attempted to address this by
introducing a client-side option skipPolling ↗
that allowed you to opt-out of this behaviour for a faster upload experience.
This was a non-breaking change that the majority could enable without any
downside, but in order to be non-breaking, could not not be default, and thus
was not an ideal solution.
In v7, we're moving this option to the server-side route configuration so that you have all the configuration for a given file route in one place.
// On the client
await uploadFiles("myUploader", {
files: [ ... ],
// Opt-out of waiting for server data
skipPolling: true
})
// On the server
import { createUploadThing } from "uploadthing/server";
const f = createUploadThing();
export const uploadRouter = {
myUploader: f(
{ image: { maxFileSize: "16MB" } },
// Opt-out of waiting for server data
{ awaitServerData: false },
),
};
Deprecations have been removed
The following were previously deprecated and have now been removed:
-
@uploadthing/reactno longer exportsgenerateComponent. UsegenerateUploadButtonandgenerateUploadDropzoneinstead.import { generateComponent } from '@uploadthing/react' const { UploadButton } = generateComponent() import { generateUploadButton } from '@uploadthing/react' const UploadButton = generateUploadButton() -
@uploadthing/react/hooksentrypoint has been removed. Use the main entrypoint instead.import { generateReactHelpers } from '@uploadthing/react/hooks' import { generateReactHelpers } from '@uploadthing/react' -
useUploadThinghook result no longer returnspermittedFileInfo. UserouteConfiginstead.const { permittedFileInfo } = useUploadThing(...) // ^? { config: RouteConfig; slug: string } // const { routeConfig } = useUploadThing(...) // ^? RouteConfig -
Adapter-specific named exports (e.g.
createUploadthingExpressHandler) have been removed. All adapters exportscreateRouteHandleras a unified naming convention.import { createRouteHandler, createUploadthingExpressHandler, } from "uploadthing/express";import { createRouteHandler, createUploadthingFastifyHandler, } from "uploadthing/fastify";import { createH3EventHandler, createRouteHandler, } from "uploadthing/h3";import { createNextPageApiHandler, createRouteHandler, } from "uploadthing/next-legacy";import { createNextRouteHandler, createRouteHandler, } from "uploadthing/next";import { createRouteHandler, createServerHandler, } from "uploadthing/server";
Remove dependency tailwind-merge
We have removed the dependency tailwind-merge from the core client libs. Some
users may still require tailwind-merge if you
are theming components with TailwindCSS,
in which case you can use the config prop to override the default class merger
to use something like tailwind-merge instead of the default class merger
(which is a simple classes.join(' ')):
import { twMerge } from 'tailwind-merge'
import { UploadButton } from '~/lib/uploadthing'
export function Page() {
return (
<UploadButton
...
config={{ cn: twMerge }}
/>
)
}
New configuration provider
In v7, we've introduced a new configuration provider that allows you to
configure UploadThing more easily. All the options that can be passed in as a
configuration object (e.g. createRouterHandler#config) can now also be passed
as an environment variable in constant case prefixed with UPLOADTHING.
const api = new UTApi({
logLevel: 'Info',
})
// is the same as
process.env.UPLOADTHING_LOG_LEVEL = 'Info'
const api = new UTApi()
If both configuration methods are passed, the options object will always take precedence.
Resumable uploads
You can now build resumable upload flows using the createUpload function from
uploadthing/client. Resumable uploads can be paused and resumed at any time as
long as the presigned URL is still valid.
📚 Read more about resumable uploads here (TODO).
The UploadThing REST API has moved
The UploadThing REST API has been moved to a separate domain,
api.uploadthing.com. The old API at uploadthing.com/api now redirects to the
new API. Check out the API Reference for more
information.
Please note that previously we required the SDK version to be passed as a
header. The new API has explicit path-based versioning, meaning the
x-uploadthing-version header is no longer required.
// Old API
curl -X POST https://uploadthing.com/api/listFiles \
-H 'x-uploadthing-version: 6.12.0' \
...
// New API
curl -X POST https://api.uploadthing.com/v6/listFiles \
...