Customizing the Strapi upload plugin

15th Dec, 2022


Update: If you are trying to do this for Strapi v4.15.1+, please refer this blog post as well.

Note: If you are just interested in the code changes to make this customization happen, please check out this github repo.

1. Goal

My goal with this post is to describe how Strapi’s upload plugin can be customized to enable uploading assets to different destinations based on what the Admin UI user selects during the file upload.

Leveraging these changes, we can customize the Strapi upload plugin to do things like -

  • Upload certain files to S3, while the others to a local destination.
  • Set certain uploaded files as private while others as public.
  • Pass additional metadata about the uploaded files to the custom upload provider.

2. Challenges with customizing the upload plugin

Modifying the schema.json for the plugin datatype models or extending the server-side API can be achieved by Strapi plugins extension. But, this does not cover customizing the Admin panel UI part of the plugin.

As a result, customizing the admin UI part for any Strapi plugin requires using patch-package. A limitation of using patch-package is that the changes need to be carefully evaluated everytime the customized plugin version is updated.

3. Steps to customize the upload plugin

3.1 Setting things up:

  • Let’s create a new strapi project with yarn create strapi-app strapi-customize-upload to have a fresh running strapi instance.
A fresh running Strapi instance
  • Since we are using patch-package to perform our customizations, let’s install it via yarn add patch-package

3.2 Writing a custom provider for the upload plugin:

  • We want to create the custom provider within our code repository (and not as a separate npm package). So, we create it within our repository:
mkdir providers
mkdir providers/strapi-provider-upload-custom

  • Next up, rather than writing the provider code from an empty slate, we can copy the code from providers/upload-aws-s3. (This means copying all the files and folders at this URL to the providers/strapi-provider-upload-custom folder).
  • We now change the following within providers/strapi-provider-upload-custom/package.json
"name": "strapi-provider-upload-custom",
"version": "0.0.1",
"description": "Custom provider for strapi-upload plugin",

  • Let’s update the actual provider code so that it supports uploading to two different locations:
const S3LocationDefault = new AWS.S3({
      apiVersion: '2006-03-01',
      ...config.s3LocationDefault,
    });

const S3Location2 = new AWS.S3({
      apiVersion: '2006-03-01',
      ...config.s3Location2,
    });

  • We will have the Strapi Admin UI send additional metadata (file.uploadDestination) that will let our provider code decide where to upload a certain file:
let S3 = file.uploadDestination && 
          file.uploadDestination === 's3Location2' ? 
            S3Location2 : S3LocationDefault;

  • We will also make the provider store this additional information about the file within the database so that this can be used for future actions (like deleting the uploaded file):
file.provider_metadata = {
    uploadDestination: file.uploadDestination || 
        's3LocationDefault'};

  • The complete modified provider file is here.

3.3 Passing config to our custom provider:

  • The config.s3LocationDefault and the config.s3Location2 used within our custom provider code in the previous section need to be defined within our plugin config (config/plugins.js):
module.exports = ({ env }) => ({
    // ...
    upload: {
      config: {
        provider: 'strapi-provider-upload-custom',
        providerOptions: {
            s3LocationDefault : {
                accessKeyId: env('AWS_ACCESS_KEY_ID'),
                secretAccessKey: env('AWS_ACCESS_SECRET'),
                region: env('AWS_REGION'),
                params: {
                  Bucket: env('AWS_BUCKET'),
                },      
            },
            s3Location2 : {
                accessKeyId: env('AWS_ACCESS_KEY_ID_2'),
                secretAccessKey: env('AWS_ACCESS_SECRET_2'),
                region: env('AWS_REGION_2'),
                params: {
                  Bucket: env('AWS_BUCKET_2'),
                },      
            },
        },
      },
    },
    // ...
  });

  • We need to provide the above variables within our .env file.
  • We also need to enable our Content Security Policy to allow the files to be loaded from the two destinations by adding the following within config/middlewares.js:
{
    name: 'strapi::security',
    config: {
      contentSecurityPolicy: {
        useDefaults: true,
        directives: {
          'connect-src': ["'self'", 'https:'],
          'img-src': ["'self'", 'data:', 'blob:', process.env.AWS_BUCKET + ".s3." + process.env.AWS_REGION + ".amazonaws.com", process.env.AWS_BUCKET + ".s3.amazonaws.com",  process.env.AWS_BUCKET_2 + ".s3." + process.env.AWS_REGION_2 + ".amazonaws.com", process.env.AWS_BUCKET_2 + ".s3.amazonaws.com"],
          'media-src': ["'self'", 'data:', 'blob:', process.env.AWS_BUCKET + ".s3." + process.env.AWS_REGION + ".amazonaws.com", process.env.AWS_BUCKET + ".s3.amazonaws.com", process.env.AWS_BUCKET_2 + ".s3." + process.env.AWS_REGION_2 + ".amazonaws.com",process.env.AWS_BUCKET_2 + ".s3.amazonaws.com"], 
          upgradeInsecureRequests: null,
        }
      }
    }
  },

  • We need to install the packages within the local providers/strapi-provider-upload-custom folder via yarn install
  • We can now add our locally created strapi-provider-upload-custom to our Strapi setup by adding the following line within the main package.json (see this file for reference):
