├── .eslintignore ├── playground ├── src │ ├── api │ │ └── .gitkeep │ ├── extensions │ │ └── .gitkeep │ ├── admin │ │ ├── webpack.config.example.js │ │ └── app.example.js │ └── index.js ├── public │ ├── uploads │ │ └── .gitkeep │ └── robots.txt ├── database │ └── migrations │ │ └── .gitkeep ├── .eslintignore ├── favicon.png ├── config │ ├── api.js │ ├── admin.js │ ├── server.js │ ├── plugins.js │ ├── middlewares.js │ └── database.js ├── .env.example ├── .editorconfig ├── .eslintrc ├── package.json ├── .gitignore └── README.md ├── .github ├── FUNDING.yml └── workflows │ ├── gh-release.yml │ └── docs.yml ├── .eslintrc ├── strapi-server.js ├── docs ├── public │ ├── icon.png │ ├── uil-image-resize-landscape.svg │ ├── uil-image-resize-landscape-dark.svg │ ├── logo-rest-cache-light.svg │ ├── fluent-emoji-cloud-with-lightning.svg │ └── logo-rest-cache-dark.svg ├── .vitepress │ ├── theme │ │ ├── custom.css │ │ ├── index.js │ │ └── Layout.vue │ └── config.js ├── index.md └── guide │ ├── index.md │ └── modifiers.md ├── src ├── index.js ├── config │ ├── schema.js │ └── index.js ├── register.js └── middleware.js ├── README.md ├── package.json ├── .gitignore └── CHANGELOG.md /.eslintignore: -------------------------------------------------------------------------------- 1 | *.json 2 | *.md -------------------------------------------------------------------------------- /playground/src/api/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /playground/public/uploads/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /playground/src/extensions/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /playground/database/migrations/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | open_collective: strapi 2 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@strapi-community" 3 | } -------------------------------------------------------------------------------- /playground/.eslintignore: -------------------------------------------------------------------------------- 1 | .cache 2 | build 3 | **/node_modules/** 4 | -------------------------------------------------------------------------------- /strapi-server.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const plugin = require('./src') 4 | 5 | module.exports = plugin 6 | -------------------------------------------------------------------------------- /docs/public/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/strapi-community/strapi-plugin-local-image-sharp/HEAD/docs/public/icon.png -------------------------------------------------------------------------------- /playground/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/strapi-community/strapi-plugin-local-image-sharp/HEAD/playground/favicon.png -------------------------------------------------------------------------------- /playground/config/api.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | rest: { 3 | defaultLimit: 25, 4 | maxLimit: 100, 5 | withCount: true, 6 | }, 7 | }; 8 | -------------------------------------------------------------------------------- /playground/public/robots.txt: -------------------------------------------------------------------------------- 1 | # To prevent search engines from seeing the site altogether, uncomment the next two lines: 2 | # User-Agent: * 3 | # Disallow: / 4 | -------------------------------------------------------------------------------- /playground/.env.example: -------------------------------------------------------------------------------- 1 | HOST=0.0.0.0 2 | PORT=1337 3 | APP_KEYS="toBeModified1,toBeModified2" 4 | API_TOKEN_SALT=tobemodified 5 | ADMIN_JWT_SECRET=tobemodified 6 | JWT_SECRET=tobemodified 7 | -------------------------------------------------------------------------------- /playground/config/admin.js: -------------------------------------------------------------------------------- 1 | module.exports = ({ env }) => ({ 2 | auth: { 3 | secret: env('ADMIN_JWT_SECRET'), 4 | }, 5 | apiToken: { 6 | salt: env('API_TOKEN_SALT'), 7 | }, 8 | }); 9 | -------------------------------------------------------------------------------- /playground/config/server.js: -------------------------------------------------------------------------------- 1 | module.exports = ({ env }) => ({ 2 | host: env('HOST', '0.0.0.0'), 3 | port: env.int('PORT', 1337), 4 | app: { 5 | keys: env.array('APP_KEYS'), 6 | }, 7 | }); 8 | -------------------------------------------------------------------------------- /docs/.vitepress/theme/custom.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --vp-c-brand: #8e75ff; 3 | --vp-c-brand-light: #a091ed; 4 | --vp-c-brand-lighter: #a091ed; 5 | --vp-c-brand-dark: #8e75ff; 6 | --vp-c-brand-darker: #8e75ff; 7 | } -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { config } = require('./config'); 4 | const { register } = require('./register') 5 | 6 | const plugin = { 7 | config, 8 | register, 9 | } 10 | 11 | module.exports = plugin -------------------------------------------------------------------------------- /playground/config/plugins.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | 'local-image-sharp': { 5 | config: { 6 | cacheDir: '.image-cache', // Can be set with env var STRAPI_PLUGIN_LOCAL_IMAGE_SHARP_CACHE_DIR 7 | }, 8 | }, 9 | }; 10 | -------------------------------------------------------------------------------- /docs/.vitepress/theme/index.js: -------------------------------------------------------------------------------- 1 | import DefaultTheme from 'vitepress/theme' 2 | import Layout from './Layout.vue' 3 | import './custom.css' 4 | 5 | export default { 6 | ...DefaultTheme, 7 | // override the Layout with a wrapper component that 8 | // injects the slots 9 | Layout 10 | } -------------------------------------------------------------------------------- /playground/config/middlewares.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | 'strapi::errors', 3 | 'strapi::security', 4 | 'strapi::cors', 5 | 'strapi::poweredBy', 6 | 'strapi::logger', 7 | 'strapi::query', 8 | 'strapi::body', 9 | 'strapi::session', 10 | 'strapi::favicon', 11 | 'strapi::public', 12 | ]; 13 | -------------------------------------------------------------------------------- /src/config/schema.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const yup = require('yup'); 4 | 5 | const pluginConfigSchema = yup.object().shape({ 6 | cacheDir: yup.string(), 7 | maxAge: yup.number().moreThan(0), 8 | paths: yup.array().of(yup.string()), 9 | }); 10 | 11 | module.exports = { 12 | pluginConfigSchema, 13 | }; 14 | -------------------------------------------------------------------------------- /playground/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [{package.json,*.yml}] 12 | indent_style = space 13 | indent_size = 2 14 | 15 | [*.md] 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /playground/src/admin/webpack.config.example.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* eslint-disable no-unused-vars */ 4 | module.exports = (config, webpack) => { 5 | // Note: we provide webpack above so you should not `require` it 6 | // Perform customizations to webpack config 7 | // Important: return the modified config 8 | return config; 9 | }; 10 | -------------------------------------------------------------------------------- /playground/config/database.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = ({ env }) => ({ 4 | connection: { 5 | client: 'sqlite', 6 | connection: { 7 | filename: path.join( 8 | __dirname, 9 | '..', 10 | env('DATABASE_FILENAME', '.tmp/data.db') 11 | ), 12 | }, 13 | useNullAsDefault: true, 14 | }, 15 | }); 16 | -------------------------------------------------------------------------------- /src/config/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { pluginConfigSchema } = require('./schema'); 4 | 5 | module.exports = { 6 | config: { 7 | default: ({ env }) => ({ 8 | cacheDir: env('STRAPI_PLUGIN_LOCAL_IMAGE_SHARP_CACHE_DIR', ''), 9 | maxAge: 3600, 10 | paths: ['/uploads'] 11 | }), 12 | validator(config) { 13 | pluginConfigSchema.validateSync(config); 14 | }, 15 | } 16 | }; 17 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: home 3 | 4 | hero: 5 | name: Local Image Sharp 6 | text: Transform images on demand with sharp 7 | tagline: Dynamically resize, format and optimize images based on url modifiers. 8 | image: 9 | light: /uil-image-resize-landscape.svg 10 | dark: /uil-image-resize-landscape-dark.svg 11 | alt: "" 12 | actions: 13 | - theme: brand 14 | text: Guide 15 | link: /guide/ 16 | - theme: alt 17 | text: View on GitHub 18 | link: https://github.com/strapi-community/strapi-plugin-local-image-sharp 19 | --- 20 | -------------------------------------------------------------------------------- /playground/src/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | /** 5 | * An asynchronous register function that runs before 6 | * your application is initialized. 7 | * 8 | * This gives you an opportunity to extend code. 9 | */ 10 | register(/*{ strapi }*/) {}, 11 | 12 | /** 13 | * An asynchronous bootstrap function that runs before 14 | * your application gets started. 15 | * 16 | * This gives you an opportunity to set up your data model, 17 | * run jobs, or perform some special logic. 18 | */ 19 | bootstrap(/*{ strapi }*/) {}, 20 | }; 21 | -------------------------------------------------------------------------------- /playground/src/admin/app.example.js: -------------------------------------------------------------------------------- 1 | const config = { 2 | locales: [ 3 | // 'ar', 4 | // 'fr', 5 | // 'cs', 6 | // 'de', 7 | // 'dk', 8 | // 'es', 9 | // 'he', 10 | // 'id', 11 | // 'it', 12 | // 'ja', 13 | // 'ko', 14 | // 'ms', 15 | // 'nl', 16 | // 'no', 17 | // 'pl', 18 | // 'pt-BR', 19 | // 'pt', 20 | // 'ru', 21 | // 'sk', 22 | // 'sv', 23 | // 'th', 24 | // 'tr', 25 | // 'uk', 26 | // 'vi', 27 | // 'zh-Hans', 28 | // 'zh', 29 | ], 30 | }; 31 | 32 | const bootstrap = (app) => { 33 | console.log(app); 34 | }; 35 | 36 | export default { 37 | config, 38 | bootstrap, 39 | }; 40 | -------------------------------------------------------------------------------- /playground/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@babel/eslint-parser", 3 | "extends": ["eslint:recommended"], 4 | "env": { 5 | "commonjs": true, 6 | "es6": true, 7 | "node": true, 8 | "browser": false 9 | }, 10 | "parserOptions": { 11 | "requireConfigFile": false, 12 | "ecmaFeatures": { 13 | "experimentalObjectRestSpread": true, 14 | "jsx": false 15 | }, 16 | "sourceType": "module" 17 | }, 18 | "globals": { 19 | "strapi": true 20 | }, 21 | "rules": { 22 | "indent": ["error", 2, { "SwitchCase": 1 }], 23 | "linebreak-style": ["error", "unix"], 24 | "no-console": 0, 25 | "quotes": ["error", "single"], 26 | "semi": ["error", "always"] 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /playground/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "playground", 3 | "private": true, 4 | "version": "0.1.0", 5 | "description": "A Strapi application", 6 | "scripts": { 7 | "develop": "strapi develop", 8 | "start": "strapi start", 9 | "build": "strapi build", 10 | "strapi": "strapi" 11 | }, 12 | "devDependencies": {}, 13 | "dependencies": { 14 | "@strapi/strapi": "4.5.5", 15 | "@strapi/plugin-users-permissions": "4.5.5", 16 | "@strapi/plugin-i18n": "4.5.5", 17 | "strapi-plugin-local-image-sharp": "link:..", 18 | "better-sqlite3": "7.4.6" 19 | }, 20 | "author": { 21 | "name": "A Strapi developer" 22 | }, 23 | "strapi": { 24 | "uuid": "de665554-7499-4829-b112-0a4ded35dc98" 25 | }, 26 | "engines": { 27 | "node": ">=14.19.1 <=18.x.x", 28 | "npm": ">=6.0.0" 29 | }, 30 | "license": "MIT" 31 | } 32 | -------------------------------------------------------------------------------- /.github/workflows/gh-release.yml: -------------------------------------------------------------------------------- 1 | name: release 2 | 3 | on: 4 | # trigger release on every tag push 5 | push: 6 | tags: 7 | - 'v*.*.*' 8 | 9 | jobs: 10 | release: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | # extract tag from the github ref (e.g. refs/tags/v1.2.3) 15 | - name: Set up tag meta 16 | id: meta 17 | run: | 18 | echo ::set-output name=tag::${GITHUB_REF#refs/tags/} 19 | 20 | # create a new release on github with discussion 21 | - name: Create release 22 | uses: softprops/action-gh-release@v1 23 | env: 24 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 25 | with: 26 | tag_name: ${{ steps.meta.outputs.tag }} 27 | name: Release ${{ steps.meta.outputs.tag }} 28 | body: View [CHANGELOG.md](https://github.com/strapi-community/strapi-plugin-local-images-sharp/blob/main/CHANGELOG.md) for details 29 | draft: false 30 | prerelease: false -------------------------------------------------------------------------------- /docs/.vitepress/theme/Layout.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 32 | 33 | -------------------------------------------------------------------------------- /docs/public/uil-image-resize-landscape.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/public/uil-image-resize-landscape-dark.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/register.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Router = require('@koa/router') 4 | const { createIPX } = require('ipx') 5 | const { resolve } = require('path') 6 | const { existsSync, mkdirSync } = require('fs') 7 | const { createMiddleware } = require('./middleware') 8 | 9 | function register({ strapi }) { 10 | const config = strapi.config.get('plugin.local-image-sharp') 11 | config.srcDir = strapi.dirs?.static?.public ?? strapi.dirs?.public 12 | 13 | strapi.log.info( 14 | `Using Local Image Sharp plugin` 15 | ); 16 | strapi.log.info( 17 | `- Source directory: ${config.srcDir}` 18 | ); 19 | 20 | if (config.cacheDir) { 21 | const cwd = process.cwd() 22 | config.cacheDir = resolve(cwd, config.cacheDir) 23 | 24 | // prevent cache directory from being in source directory 25 | if (config.cacheDir.startsWith(config.srcDir)) { 26 | throw new Error('Cache directory cannot be inside source directory') 27 | } 28 | 29 | // check if directory exists 30 | if (!existsSync(config.cacheDir)) { 31 | mkdirSync(config.cacheDir, { recursive: true }) 32 | } 33 | 34 | strapi.log.info( 35 | `- Cache directory: ${config.cacheDir}` 36 | ); 37 | } 38 | 39 | const router = new Router() 40 | config.paths.forEach(path => { 41 | const ipx = createIPX({ 42 | dir: config.srcDir + path, 43 | }) 44 | 45 | router.get(`${path}/(.*)`, createMiddleware(ipx)) 46 | }) 47 | 48 | strapi.server.use(router.routes()) 49 | } 50 | 51 | module.exports = { 52 | register, 53 | } 54 | -------------------------------------------------------------------------------- /playground/.gitignore: -------------------------------------------------------------------------------- 1 | .image-cache 2 | 3 | ############################ 4 | # OS X 5 | ############################ 6 | 7 | .DS_Store 8 | .AppleDouble 9 | .LSOverride 10 | Icon 11 | .Spotlight-V100 12 | .Trashes 13 | ._* 14 | 15 | 16 | ############################ 17 | # Linux 18 | ############################ 19 | 20 | *~ 21 | 22 | 23 | ############################ 24 | # Windows 25 | ############################ 26 | 27 | Thumbs.db 28 | ehthumbs.db 29 | Desktop.ini 30 | $RECYCLE.BIN/ 31 | *.cab 32 | *.msi 33 | *.msm 34 | *.msp 35 | 36 | 37 | ############################ 38 | # Packages 39 | ############################ 40 | 41 | *.7z 42 | *.csv 43 | *.dat 44 | *.dmg 45 | *.gz 46 | *.iso 47 | *.jar 48 | *.rar 49 | *.tar 50 | *.zip 51 | *.com 52 | *.class 53 | *.dll 54 | *.exe 55 | *.o 56 | *.seed 57 | *.so 58 | *.swo 59 | *.swp 60 | *.swn 61 | *.swm 62 | *.out 63 | *.pid 64 | 65 | 66 | ############################ 67 | # Logs and databases 68 | ############################ 69 | 70 | .tmp 71 | *.log 72 | *.sql 73 | *.sqlite 74 | *.sqlite3 75 | 76 | 77 | ############################ 78 | # Misc. 79 | ############################ 80 | 81 | *# 82 | ssl 83 | .idea 84 | nbproject 85 | public/uploads/* 86 | !public/uploads/.gitkeep 87 | 88 | ############################ 89 | # Node.js 90 | ############################ 91 | 92 | lib-cov 93 | lcov.info 94 | pids 95 | logs 96 | results 97 | node_modules 98 | .node_history 99 | 100 | ############################ 101 | # Tests 102 | ############################ 103 | 104 | testApp 105 | coverage 106 | 107 | ############################ 108 | # Strapi 109 | ############################ 110 | 111 | .env 112 | license.txt 113 | exports 114 | *.cache 115 | dist 116 | build 117 | .strapi-updater.json 118 | -------------------------------------------------------------------------------- /docs/.vitepress/config.js: -------------------------------------------------------------------------------- 1 | import { createRequire } from 'module' 2 | import { defineConfig } from 'vitepress' 3 | 4 | const require = createRequire(import.meta.url) 5 | const pkg = require('../../package.json') 6 | 7 | export default defineConfig({ 8 | title: "Local Image Sharp", 9 | description: "Dynamically resize, format and optimize images based on url modifiers.", 10 | base: "/strapi-plugin-local-image-sharp/", 11 | lastUpdated: true, 12 | themeConfig: { 13 | socialLinks: [ 14 | { icon: 'github', link: 'https://github.com/strapi-community/strapi-plugin-local-image-sharp' }, 15 | ], 16 | editLink: { 17 | pattern: 'https://github.com/strapi-community/strapi-plugin-local-image-sharp/edit/main/docs/:path', 18 | text: 'Edit this page on GitHub' 19 | }, 20 | logo: { 21 | src: "/icon.png", 22 | }, 23 | outline: [2,3], 24 | footer: { 25 | message: 'Made with ❤️ by Strapi Community' 26 | }, 27 | nav: [ 28 | { 29 | text: "Guide", 30 | link: "/guide/", 31 | activeMatch: '/guide/', 32 | }, 33 | { 34 | text: pkg.version, 35 | items: [ 36 | { 37 | text: 'Changelog', 38 | link: 'https://github.com/strapi-community/strapi-plugin-local-image-sharp/blob/main/CHANGELOG.md' 39 | }, 40 | { 41 | text: 'Strapi Community', 42 | link: 'https://github.com/strapi-community' 43 | } 44 | ] 45 | } 46 | ], 47 | sidebar: { 48 | '/guide/': [ 49 | { 50 | text: 'Guide', 51 | items: [ 52 | { text: 'Quick Start Guide', link: '/guide/' }, 53 | { text: 'Modifiers', link: '/guide/modifiers' }, 54 | ] 55 | }, 56 | ], 57 | } 58 | } 59 | }) 60 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 |

