Skip to content

Using a custom storage module

Colin Meinke edited this page Sep 17, 2016 · 18 revisions

Breaking Changes

0.10.0

In 0.10.0 we have added a small storage validation. This validation ensures that a minimum of required functions are implemented (save, exists, serve, delete). If you install 0.10.0 and your custom storage adapter has no implementation of e.g. exists, node won't start.

Besides to that, your custom storage adapter needs to call the constructor of the Base class, see https://github.com/TryGhost/Ghost/blob/master/core/server/storage/local-file-store.js#L16.

General

As of Ghost 0.6.0, it is possible to easily replace the storage layer which handles images with custom code. This means you can send your images to a 3rd party image service, CDN, or database without changing Ghost core.

The storage layer is used to store images both from an upload via the admin UI or API, and also when images are included in a zip file uploaded to the importer. The methods needed and how they are used by Ghost is described in the Storage API section below.

Note: this really is a beta-level developer feature! Please do use it, but keep an eye on the release notes for each version, as there may be significant changes to how it works in a patch release of Ghost. See the Planned changes section for more info.

Basic requirements

In order to replace the storage module, the basic requirements are:

  • Create a new folder inside /content called /storage
  • Inside of /storage you need to create your new module
  • Your module must export a constructor function that accepts a config option
  • Your module must also provide 2 required methods: save and serve
  • Depending on expected usage, your module may also need to require exists or getUniqueFileName
  • Your config.js file will need to be updated to provide config for your new storage module and set it as active.

Config.js

In your config.js file, you'll need to add a new storage block to whichever environment you want to change:

storage: {
    active: 'my-module',
    'my-module': {
        key: 'abcdef'
    }
}, 

The storage block should have 2 items:

  1. an active key, which contains the name* of your module
  2. a key which reflects the name* of your module, containing any config your module needs

* The name of your module is whatever it is called in the filesystem as per usual with node e.g. you may have content/storage/my-module.js or content/storage/my-module/index.js.

Storage API

Internally, Ghost expects there to be four methods available on the storage class: save and serve are strictly required. exists and getUniqueFileName are also used by the image importer. Please be aware that there are currently no checks to ensure that the methods are present and correct.

.save(image, [targetDir])

  • Usage: Called both in uploads and the importer to store user-provided images.
  • Takes:
    • image - the image object provided by the uploader/importer, has the properties name and path.
    • [targetDir] - optionally provided by the importer, to override the relative directory path for the image.
  • Returns: A promise which resolves to the full URI of the image, either relative to the blog or absolute.

On upload, .save() is passed an image object which has both a name and path. The .save() method is expected to store the image and return a promise which resolves to the path from which the image should be requested in future.

.serve()

  • Usage: Called to get a function which can be used as express middleware for serving images.
  • Takes: no arguments
  • Returns: a function which can be used as express middleware

Ghost calls storage.serve() as part of its middleware stack, and mounts the returned function as the middleware for serving images. If your module's .save() method returns absolute URLs, .serve() can be a no-op passthrough middleware function:

serve = function() {
    return function (req, res, next) {
      next();
    };
};

If your module needs to serve the images itself, it should use req.path to resolve the file to be served, and use res to send the image to the browser. See the express documentation for more details.

.getUniqueFileName(store, image, targetDir)

Note: .getUniqueFileName() is implemented in storage/base.js and can be optionally overridden. It is called by the image importer to determine the new path for an image, but assumes that path will be relative to the blog. More work is required to make it possible for custom storage modules that resolve absolute image URIs to work with the image importer (see Planned changes below).

  • Usage: Used in the image importer to determine the unique version of the filename (e.g. my-image-2.jpg if my-image.jpg already exists).
  • Takes:
    • store an instance of the storage module
    • image an image object with name and path properties
  • Returns: A promise which resolves to a unique target filename.

.exists(filename)

  • Usage: Called by .getUniqueFileName() in storage/base.js to determine if an image with this name exists already or not.
  • Takes: filename the name of the file which is being uploaded.
  • Returns: A promise which resolves to true or false depending on whether or not the given image has already been stored.

As exists is currently only used by the .getUniqueFileName() function, it only needs to be implemented if .getUniqueFileName() is not overridden. If you create your own version of .getUniqueFileName() then you may or may not need it.

Creating a module

Your module can depend on any npm module that Ghost already has installed. This includes useful modules like bluebird, fs-extra and request. You can also include a package.json inside of your module to add extra dependencies, but you would need to run npm install locally to your module for this to work.

The easiest way to implement a storage module is to require and extend the base storage module from Ghost. You can create a storage module without doing this, but it means implementing more of the methods yourself.

More documentation on this coming soon, in the meantime, check out these examples:

Planned Changes

As it says at the top of this page, this is a beta developer feature and if you're using it you should expect that it may fundamentally change in any version of Ghost.

In the near future, core/server/storage/base.js will likely be released as an independent npm module which can be required, with useful helper functions to do with file handling.

At the moment, the image handler has a little bit too much logic in it, which means it only works with custom storage modules in a limited capacity. This will likely be reworked so that the importer will work reliably with custom storage modules that resolve image URIs to absolute URLs.

It is also desirable to add checks to the storage handling which check that a storage module has all of the necessary methods and provides feedback if there are problems.

Additionally, we may add some magic to Ghost to help with npm installing storage modules.

Clone this wiki locally