Upload Operations
File Upload App
App administration
Storefront integration mode Choose whether this store uses the theme app extension or a manually pasted JavaScript snippet. Pass `?shop=installed-store.myshopify.com` to manage a specific store. App block instructions Use the theme app extension when the uploader should load automatically on the current store.
1. Open Shopify admin -> Online Store -> Themes -> Customize.
2. Open the product template where uploads should appear.
3. Add the app block `Upload block` or enable the app embed `Upload Functionality`.
4. Set App base URL to https://shopify.grtrading.in.
5. Save the theme.

This mode uses the app block/app embed to validate the uploader against the current installed store.
Upload API The upload API accepts browser-based multipart uploads and sends CORS headers. It only works for stores with an active installation and a valid short-lived upload token from the storefront proxy.
{
    "method": "POST",
    "url": "https://shopify.grtrading.in/api_upload.php",
    "content_type": "multipart/form-data",
    "fields": {
        "shop": "{{ shop.permanent_domain | json }}",
        "upload_token": "<short-lived token from app proxy>",
        "file[]": [
            "<binary file 1>",
            "<binary file 2>"
        ]
    },
    "cors_allowed_origins": "*",
    "store_install_status": "supply ?shop=installed-store.myshopify.com for store-specific details",
    "store_install_id": null,
    "response": {
        "success": true,
        "msg": "Uploaded 2 file(s)",
        "files": [
            {
                "success": true,
                "msg": "Image Uploaded successfully",
                "original_name": "image-1.jpg",
                "file_name": "https://app.example.com/file.php?..."
            },
            {
                "success": true,
                "msg": "Image Uploaded successfully",
                "original_name": "image-2.png",
                "file_name": "https://app.example.com/file.php?..."
            }
        ]
    }
}
JavaScript example This is the manual theme snippet for JavaScript mode.
<script src="https://code.jquery.com/jquery-4.0.0.js" defer="defer"></script>
<script>
(function () {
  function initDesignSubmissionUpload($) {
    if (window.__designSubmissionUploadInitialized) return;
    window.__designSubmissionUploadInitialized = true;

    $(function () {
      UploadFile.init();
      $(document).on("change", "#upload_prod_image", UploadFile.handleFileUpload);
      $(document).on("click", ".upl_remove", function (e) {
        e.preventDefault();
        var url = $(this).attr("data-url");
        UploadFile.removeOne(url);
      });
    });

    window.UploadFile = {
      base_url: "https://shopify.grtrading.in",
      uploadedFiles: [],
      uploadLimit: 1,

      dom: {
        input: "#upload_prod_image",
        wrap: ".upload_resume_progress",
        imgBox: ".upload_resume_progress .imgcontainer"
      },

      init: function () {
        this.getLimit();
        this.applyInputMode();
        this.renderPreview();
        this.ensurePropsContainer();
      },

      getLimit: function () {
        var lim = parseInt($(this.dom.input).attr("data-upload-limit"), 10);
        if (isNaN(lim) || lim <= 0) lim = 1;
        this.uploadLimit = lim;
        return lim;
      },

      applyInputMode: function () {
        if (this.uploadLimit > 1) $(this.dom.input).attr("multiple", "multiple");
        else $(this.dom.input).removeAttr("multiple");
      },

      getUploadRoute: function () {
        return "/api_upload.php";
      },

      getShopDomain: function () {
        if (window.GRTUploadShopUrl) return window.GRTUploadShopUrl;
        if (window.GRTUploadAppConfig && window.GRTUploadAppConfig.shop) return window.GRTUploadAppConfig.shop;
        return "{{ shop.permanent_domain | json }}";
      },

      ensurePropsContainer: function () {
        var $form = $(this.dom.input).closest("form");
        if (!$form.length) return;
        if (!$form.find("._upl_props_wrap").length) {
          $form.append('<div class="_upl_props_wrap" style="display:none!important;"></div>');
        }
      },

      addPropertyInput: function (imageUrl) {
        var $form = $(this.dom.input).closest("form");
        if (!$form.length) return;
        this.ensurePropsContainer();

        var idx = $form.find("._upl_props_wrap input._upl_prop").length + 1;
        var $hid = $("<input>", {
          type: "hidden",
          class: "_upl_prop",
          name: "properties[Uploaded Image " + idx + "]",
          value: imageUrl
        }).attr("data-url", imageUrl);

        $form.find("._upl_props_wrap").append($hid);
      },

      removeOne: function (imageUrl) {
        this.uploadedFiles = $.grep(this.uploadedFiles, function (u) { return u !== imageUrl; });
        $("._upl_props_wrap input._upl_prop[data-url='" + imageUrl.replace(/\\/g, "\\\\").replace(/'/g, "\\'") + "']").remove();
        this.renderPreview();
      },

      handleFileUpload: function (e) {
        var self = UploadFile;
        self.getLimit();
        self.processFiles(e.target.files, function () {
          $(self.dom.input).val("");
        });
      },

      processFiles: function (files, done) {
        var self = this;
        if (!files || !files.length) {
          if (typeof done === "function") done();
          return;
        }

        var remaining = Math.max(0, self.uploadLimit - self.uploadedFiles.length);
        var selected = [];
        for (var i = 0; i < files.length && i < remaining; i++) selected.push(files[i]);

        self.uploadSequential(selected, function () {
          self.renderPreview();
          if (typeof done === "function") done();
        });
      },

      uploadSequential: function (files, done) {
        var self = this;
        var i = 0;

        function next() {
          if (i >= files.length) {
            if (typeof done === "function") done();
            return;
          }
          self.uploadSingle(files[i], function () {
            i++;
            next();
          });
        }

        next();
      },

      uploadSingle: function (file, cb) {
        var self = this;
        var fd = new FormData();
        fd.append("shop", self.getShopDomain());

        if (self.uploadLimit > 1) fd.append("file[]", file);
        else fd.append("file", file);

        $.ajax({
          url: self.base_url.replace(/\/+$/, "") + self.getUploadRoute(),
          type: "POST",
          data: fd,
          contentType: false,
          processData: false,
          dataType: "json",
          success: function (res) {
            if (!res || res.success !== true || !res.files || !res.files.length) {
              if (typeof cb === "function") cb();
              return;
            }

            var paths = [];
            for (var k = 0; k < res.files.length; k++) {
              if (res.files[k] && res.files[k].success && res.files[k].file_name) {
                paths.push(res.files[k].file_name);
              }
            }

            for (var p = 0; p < paths.length; p++) {
              var full = paths[p];
              if (!full) continue;
              if (self.uploadedFiles.indexOf(full) === -1) {
                self.uploadedFiles.push(full);
                self.addPropertyInput(full);
              }
            }

            if (typeof cb === "function") cb();
          },
          error: function () {
            if (typeof cb === "function") cb();
          }
        });
      },

      renderPreview: function () {
        var html = "";
        for (var i = 0; i < this.uploadedFiles.length; i++) {
          var u = this.uploadedFiles[i];
          html += ""
            + "<span class='upl_item'>"
            +   "<img src='" + u + "' style='width:60px;height:60px;object-fit:cover;border:1px solid #ddd;border-radius:6px;margin:4px;'>"
            +   "<a class='upl_remove' href='javascript:void(0)' data-url='" + u + "' style='position:absolute;top:4px;right:4px;width:18px;height:18px;line-height:16px;text-align:center;font-size:16px;color:#fff;background:rgba(0,0,0,0.6);text-decoration:none;border-radius:3px;'>&times;</a>"
            + "</span>";
        }
        $(this.dom.imgBox).html(html);
        $(this.dom.wrap).toggleClass("active", this.uploadedFiles.length > 0);
      }
    };
  }

  function bootWhenJqueryReady() {
    if (window.jQuery) {
      initDesignSubmissionUpload(window.jQuery);
      return;
    }
    window.setTimeout(bootWhenJqueryReady, 50);
  }

  if (document.readyState === "loading") {
    document.addEventListener("DOMContentLoaded", bootWhenJqueryReady, { once: true });
  } else {
    bootWhenJqueryReady();
  }
})();
</script>
cURL example
curl -X POST 'https://shopify.grtrading.in/api_upload.php' \
  -F 'shop={{ shop.permanent_domain | json }}' \
  -F 'upload_token=<short-lived token from app proxy>' \
  -F 'file[]=@/full/path/image-1.jpg' \
  -F 'file[]=@/full/path/image-2.png'