Upload your media on your Google Cloud Storage bucket with this media provider
released June 24, 2025
npm install @strapi-community/strapi-provider-upload-google-cloud-storage
Community Google Cloud Storage Provider for Strapi Upload
strapi::security middlewaresInstall the package from your app root directory
with npm
npm install @strapi-community/strapi-provider-upload-google-cloud-storage --saveor yarn
yarn add @strapi-community/strapi-provider-upload-google-cloud-storageThe bucket should be created with fine grained access control, as the plugin will configure uploaded files with public read access.
If you are deploying to a Google Cloud Platform product that supports Application Default Credentials (such as App Engine, Cloud Run, and Cloud Functions etc.), then you can skip this step.
If you are deploying outside GCP, then follow these steps to set up authentication:
JSON for Key Typestring or JSON, be careful with indentation)You will find below many examples of configurations, for each example :
#bucketName# field and replace Bucket-name by yours previously createbaseUrl is working, but you can replace it by yours (if you use a custom baseUrl)Example with application default credentials (minimal setup)
This works only for deployment to GCP products such as App Engine, Cloud Run, and Cloud Functions etc.
Edit ./config/plugins.js
module.exports = {
upload: {
config: {
provider: '@strapi-community/strapi-provider-upload-google-cloud-storage',
providerOptions: {
bucketName: '#bucketName#',
publicFiles: false,
uniform: false,
basePath: '',
},
},
},
//...
}If you set publicFiles to false, the assets will be signed on the Content Manager (not the Content API). Consequently, they will only be visible to users who are authenticated.
You can set the expiry time of the signed URL by setting the expires option in the providerOptions object. See more in expires.
Example with credentials for outside GCP account
Edit ./config/plugins.js
module.exports = {
upload: {
config: {
provider: '@strapi-community/strapi-provider-upload-google-cloud-storage',
providerOptions: {
bucketName: '#bucketName#',
publicFiles: true,
uniform: false,
serviceAccount: {}, // replace `{}` with your serviceAccount JSON object
baseUrl: 'https://storage.googleapis.com/{bucket-name}',
basePath: '',
},
},
},
//...
}If you have different upload provider by environment, you can override plugins.js file by environment :
config/env/development/plugins.jsconfig/env/production/plugins.jsThis file, under config/env/{env}/ will be overriding default configuration present in main folder config.
Example with environment variable
module.exports = ({ env }) => ({
upload: {
config: {
provider: '@strapi-community/strapi-provider-upload-google-cloud-storage',
providerOptions: {
serviceAccount: env.json('GCS_SERVICE_ACCOUNT'),
bucketName: env('GCS_BUCKET_NAME'),
basePath: env('GCS_BASE_PATH'),
baseUrl: env('GCS_BASE_URL'),
publicFiles: env('GCS_PUBLIC_FILES'),
uniform: env('GCS_UNIFORM'),
},
},
},
//...
});Environment variable can be changed has your way.
strapi::security middlewares to avoid CSP blocked urlEdit ./config/middlewares.js
img-src and media-src add your own CDN url, by default it's storage.googleapis.com but you need to add your own CDN urlmodule.exports = [
'strapi::errors',
{
name: 'strapi::security',
config: {
contentSecurityPolicy: {
useDefaults: true,
directives: {
'connect-src': ["'self'", 'https:'],
'img-src': ["'self'", 'data:', 'blob:', 'storage.googleapis.com'],
'media-src': ["'self'", 'data:', 'blob:', 'storage.googleapis.com'],
upgradeInsecureRequests: null,
},
},
},
},
'strapi::cors',
'strapi::poweredBy',
'strapi::logger',
'strapi::query',
'strapi::body',
'strapi::favicon',
'strapi::public',
];serviceAccount :JSON data provide by Google Account (explained before).
For GCP environments (App Engine, Cloud Run, Cloud Functions, GKE, Compute Engine): You can leave this omitted, and authentication will work automatically using Application Default Credentials (ADC). The provider will detect the GCP environment and attempt to use ADC for signing URLs.
For non-GCP environments (local development, on-premises, other cloud providers):
You must provide explicit service account credentials with both client_email and private_key fields for signed URL generation when publicFiles is set to false.
Behavior:
Can be set as a String, JSON Object, or omitted.
Example for GCP environments (minimal setup):
// No serviceAccount needed - uses ADC automatically
{
bucketName: 'my-bucket',
publicFiles: false, // Signed URLs will work with ADC
}Example for non-GCP environments:
{
bucketName: 'my-bucket',
publicFiles: false,
serviceAccount: {
project_id: 'your-project',
client_email: 'your-service-account@your-project.iam.gserviceaccount.com',
private_key: '-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n'
}
}bucketName :The name of the bucket on Google Cloud Storage.
You can find more information on Google Cloud documentation.
baseUrl :Define your base Url, first is default value :
basePath :Define base path to save each media document.
publicFiles:Boolean to define a public attribute to file when upload file to storage.
trueuniform:Boolean to define uniform access, when uniform bucket-level access is enabled.
falseskipCheckBucket:Boolean to define skipCheckBucket, when skipCheckBucket is enabled, we skip to check if the bucket exist. It's useful for private bucket.
falsecacheMaxAge:Number to set the cache-control header for uploaded files in seconds. This value is used by the default metadata function to set the Cache-Control header as public, max-age=${cacheMaxAge}.
3600 (1 hour)Example:
module.exports = {
upload: {
config: {
provider: '@strapi-community/strapi-provider-upload-google-cloud-storage',
providerOptions: {
bucketName: 'my-bucket',
cacheMaxAge: 604800, // 7 days in seconds
},
},
},
};Note: If you provide a custom metadata function, the cacheMaxAge option will be ignored. You'll need to handle caching in your custom metadata function if needed.
gzip:Value to define if files are uploaded and stored with gzip compression.
true, false, autoautoexpires:Value to define expiration time for signed URLS. Files are signed when publicFiles is set to false.
Date, number, string900000 (15 minutes)604800000 (7 days)metadata:Function that is executed to compute the metadata for a file when it is uploaded.
When no function is provided, the following metadata is used (using the configured cacheMaxAge value):
{
contentDisposition: `inline; filename="${file.name}"`,
cacheControl: `public, max-age=${cacheMaxAge}`, // Uses the cacheMaxAge from your configuration
}undefinedExample:
metadata: (file: File) => ({
cacheControl: `public, max-age=${60 * 60 * 24 * 7}`, // One week
contentLanguage: 'en-US',
contentDisposition: `attachment; filename="${file.name}"`,
}),The available properties can be found in the Cloud Storage JSON API documentation.
generateUploadFileName:Function that is executed to generate the name of the uploaded file. This method can give more control over the file name and can for example be used to include a custom hashing function or dynamic path.
When no function is provided, the default algorithm is used.
undefinedExample:
generateUploadFileName: async (basePath: string, file: File) => {
const hash = await ...; // Some hashing function, for example MD-5
const extension = file.ext.toLowerCase().substring(1);
return `${extension}/${slugify(path.parse(file.name).name)}-${hash}.${extension}`;
},getContentType:Function that is executed to get the content type for a file when it is uploaded.
When no function is provided, the following content type is used:
file.mimeImportant: When a custom getContentType function is provided, the file's MIME type will be updated both in Google Cloud Storage metadata and in the Strapi database to ensure consistency.
undefinedExample:
getContentType: (file: File) => {
// Custom logic to determine content type
if (file.ext === '.csv') {
return 'text/csv';
}
return file.mime; // Fallback to original MIME type
},Note: This function affects both the contentType set in Google Cloud Storage and the mime field stored in the Strapi database.
Error uploading file to Google Cloud Storage: Cannot insert legacy ACL for an object when uniform bucket-level access is enabled
When this error occurs, you need to set uniform variable to true.
Error: Error parsing data "Service Account JSON", please be sure to copy/paste the full JSON file
When this error occurs, it's probably because you have missed something with the service account json configuration.
Follow this step :
ServiceAccount json fileServiceAccount in plugins.js config file in JSONError: Cannot generate signed URLs without service account credentials
This error occurs when:
publicFiles: false (requiring signed URLs)serviceAccount credentialsSolutions:
Storage Object Admin or Storage Admin role)serviceAccount configuration with client_email and private_keypublicFiles: true to use direct URLs instead of signed URLsError: Failed to generate signed URL in GCP environment
This error occurs in GCP environments when Application Default Credentials (ADC) cannot sign URLs, typically due to:
Solutions:
Storage Object Admin or Storage Admin roleserviceAccount credentials if ADC continues to failYou can also used official support platform of Strapi, and search [VirtusLab] prefixed people (maintainers)
See the MIT License file for licensing information.
Share your work with the community and get it listed in the Strapi ecosystem for everyone to discover and use.
Submit
