{"_id":"59365227e16643001bac504c","category":{"_id":"59365227e16643001bac5034","version":"59365226e16643001bac5030","project":"543026235eceb608003fde5f","__v":0,"sync":{"url":"","isSync":false},"reference":false,"createdAt":"2016-09-19T11:41:31.988Z","from_sync":false,"order":3,"slug":"test","title":"Advanced"},"project":"543026235eceb608003fde5f","user":"5736eb0b1a48812200566f0d","parentDoc":null,"version":{"_id":"59365226e16643001bac5030","project":"543026235eceb608003fde5f","__v":1,"createdAt":"2017-06-06T06:56:38.999Z","releaseDate":"2017-06-06T06:56:38.999Z","categories":["59365227e16643001bac5031","59365227e16643001bac5032","59365227e16643001bac5033","59365227e16643001bac5034"],"is_deprecated":false,"is_hidden":false,"is_beta":true,"is_stable":true,"codename":"","version_clean":"1.0.0","version":"1.0.0"},"__v":0,"updates":[],"next":{"pages":[],"description":""},"createdAt":"2017-06-05T12:56:01.457Z","link_external":false,"link_url":"","githubsync":"","sync_unique":"","hidden":false,"api":{"results":{"codes":[]},"settings":"","auth":"required","params":[],"url":""},"isReference":false,"order":0,"body":"[block:api-header]\n{\n  \"title\": \"General\"\n}\n[/block]\nIt 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.\n\nThe 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. \n\n[block:callout]\n{\n  \"type\": \"warning\",\n  \"title\": \"Beta Feature\",\n  \"body\": \"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.\"\n}\n[/block]\n\n[block:api-header]\n{\n  \"title\": \"Using a custom storage adapter\"\n}\n[/block]\nBy default Ghost stores images on your filesystem. The default location is the Ghost content path in your Ghost folder under `content/images`. It might be that you have configured a custom [content path](https://docs.ghost.org/docs/configuring-ghost#section-paths).\n\n\n## Config\n\nYour [custom configuration file](doc:configuring-ghost) will need to be updated to provide config for your new storage module and set it as active.\n\n```\nstorage: {\n    active: 'my-module',\n    'my-module': {\n        key: 'abcdef'\n    }\n}, \n```\n\nThe `storage` block should have 2 items:\n\n1. an `active` key, which contains the name\\* of your module\n2. a key which reflects the name\\* of your module, containing any config your module needs\n\n\n## Known custom storage adapters\n[block:callout]\n{\n  \"type\": \"warning\",\n  \"title\": \"Warning\",\n  \"body\": \"The known custom storage adapters might not work yet with `1.0` beta.\"\n}\n[/block]\n- [local-file-store](https://github.com/TryGhost/Ghost/blob/0304816/core/server/storage/local-file-store.js) (Ghost's default) saves images to the local filesystem\n- [http-store](https://gist.github.com/ErisDS/559e11bf3e84b89a9594) passes image requests through to an HTTP endpoint\n- [s3-store](https://github.com/spanishdict/ghost-s3-compat) saves to Amazon S3 and proxies requests to S3\n- [s3-store](https://github.com/colinmeinke/ghost-storage-adapter-s3) saves to Amazon S3 and works with 0.10+\n- [qn-store](https://github.com/Minwe/qn-store) saves to [Qiniu storage](http://www.qiniu.com/)\n- [ghost-cloudinary-store](https://github.com/sethbrasile/ghost-cloudinary-store) saves to [Cloudinary](http://cloudinary.com/)\n- [upyun-ghost-store](https://github.com/sanddudu/upyun-ghost-store) saves to [Upyun](https://www.upyun.com)\n- [ghost-google-drive](https://github.com/robincsamuel/ghost-google-drive) saves to [Google drive](https://drive.google.com)\n- [ghost-azure-storage](https://github.com/tparnell8/ghost-azurestorage) saves to [azure storage](https://azure.microsoft.com/en-us/services/storage/)\n- [ghost-imgur](https://github.com/wrenth04/ghost-imgur) saves to [Imgur](http://imgur.com/)\n- [google-cloud-storage](https://github.com/thombuchi/ghost-google-cloud-storage)\n- [ghost-oss-store](https://github.com/MT-Libraries/ghost-oss-store) saves to [Aliyun OSS](https://www.aliyun.com/product/oss)\n- [ghost-b2](https://github.com/martiendt/ghost-storage-adapter-b2)\n[block:api-header]\n{\n  \"title\": \"Creating a custom storage adapter\"\n}\n[/block]\nIn order to replace the storage module, here are the basic requirements.\n\n## Location\n\n1. Create a new folder named `storage` inside `content/adapters`.\n2. Inside of `content/adapters/storage` you can then either create a folder or a file e.g. `content/adapters/storage/my-module.js` or `content/adapters/storage/my-module`\n2.1. If you would like to create a folder, create a file called `index.js` in your new folder.\n2.2 If you would like to create a file, create a file named e.g. `my-module.js`.\n[block:callout]\n{\n  \"type\": \"info\",\n  \"title\": \"Note\",\n  \"body\": \"If you are using a [custom content path](doc:configuring-ghost#section-paths), you have to use this location, otherwise Ghost can't detect your new module.\"\n}\n[/block]\n## Base Adapter Class Inheritance\n\nYour custom storage adapter must inherit from your [Base Storage Adapter](https://github.com/TryGhost/Ghost-Storage-Base). By default the Base Storage Adapter is installed via Ghost and should be available in your custom adapter.\n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"'use strict';\\n\\nvar BaseAdapter = require('ghost-storage-base');\\n\\nclass MyCustomAdapter extends BaseAdapter{\\n  constructor() {\\n    super();\\n  }\\n}\\n\\nmodule.exports = MyCustomAdapter;\",\n      \"language\": \"javascript\",\n      \"name\": \"Class Inheritance\"\n    }\n  ]\n}\n[/block]\n\n[block:callout]\n{\n  \"type\": \"info\",\n  \"title\": \"Note\",\n  \"body\": \"`module.exports.MyCustomAdaper = MyCustomAdapter;` won't work, because the storage logic in core can't guess how your adapter is called.\"\n}\n[/block]\n## Required methods\n\nYour custom adapter must implement four required functions.\n\n- save\n- exists\n- serve\n- delete\n- read\n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"'use strict';\\n\\nvar BaseAdapter = require('ghost-storage-base');\\n\\nclass MyCustomAdapter extends BaseAdapter{\\n  constructor() {\\n    super();\\n  }\\n  \\n  exists() {\\n\\n  }\\n\\n  save() {\\n\\n  }\\n\\n  serve() {\\n    return function customServe(req, res, next) {\\n      next();\\n    }\\n  }\\n\\n  delete() {\\n\\n  }\\n\\n  read() {\\n\\n  }\\n}\\n\\nmodule.exports = MyCustomAdapter;\",\n      \"language\": \"javascript\",\n      \"name\": \"Required methods\"\n    }\n  ]\n}\n[/block]\n## Methods explained\n\n### `save`\n\n\n[block:parameters]\n{\n  \"data\": {\n    \"h-0\": \"Usages\",\n    \"h-1\": \"Arguments\",\n    \"h-2\": \"Returns\",\n    \"0-0\": \"- [upload images](https://github.com/TryGhost/Ghost/blob/master/core/server/api/upload.js#L24)\\n- [import images](https://github.com/TryGhost/Ghost/tree/master/core/server/data/importer/importers/image.js#L66)\",\n    \"0-1\": \"`image` - an image object with properties name and path\\n\\n`targetDir` [optional] - a path to where to store the image\\n\\nSee example implementation [here](https://github.com/TryGhost/Ghost/blob/master/core/server/adapters/storage/LocalFileStorage.js#L35).\",\n    \"0-2\": \"A *promise* which resolves to the full URI of the image, either relative to the blog or absolute.\"\n  },\n  \"cols\": 3,\n  \"rows\": 1\n}\n[/block]\nThe `.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.\n\n### `serve`\n[block:parameters]\n{\n  \"data\": {\n    \"h-0\": \"Usages\",\n    \"h-1\": \"Arguments\",\n    \"h-2\": \"Returns\",\n    \"0-0\": \"Ghost calls `.serve()` as part of its middleware stack, and mounts the returned function as the middleware for serving images.\\n\\n- [express middleware](https://github.com/TryGhost/Ghost/blob/master/core/server/blog/app.js#L63)\",\n    \"0-1\": \"no arguments\\n\\nSee example implementation [here](https://github.com/TryGhost/Ghost/blob/master/core/server/adapters/storage/LocalFileStorage.js#L80).\",\n    \"0-2\": \"A *function* which can be used as express middleware.\"\n  },\n  \"cols\": 3,\n  \"rows\": 1\n}\n[/block]\n\n[block:callout]\n{\n  \"type\": \"info\",\n  \"title\": \"Note\",\n  \"body\": \"If your module's `.save()` method returns absolute URLs, the implementation of `.serve()` can be empty.\"\n}\n[/block]\nIf 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](http://expressjs.com/4x/api.html#res) documentation for more details.\n\n### `getUniqueFileName`\n[block:parameters]\n{\n  \"data\": {\n    \"h-0\": \"Usages\",\n    \"h-1\": \"Arguments\",\n    \"h-2\": \"Returns\",\n    \"0-0\": \"- [image importer](https://github.com/TryGhost/Ghost/blob/master/core/server/data/importer/handlers/image.js#L39)\",\n    \"0-1\": \"`image` - an image object with name and path properties\",\n    \"0-2\": \"A *promise* which resolves to a unique target filename.\"\n  },\n  \"cols\": 3,\n  \"rows\": 1\n}\n[/block]\nThis method 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.\n\n### `exists`\n[block:parameters]\n{\n  \"data\": {\n    \"h-0\": \"Usages\",\n    \"h-1\": \"Arguments\",\n    \"h-2\": \"Returns\",\n    \"0-0\": \"Is used by the Base storage adapter to check whether a file exists or not.\",\n    \"0-1\": \"`filename` - the name of the file which is being uploaded.\",\n    \"0-2\": \"A *promise* which resolves to `true` or `false` depending on whether or not the given image has already been stored.\"\n  },\n  \"cols\": 3,\n  \"rows\": 1\n}\n[/block]\n## Make your module available\n\nIt's good practise to create a public Github repository to make your module available for others.\nYou can share your new module in [our slack channel](https://slack.ghost.org) or you can request a suggestion by pressing the `Suggest Edits` button on top.\n\nTake a look at [existing modules](https://docs.ghost.org/docs/using-a-custom-storage-module#section-known-custom-storage-adapters) and learn from them.\n\n## Example implementation\n\nTake a look at our [default local storage implementation](https://github.com/TryGhost/Ghost/blob/master/core/server/adapters/storage/LocalFileStorage.js).","excerpt":"","slug":"using-a-custom-storage-module","type":"basic","title":"Using a custom storage module"}

