Plugin Development for Craft Cloud

As long as your plugin’s design and implementation follows our established best practices, it should work on Craft Cloud without changes. All Craft features are available on Cloud, making the platform’s architectural differences inconsequential when using official, documented APIs.

Plugins must be compatible with at least Craft 4.5.10 (the minimum version of Craft required to run on Cloud), but they may support earlier versions.

Want to test your plugin on Cloud? Get in touch for a free sandbox environment!

Special Considerations #

There are still a few things about Craft Cloud that may require reevaluation of some assumptions about how web applications are run. The Cloud module and Craft itself provide everything you need to make your plugin reliable and resilient across the widest possible variety of hosting platforms.

Ephemeral Filesystem #

Craft Cloud sets the CRAFT_EPHEMERAL config override, which tells Craft to treat file writes as unreliable (or downright impossible). Plugins should honor this setting by checking craft\helpers\App::isEphemeral() before trying to perform any writes to the disk.

If you must write files to disk, use the /tmp directory. Do not hard-code this path, or construct it dynamically. Instead, use Craft’s Path service to get information about system directories’ locations:

$path = Craft::$app->getPath();

$tmp = $path->getTempPath();
$storage = $path->getStoragePath();
$cache = $path->getCachePath();

// ...


Writing logs directly to a file is not recommended. Always use the Logger component (Craft::$app->getLogger()) or the static Craft::info(), Craft::warning(), and Craft::error() convenience methods to ensure logs are sent to Cloud’s aggregated log target.

File Responses #

In situations where a controller needs to return a file, it should instead upload the artifact to S3 and redirect to a pre-signed URL. For control panel requests, the Cloud module handles this automatically; $this->sendFile($path) and any other methods that set a response’s stream property will work normally.

The web runtime is implicitly authorized to interact with its environment’s storage bucket—see the Cloud module for an example of how we get a URL to an uploaded object.

Asset Filesystems #

While we recommend that projects on Cloud use our first-party Cloud filesystem, it is not a requirement. Filesystem types provided by plugins may need to implement a custom uploader to send binary files directly to the storage provider (rather than POST them directly to the Craft application).

The Cloud filesystem’s client-side and server-side code is available for reference.

Other File Uploads #

In situations where you need a file from a user (like a plugin that works with CSVs), consider providing an asset selection input, instead:

{% import forms from '_includes/forms' %}

{{ forms.elementSelect({
  name: 'file',
  elementType: 'craft\\elements\\Asset',
  limit: 1,
}) }}

Doing so will take advantage of the existing volumes and filesystems, while still providing access to the underlying file’s content via craft\elements\Asset::getStream().

Other Best Practices #

Plugins that already embrace our existing coding guidelines and best practices should be Cloud-ready—or take significantly less time to make compatible.

In addition, these tips can help avoid hiccups when your plugins are deployed to Craft Cloud:

  • Asset BundlessourcePath must begin with your plugin’s predefined alias.
    • Assets must be registered for use in the front-end via craft\web\View::registerAssetBundle(). Publishing one-off assets is not supported—if you want an asset to be available at runtime, it must be encapsulated in a bundle.
    • Bundle classes must be instantiable even if Craft is not fully installed, or cannot connect to a database. Plugin settings and other system state may be unavailable to the build environment when we are publishing assets.
    • Your plugin’s main composer.json should always have a type of craft-plugin, and any downstream packages containing asset bundles (i.e. utilities shared by multiple plugins) must have a type beginning with craft or yii to be picked up by our publishing routine.
  • Implement batched jobs if your workload is apt to exceed 15 minutes. It is often better to spawn many small jobs than a single long-running one.
  • Don’t set cookies unless absolutely necessary—like in response to a user action.
    • If possible, register JavaScript to fetch CSRF tokens asynchronously. Using {{ csrfInput() }} in a front-end template will immediately set a cookie and prevent caching.

Publishing Your Plugin #

Cloud-compatible plugins go through the same submission and approval process as regular plugins. Once you’ve verified your plugin works on Cloud, be sure and check the Tested on Craft Cloud in its management screen on Craft Console!

Getting Help #

Please reach out to us if you have questions about your plugin’s compatibility.

Applies to Craft CMS 4 and Craft Cloud.