oh i see. do you have an example of this
oh i see. do you have an example of this? are the env vars the same for multiple installations?
3 Replies
I don't have an example to point to, but the
And then you would do the whole component initialization twice, probably in two different files just to keep it straight, but you could it one file also.
When you call
use
method takes an optional object with a name for this purpose:
// convex/convex.config.ts
import { defineApp } from "convex/server";
import r2 from "@convex-dev/r2/convex.config";
const app = defineApp();
app.use(r2, { name: "bucket1" });
app.use(r2, { name: "bucket2" });
export default app;
// convex/convex.config.ts
import { defineApp } from "convex/server";
import r2 from "@convex-dev/r2/convex.config";
const app = defineApp();
app.use(r2, { name: "bucket1" });
app.use(r2, { name: "bucket2" });
export default app;
new R2()
both instances will default to the same env vars, so you probably want to define all of the env vars inline (you can still use env vars, just specify inline):
const r2bucket1 = new R2(components.bucket1, {
R2_BUCKET: process.env.R2_BUCKET_1,
R2_ENDPOINT: process.env.R2_ENDPOINT_1,
R2_ACCESS_KEY_ID: process.env.R2_ACCESS_KEY_ID_1,
R2_SECRET_ACCESS_KEY: process.env.R2_SECRET_ACCESS_KEY_1,
});
const r2bucket2 = new R2(components.bucket2, {
R2_BUCKET: process.env.R2_BUCKET_2,
R2_ENDPOINT: process.env.R2_ENDPOINT_2,
R2_ACCESS_KEY_ID: process.env.R2_ACCESS_KEY_ID_2,
R2_SECRET_ACCESS_KEY: process.env.R2_SECRET_ACCESS_KEY_2,
});
const r2bucket1 = new R2(components.bucket1, {
R2_BUCKET: process.env.R2_BUCKET_1,
R2_ENDPOINT: process.env.R2_ENDPOINT_1,
R2_ACCESS_KEY_ID: process.env.R2_ACCESS_KEY_ID_1,
R2_SECRET_ACCESS_KEY: process.env.R2_SECRET_ACCESS_KEY_1,
});
const r2bucket2 = new R2(components.bucket2, {
R2_BUCKET: process.env.R2_BUCKET_2,
R2_ENDPOINT: process.env.R2_ENDPOINT_2,
R2_ACCESS_KEY_ID: process.env.R2_ACCESS_KEY_ID_2,
R2_SECRET_ACCESS_KEY: process.env.R2_SECRET_ACCESS_KEY_2,
});
convex/r2/profilePictures.ts
import { R2 } from '@convex-dev/r2'
import { components } from '../_generated/api'
// Dedicated R2 instance for profile pictures
export const profilePicturesR2 = new R2(components.profilePictures, {
R2_BUCKET: process.env.R2_PFP_BUCKET,
R2_ENDPOINT: process.env.R2_ENDPOINT,
R2_ACCESS_KEY_ID: process.env.R2_ACCESS_KEY_ID,
R2_SECRET_ACCESS_KEY: process.env.R2_SECRET_ACCESS_KEY,
})
export const { generateUploadUrl, syncMetadata } = profilePicturesR2.clientApi({
checkUpload: async (ctx, bucket) => {
// For profile pictures, allow uploads during onboarding
// Just check that user is authenticated (but may not exist in DB yet)
const user = await ctx.auth.getUserIdentity()
if (!user) {
throw new Error('Authentication required for profile picture upload')
}
// Allow upload - don't require user to exist in database yet
// This enables onboarding uploads before user creation
},
onUpload: async (ctx, key) => {
console.log(`Profile picture uploaded with key: ${key}`)
}
})
convex/r2/profilePictures.ts
import { R2 } from '@convex-dev/r2'
import { components } from '../_generated/api'
// Dedicated R2 instance for profile pictures
export const profilePicturesR2 = new R2(components.profilePictures, {
R2_BUCKET: process.env.R2_PFP_BUCKET,
R2_ENDPOINT: process.env.R2_ENDPOINT,
R2_ACCESS_KEY_ID: process.env.R2_ACCESS_KEY_ID,
R2_SECRET_ACCESS_KEY: process.env.R2_SECRET_ACCESS_KEY,
})
export const { generateUploadUrl, syncMetadata } = profilePicturesR2.clientApi({
checkUpload: async (ctx, bucket) => {
// For profile pictures, allow uploads during onboarding
// Just check that user is authenticated (but may not exist in DB yet)
const user = await ctx.auth.getUserIdentity()
if (!user) {
throw new Error('Authentication required for profile picture upload')
}
// Allow upload - don't require user to exist in database yet
// This enables onboarding uploads before user creation
},
onUpload: async (ctx, key) => {
console.log(`Profile picture uploaded with key: ${key}`)
}
})
convex/r2/media.ts
import { R2 } from '@convex-dev/r2'
import { components } from '../_generated/api'
// R2 instance for general media uploads (messages, threads, etc.)
export const mediaR2 = new R2(components.media, {
R2_BUCKET: process.env.R2_MEDIA_BUCKET,
R2_ENDPOINT: process.env.R2_ENDPOINT,
R2_ACCESS_KEY_ID: process.env.R2_ACCESS_KEY_ID,
R2_SECRET_ACCESS_KEY: process.env.R2_SECRET_ACCESS_KEY
})
export const { generateUploadUrl, syncMetadata } = mediaR2.clientApi({
checkUpload: async (ctx, bucket) => {
// Get current user from auth
const user = await ctx.auth.getUserIdentity()
if (!user) {
throw new Error('Authentication required for file upload')
}
// Validate user exists in database
const userRecord = await ctx.db
.query('users')
.withIndex('by_privy_id', (q) => q.eq('privy_id', user.subject))
.first()
if (!userRecord) {
throw new Error('User not found in database')
}
},
onUpload: async (ctx, key) => {
console.log(`Media file uploaded with key: ${key}`)
}
})
convex/r2/media.ts
import { R2 } from '@convex-dev/r2'
import { components } from '../_generated/api'
// R2 instance for general media uploads (messages, threads, etc.)
export const mediaR2 = new R2(components.media, {
R2_BUCKET: process.env.R2_MEDIA_BUCKET,
R2_ENDPOINT: process.env.R2_ENDPOINT,
R2_ACCESS_KEY_ID: process.env.R2_ACCESS_KEY_ID,
R2_SECRET_ACCESS_KEY: process.env.R2_SECRET_ACCESS_KEY
})
export const { generateUploadUrl, syncMetadata } = mediaR2.clientApi({
checkUpload: async (ctx, bucket) => {
// Get current user from auth
const user = await ctx.auth.getUserIdentity()
if (!user) {
throw new Error('Authentication required for file upload')
}
// Validate user exists in database
const userRecord = await ctx.db
.query('users')
.withIndex('by_privy_id', (q) => q.eq('privy_id', user.subject))
.first()
if (!userRecord) {
throw new Error('User not found in database')
}
},
onUpload: async (ctx, key) => {
console.log(`Media file uploaded with key: ${key}`)
}
})
import aggregate from '@convex-dev/aggregate/convex.config'
import migrations from '@convex-dev/migrations/convex.config'
import r2 from '@convex-dev/r2/convex.config'
import workpool from '@convex-dev/workpool/convex.config'
import { defineApp } from 'convex/server'
const app = defineApp()
app.use(aggregate, { name: 'fameAggregate' })
app.use(aggregate, { name: 'streakAggregate' })
app.use(workpool, { name: 'indexerWorkpool' })
app.use(r2, { name: 'media' }) // For authenticated uploads (messages, etc.)
app.use(r2, { name: 'profilePictures' }) // For profile picture uploads (including onboarding)
app.use(migrations)
export default app
import aggregate from '@convex-dev/aggregate/convex.config'
import migrations from '@convex-dev/migrations/convex.config'
import r2 from '@convex-dev/r2/convex.config'
import workpool from '@convex-dev/workpool/convex.config'
import { defineApp } from 'convex/server'
const app = defineApp()
app.use(aggregate, { name: 'fameAggregate' })
app.use(aggregate, { name: 'streakAggregate' })
app.use(workpool, { name: 'indexerWorkpool' })
app.use(r2, { name: 'media' }) // For authenticated uploads (messages, etc.)
app.use(r2, { name: 'profilePictures' }) // For profile picture uploads (including onboarding)
app.use(migrations)
export default app
Can you try logging the variables where you instantiate the component class instances to confirm nothing unexpected is happening there
Actually, you can log
mediaR2.options
and profilePicturesR2.options
to get the exact values each ended up with.