{"id":15287246,"url":"https://github.com/sulu/suluheadlessbundle","last_synced_at":"2025-04-12T20:42:13.978Z","repository":{"id":38107081,"uuid":"235275696","full_name":"sulu/SuluHeadlessBundle","owner":"sulu","description":"Bundle that provides controllers and services for using Sulu as headless content management system","archived":false,"fork":false,"pushed_at":"2025-04-11T09:24:52.000Z","size":782,"stargazers_count":50,"open_issues_count":20,"forks_count":25,"subscribers_count":7,"default_branch":"0.10","last_synced_at":"2025-04-11T10:40:57.843Z","etag":null,"topics":["bundle","hacktoberfest","headless","php","sulu","sulu-bundle","symfony"],"latest_commit_sha":null,"homepage":"","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/sulu.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-01-21T06:59:03.000Z","updated_at":"2025-04-11T09:24:27.000Z","dependencies_parsed_at":"2024-06-10T13:03:52.018Z","dependency_job_id":"398f64bf-8db0-4656-92d2-964ca3e4a64f","html_url":"https://github.com/sulu/SuluHeadlessBundle","commit_stats":{"total_commits":127,"total_committers":20,"mean_commits":6.35,"dds":0.6456692913385826,"last_synced_commit":"9ec2faa7c2a85187fc88eeb9f1103bb90508447d"},"previous_names":[],"tags_count":18,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sulu%2FSuluHeadlessBundle","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sulu%2FSuluHeadlessBundle/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sulu%2FSuluHeadlessBundle/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sulu%2FSuluHeadlessBundle/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sulu","download_url":"https://codeload.github.com/sulu/SuluHeadlessBundle/tar.gz/refs/heads/0.10","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248631668,"owners_count":21136554,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bundle","hacktoberfest","headless","php","sulu","sulu-bundle","symfony"],"created_at":"2024-09-30T15:27:06.305Z","updated_at":"2025-04-12T20:42:13.953Z","avatar_url":"https://github.com/sulu.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003ch1 align=\"center\"\u003eSuluHeadlessBundle\u003c/h1\u003e\n\n\u003cp align=\"center\"\u003e\n    \u003ca href=\"https://sulu.io/\" target=\"_blank\"\u003e\n        \u003cimg width=\"30%\" src=\"https://sulu.io/uploads/media/800x/00/230-Official%20Bundle%20Seal.svg?v=2-6\u0026inline=1\" alt=\"Official Sulu Bundle Badge\"\u003e\n    \u003c/a\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n    \u003ca href=\"LICENSE\" target=\"_blank\"\u003e\n        \u003cimg src=\"https://img.shields.io/github/license/sulu/SuluHeadlessBundle.svg\" alt=\"GitHub license\"\u003e\n    \u003c/a\u003e\n    \u003ca href=\"https://github.com/sulu/SuluHeadlessBundle/actions\" target=\"_blank\"\u003e\n        \u003cimg src=\"https://img.shields.io/github/actions/workflow/status/sulu/SuluHeadlessBundle/test-application.yaml\" alt=\"Test workflow status\"\u003e\n    \u003c/a\u003e\n    \u003ca href=\"https://github.com/sulu/sulu/releases\" target=\"_blank\"\u003e\n        \u003cimg src=\"https://img.shields.io/badge/sulu%20compatibility-%3E=2.0-52b6ca.svg\" alt=\"Sulu compatibility\"\u003e\n    \u003c/a\u003e\n\u003c/p\u003e\n\u003cbr/\u003e\n\nThe **SuluHeadlessBundle** provides controllers and services for using the [Sulu](https://sulu.io/) content \nmanagement system in a headless way. \n\nTo achieve this, the bundle includes a controller that allows to retrieve the \ncontent of a **Sulu page as plain JSON content**. Furthermore, the bundle provides APIs for accessing features that are \navailable via Twig extensions in traditional templates such as navigation contexts and snippet areas. Finally, the bundle includes\nan optional **single page application setup** that is built upon React and MobX and utilizes the functionality of \nthe bundle.\n\n\nThe SuluHeadlessBundle is compatible with Sulu **starting from version 2.0**. Have a look at the `require` section in \nthe [composer.json](composer.json) to find an \n**up-to-date list of the requirements** of the bundle.\nPlease be aware that this bundle is **still under development** and might not cover every use-case yet.\nDepending on the feedback of the community, future versions of the bundle might contain breaking changes.\n\n\n## 🚀\u0026nbsp; Installation and Usage\n\n### Install the bundle \n\nExecute the following [composer](https://getcomposer.org/) command to add the bundle to the dependencies of your \nproject:\n\n```bash\ncomposer require sulu/headless-bundle\n```\n\n### Enable the bundle \n\nEnable the bundle by adding it to the list of registered bundles in the `config/bundles.php` file of your project:\n\n```php\nreturn [\n    /* ... */\n    Sulu\\Bundle\\HeadlessBundle\\SuluHeadlessBundle::class =\u003e ['all' =\u003e true],\n];\n```\n\n### Include the routes of the bundle\n\nInclude the routes of the bundle in a new `config/routes/sulu_headless_website.yml` file in your project:\n\n```yaml\nsulu_headless:\n    type: portal\n    resource: \"@SuluHeadlessBundle/Resources/config/routing_website.yml\"\n```\n\n### Set the controller of you template\n\nTo provide an API for retrieving the content of a page in the JSON format, the controller of the page template\nmust be set to the `HeadlessWebsiteController` included in this bundle:\n\n```xml\n\u003c?xml version=\"1.0\" ?\u003e\n\u003ctemplate xmlns=\"...\" xmlns:xsi=\"...\" xsi:schemaLocation=\"...\"\u003e\n    \u003c!-- ... --\u003e\n    \u003ccontroller\u003eSulu\\Bundle\\HeadlessBundle\\Controller\\HeadlessWebsiteController::indexAction\u003c/controller\u003e\n    \u003c!-- ... --\u003e\n\u003c/template\u003e\n```\n\nThis controller will provide the **content of the page as JSON object** if the page is requested in the JSON format \nvia `{pageUrl}.json`.\n\n\n## 💡\u0026nbsp; Key Concepts\n\n### Deliver content of pages with the HeadlessWebsiteController \n\nThe main use-case of the SuluHeadlessBundle is **delivering the content of a page as a JSON object**. This can be \nenabled individually per template by setting the controller of the template of the page \nto `Sulu\\Bundle\\WebsiteBundle\\Controller\\DefaultController::indexAction`. When using the `HeadlessWebsiteController`\nas controller for a template, the content of the page is available as JSON object via `{pageUrl}.json`.\n\nAdditionally to the content of the page, the JSON object returned by the `HeadlessWebsiteController` contains **meta \ninformation** such as the page template and the data of the page excerpt:\n\n```json\n{\n   \"id\": \"a5181a5a-b030-4933-b3b0-e9faf7ec756c\",\n   \"type\": \"page\",\n   \"template\": \"headless-template\",\n   \"content\": {\n      \"title\": \"Headless Example Page\",\n      \"url\": \"/headless-example\",\n      \"contacts\": [\n         {\n            \"id\": 416,\n            \"firstName\": \"Homer\",\n            \"lastName\": \"Simpson\",\n            \"fullName\": \"Homer Simpson\",\n            \"title\": \"Dr. \",\n            \"position\": \"Nuclear safety Inspector at the Springfield Nuclear Power Plan\"\n         }\n      ]\n   },\n   \"view\": {\n      \"title\": [],\n      \"url\": [],\n      \"contacts\": []\n   },\n   \"extension\": {\n      \"seo\": {\n         \"title\": \"\",\n         \"description\": \"\",\n         \"keywords\": \"\",\n         \"canonicalUrl\": \"\",\n         \"noIndex\": false,\n         \"noFollow\": false,\n         \"hideInSitemap\": false\n      },\n      \"excerpt\": {\n         \"title\": \"\",\n         \"more\": \"\",\n         \"description\": \"\",\n         \"categories\": [],\n         \"tags\": [],\n         \"icon\": [],\n         \"images\": []\n      }\n   },\n   \"author\": \"2\",\n   \"authored\": \"2019-12-03T11:01:38+0100\",\n   \"changer\": 2,\n   \"changed\": \"2020-01-30T07:47:46+0100\",\n   \"creator\": 2,\n   \"created\": \"2019-12-03T11:01:38+0100\"\n}\n```\n\nIf the content of a page that uses the `HeadlessWebsiteController` is requested without the `.json` suffix, the\ncontroller will render Twig template that is set as `view` of the template of the page. \nIn this case, the data that would have been returned in case of a `.json` request is available in the twig \ntemplate via a `headless` variable. \nThis behaviour is compatible with the default Sulu `WebsiteController` and **allows to start a javascript application** \nthat utilizes the functionality of the SuluHeadlessBundle after the initial request of the user. \n\n#### Resolve content data to scalar values via ContentTypeResolver\n\nInternally, Sulu uses `ContentType` services that are responsible for persisting page content when a page is modified \nand resolving the data that is passed to the Twig template when a page is rendered. Unfortunately, some `ContentType` \nservices pass non-scalar values such as media entities to the Twig template. As a JSON object must contain only scalar\nvalues, the SuluHeadlessBundle cannot use the existing `ContentType` services for resolving the content of a page.\n\nTo solve this problem, the SuluHeadlessBundle introduces `ContentTypeResolver` services to resolve the content of\npages to scalar values. The bundle already includes `ContentTypeResolver` services for various content types.\nIf your project includes custom content types or if you are not satisfied with an existing `ContentTypeResolver`, \nyou can register your own `ContentTypeResolver` by implementing the `ContentTypeResolverInterface` and\nadding a `sulu_headless.content_type_resolver` tag to the service.\n\n### Provide popular Sulu functionality via JSON APIs\n\nThe Sulu content management system comes with various services and Twig extensions to simplify the development and the\nrendering complex websites. This functionality is not available when serving the content of the website in a headless \nway, therefore the SuluHeadlessBundle includes controllers to **provide JSON APIs for accessing these features**.\n\nThe APIs are registered as portal URLs and therefore their path is prefixed with the URL of the webspace.\nIf you have configured a language-specific URL for your webspace, the API URL will look something like this:\n\n - `https://example.org/en/api/...`\n\n#### Navigation\n\n`/api/navigations/{contextKey}`\n\n| Parameter        | Type    | Default Value | Description\n|------------------|---------|---------------|---------------------------------------\n| depth            | integer | `1`           | Maximum depth of the navigation tree that is loaded.\n| flat             | boolean | `false`       | Return navigation as flat list instead of tree.\n| excerpt          | boolean | `false`       | Include excerpt data in the returned navigation.\n\nExample: `/api/navigations/main?depth=2\u0026flat=false\u0026excerpt=true`\n\n#### Search\n\n`/api/search?q={searchTerm}`\n\n| Parameter        | Type    | Default Value | Description\n|------------------|---------|---------------|---------------------------------------\n| q                | string  |               | The text you want to search for.\n\nExample: `/api/search?q=CMS`\n\n#### Snippet Areas\n\n`/api/snippet-areas/{area}`\n\n| Parameter        | Type    | Default Value | Description\n|------------------|---------|---------------|---------------------------------------------------\n| includeExtension | boolean | `false`       | Include extension data (e.g. excerpt) in the returned result.\n\nExample: `/api/snippet-areas/settings?includeExtension=true`\n\n### Reference single page application implementation\n\nThe SuluHeadlessBundle is completely **frontend independent** and does not require the use of a specific technology or \nframework. Still, the bundle contains an **independent and optional single page application setup** in the \n`Resources/js-website` directory that allows you to quick-start your project and serves as a reference implementation\nfor utilizing the bundle functionality. \n\nThe provided reference implementation builds upon **React** as rendering library and utilizes **MobX** for state \nmanagement. It is built around a central `viewRegistry` singleton that allows you to register React components \nas view for specific types of resources (eg. pages of a specific template). The application contains a router that will \nintercept the navigation of the browser, load the JSON data for the requested resource and render the respective view\nwith the loaded data. \n\n![Reference Frontend Implementation](https://user-images.githubusercontent.com/1698337/73056284-f7175100-3e8e-11ea-9e67-9371d8c65099.jpg)\n\nTo use the provided single page application setup, you need to include the following lines in your Twig template to\ninitialize and start the application:\n\n```twig\n{% block content %}\n    {# ... #}\n\n    {# define container element for rendering single page application #}\n    \u003cdiv id=\"sulu-headless-container\"\u003e\u003c/div\u003e\n    \n    {# initialize application with json data of current page to prevent second request on first load #}\n    \u003cscript\u003ewindow.SULU_HEADLESS_VIEW_DATA = {{ headless|json_encode|raw }};\u003c/script\u003e\n    \u003cscript\u003ewindow.SULU_HEADLESS_API_ENDPOINT = '{{ sulu_content_path('/api') }}';\u003c/script\u003e\n    \n    {# start single page application by including built javascript code #}\n    \u003cscript src=\"/build/headless/js/index.js\"\u003e\u003c/script\u003e\n{% endblock %}\n```\n\nAdditionally, you need to add the following files to your project to setup the single page application:\n\n\u003cdetails\u003e\n\u003csummary\u003eassets/headless/package.json\u003c/summary\u003e\n\n```json\n{\n  \"name\": \"my-frontend-application\",\n  \"main\": \"src/index.js\",\n  \"private\": true,\n  \"scripts\": {\n    \"build\": \"webpack src/index.js -o ../../public/build/headless/js/index.js --module-bind js=babel-loader -p --display-modules --sort-modules-by size\",\n    \"watch\": \"webpack src/index.js -w -o ../../public/build/headless/js/index.js  --module-bind js=babel-loader --mode=development --devtool source-map\"\n  },\n  \"dependencies\": {\n    \"sulu-headless-bundle\": \"file:../../vendor/sulu/headless-bundle/Resources/js-website\",\n    \"core-js\": \"^3.0.0\",\n    \"loglevel\": \"^1.0.0\",\n    \"mobx\": \"^4.0.0\",\n    \"mobx-react\": \"^5.0.0\",\n    \"prop-types\": \"^15.7.0\",\n    \"react\": \"^16.8.0\",\n    \"react-dom\": \"^16.8.0\",\n    \"whatwg-fetch\": \"^3.0.0\",\n    \"history\": \"^4.10.1\"\n  },\n  \"devDependencies\": {\n    \"@babel/core\": \"^7.6.0\",\n    \"@babel/plugin-proposal-class-properties\": \"^7.5.5\",\n    \"@babel/plugin-proposal-decorators\": \"^7.6.0\",\n    \"@babel/preset-env\": \"^7.6.0\",\n    \"@babel/preset-react\": \"^7.0.0\",\n    \"babel-eslint\": \"^10.0.3\",\n    \"babel-loader\": \"^8.0.6\",\n    \"webpack\": \"^4.40.2\",\n    \"webpack-cli\": \"^3.3.8\"\n  }\n}\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eassets/headless/webpack.config.js\u003c/summary\u003e\n\n```javascript\nconst path = require('path');\nconst nodeModulesPath = path.resolve(__dirname, 'node_modules');\n\n/* eslint-disable-next-line no-unused-vars */\nmodule.exports = (env, argv) =\u003e {\n    return {\n        resolve: {\n            modules: [nodeModulesPath, 'node_modules'],\n        },\n        resolveLoader: {\n            modules: [nodeModulesPath, 'node_modules'],\n        },\n    };\n};\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eassets/headless/babel.config.js\u003c/summary\u003e\n\n```javascript\nmodule.exports = {\n    presets: ['@babel/env', '@babel/react'],\n    plugins: [\n        ['@babel/plugin-proposal-decorators', {'legacy': true}],\n        ['@babel/plugin-proposal-class-properties', {'loose': true}]\n    ]\n};\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eassets/headless/src/index.js\u003c/summary\u003e\n\n```javascript\nimport { startApp } from 'sulu-headless-bundle';\nimport viewRegistry from 'sulu-headless-bundle/src/registries/viewRegistry';\nimport HeadlessTemplatePage from './views/HeadlessTemplatePage';\n\n// register views for rendering page templates\nviewRegistry.add('page', 'headless-template', HeadlessTemplatePage);\n\n// register views for rendering article templates\n// viewRegistry.add('article', 'headless-template', HeadlessTemplateArticle);\n\n// start react application in specific DOM element\nstartApp(document.getElementById('sulu-headless-container'));\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eassets/headless/src/views/HeadlessTemplatePage.js\u003c/summary\u003e\n\n```javascript\nimport React from 'react';\nimport { observer } from 'mobx-react';\n\n@observer\nclass HeadlessTemplatePage extends React.Component {\n    render() {\n        const serializedData = JSON.stringify(this.props.data, null, 2);\n\n        return (\u003cpre\u003e{ serializedData }\u003c/pre\u003e);\n    }\n}\n\nexport default HeadlessTemplatePage;\n```\n\u003c/details\u003e\n\nFinally, you can build your frontend application by executing `npm install` and `npm run build` in the `assets/headless`\ndirectory.\n\n\n## ❤️\u0026nbsp; Support and Contributions\n\nThe Sulu content management system is a **community-driven open source project** backed by various partner companies. \nWe are committed to a fully transparent development process and **highly appreciate any contributions**. \n\nIn case you have questions, we are happy to welcome you in our official [Slack channel](https://sulu.io/services-and-support).\nIf you found a bug or miss a specific feature, feel free to **file a new issue** with a respective title and description \non the the [sulu/SuluHeadlessBundle](https://github.com/sulu/SuluHeadlessBundle) repository.\n\n\n## 📘\u0026nbsp; License\n\nThe Sulu content management system is released under the under terms of the [MIT License](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsulu%2Fsuluheadlessbundle","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsulu%2Fsuluheadlessbundle","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsulu%2Fsuluheadlessbundle/lists"}