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.

BREAKING

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:

Token

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: "", 
  },
});
BREAKING

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 };
BREAKING

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>() 
BREAKING

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.

BREAKING

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 }, 
  ),
};
BREAKING

Deprecations have been removed

The following were previously deprecated and have now been removed:

  • @uploadthing/react no longer exports generateComponent. Use generateUploadButton and generateUploadDropzone 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 returns permittedFileInfo. Use routeConfig instead.

    const { permittedFileInfo } = useUploadThing(...) 
    //      ^? { config: RouteConfig; slug: string }
    const { routeConfig } = useUploadThing(...) 
    //      ^? RouteConfig
    
  • Adapter-specific named exports (e.g. createUploadthingExpressHandler) have been removed. All adapters exports createRouteHandler 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";
    
BREAKING

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 }}
    />
  )
}
FEATURE

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()
FEATURE

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).

MISC

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 \
  ...

Was this page helpful?