"strapi-provider-upload-custom": "file:providers/strapi-provider-upload-custom"

  • We now need to install, build and run our main Strapi setup:
yarn
yarn build
yarn develop

  • We can now test if our strapi-provider-upload-custom is working as expected by trying to upload a few files via the Strapi Admin UI. At this point, all the uploaded files will go to the default S3 bucket (specified within .env AWS_BUCKET variable).
Testing the custom upload provider

3.4 Customizing the Upload plugin Admin UI:

  • Since we are going to leverage patch-package for this part of the customization, we need to modify the node_modules/@strapi/plugin-upload code on our local setup. (I prefer opening this folder in a separate instance of my code editor).
  • We specifically want to add the dropdown to allow the admin user to select an upload destination:
Enable selection of upload destination
  • This change needs to go into the admin/src/components/UploadAssetDialog/PendingAssetStep/PendingAssetStep.js file.
  • Here, we add our new field to the UI code and a React state variable to hold the value:
const UploadDestinationSelect = ({value, setValue}) => {
  return (
     <Combobox label="Upload Destination" value={value} onChange={setValue}>
          <ComboboxOption value="s3LocationDefault">Default Upload Destination</ComboboxOption>
          <ComboboxOption value="s3Location2">Upload Destination 2</ComboboxOption>
        </Combobox>
  );
}

const [selectedDestination, setSelectedDestination] = useState('s3LocationDefault');

  • We then pass the selected destination to the next screen by creating a custom property uploadDestination:
asset.uploadDestination = selectedDestination

  • Next up, we modify the plugin-upload/admin/src/hooks/useUpload.js to pass this asset.uploadDestination value via the HTTP POST request it makes to upload the selected file.
  const { rawFile, caption, name, alternativeText, uploadDestination } = asset;
  ...
  ...
    formData.append(
    'fileInfo',
    JSON.stringify({
      name,
      caption,
      alternativeText,
      folder: folderId,
      uploadDestination
    })
  );

  • On the server side, the uploaded asset’s fileInfo should now be expected to contain uploadDestination (like the rest of the properties).
  • On the server side, we now need to make changes so that the received uploadDestination gets passed to our custom provider. For this, we change plugin-upload/server/services/upload.js.
    const entity = {
      name: usedName,
      alternativeText: fileInfo.alternativeText,
      ...
      ...
      ...
      uploadDestination: fileInfo.uploadDestination
    };

  • Once the above changes are made, we run npx patch-package @strapi/plugin-upload to create our patch-package file (complete patch file generated can be seen here).
  • We now need to rebuild our Strapi admin via yarn build and then run yarn develop.

4. Verifying our customization:

  • At this point, we can check if our changes are working as expected by uploading two files via the Strapi upload plugin - one for each destination.
  • We can verify the URLs of the uploaded files to confirm if things are working as expected.
Verifying our upload customization
Punit Sethi
Punit Sethi
My tryst with Strapi:

Back in mid-2021, one of my clients was having issues with their in-house CMS. Despite me being their frontend architect, they trusted me to build their CMS solution. At this point, evaluation of various frameworks, approaches and products led me to Strapi. And, I ended up implementing Strapi CMS for their data requirements.

Fast-forward to now, I have worked with multiple orgs on implementing, upgrading & customizing Strapi setups based on their requirements.

Read my other posts on customizing Strapi.


Copyright (c) 2017-2024 Tezify All Rights Reserved. Created in India. GSTIN : 24BBQPS3732P1ZW.