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/client
now 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 thesechanges 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/react
no longer exportsgenerateComponent
. UsegenerateUploadButton
andgenerateUploadDropzone
instead.import { generateComponent } from '@uploadthing/react' const { UploadButton } = generateComponent() import { generateUploadButton } from '@uploadthing/react' const UploadButton = generateUploadButton()
-
@uploadthing/react/hooks
entrypoint has been removed. Use the main entrypoint instead.import { generateReactHelpers } from '@uploadthing/react/hooks' import { generateReactHelpers } from '@uploadthing/react'
-
useUploadThing
hook result no longer returnspermittedFileInfo
. UserouteConfig
instead.const { permittedFileInfo } = useUploadThing(...) // ^? { config: RouteConfig; slug: string } const { routeConfig } = useUploadThing(...) // ^? RouteConfig
-
Adapter-specific named exports (e.g.
createUploadthingExpressHandler
) have been removed. All adapters exportscreateRouteHandler
as 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 \
...