Using a custom storage module


[block:api-header] { "title": "General" } [/block] 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. [block:callout] { "type": "warning", "title": "Beta Feature", "body": "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." } [/block] [block:api-header] { "title": "Using a custom storage adapter" } [/block] By default Ghost stores images on your filesystem. The default location is the Ghost content path in your Ghost folder under `content/images`. It might be that you have configured a custom [content path](https://docs.ghost.org/docs/configuring-ghost#section-paths). ## Config Your [custom configuration file](doc:configuring-ghost) will need to be updated to provide config for your new storage module and set it as active. ``` 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 ## Known custom storage adapters [block:callout] { "type": "warning", "title": "Warning", "body": "The known custom storage adapters might not work yet with `1.0` beta." } [/block] - [local-file-store](https://github.com/TryGhost/Ghost/blob/0304816/core/server/storage/local-file-store.js) (Ghost's default) saves images to the local filesystem - [http-store](https://gist.github.com/ErisDS/559e11bf3e84b89a9594) passes image requests through to an HTTP endpoint - [s3-store](https://github.com/spanishdict/ghost-s3-compat) saves to Amazon S3 and proxies requests to S3 - [s3-store](https://github.com/colinmeinke/ghost-storage-adapter-s3) saves to Amazon S3 and works with 0.10+ - [qn-store](https://github.com/Minwe/qn-store) saves to [Qiniu storage](http://www.qiniu.com/) - [ghost-cloudinary-store](https://github.com/sethbrasile/ghost-cloudinary-store) saves to [Cloudinary](http://cloudinary.com/) - [upyun-ghost-store](https://github.com/sanddudu/upyun-ghost-store) saves to [Upyun](https://www.upyun.com) - [ghost-google-drive](https://github.com/robincsamuel/ghost-google-drive) saves to [Google drive](https://drive.google.com) - [ghost-azure-storage](https://github.com/tparnell8/ghost-azurestorage) saves to [azure storage](https://azure.microsoft.com/en-us/services/storage/) - [ghost-imgur](https://github.com/wrenth04/ghost-imgur) saves to [Imgur](http://imgur.com/) - [google-cloud-storage](https://github.com/thombuchi/ghost-google-cloud-storage) - [ghost-oss-store](https://github.com/MT-Libraries/ghost-oss-store) saves to [Aliyun OSS](https://www.aliyun.com/product/oss) - [ghost-b2](https://github.com/martiendt/ghost-storage-adapter-b2) [block:api-header] { "title": "Creating a custom storage adapter" } [/block] In order to replace the storage module, here are the basic requirements. ## Location 1. Create a new folder named `storage` inside `content/adapters`. 2. Inside of `content/adapters/storage` you can then either create a folder or a file e.g. `content/adapters/storage/my-module.js` or `content/adapters/storage/my-module` 2.1. If you would like to create a folder, create a file called `index.js` in your new folder. 2.2 If you would like to create a file, create a file named e.g. `my-module.js`. [block:callout] { "type": "info", "title": "Note", "body": "If you are using a [custom content path](doc:configuring-ghost#section-paths), you have to use this location, otherwise Ghost can't detect your new module." } [/block] ## Base Adapter Class Inheritance Your custom storage adapter must inherit from your [Base Storage Adapter](https://github.com/TryGhost/Ghost-Storage-Base). By default the Base Storage Adapter is installed via Ghost and should be available in your custom adapter. [block:code] { "codes": [ { "code": "'use strict';\n\nvar BaseAdapter = require('ghost-storage-base');\n\nclass MyCustomAdapter extends BaseAdapter{\n constructor() {\n super();\n }\n}\n\nmodule.exports = MyCustomAdapter;", "language": "javascript", "name": "Class Inheritance" } ] } [/block] [block:callout] { "type": "info", "title": "Note", "body": "`module.exports.MyCustomAdaper = MyCustomAdapter;` won't work, because the storage logic in core can't guess how your adapter is called." } [/block] ## Required methods Your custom adapter must implement four required functions. - save - exists - serve - delete - read [block:code] { "codes": [ { "code": "'use strict';\n\nvar BaseAdapter = require('ghost-storage-base');\n\nclass MyCustomAdapter extends BaseAdapter{\n constructor() {\n super();\n }\n \n exists() {\n\n }\n\n save() {\n\n }\n\n serve() {\n return function customServe(req, res, next) {\n next();\n }\n }\n\n delete() {\n\n }\n\n read() {\n\n }\n}\n\nmodule.exports = MyCustomAdapter;", "language": "javascript", "name": "Required methods" } ] } [/block] ## Methods explained ### `save` [block:parameters] { "data": { "h-0": "Usages", "h-1": "Arguments", "h-2": "Returns", "0-0": "- [upload images](https://github.com/TryGhost/Ghost/blob/master/core/server/api/upload.js#L24)\n- [import images](https://github.com/TryGhost/Ghost/tree/master/core/server/data/importer/importers/image.js#L66)", "0-1": "`image` - an image object with properties name and path\n\n`targetDir` [optional] - a path to where to store the image\n\nSee example implementation [here](https://github.com/TryGhost/Ghost/blob/master/core/server/adapters/storage/LocalFileStorage.js#L35).", "0-2": "A *promise* which resolves to the full URI of the image, either relative to the blog or absolute." }, "cols": 3, "rows": 1 } [/block] 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` [block:parameters] { "data": { "h-0": "Usages", "h-1": "Arguments", "h-2": "Returns", "0-0": "Ghost calls `.serve()` as part of its middleware stack, and mounts the returned function as the middleware for serving images.\n\n- [express middleware](https://github.com/TryGhost/Ghost/blob/master/core/server/blog/app.js#L63)", "0-1": "no arguments\n\nSee example implementation [here](https://github.com/TryGhost/Ghost/blob/master/core/server/adapters/storage/LocalFileStorage.js#L80).", "0-2": "A *function* which can be used as express middleware." }, "cols": 3, "rows": 1 } [/block] [block:callout] { "type": "info", "title": "Note", "body": "If your module's `.save()` method returns absolute URLs, the implementation of `.serve()` can be empty." } [/block] 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](http://expressjs.com/4x/api.html#res) documentation for more details. ### `getUniqueFileName` [block:parameters] { "data": { "h-0": "Usages", "h-1": "Arguments", "h-2": "Returns", "0-0": "- [image importer](https://github.com/TryGhost/Ghost/blob/master/core/server/data/importer/handlers/image.js#L39)", "0-1": "`image` - an image object with name and path properties", "0-2": "A *promise* which resolves to a unique target filename." }, "cols": 3, "rows": 1 } [/block] This method 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. ### `exists` [block:parameters] { "data": { "h-0": "Usages", "h-1": "Arguments", "h-2": "Returns", "0-0": "Is used by the Base storage adapter to check whether a file exists or not.", "0-1": "`filename` - the name of the file which is being uploaded.", "0-2": "A *promise* which resolves to `true` or `false` depending on whether or not the given image has already been stored." }, "cols": 3, "rows": 1 } [/block] ## Make your module available It's good practise to create a public Github repository to make your module available for others. You can share your new module in [our slack channel](https://slack.ghost.org) or you can request a suggestion by pressing the `Suggest Edits` button on top. Take a look at [existing modules](https://docs.ghost.org/docs/using-a-custom-storage-module#section-known-custom-storage-adapters) and learn from them. ## Example implementation Take a look at our [default local storage implementation](https://github.com/TryGhost/Ghost/blob/master/core/server/adapters/storage/LocalFileStorage.js).