T
TanStack•3mo ago
deep-jade

Upload file using server actions, the right way

Send type File to a server action. Currently the serializer does not let me pass it. Any guide or recommendation?
6 Replies
deep-jade
deep-jadeOP•3mo ago
Found my issue. I should use FormData and validate formData instead of data. Can't pass directly File without FormData for obvious reasons. I was unaware of the possibility of retrieving formData in the server action validation instead of data, so FormData or File didn't work with data, but it does work when checking formData. Not sure If I'm explaining it super clear if anybody has de same issue, but feel free to ask 🙂
mute-gold
mute-gold•2mo ago
Hi @AlberTenez , can you provide the example code for how to (1) upload, (2) validate on server? I am running into this same issue
deep-jade
deep-jadeOP•2mo ago
Sure this is the example of createServerFn:
/**
* Upload an image for a product
*/
export const uploadProductImage = createServerFn({ method: 'POST' })
.validator((formData: FormData) => {
const id = formData.get('id');
const image = formData.get('image');

if (!id || typeof id !== 'string') {
throw new Error('Product ID is required');
}

if (!image || !(image instanceof File)) {
throw new Error('Image file is required');
}

return { id, image };
})
.handler(async ({ data }) => {
const session = await getValidatedSession();
if (!session) {
return {
error: true as const,
message: 'Authentication required',
};
}

const { id, image } = data;

return handleApiResponse(() =>
api().api.products[':id'].images.$post(
{
param: { id },
form: { image },
},
{
headers: {
Authorization: `Bearer ${session}`,
},
},
),
);
});
/**
* Upload an image for a product
*/
export const uploadProductImage = createServerFn({ method: 'POST' })
.validator((formData: FormData) => {
const id = formData.get('id');
const image = formData.get('image');

if (!id || typeof id !== 'string') {
throw new Error('Product ID is required');
}

if (!image || !(image instanceof File)) {
throw new Error('Image file is required');
}

return { id, image };
})
.handler(async ({ data }) => {
const session = await getValidatedSession();
if (!session) {
return {
error: true as const,
message: 'Authentication required',
};
}

const { id, image } = data;

return handleApiResponse(() =>
api().api.products[':id'].images.$post(
{
param: { id },
form: { image },
},
{
headers: {
Authorization: `Bearer ${session}`,
},
},
),
);
});
And using tanstack/form onSubmit to use the action:
const formData = new FormData();
formData.append('id', id);
formData.append('image', imageFile);

const uploadResult = await uploadProductImage({ data: formData });
const formData = new FormData();
formData.append('id', id);
formData.append('image', imageFile);

const uploadResult = await uploadProductImage({ data: formData });
Hope it helps I reviewed the docs and seems undocumented. Maybe a tanstack team memeber can validate the approach? I managed by checking the types, happy to add it to the docs if this is the correct approach
deep-jade
deep-jadeOP•2mo ago
could be here, which mentions about request.formData -> https://tanstack.com/start/latest/docs/framework/react/server-routes
Server Routes | TanStack Start React Docs
Server routes are a powerful feature of TanStack Start that allow you to create server-side endpoints in your application and are useful for handling raw HTTP requests, form submissions, user authenti...
eager-peach
eager-peach•5w ago
LGTM PR to the docs always welcome
deep-jade
deep-jadeOP•3w ago
Let me do it. Sorry not super active in Discord 🙂

Did you find this page helpful?