Conversations about creating, managing, and using metafields to store and retrieve custom data for apps and themes.
Hi all. I'm using a Custom Data field to allow to upload a PDF file each product on their Shopify store. The file is going to be downloadable from each product page.
I would like to write a function in C# to upload files via the API but I can't understand what the correct procedure is.
This is my code example (it doesn't work):
public async Task UploadFile(byte[] fileContent, string fileName) { using (var client = new HttpClient()) { var requestUri = $"https://{_shopifyStoreUrl}/admin/api/2023-10/files.json"; client.DefaultRequestHeaders.Add("X-Shopify-Access-Token", _accessToken); var content = new MultipartFormDataContent(); var fileContentContent = new ByteArrayContent(fileContent); fileContentContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data") { Name = "\"file\"", FileName = $"\"{fileName}\"" }; content.Add(fileContentContent); var response = await client.PostAsync(requestUri, content); response.EnsureSuccessStatusCode(); } }
Can you help me understand where I'm wrong?
The response is: "StatusCode: 406, ReasonPhrase: 'Not Acceptable'"
Thanks a lot.
Hey @MarcoIngShop,
I can't speak to the C# code specifically but can point you in the right direction. I don't think there's a `/files` endpoint - but you should be able to create a file with this (GraphQL) mutation.
Scott | Developer Advocate @ Shopify
Thank you for your suggestion. I understood that the operation must be done in 2 steps.
The first is GenerateUploadUrl and then upload.
I think I managed to do the first step but then it fails when I send the file.
I have upload url and parameters.
Could you help me understand where the error is in the second step?
This is my code:
public async Task<string> GenerateUploadUrlAndUploadFile(string fileName, byte[] fileContent)
{
var uploadDetails = await GenerateUploadUrl(fileName, fileContent.Length);
if (string.IsNullOrEmpty(uploadDetails))
{
throw new Exception("Unable to generate upload URL.");
}
// Estrai i dettagli di caricamento dalla risposta
var uploadData = JsonConvert.DeserializeObject<JObject>(uploadDetails);
var uploadUrl = uploadData["data"]["stagedUploadsCreate"]["stagedTargets"][0]["url"].ToString();
var uploadParameters = uploadData["data"]["stagedUploadsCreate"]["stagedTargets"][0]["parameters"].ToObject<List<Dictionary<string, string>>>();
// Prepara la richiesta di caricamento
var requestMessage = new HttpRequestMessage(HttpMethod.Post, uploadUrl)
{
Content = new ByteArrayContent(fileContent)
};
// Imposta i parametri come header della richiesta
foreach (var parameter in uploadParameters)
{
requestMessage.Headers.TryAddWithoutValidation(parameter["name"], parameter["value"]);
}
// Esegui la richiesta di caricamento
var uploadResult = await _httpClient.SendAsync(requestMessage);
if (!uploadResult.IsSuccessStatusCode)
{
Console.WriteLine("Failed to upload file. Response content: " + responseContent);
throw new Exception("Failed to upload file. Status: " + uploadResult.StatusCode);
}
// Restituisci l'URL della risorsa caricata o un'altra informazione rilevante
return uploadResult.Headers.Location.ToString();
}
private async Task<string> GenerateUploadUrl(string fileName, long fileSize)
{
var mutation = @"
mutation stagedUploadsCreate($input: [StagedUploadInput!]!) {
stagedUploadsCreate(input: $input) {
stagedTargets {
parameters {
name
value
}
url
resourceUrl
}
}
}";
var variables = new
{
input = new[]
{
new
{
resource = "FILE",
filename = fileName,
mimeType = "application/pdf",
httpMethod = "POST",
fileSize = fileSize.ToString()
}
}
};
var requestBody = new
{
query = mutation,
variables = variables
};
var requestContent = new StringContent(JsonConvert.SerializeObject(requestBody), Encoding.UTF8, "application/json");
_httpClient.DefaultRequestHeaders.Clear();
_httpClient.DefaultRequestHeaders.Add("X-Shopify-Access-Token", _accessToken);
var response = await _httpClient.PostAsync($"https://{_shopifyStoreUrl}/admin/api/2023-10/graphql.json", requestContent);
var responseString = await response.Content.ReadAsStringAsync();
return response.IsSuccessStatusCode ? responseString : null;
}
Hey @MarcoIngShop
Are you able to capture the request ID from the respond headers? This will let me examine the logs.
Scott | Developer Advocate @ Shopify
X-GUploader-UploadID --> ABPtcPqz1u45k2z5XfYw5nAdUtiIcSiOMp3mvHZ8spxt2_MB3PlIus6aMscZ73SIoVfc_tA1kxcXZCt7oQ
I thik that this is the point "critical".
I would like to understand if I have to make a put or a post and if the parameters I send are correct (if they go in the header or in the body).
//Load File
var formContent = new MultipartFormDataContent();
foreach (var parameter in uploadParameters)
{
formContent.Add(new StringContent(parameter["value"]), parameter["name"]);
}
string base64String = Convert.ToBase64String(fileContent);
formContent.Add(new StringContent(base64String),"data");
formContent.Add(new StringContent(fileName), "filename");
//formContent.Add(new ByteArrayContent(fileContent), "data", fileName);
//formContent.Add(new ByteArrayContent(fileContent),"data");
//HttpContent httpContent = new HttpContent();
var uploadResult = await _httpClient.PostAsync(uploadUrl, formContent);
if (!uploadResult.IsSuccessStatusCode)
{
throw new Exception("Failed to upload file.");
}
I can't speak to the code directly, but this article breaks down the steps - maybe try running through this first: https://shopify.dev/docs/apps/online-store/media/products#generate-the-upload-url-and-parameters
It's specifically for product media, so difference would be:
- you would specify `FILE` for the `StagedUploadTargetGenerateUploadResource`
- after uploading the file, you can add the file to the Files page in Shopify admin using the fileCreate mutation.
Scott | Developer Advocate @ Shopify
Hi,
This article is missing the example in "UPLOAD SECTION" of when a generic "FILE" type must be uploaded (in my case a PDF) so I can't understand if the request must be of type POST or PUT and where (and how) the parameters should be passed.
Can you tell me if for a generic FILE (pdf) type the request must be POST OR PUT and how the parameters should be passed?
In the first step I followed the instructions by putting the type "FILE" and I received the correct parameters but when it says to upload I tried various ways without success.
Thanks a lot.
Hey @MarcoIngShop
Just tried it out - we could use better docs on the process 😅 I had success with the following method:
1. Generate a staged upload URL
mutation generateStagedUploads {
stagedUploadsCreate(input: [
{
filename: "example.pdf",
mimeType: "application/pdf",
resource: FILE
}
])
{
stagedTargets {
url
resourceUrl
parameters {
name
value
}
}
userErrors {
field, message
}
}
}
2. Upload the file (A PUT request to `data.stagedUploadsCreate.stagedTargets.url` from step 1. If you want to test in Postman here's an example screenshot.
3. If you get a 200 OK from step 2, then you can use the `fileCreate` mutation to add the file to the 'files' section of the admin. You only need to pass the originalSource param, which is the `data.stagedUploadsCreate.stagedTargets.url` value from step one without any of the params.
{
"files": [
{
"originalSource": "https://shopify-staged-uploads.storage.googleapis.com/tmp/.../example.pdf"
}
]
}
Let me know how you go!
Scott | Developer Advocate @ Shopify
Can you check the log for this request:
X-GUploader-UploadID: ABPtcPqgQzIR9moKLvUInt0f35gTM7dK1NMGRDfIT2mEa1iEyUaGLDWVfniZHVTqTIeasppOz0s
Date: Thu, 23 Nov 2023 20:26:05 GMT
Server: UploadServer
Alt-Svc: h3=":443"; ma=2592000
Alt-Svc: h3-29=":443"; ma=2592000
Content-Type: text/plain; charset=utf-8
Content-Length: 25
Thanks a lot.