New:Introducing Socket Scanning for VS Code Marketplace Extensions.Learn more →
Get Started

@mzedstudio/uploadthingtrack

Package Overview
Dependencies
Maintainers
1
Versions
7
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@mzedstudio/uploadthingtrack

UploadThing file tracking, access control, and cleanup for Convex.

Source
npmnpm
Version
0.1.1
Version published
Weekly downloads
0
Maintainers
1
Weekly downloads
 
Created
Source

@mzedstudio/uploadthingtrack

A Convex component for tracking UploadThing files with access control, expiration, and webhook verification.

UploadThing handles file storage. This component adds the metadata layer: who uploaded what, who can see it, and when it expires.

Features

  • File tracking -- stores URL, key, name, size, MIME type, and upload time for every file
  • User association -- ties each file to a userId for ownership and dashboards
  • Access control -- per-file and per-folder visibility rules (public / private / restricted)
  • Expiration -- configurable TTL by file, MIME type, file type, or a global default
  • Replacement -- re-uploading with the same key updates the record in place
  • Tags and filters -- tag files and query by user, folder, tag, or MIME type
  • Webhook verification -- HMAC SHA-256 signature validation for UploadThing callbacks
  • Cleanup -- batch deletion of expired file records
  • Usage stats -- total files and bytes per user

Installation

npm install @mzedstudio/uploadthingtrack

Setup

1. Register the component

// convex/convex.config.ts
import { defineApp } from "convex/server";
import uploadthingFileTracker from "@mzedstudio/uploadthingtrack/convex.config.js";

const app = defineApp();
app.use(uploadthingFileTracker, { name: "uploadthingFileTracker" });
export default app;

2. Create the client

// convex/uploadthing.ts
import { UploadThingFiles } from "@mzedstudio/uploadthingtrack";
import { components } from "./_generated/api";

const uploadthing = new UploadThingFiles(components.uploadthingFileTracker);

3. Mount the webhook route

// convex/http.ts
import { httpRouter } from "convex/server";
import { registerRoutes } from "@mzedstudio/uploadthingtrack";
import { components } from "./_generated/api";

const http = httpRouter();
registerRoutes(http, components.uploadthingFileTracker);
export default http;

Set UPLOADTHING_API_KEY as an environment variable in the Convex dashboard. The webhook handler reads it automatically.

4. Configure the component (optional)

export const setup = mutation({
  handler: async (ctx) => {
    await uploadthing.setConfig(ctx, {
      config: {
        uploadthingApiKey: process.env.UPLOADTHING_API_KEY,
        defaultTtlMs: 30 * 24 * 60 * 60 * 1000, // 30 days
        ttlByMimeType: { "image/png": 90 * 24 * 60 * 60 * 1000 },
        ttlByFileType: { avatar: 365 * 24 * 60 * 60 * 1000 },
      },
    });
  },
});

Usage

Querying files

import { query } from "./_generated/server";
import { v } from "convex/values";

export const listMyFiles = query({
  args: { userId: v.string() },
  handler: async (ctx, args) => {
    return await uploadthing.listFiles(ctx, {
      ownerUserId: args.userId,
      viewerUserId: args.userId,
    });
  },
});

export const getFile = query({
  args: { key: v.string(), viewerUserId: v.optional(v.string()) },
  handler: async (ctx, args) => {
    return await uploadthing.getFile(ctx, args);
  },
});

Inserting files manually

import { mutation } from "./_generated/server";

export const trackFile = mutation({
  args: { /* ... */ },
  handler: async (ctx, args) => {
    await uploadthing.upsertFile(ctx, {
      file: {
        key: args.key,
        url: args.url,
        name: args.name,
        size: args.size,
        mimeType: args.mimeType,
      },
      userId: args.userId,
      options: {
        folder: "uploads",
        tags: ["document"],
      },
    });
  },
});

Access control

// Make a file public
await uploadthing.setFileAccess(ctx, {
  key: "file_abc",
  access: { visibility: "public" },
});

// Restrict a folder to specific users
await uploadthing.setFolderAccess(ctx, {
  folder: "team-docs",
  access: {
    visibility: "restricted",
    allowUserIds: ["user_1", "user_2"],
  },
});

// Remove a file-level rule (falls back to folder rule)
await uploadthing.setFileAccess(ctx, { key: "file_abc", access: null });

File-level rules always override folder-level rules. Deny lists take precedence over allow lists.

Filtering

// By tag
await uploadthing.listFiles(ctx, {
  ownerUserId: userId,
  tag: "avatar",
});

// By MIME type
await uploadthing.listFiles(ctx, {
  ownerUserId: userId,
  mimeType: "image/png",
});

// By folder
await uploadthing.listFiles(ctx, {
  ownerUserId: userId,
  folder: "documents",
});

Usage stats

const stats = await uploadthing.getUsageStats(ctx, { userId });
// { totalFiles: 42, totalBytes: 1048576 }

Cleanup

import { action } from "./_generated/server";

export const cleanup = action({
  handler: async (ctx) => {
    // Preview what would be deleted
    const preview = await uploadthing.cleanupExpired(ctx, { dryRun: true });

    // Actually delete expired records
    const result = await uploadthing.cleanupExpired(ctx, { batchSize: 100 });
    // { deletedCount: 12, keys: [...], hasMore: false }
  },
});

TTL Precedence

When determining a file's expiration, the first match wins:

  • Explicit expiresAt timestamp
  • Per-file ttlMs
  • ttlByFileType from config
  • ttlByMimeType from config
  • defaultTtlMs from config
  • No expiration

API Reference

UploadThingFiles class

MethodContextDescription
upsertFile(ctx, args)mutationInsert or replace a file record by key
getFile(ctx, args)queryGet a file by key with access control
listFiles(ctx, args)queryList files with filters
setFileAccess(ctx, args)mutationSet or clear file-level access rules
setFolderAccess(ctx, args)mutationSet or clear folder-level access rules
setConfig(ctx, args)mutationUpdate component configuration
getConfig(ctx)queryRead current configuration
getUsageStats(ctx, args)queryGet total files and bytes for a user
cleanupExpired(ctx, args)actionDelete expired file records
handleCallback(ctx, args)actionHandle an UploadThing webhook

registerRoutes(http, component, options?)

Mounts the UploadThing webhook at /webhooks/uploadthing (configurable via options.path).

Exported types

  • AccessRule -- { visibility, allowUserIds?, denyUserIds? }
  • FileInfo -- { key, url, name, size, mimeType, ... }
  • FileUpsertOptions -- { tags?, folder?, access?, metadata?, expiresAt?, ttlMs?, fileType? }
  • ConfigUpdate -- { uploadthingApiKey?, defaultTtlMs?, ttlByMimeType?, ... }

Validators (accessRuleValidator, fileInfoValidator, etc.) are also exported for use in your own function definitions.

Testing

This component exports a test helper for use with convex-test:

import { convexTest } from "convex-test";
import { register } from "@mzedstudio/uploadthingtrack/test";
import schema from "./schema";

const modules = import.meta.glob("./**/*.ts");

test("my test", async () => {
  const t = convexTest(schema, modules);
  register(t, "uploadthingFileTracker");
  // ... test your functions that use the component
});

License

Apache-2.0

Keywords

convex

FAQs

Package last updated on 07 Feb 2026

Related posts