{"id":15118354,"url":"https://github.com/TimMikeladze/next-upload","last_synced_at":"2025-09-28T00:32:30.050Z","repository":{"id":185001392,"uuid":"672432443","full_name":"TimMikeladze/next-upload","owner":"TimMikeladze","description":"🗃️ Turn-key solution for signed \u0026 secure file-uploads to an S3 compliant storage service such as R2, AWS, or Minio. Built for Next.js. Generates signed URLs for uploading files directly to your storage service and optionally integrates with a database to store additional metadata about your files.","archived":false,"fork":false,"pushed_at":"2024-10-29T00:27:14.000Z","size":2061,"stargazers_count":99,"open_issues_count":24,"forks_count":5,"subscribers_count":2,"default_branch":"master","last_synced_at":"2024-10-29T15:17:27.891Z","etag":null,"topics":["blob-storage","hacktoberfest","next","next-s3","next-upload","nextjs","nextjs-upload","post-policy","r2","s3","signed-url","upload","uploads"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/TimMikeladze.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"ko_fi":"linesofcodedev","custom":["https://www.paypal.me/TimMikeladze"]}},"created_at":"2023-07-30T04:14:03.000Z","updated_at":"2024-09-18T08:42:10.000Z","dependencies_parsed_at":null,"dependency_job_id":"efd86148-783b-4dd0-a953-75a823f136a5","html_url":"https://github.com/TimMikeladze/next-upload","commit_stats":{"total_commits":130,"total_committers":3,"mean_commits":"43.333333333333336","dds":"0.16153846153846152","last_synced_commit":"0394b8befeda47298863a27ecf86a7b622c906f6"},"previous_names":["timmikeladze/next-upload"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TimMikeladze%2Fnext-upload","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TimMikeladze%2Fnext-upload/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TimMikeladze%2Fnext-upload/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TimMikeladze%2Fnext-upload/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/TimMikeladze","download_url":"https://codeload.github.com/TimMikeladze/next-upload/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":234475315,"owners_count":18839358,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["blob-storage","hacktoberfest","next","next-s3","next-upload","nextjs","nextjs-upload","post-policy","r2","s3","signed-url","upload","uploads"],"created_at":"2024-09-26T01:46:14.086Z","updated_at":"2025-09-28T00:32:24.715Z","avatar_url":"https://github.com/TimMikeladze.png","language":"TypeScript","funding_links":["https://ko-fi.com/linesofcodedev","https://www.paypal.me/TimMikeladze"],"categories":["TypeScript"],"sub_categories":[],"readme":"# 🗃️ next-upload\n\nA turn-key solution for integrating Next.js with signed \u0026 secure file-uploads to an S3 compliant storage service such as R2, AWS, or Minio. Generates signed URLs for uploading files directly to your storage service and optionally integrates with your database to store additional metadata about your files.\n\nCheck out this [example](https://github.com/TimMikeladze/next-upload/tree/master/examples/next-upload-example) of a Next.js codebase showcasing an advanced implementation of `next-upload` with different storage services and databases.\n\n\u003e 🚧 Under active development. Expect breaking changes until v1.0.0.\n\n## 📡 Install\n\n```console\nnpm install next-upload\n\nyarn add next-upload\n\npnpm add next-upload\n```\n\n\u003e 👋 Hello there! Follow me [@linesofcode](https://twitter.com/linesofcode) or visit [linesofcode.dev](https://linesofcode.dev) for more cool projects like this one.\n\n## 🚀 Getting Started\n\nFirst let's create a `NextUploadConfig` that defines how to connect to a storage service, different types of file uploads, size limits and more. The example below uses AWS S3 as the storage service but you can use any S3 compatible service.\n\n**src/app/upload/config.ts**\n\n```tsx\nimport { type NextUploadConfig } from 'next-upload/client';\nimport { NextUpload } from 'next-upload';\n\nexport const config: NextUploadConfig = {\n  maxSize: '1mb',\n  bucket: NextUpload.bucketFromEnv('my-project-name'),\n  client: {\n    region: 'us-west-1',\n    endpoint: 'https://s3.us-west-1.amazonaws.com',\n    credentials: {\n      secretAccessKey: process.env.S3_SECRET_KEY,\n      accessKeyId: process.env.S3_ACCESS_KEY,\n    },\n  },\n};\n```\n\nNow to integrate with Next.js we need to create an HTTP route that will handle `next-upload` related requests such as generating signed URLs. In the example below we are using a `POST` route at `/upload` with the Next.js App router. If you are using the Pages router or a different framework you can leverage the `NextUpload.pagesApiHandler` or `NextUpload.rawHandler` functions directly to achieve the same result.\n\n\u003e 🔒 This a good place to add authentication to your upload route to restrict who has access to upload files.\n\n**src/app/upload/route.ts**\n\n```tsx\nimport { NextUpload } from 'next-upload';\nimport { config } from './config';\nimport { NextRequest } from 'next/server';\n\nconst nup = new NextUpload(config);\n\nexport const POST = (request: NextRequest) =\u003e nup.handler(request);\n\nexport const dynamic = 'force-dynamic';\n\n// Optionally, if your application supports it you can run next-upload in the Edge runtime.\nexport const runtime = 'edge'; \n```\n\nAt this point you can import helper functions from `next-upload/client` to send files to your storage service in one line of code.\n\nHow your application handles file-uploads in the browser is up to you. The example below uses `react-dropzone` to send files to storage via the `upload` function.\n\n**src/components/FileUpload.tsx**\n\n```tsx\n'use client';\n\nimport { useDropzone } from 'react-dropzone';\nimport { useNextUpload } from 'next-upload/react';\n\nconst FileUpload = () =\u003e {\n  const nup = useNextUpload();\n\n  const onDropAccepted = (acceptedFiles: File[]) =\u003e\n    nup.upload(\n      acceptedFiles.map((file) =\u003e ({\n        file,\n      }))\n    );\n\n  const { getRootProps, getInputProps, isDragActive } = useDropzone({\n    onDropAccepted,\n  });\n\n  return (\n    \u003cdiv {...getRootProps()}\u003e\n      \u003cinput {...getInputProps()} /\u003e\n      {isDragActive ? (\n        \u003cp\u003eDrop the files here ...\u003c/p\u003e\n      ) : (\n        \u003cp\u003eDrag 'n' drop some files here, or click to select files\u003c/p\u003e\n      )}\n      {nup.isUploading \u0026\u0026 \u003cp\u003eUploading...\u003c/p\u003e}\n    \u003c/div\u003e\n  );\n};\n\nexport default FileUpload;\n```\n\n## 🧳 Storage\n\nIt's often useful to save an additional reference to the uploaded file in your database. This can be used for things like displaying a list of files that have been uploaded or associating a file with a specific user within your application without having to query the storage service directly. `next-upload` provides an interface that can be implemented with any database of your choice.  \n\n\u003e 🙋 Is your database missing? Implementing the [`Store`](https://github.com/TimMikeladze/next-upload/blob/master/src/types.ts#L55) is very straight-forward. Feel free to open a PR with your implementation or [open an issue](https://github.com/TimMikeladze/next-upload/issues/new) to request a new asset store implementation.\n\nOut of the box `next-upload` provides the following asset store implementations:\n\n### 🗝️ KeyvStore - all popular databases supported\n\nWorks with any [keyv](https://github.com/jaredwray/) enabled store. This includes popular databases such as Postgres, MySQL and Mongo. This is a simple option for getting started with an asset store with minimal overhead. **Warning:** Using keyv is inherently slower than using a more specific database client. If you are expecting a high volume of reads/writes to your asset store you should consider using a different implementation.\n\n**src/app/upload/nup.ts**\n```tsx\nimport { nextUploadConfig } from '@/app/upload/config';\nimport KeyvPostgres from '@keyv/postgres';\nimport Keyv from 'keyv';\nimport { NextUpload } from 'next-upload';\nimport { NextUploadKeyvStore } from 'next-upload/store/keyv';\n\nexport const nup = new NextUpload(\n  nextUploadConfig,\n  new NextUploadKeyvStore(\n    new Keyv({\n      namespace: NextUpload.namespaceFromEnv('my-project-name'),\n      store: new KeyvPostgres({\n        uri: `${process.env.PG_CONNECTION_STRING}/${process.env.PG_DB}`,\n      }),\n    })\n  )\n);\n```\n\n### ☔️ Drizzle\n\nWorks with a [Drizzle](https://github.com/drizzle-team/drizzle-orm) enabled database. This is a great option if you are already using Drizzle in your application and want tighter integration with your database schema. It also provides a more performant option for high volume reads/writes to your asset store. \n\n#### 🐘 NextUploadDrizzlePgStore\n\nThe following Postgres clients are directly supported. Other Postgres clients most likely will work but may raise TypeScript errors during initialization of the `NextUploadDrizzlePgStore` instance.\n\n- [Postgres.JS](https://orm.drizzle.team/docs/quick-postgresql/postgresjs)\n- [node-postgres](https://orm.drizzle.team/docs/quick-postgresql/node-postgres)\n- [Neon](https://orm.drizzle.team/docs/quick-postgresql/neon)\n\n**src/db/schema.ts**\n```tsx\nexport { nextUploadAssetsTable } from 'next-upload/store/drizzle/postgres-js';\n```\n\n**src/app/upload/nup.ts**\n```tsx\nimport { nextUploadConfig } from '@/app/nextUploadConfig';\nimport { getDbPostgresJs } from '@/drizzle/getDbPostgresJs';\nimport { NextUpload } from 'next-upload';\nimport { NextUploadDrizzlePgStore } from 'next-upload/store/drizzle/postgres-js';\n\nexport const nup = new NextUpload(\n  nextUploadConfig,\n  new NextUploadDrizzlePgStore(getDbPostgresJs())\n);\n\n```\n\n### 🔗 Getting an Asset Url\n\nOnce you have uploaded a file you can retrieve the url, id and metadata of the asset using the `NextUpload.getAsset` function.\n\n```tsx\nconst { asset } = await nup.getAsset({\n  id: 'id of the asset',\n  // or provide a path\n  path: 'path of the asset',\n})\n\n```\n\n### 🗑️ Deleting Assets\n\nYou can delete an asset with `NextUpload.deleteAsset`. This will delete the asset from your storage service and the asset store if you have one configured.\n\n```tsx\nawait nup.deleteAsset({\n  id: 'id of the asset',\n  // or provide a path\n  path: 'path of the asset',\n})\n```\n\n### 🔎 Retrieving Assets\n\nOnce you have uploaded a file you can retrieve it from the database using the `Store` instance.\n\n```tsx\nconst store = nup.getStore();\n\nawait store.find('id of the asset');\n```\n\n## 📝 Metadata\n\nUsing an `Store` enables you to save additional metadata about the file as part of the upload process. This can be useful for storing things like the original file name, user id of the uploader, or any other information you want to associate with the file.\n\nTo get started simply pass a `metadata` object to the `upload` function.\n\n```tsx\nimport { upload } from 'next-upload/client';\n\nawait upload(\n  acceptedFiles.map((file) =\u003e ({\n    file,\n    metadata: {\n      userId: '123',\n    },\n  })),\n  config\n);\n```\n\n## ✅ Verifying uploads \n\nIn certain scenarios you may need to mark an upload as verified once it has been processed by your application.\n\nTo enable verification, set the `verifyAssets` config to `true` and instantiate `NextUpload` with an `Store` instance.\n\nNow any file that is uploaded will have a `verified` property set to `false` by default. Once you have processed the file you can mark it as verified by calling `NextUpload.verifyAsset(id)`.\n\n## ✂️ Pruning assets\n\nAt any time you can call a `NextUpload.pruneAssets` to delete any assets that have expired due to lack of verification or to remove dangling files or records from the system.\n\nConsider setting up a cron job to run this function on a regular basis.\n\n\u003c!-- TSDOC_START --\u003e\n\n## :wrench: Constants\n\n- [defaultEnabledHandlerActions](#gear-defaultenabledhandleractions)\n\n### :gear: defaultEnabledHandlerActions\n\n| Constant | Type |\n| ---------- | ---------- |\n| `defaultEnabledHandlerActions` | `NextUploadAction[]` |\n\n\n## :factory: NextUpload\n\n### Methods\n\n- [generatePresignedPostPolicy](#gear-generatepresignedpostpolicy)\n- [namespaceFromEnv](#gear-namespacefromenv)\n- [bucketFromEnv](#gear-bucketfromenv)\n- [getIdFromPath](#gear-getidfrompath)\n- [getUploadTypeFromPath](#gear-getuploadtypefrompath)\n- [calculateExpires](#gear-calculateexpires)\n- [isExpired](#gear-isexpired)\n- [getBucket](#gear-getbucket)\n- [getClient](#gear-getclient)\n- [init](#gear-init)\n- [bucketExists](#gear-bucketexists)\n- [generatePresignedPostPolicy](#gear-generatepresignedpostpolicy)\n- [pruneAssets](#gear-pruneassets)\n- [verifyAsset](#gear-verifyasset)\n- [deleteAsset](#gear-deleteasset)\n- [getAsset](#gear-getasset)\n\n#### :gear: generatePresignedPostPolicy\n\n| Method | Type |\n| ---------- | ---------- |\n| `generatePresignedPostPolicy` | `(args: any, request: NextToolRequest or undefined) =\u003e Promise\u003c{ postPolicy: SignedPostPolicy; }\u003e` |\n\n#### :gear: namespaceFromEnv\n\n| Method | Type |\n| ---------- | ---------- |\n| `namespaceFromEnv` | `(project?: string or undefined) =\u003e string` |\n\n#### :gear: bucketFromEnv\n\n| Method | Type |\n| ---------- | ---------- |\n| `bucketFromEnv` | `(project?: string or undefined) =\u003e string` |\n\n#### :gear: getIdFromPath\n\n| Method | Type |\n| ---------- | ---------- |\n| `getIdFromPath` | `(path: string) =\u003e string` |\n\n#### :gear: getUploadTypeFromPath\n\n| Method | Type |\n| ---------- | ---------- |\n| `getUploadTypeFromPath` | `(path: string) =\u003e string` |\n\n#### :gear: calculateExpires\n\n| Method | Type |\n| ---------- | ---------- |\n| `calculateExpires` | `(ttl: number) =\u003e number or null` |\n\n#### :gear: isExpired\n\n| Method | Type |\n| ---------- | ---------- |\n| `isExpired` | `(timestamp: number or null or undefined) =\u003e boolean or 0` |\n\n#### :gear: getBucket\n\n| Method | Type |\n| ---------- | ---------- |\n| `getBucket` | `() =\u003e string` |\n\n#### :gear: getClient\n\n| Method | Type |\n| ---------- | ---------- |\n| `getClient` | `() =\u003e S3` |\n\n#### :gear: init\n\n| Method | Type |\n| ---------- | ---------- |\n| `init` | `() =\u003e Promise\u003cvoid\u003e` |\n\n#### :gear: bucketExists\n\n| Method | Type |\n| ---------- | ---------- |\n| `bucketExists` | `() =\u003e Promise\u003cboolean\u003e` |\n\n#### :gear: generatePresignedPostPolicy\n\n| Method | Type |\n| ---------- | ---------- |\n| `generatePresignedPostPolicy` | `(args: GeneratePresignedPostPolicyArgs, request?: NextUploadRequest or undefined) =\u003e Promise\u003c{ postPolicy: SignedPostPolicy; }\u003e` |\n\n#### :gear: pruneAssets\n\n| Method | Type |\n| ---------- | ---------- |\n| `pruneAssets` | `() =\u003e Promise\u003cboolean\u003e` |\n\n#### :gear: verifyAsset\n\n| Method | Type |\n| ---------- | ---------- |\n| `verifyAsset` | `(args: VerifyAssetArgs or VerifyAssetArgs[]) =\u003e Promise\u003c{ asset: Asset; assets: Asset[]; }\u003e` |\n\n#### :gear: deleteAsset\n\n| Method | Type |\n| ---------- | ---------- |\n| `deleteAsset` | `(args: DeleteArgs or DeleteArgs[]) =\u003e Promise\u003cboolean\u003e` |\n\n#### :gear: getAsset\n\n| Method | Type |\n| ---------- | ---------- |\n| `getAsset` | `(args: GetAssetArgs or GetAssetArgs[], request?: NextUploadRequest or undefined) =\u003e Promise\u003c{ asset: GetAsset; assets: GetAsset[]; }\u003e` |\n\n\n## :nut_and_bolt: Enum\n\n- [NextUploadAction](#gear-nextuploadaction)\n\n### :gear: NextUploadAction\n\n\n\n| Property | Type | Description |\n| ---------- | ---------- | ---------- |\n| `deleteAsset` | `'deleteAsset'` |  |\n| `generatePresignedPostPolicy` | `'generatePresignedPostPolicy'` |  |\n| `getAsset` | `'getAsset'` |  |\n| `pruneAssets` | `'pruneAssets'` |  |\n| `verifyAsset` | `'verifyAsset'` |  |\n\n\n\u003c!-- TSDOC_END --\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FTimMikeladze%2Fnext-upload","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FTimMikeladze%2Fnext-upload","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FTimMikeladze%2Fnext-upload/lists"}