├── .eslintrc ├── .gitignore ├── modules └── @apostrophecms │ ├── open-graph-file │ └── index.js │ ├── open-graph-image │ └── index.js │ ├── open-graph-user │ └── index.js │ ├── open-graph-file-tag │ └── index.js │ ├── open-graph-global │ └── index.js │ ├── open-graph-image-tag │ └── index.js │ └── open-graph-doc-type │ └── index.js ├── i18n ├── sk.json ├── en.json ├── fr.json ├── pt-BR.json ├── de.json ├── it.json └── es.json ├── package.json ├── .github └── workflows │ └── test.yml ├── CHANGELOG.md ├── LICENSE.md ├── index.js ├── lib └── nodes.js └── README.md /.eslintrc: -------------------------------------------------------------------------------- 1 | { "extends": "apostrophe" } 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore MacOS X metadata forks (fusefs) 2 | ._* 3 | package-lock.json 4 | *.DS_Store 5 | node_modules 6 | 7 | # vim swp files 8 | .*.sw* 9 | -------------------------------------------------------------------------------- /modules/@apostrophecms/open-graph-file/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | improve: '@apostrophecms/file', 3 | options: { 4 | openGraph: false 5 | } 6 | }; 7 | -------------------------------------------------------------------------------- /modules/@apostrophecms/open-graph-image/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | improve: '@apostrophecms/image', 3 | options: { 4 | openGraph: false 5 | } 6 | }; 7 | -------------------------------------------------------------------------------- /modules/@apostrophecms/open-graph-user/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | improve: '@apostrophecms/user', 3 | options: { 4 | openGraph: false 5 | } 6 | }; 7 | -------------------------------------------------------------------------------- /modules/@apostrophecms/open-graph-file-tag/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | improve: '@apostrophecms/file-tag', 3 | options: { 4 | openGraph: false 5 | } 6 | }; 7 | -------------------------------------------------------------------------------- /modules/@apostrophecms/open-graph-global/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | improve: '@apostrophecms/global', 3 | options: { 4 | openGraph: false 5 | } 6 | }; 7 | -------------------------------------------------------------------------------- /modules/@apostrophecms/open-graph-image-tag/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | improve: '@apostrophecms/image-tag', 3 | options: { 4 | openGraph: false 5 | } 6 | }; 7 | -------------------------------------------------------------------------------- /i18n/sk.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Open Graph titul", 3 | "titleHelp": "Názov vášho obsahu bez akejkoľvek obchodnej značky, ako je napríklad názov vašej stránky.", 4 | "description": "Open Graph popis", 5 | "descriptionHelp": "Stručný popis obsahu, zvyčajne 2 až 4 vety. Toto sa zobrazí pod názvom príspevku.", 6 | "type": "Open Graph typ", 7 | "typeHelp": "Typ média vášho obsahu. Pozrite si https://ogp.me/#types.", 8 | "image": "Open Graph obrázok", 9 | "group": "Open Graph" 10 | } -------------------------------------------------------------------------------- /i18n/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Open Graph Title", 3 | "titleHelp": "The title of your content without any branding such as your site name.", 4 | "description": "Open Graph Description", 5 | "descriptionHelp": "A brief description of the content, usually between 2 and 4 sentences. This will displayed below the title of the post.", 6 | "type": "Open Graph Type", 7 | "typeHelp": "The type of media of your content. See https://ogp.me/#types.", 8 | "image": "Open Graph Image", 9 | "group": "Open Graph" 10 | } -------------------------------------------------------------------------------- /i18n/fr.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Titre Open Graph", 3 | "titleHelp": "Le titre de votre contenu sans aucune marque comme le nom de votre site.", 4 | "description": "Description Open Graph", 5 | "descriptionHelp": "Une brève description du contenu, généralement entre 2 et 4 phrases. Cela s'affichera sous le titre du post.", 6 | "type": "Type Open Graph", 7 | "typeHelp": "Le type de média de votre contenu. Voir https://ogp.me/#types.", 8 | "image": "Image Open Graph", 9 | "group": "Open Graph" 10 | } 11 | -------------------------------------------------------------------------------- /i18n/pt-BR.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Título do Open Graph", 3 | "titleHelp": "O título do seu conteúdo sem nenhuma marca, como o nome do seu site.", 4 | "description": "Descrição do Open Graph", 5 | "descriptionHelp": "Uma breve descrição do conteúdo, geralmente entre 2 e 4 frases. Isso será exibido abaixo do título do post.", 6 | "type": "Tipo do Open Graph", 7 | "typeHelp": "O tipo de mídia do seu conteúdo. Veja https://ogp.me/#types.", 8 | "image": "Imagem do Open Graph", 9 | "group": "Open Graph" 10 | } 11 | -------------------------------------------------------------------------------- /i18n/de.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Open Graph Titel", 3 | "titleHelp": "Der Titel Ihres Inhalts ohne Branding wie den Namen Ihrer Website.", 4 | "description": "Open Graph Beschreibung", 5 | "descriptionHelp": "Eine kurze Beschreibung des Inhalts, normalerweise zwischen 2 und 4 Sätzen. Dies wird unter dem Titel des Beitrags angezeigt.", 6 | "type": "Open Graph Typ", 7 | "typeHelp": "Die Art der Medien Ihres Inhalts. Siehe https://ogp.me/#types.", 8 | "image": "Open Graph Bild", 9 | "group": "Open Graph" 10 | } 11 | -------------------------------------------------------------------------------- /i18n/it.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Titolo Open Graph", 3 | "titleHelp": "Il titolo del tuo contenuto senza alcun marchio come il nome del tuo sito.", 4 | "description": "Descrizione Open Graph", 5 | "descriptionHelp": "Una breve descrizione del contenuto, solitamente tra 2 e 4 frasi. Questo verrà visualizzato sotto il titolo del post.", 6 | "type": "Tipo Open Graph", 7 | "typeHelp": "Il tipo di media del tuo contenuto. Vedi https://ogp.me/#types.", 8 | "image": "Immagine Open Graph", 9 | "group": "Open Graph" 10 | } 11 | -------------------------------------------------------------------------------- /i18n/es.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Título de Open Graph", 3 | "titleHelp": "El título de tu contenido sin ninguna marca, como el nombre de tu sitio.", 4 | "description": "Descripción de Open Graph", 5 | "descriptionHelp": "Una breve descripción del contenido, generalmente entre 2 y 4 oraciones. Esto se mostrará debajo del título de la publicación.", 6 | "type": "Tipo de Open Graph", 7 | "typeHelp": "El tipo de medio de tu contenido. Ver https://ogp.me/#types.", 8 | "image": "Imagen de Open Graph", 9 | "group": "Open Graph" 10 | } 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@apostrophecms/open-graph", 3 | "version": "1.2.3", 4 | "description": "Open Graph fields for ApostropheCMS", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "npm run lint", 8 | "lint": "eslint ." 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/apostrophecms/open-graph.git" 13 | }, 14 | "homepage": "https://github.com/apostrophecms/open-graph#readme", 15 | "author": "Apostrophe Technologies", 16 | "license": "MIT", 17 | "devDependencies": { 18 | "eslint-config-apostrophe": "^5.0.0" 19 | } 20 | } -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | push: 5 | branches: ["main"] 6 | pull_request: 7 | branches: ["*"] 8 | 9 | workflow_dispatch: 10 | 11 | jobs: 12 | test: 13 | runs-on: ubuntu-latest 14 | strategy: 15 | matrix: 16 | node-version: [18, 20] 17 | mongodb-version: [6.0, 7.0] 18 | 19 | steps: 20 | - name: Git checkout 21 | uses: actions/checkout@v4 22 | 23 | - name: Use Node.js ${{ matrix.node-version }} 24 | uses: actions/setup-node@v4 25 | with: 26 | node-version: ${{ matrix.node-version }} 27 | 28 | - name: Start MongoDB 29 | uses: supercharge/mongodb-github-action@1.11.0 30 | with: 31 | mongodb-version: ${{ matrix.mongodb-version }} 32 | 33 | - run: npm install 34 | 35 | - run: npm test 36 | env: 37 | CI: true 38 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 1.2.3 (2025-08-06) 4 | 5 | * Bumps `eslint-config-apostrophe` to `5`, fixes errors, removes unused dependencies. 6 | * Removes circle ci, adds github actions. 7 | * Inject nodes instead of async Nunjucks component to support external frontends. 8 | 9 | ## 1.2.2 (2024-09-05) 10 | 11 | * Updates the documentation and package description. No code changes. 12 | * Add AI and community-reviewed translation strings. 13 | 14 | ## 1.2.1 (2023-03-06) 15 | 16 | * Removes `apostrophe` as a peer dependency. 17 | 18 | ## 1.2.0 (2021-11-15) 19 | 20 | * Adds Slovak (`sk`) locale strings for static text. Thanks to [Michael Huna](https://github.com/Miselrkba) for the contribution. 21 | 22 | ## 1.1.0 (2021-10-28) 23 | 24 | * Adds localization strings for the `en` locale. 25 | * Fixes an error where the doc-type fields were placed in the SEO module field group. 26 | 27 | ## 1.0.0 28 | 29 | * Initial release for Apostrophe 3 30 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2021 Apostrophe Technologies 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const { getTags } = require('./lib/nodes'); 4 | 5 | module.exports = { 6 | options: { 7 | alias: 'openGraph', 8 | i18n: { 9 | ns: 'aposOg', 10 | browser: true 11 | } 12 | }, 13 | bundle: { 14 | directory: 'modules', 15 | modules: getBundleModuleNames() 16 | }, 17 | init(self) { 18 | self.appendNodes('head', 'tags'); 19 | if (!self.apos.baseUrl) { 20 | self.apos.util.warnDevOnce( 21 | 'aposOgBaseUrl', 22 | '⚠️ You do not have the `baseUrl` value set for this application. The Open Graph image will not work correctly without this set.' 23 | ); 24 | } 25 | }, 26 | methods(self) { 27 | return { 28 | tags(req) { 29 | return getTags(req.data, self.apos); 30 | } 31 | }; 32 | } 33 | }; 34 | 35 | function getBundleModuleNames() { 36 | const source = path.join(__dirname, './modules/@apostrophecms'); 37 | return fs 38 | .readdirSync(source, { withFileTypes: true }) 39 | .filter(dirent => dirent.isDirectory()) 40 | .map(dirent => `@apostrophecms/${dirent.name}`); 41 | } 42 | -------------------------------------------------------------------------------- /modules/@apostrophecms/open-graph-doc-type/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | improve: '@apostrophecms/doc-type', 3 | fields(self, options) { 4 | if (options.openGraph !== false) { 5 | return { 6 | add: { 7 | openGraphTitle: { 8 | label: 'aposOg:title', 9 | type: 'string', 10 | help: 'aposOg:titleHelp' 11 | }, 12 | openGraphDescription: { 13 | label: 'aposOg:description', 14 | type: 'string', 15 | textarea: true, 16 | help: 'aposOg:descriptionHelp' 17 | }, 18 | openGraphType: { 19 | label: 'aposOg:type', 20 | type: 'string', 21 | htmlHelp: 'aposOg:typeHelp' 22 | }, 23 | // TODO this needs minSize and aspectRatio options when they exist 24 | _openGraphImage: { 25 | type: 'relationship', 26 | label: 'aposOg:image', 27 | max: 1, 28 | withType: '@apostrophecms/image' 29 | } 30 | }, 31 | group: { 32 | openGraph: { 33 | label: 'aposOg:group', 34 | fields: [ 35 | 'openGraphTitle', 36 | 'openGraphDescription', 37 | 'openGraphType', 38 | '_openGraphImage' 39 | ], 40 | last: true 41 | } 42 | } 43 | }; 44 | } 45 | } 46 | }; 47 | -------------------------------------------------------------------------------- /lib/nodes.js: -------------------------------------------------------------------------------- 1 | function getTags(data, apos) { 2 | const nodes = []; 3 | 4 | // Determine context (piece or page) 5 | const context = data.piece || data.page; 6 | if (!context) { 7 | return nodes; 8 | } 9 | 10 | // Set Open Graph URL 11 | const openGraphUrl = context._url; 12 | 13 | // Set Open Graph Title 14 | const openGraphTitle = context.openGraphTitle || context.title; 15 | 16 | // Set Open Graph Description 17 | const openGraphDescription = context.openGraphDescription; 18 | 19 | // Set Open Graph Type 20 | const openGraphType = context.openGraphType; 21 | 22 | // Set Open Graph Image 23 | let openGraphImagePath; 24 | if (context._openGraphImage) { 25 | const attachment = apos.image.first(context._openGraphImage); 26 | if (attachment) { 27 | openGraphImagePath = apos.attachment.url(attachment, { size: 'max' }); 28 | } 29 | } 30 | 31 | // Create meta tags as node objects 32 | 33 | if (openGraphUrl) { 34 | nodes.push({ 35 | name: 'meta', 36 | attrs: { 37 | property: 'og:url', 38 | content: openGraphUrl 39 | } 40 | }); 41 | } 42 | 43 | nodes.push({ 44 | name: 'meta', 45 | attrs: { 46 | property: 'og:type', 47 | content: openGraphType || 'website' 48 | } 49 | }); 50 | 51 | if (openGraphTitle) { 52 | nodes.push({ 53 | name: 'meta', 54 | attrs: { 55 | property: 'og:title', 56 | content: openGraphTitle 57 | } 58 | }); 59 | } 60 | 61 | if (openGraphDescription) { 62 | nodes.push({ 63 | name: 'meta', 64 | attrs: { 65 | property: 'og:description', 66 | content: openGraphDescription 67 | } 68 | }); 69 | } 70 | 71 | if (openGraphImagePath) { 72 | nodes.push({ 73 | name: 'meta', 74 | attrs: { 75 | property: 'og:image', 76 | content: openGraphImagePath 77 | } 78 | }); 79 | } 80 | 81 | return nodes; 82 | } 83 | 84 | module.exports = { 85 | getTags 86 | }; 87 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
17 | 18 | ## Compatibility 19 | This version requires the latest ApostropheCMS. When adding this module to an existing project, run `npm update` to ensure all ApostropheCMS modules are up-to-date. 20 | 21 | ## Installation 22 | 23 | ```bash 24 | npm install @apostrophecms/open-graph 25 | ``` 26 | 27 | ## Use 28 | 29 | Configure `@apostrophecms/open-graph` in `app.js`. 30 | 31 | ```js 32 | const apos = require('apostrophe')({ 33 | shortName: 'project', 34 | modules: { 35 | '@apostrophecms/open-graph': {} 36 | } 37 | }); 38 | ``` 39 | 40 | ### Setting the `baseUrl` 41 | 42 | Open Graph images *will not be set* with absolute URLs if the `baseUrl` is not set. This should either be set statically in `app.js`, but more likely, in the environment configuration, such as in `data/local.js`. Some social media platforms consider an aboslute URL to be a requirement and *will not accept the image URL without it*. 43 | 44 | #### In `app.js` as part of your main Apostrophe app 45 | ```js 46 | require('apostrophe')({ 47 | shortName: 'mysite', 48 | baseUrl: 'https://mysite.com', 49 | modules: { 50 | // other module configurations 51 | } 52 | }); 53 | ``` 54 | #### As part of an environment configuration in `data/local.js` 55 | ```js 56 | module.exports = { 57 | baseUrl: 'https://mysite.com', 58 | modules: { 59 | // other environment-specific module configurations 60 | } 61 | }; 62 | ``` 63 | ### Opting out of Open Graph Fields 64 | 65 | Adding `openGraph: false` to any module will prevent Open Graph fields from being added. Good use cases for this are utility modules, special page types, or piece types that don't have index or show pages. 66 | 67 | ```js 68 | require('apostrophe')({ 69 | shortName: 'mysite', 70 | baseUrl: 'https://mysite.com', 71 | modules: { 72 | category: { 73 | options: { 74 | openGraph: false; 75 | } 76 | } 77 | } 78 | }); 79 | ``` 80 | 81 | The following modules opt out of the Open Graph fields by default: 82 | - `@apostrophecms/global` 83 | - `@apostrophecms/user` 84 | - `@apostrophecms/image` 85 | - `@apostrophecms/image-tag` 86 | - `@apostrophecms/file` 87 | - `@apostrophecms/file-tag` 88 | 89 | ### Field Reference 90 | The following are the fields that are added to pieces and pages 91 | 92 | |Name |Description | Module Effected | 93 | --- | --- | --- 94 | |`openGraphTitle`|OG Title, populates ``|`@apostrophecms/doc-type` 95 | |`openGraphDescription`|OG Description, populates ``|`@apostrophecms/doc-type` 96 | |`openGraphType`|OG Type, populates ``, defaults to 'website'|`@apostrophecms/doc-type` 97 | |`openGraphImage`|OG Image, populates ``|`@apostrophecms/doc-type` --------------------------------------------------------------------------------