Local Image Sharp

3 | 4 |

Dynamically resize, format and optimize images based on url modifiers.

5 | 6 |

7 | 8 | Strapi Discord 9 | 10 | 11 | NPM Version 12 | 13 | 14 | Monthly download on NPM 15 | 16 |

17 |
18 | 19 | ## Table of Contents 20 | 21 | - [✨ Features](#-features) 22 | - [🖐 Installation](#-installation) 23 | - [🚚 Usage](#-usage) 24 | - [Contributing](#contributing) 25 | - [License](#license) 26 | 27 | 28 | ## ✨ Features 29 | 30 | Convert any uploaded images with local provider using sharp modifier. 31 | No extra configuration needed, the modifiers will be applied based on the url. 32 | 33 | This is made by [ipx](https://github.com/unjs/ipx) 34 | 35 | ## 🚚 Getting Started 36 | 37 | [Read the Docs to Learn More.](https://strapi-community.github.io/strapi-plugin-local-image-sharp/) 38 | 39 | ## Contributing 40 | 41 | I/We are actively looking for contributors, maintainers, and others to help shape this package. As this plugins sole purpose within the Strapi community is to be used by other developers and plugin maintainers to get fast responses time. 42 | 43 | If interested please feel free to email the lead maintainer Sacha at: sacha@digisquad.io or ping `stf#3254` on Discord. 44 | 45 | ## License 46 | 47 | See the [LICENSE](./LICENSE.md) file for licensing information. -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: docs 2 | 3 | on: 4 | # trigger deployment on push to main branch and if docs/ is updated 5 | push: 6 | branches: [main] 7 | paths: 8 | - "docs/**" 9 | # trigger deployment manually 10 | workflow_dispatch: 11 | 12 | jobs: 13 | docs: 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - uses: actions/checkout@v2 18 | with: 19 | # fetch all commits to get last updated time or other git log info 20 | fetch-depth: 0 21 | 22 | - name: Setup Node.js 23 | uses: actions/setup-node@v1 24 | with: 25 | # choose node.js version to use 26 | node-version: "14" 27 | 28 | # cache node_modules 29 | - name: Cache dependencies 30 | uses: actions/cache@v2 31 | id: yarn-cache 32 | with: 33 | path: | 34 | **/node_modules 35 | key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} 36 | restore-keys: | 37 | ${{ runner.os }}-yarn- 38 | 39 | # install dependencies if the cache did not hit 40 | - name: Install dependencies 41 | if: steps.yarn-cache.outputs.cache-hit != 'true' 42 | run: yarn --frozen-lockfile 43 | 44 | # run build script 45 | - name: Build VitePress site 46 | run: yarn docs:build 47 | 48 | # please check out the docs of the workflow for more details 49 | # @see https://github.com/crazy-max/ghaction-github-pages 50 | - name: Deploy to GitHub Pages 51 | uses: crazy-max/ghaction-github-pages@v2 52 | with: 53 | # deploy to gh-pages branch 54 | target_branch: gh-pages 55 | # deploy the default output dir of VitePress 56 | build_dir: docs/.vitepress/dist 57 | env: 58 | # @see https://docs.github.com/en/actions/reference/authentication-in-a-workflow#about-the-github_token-secret 59 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 60 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "strapi-plugin-local-image-sharp", 3 | "version": "1.7.0", 4 | "description": "Dynamically resize, format and optimize images based on url modifiers", 5 | "strapi": { 6 | "displayName": "Local Image Sharp", 7 | "name": "local-image-sharp", 8 | "description": "Dynamically resize, format and optimize images based on url modifiers", 9 | "required": false, 10 | "kind": "plugin" 11 | }, 12 | "keywords": [ 13 | "strapi", 14 | "plugin", 15 | "images", 16 | "sharp", 17 | "optimize", 18 | "resize" 19 | ], 20 | "files": [ 21 | "src", 22 | "strapi-server.js" 23 | ], 24 | "scripts": { 25 | "docs:dev": "vitepress dev docs", 26 | "docs:build": "vitepress build docs", 27 | "docs:serve": "vitepress serve docs", 28 | "eslint": "eslint .", 29 | "eslint:fix": "eslint . --fix", 30 | "release": "npx standard-version && git push --follow-tags origin main && npm publish" 31 | }, 32 | "dependencies": { 33 | "@koa/router": "^10.1.1", 34 | "etag": "^1.8.1", 35 | "ipx": "^0.9.10", 36 | "ohash": "^1.0.0", 37 | "qs": "^6.10.3", 38 | "ufo": "^0.8.5" 39 | }, 40 | "peerDependencies": { 41 | "@strapi/strapi": "^4.0.0", 42 | "yup": "^0.32.9" 43 | }, 44 | "devDependencies": { 45 | "@babel/eslint-parser": "^7.19.1", 46 | "@giscus/vue": "^2.2.6", 47 | "@strapi-community/eslint-config": "^0.4.3", 48 | "eslint": "8.20.0", 49 | "vitepress": "^1.0.0-alpha.35" 50 | }, 51 | "author": { 52 | "name": "Sacha Stafyniak", 53 | "email": "sacha@digisquad.io", 54 | "url": "https://digisquad.io" 55 | }, 56 | "maintainers": [ 57 | { 58 | "name": "Strapi Community", 59 | "url": "https://github.com/strapi-community" 60 | }, 61 | { 62 | "name": "Sacha Stafyniak", 63 | "email": "sacha@digisquad.io", 64 | "url": "https://digisquad.io", 65 | "lead": true 66 | } 67 | ], 68 | "bugs": { 69 | "url": "https://github.com/strapi-community/strapi-plugin-local-image-sharp/issues" 70 | }, 71 | "homepage": "https://github.com/strapi-community/strapi-plugin-local-image-sharp", 72 | "engines": { 73 | "node": ">=14.x.x <=18.x.x", 74 | "npm": ">=6.0.0" 75 | }, 76 | "license": "MIT" 77 | } 78 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ############################ 2 | # OS X 3 | ############################ 4 | 5 | .DS_Store 6 | .AppleDouble 7 | .LSOverride 8 | Icon 9 | .Spotlight-V100 10 | .Trashes 11 | ._* 12 | 13 | 14 | ############################ 15 | # Linux 16 | ############################ 17 | 18 | *~ 19 | 20 | 21 | ############################ 22 | # Windows 23 | ############################ 24 | 25 | Thumbs.db 26 | ehthumbs.db 27 | Desktop.ini 28 | $RECYCLE.BIN/ 29 | *.cab 30 | *.msi 31 | *.msm 32 | *.msp 33 | 34 | 35 | ############################ 36 | # Packages 37 | ############################ 38 | 39 | *.7z 40 | *.csv 41 | *.dat 42 | *.dmg 43 | *.gz 44 | *.iso 45 | *.jar 46 | *.rar 47 | *.tar 48 | *.zip 49 | *.com 50 | *.class 51 | *.dll 52 | *.exe 53 | *.o 54 | *.seed 55 | *.so 56 | *.swo 57 | *.swp 58 | *.swn 59 | *.swm 60 | *.out 61 | *.pid 62 | 63 | 64 | ############################ 65 | # Logs and databases 66 | ############################ 67 | 68 | .tmp 69 | *.log 70 | *.sql 71 | *.sqlite 72 | 73 | 74 | ############################ 75 | # Misc. 76 | ############################ 77 | 78 | *# 79 | .idea 80 | nbproject 81 | .vscode/ 82 | 83 | 84 | ############################ 85 | # Node.js 86 | ############################ 87 | 88 | lib-cov 89 | lcov.info 90 | pids 91 | logs 92 | results 93 | build 94 | node_modules 95 | .node_history 96 | package-lock.json 97 | **/package-lock.json 98 | !docs/package-lock.json 99 | *.heapsnapshot 100 | 101 | 102 | ############################ 103 | # Tests 104 | ############################ 105 | 106 | testApp 107 | coverage 108 | cypress/screenshots 109 | cypress/videos 110 | 111 | 112 | ############################ 113 | # Documentation 114 | ############################ 115 | 116 | dist 117 | 118 | ############################ 119 | # Builds 120 | ############################ 121 | 122 | packages/generators/app/files/public/ 123 | schema.graphql 124 | 125 | ############################ 126 | # Example app 127 | ############################ 128 | 129 | .dev 130 | # *.cache 131 | 132 | ############################ 133 | # Visual Studio Code 134 | ############################ 135 | 136 | front-workspace.code-workspace 137 | .yarn 138 | .yarnrc 139 | 140 | cache -------------------------------------------------------------------------------- /playground/README.md: -------------------------------------------------------------------------------- 1 | # 🚀 Getting started with Strapi 2 | 3 | Strapi comes with a full featured [Command Line Interface](https://docs.strapi.io/developer-docs/latest/developer-resources/cli/CLI.html) (CLI) which lets you scaffold and manage your project in seconds. 4 | 5 | ### `develop` 6 | 7 | Start your Strapi application with autoReload enabled. [Learn more](https://docs.strapi.io/developer-docs/latest/developer-resources/cli/CLI.html#strapi-develop) 8 | 9 | ``` 10 | npm run develop 11 | # or 12 | yarn develop 13 | ``` 14 | 15 | ### `start` 16 | 17 | Start your Strapi application with autoReload disabled. [Learn more](https://docs.strapi.io/developer-docs/latest/developer-resources/cli/CLI.html#strapi-start) 18 | 19 | ``` 20 | npm run start 21 | # or 22 | yarn start 23 | ``` 24 | 25 | ### `build` 26 | 27 | Build your admin panel. [Learn more](https://docs.strapi.io/developer-docs/latest/developer-resources/cli/CLI.html#strapi-build) 28 | 29 | ``` 30 | npm run build 31 | # or 32 | yarn build 33 | ``` 34 | 35 | ## ⚙️ Deployment 36 | 37 | Strapi gives you many possible deployment options for your project. Find the one that suits you on the [deployment section of the documentation](https://docs.strapi.io/developer-docs/latest/setup-deployment-guides/deployment.html). 38 | 39 | ## 📚 Learn more 40 | 41 | - [Resource center](https://strapi.io/resource-center) - Strapi resource center. 42 | - [Strapi documentation](https://docs.strapi.io) - Official Strapi documentation. 43 | - [Strapi tutorials](https://strapi.io/tutorials) - List of tutorials made by the core team and the community. 44 | - [Strapi blog](https://docs.strapi.io) - Official Strapi blog containing articles made by the Strapi team and the community. 45 | - [Changelog](https://strapi.io/changelog) - Find out about the Strapi product updates, new features and general improvements. 46 | 47 | Feel free to check out the [Strapi GitHub repository](https://github.com/strapi/strapi). Your feedback and contributions are welcome! 48 | 49 | ## ✨ Community 50 | 51 | - [Discord](https://discord.strapi.io) - Come chat with the Strapi community including the core team. 52 | - [Forum](https://forum.strapi.io/) - Place to discuss, ask questions and find answers, show your Strapi project and get feedback or just talk with other Community members. 53 | - [Awesome Strapi](https://github.com/strapi/awesome-strapi) - A curated list of awesome things related to Strapi. 54 | 55 | --- 56 | 57 | 🤫 Psst! [Strapi is hiring](https://strapi.io/careers). 58 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | ## [1.7.0](https://github.com/strapi-community/strapi-plugin-local-image-sharp/compare/v1.6.0...v1.7.0) (2023-07-31) 6 | 7 | 8 | ### Features 9 | 10 | * allow to configure custom paths ([#29](https://github.com/strapi-community/strapi-plugin-local-image-sharp/issues/29)) ([0992821](https://github.com/strapi-community/strapi-plugin-local-image-sharp/commit/0992821469bed878534ed8b1c5058447f2a8151d)) 11 | 12 | 13 | ### Bug Fixes 14 | 15 | * docs ([#22](https://github.com/strapi-community/strapi-plugin-local-image-sharp/issues/22)) ([c1519a4](https://github.com/strapi-community/strapi-plugin-local-image-sharp/commit/c1519a4b316a8a2501ac0f3b60029ab5a9102d75)) 16 | 17 | ## [1.6.0](https://github.com/strapi-community/strapi-plugin-local-image-sharp/compare/v1.4.0...v1.6.0) (2023-02-28) 18 | 19 | 20 | ### Features 21 | 22 | * add `maxAge` option for Cache-Control HTTP response header ([#20](https://github.com/strapi-community/strapi-plugin-local-image-sharp/issues/20)) ([d81843b](https://github.com/strapi-community/strapi-plugin-local-image-sharp/commit/d81843bdeb4fd8611873c73c8b1275569a9980dd)) 23 | 24 | ## [1.4.0](https://github.com/strapi-community/strapi-plugin-local-image-sharp/compare/v1.3.0...v1.4.0) (2023-01-06) 25 | 26 | 27 | ### Features 28 | 29 | * add cacheDir option to save generated file ([3b52e0f](https://github.com/strapi-community/strapi-plugin-local-image-sharp/commit/3b52e0f3b8ebfb537690bf970c0a340ddd36aeaa)) 30 | 31 | ## [1.3.0](https://github.com/strapi-community/strapi-plugin-local-image-sharp/compare/v1.2.1...v1.3.0) (2023-01-06) 32 | 33 | 34 | ### Features 35 | 36 | * add avif to allowed types ([26b256f](https://github.com/strapi-community/strapi-plugin-local-image-sharp/commit/26b256f8d334bd14652d3120ed279ec190374d45)) 37 | * create documentation with vitepress ([dd9b374](https://github.com/strapi-community/strapi-plugin-local-image-sharp/commit/dd9b374d94258841796fcb67088c4403f0ff85eb)) 38 | 39 | 40 | ### Bug Fixes 41 | 42 | * broken path modifiers ([5e75ff6](https://github.com/strapi-community/strapi-plugin-local-image-sharp/commit/5e75ff6c3601cfb6685c5722b2a7022fc8f2cd66)), closes [#12](https://github.com/strapi-community/strapi-plugin-local-image-sharp/issues/12) 43 | 44 | ### [1.2.1](https://github.com/strapi-community/strapi-plugin-local-image-sharp/compare/v1.2.0...v1.2.1) (2022-10-12) 45 | -------------------------------------------------------------------------------- /docs/guide/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Quick Start Guide 3 | --- 4 | 5 | # Quick Start Guide 6 | 7 | Convert any uploaded images with local provider using sharp modifier. 8 | No extra configuration needed, the modifiers will be applied based on the url. 9 | 10 | > This is made using [ipx](https://github.com/unjs/ipx) 11 | 12 | To install this plugin, run the following command in your Strapi project: 13 | 14 | ```bash 15 | yarn add strapi-plugin-local-image-sharp 16 | ``` 17 | 18 | ## Usage 19 | 20 | This plugin works by setting modifiers either the path, or in the query string parameters. 21 | 22 | - Original image: 23 | `http://localhost:1337/uploads/buffalo_56442f4096.png` 24 | - WebP (Path modifier): 25 | `http://localhost:1337/uploads/format_webp/buffalo_56442f4096.png` 26 | - WebP (Query parameters): 27 | `http://localhost:1337/uploads/buffalo_56442f4096.png?format=webp` 28 | 29 | ### Using path modifiers 30 | 31 | Change format to `webp` and keep other things same as source: 32 | 33 | `http://localhost:1337/uploads/f_webp/buffalo_56442f4096.png` 34 | 35 | Keep original format `png` and set width to `200`: 36 | 37 | `http://localhost:1337/uploads/w_200/buffalo_56442f4096.png` 38 | 39 | You can combine modifiers using a coma, for example: 40 | Resize to `200x200px` using `embed` method and change format to `webp`: 41 | 42 | `http://localhost:1337/uploads/embed,f_webp,s_200x200/buffalo_56442f4096.png` 43 | 44 | ### Using query parameters modifiers 45 | 46 | Change format to `webp` and keep other things same as source: 47 | 48 | `http://localhost:1337/uploads/buffalo_56442f4096.png?format=webp` 49 | 50 | Keep original format `png` and set width to `200`: 51 | 52 | `http://localhost:1337/uploads/buffalo_56442f4096.png?width=200` 53 | 54 | You can combine modifiers using a coma, for example: 55 | Resize to `200x200px` using `embed` method and change format to `webp`: 56 | 57 | `http://localhost:1337/uploads/buffalo_56442f4096.png?format=webp&resize=200x200&embed` 58 | 59 | ## Configuration 60 | 61 | ### `cacheDir` 62 | 63 | The directory where the generated files will be stored. 64 | 65 | > _By default, no value is set, so cache is disabled, meaning that the image will be generated on every request._ 66 | 67 | You can set the cache directory using `STRAPI_PLUGIN_LOCAL_IMAGE_SHARP_CACHE_DIR` environment variable. Or you can set it in `config/plugins.js`: 68 | 69 | ::: code-group 70 | 71 | ```bash [enviroment variables] 72 | STRAPI_PLUGIN_LOCAL_IMAGE_SHARP_CACHE_DIR=.image-cache yarn start 73 | # or STRAPI_PLUGIN_LOCAL_IMAGE_SHARP_CACHE_DIR=.image-cache yarn develop 74 | ``` 75 | 76 | ```js [config/plugins.js] 77 | "use strict"; 78 | 79 | module.exports = { 80 | // ... 81 | 82 | "local-image-sharp": { 83 | config: { 84 | cacheDir: ".image-cache", 85 | }, 86 | }, 87 | 88 | // ... 89 | }; 90 | ``` 91 | 92 | ::: 93 | 94 | ::: info 95 | When providing a relative path, it will be resolved from the root of your project. 96 | ::: 97 | 98 | ::: tip 99 | Don't forget to add `.image-cache` to your `.gitignore` file. 100 | ::: 101 | 102 | ### `maxAge` 103 | 104 | You can set the `Cache-Control` HTTP response header to improve the load performance. It's a good practice cache static resources using HTTP caching. [See more here](https://developer.chrome.com/docs/lighthouse/performance/uses-long-cache-ttl) 105 | 106 | ```js [config/plugins.js] 107 | "use strict"; 108 | 109 | module.exports = { 110 | // ... 111 | 112 | "local-image-sharp": { 113 | config: { 114 | maxAge: 31536000, // which corresponds to 1 year: 60 seconds × 60 minutes × 24 hours × 365 days = 31536000 seconds. 115 | }, 116 | }, 117 | 118 | // ... 119 | }; 120 | ``` 121 | 122 | ### `paths` 123 | 124 | Add custom paths to images if you use more than the default path '/uploads'. 125 | 126 | > _By default, '/uploads' is set, to cache all default upload routes._ 127 | 128 | You can set the paths in `config/plugins.js`: 129 | 130 | 131 | ```js [config/plugins.js] 132 | "use strict"; 133 | 134 | module.exports = { 135 | // ... 136 | 137 | "local-image-sharp": { 138 | config: { 139 | paths: ['/uploads','/custom'], 140 | }, 141 | }, 142 | 143 | // ... 144 | }; 145 | ``` -------------------------------------------------------------------------------- /src/middleware.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const qs = require('qs'); 4 | const { decode } = require('ufo'); 5 | const { hash } = require('ohash'); 6 | const { join } = require('path'); 7 | const { createReadStream, existsSync } = require('fs'); 8 | const { writeFile, readFile } = require('fs/promises'); 9 | const getEtag = require('etag'); 10 | 11 | function createMiddleware(ipx) { 12 | const config = strapi.config.get('plugin.local-image-sharp'); 13 | 14 | return async function ipxMiddleware(ctx, next) { 15 | let path = null; 16 | config.paths.forEach(target => { 17 | if (ctx.req.url.includes(target)) { 18 | path = ctx.req.url.split(target).join(''); 19 | } 20 | }); 21 | 22 | if (!path) { 23 | const statusCode = 500; 24 | const statusMessage = 'No path found'; 25 | strapi.log.debug(statusMessage); 26 | ctx.status = statusCode 27 | return; 28 | } 29 | 30 | const [url, query] = path.split('?'); 31 | const [firstSegment = '', ...idSegments] = url 32 | .substr(1 /* leading slash */) 33 | .split('/'); 34 | const allowedTypes = [ 35 | 'JPEG', 36 | 'PNG', 37 | 'GIF', 38 | 'SVG', 39 | 'TIFF', 40 | 'ICO', 41 | 'DVU', 42 | 'JPG', 43 | 'WEBP', 44 | 'AVIF', 45 | ]; 46 | let id; 47 | let modifiers; 48 | 49 | let tempFilePath; 50 | let tempTypePath; 51 | let tempEtagPath; 52 | 53 | // extract modifiers from query string 54 | if (!idSegments.length && firstSegment) { 55 | id = firstSegment; 56 | modifiers = qs.parse(query); 57 | } else { 58 | // extract modifiers from url segments 59 | id = decode(idSegments.join('/')); // decode is a shortend version of decodeURIComponent 60 | modifiers = Object.create(null); 61 | if (firstSegment !== '_') { 62 | for (const p of firstSegment.split(',')) { 63 | const [key, value = ''] = p.split('_'); 64 | modifiers[key] = decode(value); 65 | } 66 | } 67 | } 68 | 69 | // if no id or no modifiers or not allowed type, skip 70 | if ( 71 | !id || 72 | !Object.keys(modifiers).length || 73 | !allowedTypes.includes(id.split('.').pop().toUpperCase()) 74 | ) { 75 | await next(); 76 | return; 77 | } 78 | 79 | const objectHash = hash({ id, modifiers }); 80 | 81 | // If cache enabled, check if file exists 82 | if (config.cacheDir) { 83 | tempFilePath = join(config.cacheDir, `${objectHash}.raw`); 84 | tempTypePath = join(config.cacheDir, `${objectHash}.mime`); 85 | tempEtagPath = join(config.cacheDir, `${objectHash}.etag`); 86 | 87 | if (existsSync(tempFilePath)) { 88 | try { 89 | const [type, etag] = await Promise.all([ 90 | readFile(tempTypePath, 'utf-8'), 91 | readFile(tempEtagPath, 'utf-8'), 92 | ]); 93 | const stream = createReadStream(tempFilePath); 94 | 95 | ctx.set('ETag', etag); 96 | if (etag && ctx.req.headers['if-none-match'] === etag) { 97 | ctx.status = 304; 98 | return; 99 | } 100 | 101 | // Cache-Control 102 | if (config.maxAge) { 103 | ctx.set( 104 | 'Cache-Control', 105 | `max-age=${+config.maxAge}, public, s-maxage=${+config.maxAge}` 106 | ); 107 | } 108 | 109 | // Mime 110 | if (type) { 111 | ctx.set('Content-Type', type); 112 | } 113 | ctx.body = stream; 114 | return; 115 | } catch { 116 | // file not found, continue to generate fresh image 117 | } 118 | } 119 | } 120 | 121 | // Create request 122 | const img = ipx(id, modifiers, ctx.req.options); 123 | // Get image meta from source 124 | try { 125 | const src = await img.src(); 126 | 127 | // Caching headers 128 | if (src.mtime) { 129 | if (ctx.req.headers['if-modified-since']) { 130 | if (new Date(ctx.req.headers['if-modified-since']) >= src.mtime) { 131 | ctx.status = 304; 132 | return; 133 | } 134 | } 135 | ctx.set('Last-Modified', `${+src.mtime}`); 136 | } 137 | 138 | const maxAge = src.maxAge ?? config.maxAge; 139 | 140 | if (maxAge) { 141 | ctx.set( 142 | 'Cache-Control', 143 | `max-age=${+maxAge}, public, s-maxage=${+maxAge}` 144 | ); 145 | } 146 | 147 | // Get converted image 148 | const { data, format } = await img.data(); 149 | 150 | // ETag 151 | const etag = getEtag(data); 152 | 153 | // If cache enabled, write image to temp dir 154 | if (tempTypePath && tempFilePath) { 155 | Promise.all([ 156 | writeFile(tempTypePath, `image/${format}`, 'utf-8'), 157 | writeFile(tempEtagPath, etag, 'utf-8'), 158 | writeFile(tempFilePath, data), 159 | ]).catch(() => { 160 | // console.error(error); 161 | }); 162 | } 163 | 164 | ctx.set('ETag', etag); 165 | if (etag && ctx.req.headers['if-none-match'] === etag) { 166 | ctx.status = 304; 167 | return; 168 | } 169 | 170 | // Mime 171 | if (format) { 172 | ctx.set('Content-Type', `image/${format}`); 173 | } 174 | 175 | ctx.body = data; 176 | } catch (error) { 177 | const statusCode = parseInt(error.statusCode, 10) || 500; 178 | const statusMessage = error.message 179 | ? `IPX Error (${error.message})` 180 | : `IPX Error (${statusCode})`; 181 | strapi.log.debug(statusMessage); 182 | // console.error(error); 183 | 184 | ctx.status = statusCode; 185 | } 186 | }; 187 | } 188 | 189 | module.exports = { 190 | createMiddleware, 191 | }; 192 | -------------------------------------------------------------------------------- /docs/public/logo-rest-cache-light.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/public/fluent-emoji-cloud-with-lightning.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/public/logo-rest-cache-dark.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/guide/modifiers.md: -------------------------------------------------------------------------------- 1 | 2 | # Modifiers 3 | 4 | ## `width` / `w` 5 | 6 | Resize the image to the specified width in pixels. The height is calculated with the same aspect ratio. 7 | 8 | **Example (Path modifier)**: 9 | `http://localhost:1337/uploads/width_200/buffalo.png` 10 | 11 | **Example (Query parameter)**: 12 | `http://localhost:1337/uploads/buffalo.png?width=200` 13 | 14 | **Sharp documentation**: 15 | [https://sharp.pixelplumbing.com/api-resize#resize](https://sharp.pixelplumbing.com/api-resize#resize) 16 | 17 | ## `height` / `h` 18 | 19 | Resize the image to the specified height in pixels. The width is calculated with the same aspect ratio. 20 | 21 | **Example (Path modifier)**: 22 | `http://localhost:1337/uploads/height_200/buffalo.png` 23 | 24 | **Example (Query parameter)**: 25 | `http://localhost:1337/uploads/buffalo.png?height=200` 26 | 27 | **Sharp documentation**: 28 | [https://sharp.pixelplumbing.com/api-resize#resize](https://sharp.pixelplumbing.com/api-resize#resize) 29 | 30 | ## `resize` / `s` 31 | 32 | Resize the image to the specified width and height in pixels. The aspect ratio is not preserved. 33 | 34 | **Example (Path modifier)**: 35 | `http://localhost:1337/uploads/s_200x200/buffalo.png` 36 | 37 | **Example (Query parameter)**: 38 | `http://localhost:1337/uploads/buffalo.png?resize=200x200` 39 | 40 | **Sharp documentation**: 41 | [https://sharp.pixelplumbing.com/api-resize#resize](https://sharp.pixelplumbing.com/api-resize#resize) 42 | 43 | ## `fit` 44 | 45 | Resize the image to the specified width and height in pixels. The aspect ratio is preserved. 46 | 47 | **Example (Path modifier)**: 48 | `http://localhost:1337/uploads/s_200x200,fit_outside/buffalo.png` 49 | 50 | **Example (Query parameter)**: 51 | `http://localhost:1337/uploads/buffalo.png?resize=200x200&fit=outside` 52 | 53 | **Sharp documentation**: 54 | [https://sharp.pixelplumbing.com/api-resize#resize](https://sharp.pixelplumbing.com/api-resize#resize) 55 | 56 | ## `position` / `pos` 57 | 58 | Sets `position` option for `resize`. Only works with `fit` and `resize` modifiers. 59 | 60 | **Example (Path modifier)**: 61 | `http://localhost:1337/uploads/s_200x200,pos_top/buffalo.png` 62 | 63 | **Example (Query parameter)**: 64 | `http://localhost:1337/uploads/buffalo.png?resize=200x200&position=top` 65 | 66 | **Sharp documentation**: 67 | [https://sharp.pixelplumbing.com/api-resize#resize](https://sharp.pixelplumbing.com/api-resize#resize) 68 | 69 | ## `trim` 70 | 71 | Trim the image to remove any border that matches the specified background color. 72 | 73 | **Example (Path modifier)**: 74 | `http://localhost:1337/uploads/trim_ffffff/buffalo.png` 75 | 76 | **Example (Query parameter)**: 77 | `http://localhost:1337/uploads/buffalo.png?trim=ffffff` 78 | 79 | **Sharp documentation**: 80 | [https://sharp.pixelplumbing.com/api-operation#trim](https://sharp.pixelplumbing.com/api-operation#trim) 81 | 82 | ## `format` / `f` 83 | 84 | Convert the image to the specified format. The format must be one of the following: `jpeg`, `jpg`, `png`, `webp`, `tiff`, `raw`, `gif`, `svg`, `heif`, `heic`, `avif`. 85 | 86 | **Example (Path modifier)**: 87 | `http://localhost:1337/uploads/format_jpg/buffalo.png` 88 | 89 | **Example (Query parameter)**: 90 | `http://localhost:1337/uploads/buffalo.png?format=jpg` 91 | 92 | **Sharp documentation**: 93 | [https://sharp.pixelplumbing.com/api-output#toformat](https://sharp.pixelplumbing.com/api-output#toformat) 94 | 95 | ## `quality` / `q` 96 | 97 | Set the quality of the output image. This is only applicable to JPEG and WebP images. 98 | 99 | **Example (Path modifier)**: 100 | `http://localhost:1337/uploads/quality_50/buffalo.png` 101 | 102 | **Example (Query parameter)**: 103 | `http://localhost:1337/uploads/buffalo.png?quality=50` 104 | 105 | **Sharp documentation**: 106 | [https://sharp.pixelplumbing.com/api-output#jpeg](https://sharp.pixelplumbing.com/api-output#jpeg) 107 | 108 | ## `rotate` / `r` 109 | 110 | Rotate the image by the specified number of degrees. 111 | 112 | **Example (Path modifier)**: 113 | `http://localhost:1337/uploads/rotate_90/buffalo.png` 114 | 115 | **Example (Query parameter)**: 116 | `http://localhost:1337/uploads/buffalo.png?rotate=90` 117 | 118 | **Sharp documentation**: 119 | [https://sharp.pixelplumbing.com/api-operation#rotate](https://sharp.pixelplumbing.com/api-operation#rotate) 120 | 121 | ## `enlarge` 122 | 123 | Enlarge the output image to be at least as wide and as high as the specified width and height in pixels. The aspect ratio is not preserved. 124 | 125 | **Example (Path modifier)**: 126 | `http://localhost:1337/uploads/enlarge_200x200/buffalo.png` 127 | 128 | **Example (Query parameter)**: 129 | `http://localhost:1337/uploads/buffalo.png?enlarge=200x200` 130 | 131 | **Sharp documentation**: 132 | [https://sharp.pixelplumbing.com/api-resize#resize](https://sharp.pixelplumbing.com/api-resize#resize) 133 | 134 | ## `flip` 135 | 136 | Flip the image about the vertical Y axis. This is equivalent to a horizontal flip if the image is displayed in the correct orientation. 137 | 138 | **Example (Path modifier)**: 139 | `http://localhost:1337/uploads/flip/buffalo.png` 140 | 141 | **Example (Query parameter)**: 142 | `http://localhost:1337/uploads/buffalo.png?flip` 143 | 144 | **Sharp documentation**: 145 | [https://sharp.pixelplumbing.com/api-operation#flip](https://sharp.pixelplumbing.com/api-operation#flip) 146 | 147 | ## `flop` 148 | 149 | Flip the image about the horizontal X axis. This is equivalent to a horizontal flip. 150 | 151 | **Example (Path modifier)**: 152 | `http://localhost:1337/uploads/flop/buffalo.png` 153 | 154 | **Example (Query parameter)**: 155 | `http://localhost:1337/uploads/buffalo.png?flop` 156 | 157 | **Sharp documentation**: 158 | [https://sharp.pixelplumbing.com/api-operation#flop](https://sharp.pixelplumbing.com/api-operation#flop) 159 | 160 | ## `sharpen` 161 | 162 | Sharpen the image. This can be used to increase the perceived sharpness of an image. 163 | 164 | **Example (Path modifier)**: 165 | `http://localhost:1337/uploads/sharpen/buffalo.png` 166 | 167 | **Example (Query parameter)**: 168 | `http://localhost:1337/uploads/buffalo.png?sharpen` 169 | 170 | **Sharp documentation**: 171 | [https://sharp.pixelplumbing.com/api-operation#sharpen](https://sharp.pixelplumbing.com/api-operation#sharpen) 172 | 173 | ## `median` 174 | 175 | Apply a median filter to the image. This can be used to reduce noise in an image. 176 | 177 | **Example (Path modifier)**: 178 | `http://localhost:1337/uploads/median/buffalo.png` 179 | 180 | **Example (Query parameter)**: 181 | `http://localhost:1337/uploads/buffalo.png?median` 182 | 183 | **Sharp documentation**: 184 | [https://sharp.pixelplumbing.com/api-operation#median](https://sharp.pixelplumbing.com/api-operation#median) 185 | 186 | ## `gamma` 187 | 188 | Apply gamma correction to the image. This can be used to adjust the overall brightness of an image. 189 | 190 | **Example (Path modifier)**: 191 | `http://localhost:1337/uploads/gamma_1.5/buffalo.png` 192 | 193 | **Example (Query parameter)**: 194 | `http://localhost:1337/uploads/buffalo.png?gamma=1.5` 195 | 196 | **Sharp documentation**: 197 | [https://sharp.pixelplumbing.com/api-operation#gamma](https://sharp.pixelplumbing.com/api-operation#gamma) 198 | 199 | ## `negate` 200 | 201 | Negate the image. This can be used to invert the colors of an image. 202 | 203 | **Example (Path modifier)**: 204 | `http://localhost:1337/uploads/negate/buffalo.png` 205 | 206 | **Example (Query parameter)**: 207 | `http://localhost:1337/uploads/buffalo.png?negate` 208 | 209 | **Sharp documentation**: 210 | [https://sharp.pixelplumbing.com/api-operation#negate](https://sharp.pixelplumbing.com/api-operation#negate) 211 | 212 | ## `normalize` 213 | 214 | Normalize the image. This can be used to improve the contrast in an image. 215 | 216 | **Example (Path modifier)**: 217 | `http://localhost:1337/uploads/normalize/buffalo.png` 218 | 219 | **Example (Query parameter)**: 220 | `http://localhost:1337/uploads/buffalo.png?normalize` 221 | 222 | **Sharp documentation**: 223 | [https://sharp.pixelplumbing.com/api-operation#normalize](https://sharp.pixelplumbing.com/api-operation#normalize) 224 | 225 | ## `threshold` 226 | 227 | Threshold the image. This can be used to convert an image to black and white. 228 | 229 | **Example (Path modifier)**: 230 | `http://localhost:1337/uploads/threshold_128/buffalo.png` 231 | 232 | **Example (Query parameter)**: 233 | `http://localhost:1337/uploads/buffalo.png?threshold=128` 234 | 235 | **Sharp documentation**: 236 | [https://sharp.pixelplumbing.com/api-operation#threshold](https://sharp.pixelplumbing.com/api-operation#threshold) 237 | 238 | ## `grayscale` 239 | 240 | Convert the image to grayscale. This can be used to remove color from an image. 241 | 242 | **Example (Path modifier)**: 243 | `http://localhost:1337/uploads/grayscale/buffalo.png` 244 | 245 | **Example (Query parameter)**: 246 | `http://localhost:1337/uploads/buffalo.png?grayscale` 247 | 248 | **Sharp documentation**: 249 | [https://sharp.pixelplumbing.com/api-operation#greyscale](https://sharp.pixelplumbing.com/api-operation#greyscale) 250 | 251 | ## `animated` 252 | 253 | **experimental** --------------------------------------------------------------------------------