PageSourceSearch

https://cromwellcms.com/assets/js/3fce7679.8b1e3a77.js

js cromwellcms.com collected 2026-10-04 13:34:05 UTC 15,022 bytes, 1 lines download raw bytes

1"use strict";(self.webpackChunk_cromwell_website=self.webpackChunk_cromwell_website||[]).push([[9781],{8193:function(e,t,n){n.r(t),n.d(t,{frontMatter:function(){return l},contentTitle:function(){return s},metadata:function(){return u},toc:function(){return p},default:function(){return c}});var a=n(5773),i=n(808),r=(n(7378),n(5318)),o=["components"],l={sidebar_position:5},s="Custom data types",u={unversionedId:"features/custom-data",id:"features/custom-data",isDocsHomePage:!1,title:"Custom data types",description:"In WordPress there are many solutions for storing specific data in your CMS.",source:"@site/docs/features/custom-data.md",sourceDirName:"features",slug:"/features/custom-data",permalink:"/docs/features/custom-data",tags:[],version:"current",sidebarPosition:5,frontMatter:{sidebar_position:5},sidebar:"tutorialSidebar",previous:{title:"Payments",permalink:"/docs/features/payments"},next:{title:"Deployment and Scalability",permalink:"/docs/features/scalability"}},p=[{value:"Custom fields",id:"custom-fields",children:[{value:"How this works",id:"how-this-works",children:[]}]},{value:"Custom entities",id:"custom-entities",children:[]},{value:"GraphQL",id:"graphql",children:[{value:"Sorting & filtering",id:"sorting--filtering",children:[]}]},{value:"Register custom entity or field in the code",id:"register-custom-entity-or-field-in-the-code",children:[]}],d={toc:p};function c(e){var t=e.components,n=(0,i.Z)(e,o);return(0,r.kt)("wrapper",(0,a.Z)({},d,n,{components:t,mdxType:"MDXLayout"}),(0,r.kt)("h1",{id:"custom-data-types"},"Custom data types"),(0,r.kt)("p",null,"In WordPress there are many solutions for storing specific data in your CMS.",(0,r.kt)("br",{parentName:"p"}),"\n","As a core feature WordPress provides custom post types. That is very convenient if you want to manage in the CMS other basic types of content: products, movies or whatever.\nAnd if you want to extend existing posts with some custom fields, you can install a plugin to handle the data."),(0,r.kt)("p",null,"In Cromwell CMS these concepts are implemented in the core. In design this is a compilation of three well-known features:   "),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"https://wordpress.org/support/article/post-types/"},"Wordpress Post Types")),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"https://wordpress.org/plugins/advanced-custom-fields/"},"Advanced Custom Fields")),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("a",{parentName:"li",href:"https://wordpress.org/plugins/codepress-admin-columns/"},"Admin Columns"))),(0,r.kt)("iframe",{width:"560",height:"315",src:"https://www.youtube.com/embed/UbV20L6ZBMc",title:"YouTube video player",frameborder:"0",allow:"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture",allowfullscreen:!0}),(0,r.kt)("h2",{id:"custom-fields"},"Custom fields"),(0,r.kt)("p",null,'Let\'s say you want to add a new field for products called "Brand". In the admin panel go to the ',(0,r.kt)("inlineCode",{parentName:"p"},"Settings -> Custom data -> Product")," and click plus button in the Product section. This will create a custom field.  "),(0,r.kt)("p",null,"A field has several properties:  "),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"Key. The key will be used in GraphQL queries to retrieve your data. Note that ",(0,r.kt)("strong",{parentName:"li"},"key must be unique")," for one entity. "),(0,r.kt)("li",{parentName:"ul"},"Label will be displayed in the admin panel edit page. "),(0,r.kt)("li",{parentName:"ul"},"Field type. You can choose one of many field types:",(0,r.kt)("ul",{parentName:"li"},(0,r.kt)("li",{parentName:"ul"},"Simple text. Just a text string."),(0,r.kt)("li",{parentName:"ul"},"Text editor. Rich text editor that used to write posts."),(0,r.kt)("li",{parentName:"ul"},"Select. User will be able to pick a defined value"),(0,r.kt)("li",{parentName:"ul"},"Image. Will store URL to an image. It can be one from CMS media or any external source."),(0,r.kt)("li",{parentName:"ul"},"Gallery. Several images in one field."),(0,r.kt)("li",{parentName:"ul"},"Color. Uses admin panel Colorpicker to choose a color. ")))),(0,r.kt)("p",null,'Type "Brand" in Key and in Label and hit "SAVE" button at the top.\nNow go ',(0,r.kt)("inlineCode",{parentName:"p"},"Store -> Products -> Edit a product"),' and you will notice a new field called "Brand" at the bottom of the page.\nIf you want to see "Brand" column in the product list, go back to the list and hit gear icon at the top right. Click a checkbox against Brand and close the popup by clicking anywhere on the page. Column Brands will appear in the list. Now you can search or sort all products by this new column.'),(0,r.kt)("h3",{id:"how-this-works"},"How this works"),(0,r.kt)("p",null,"Cromwell CMS has meta data tables to store any key-value pairs. Data of custom fields stored in this meta data. After user fills any text into your custom field, CMS will create a record in meta table with entity id, key and value. After that we can search or sort over meta data using SQL joins and TypeORM query builder.",(0,r.kt)("br",{parentName:"p"}),"\n","The concept is similar to WordPres but there's a difference. WordPress has one table for all metadata called wp_postmeta. On huge websites it can vastly bloat which will slow down queries. That's why in Cromwell CMS we have a meta table per default entity. Products will store meta in crw_product_meta, posts in crw_post_meta.    "),(0,r.kt)("h2",{id:"custom-entities"},"Custom entities"),(0,r.kt)("p",null,"Custom entities are similar to custom post types in WordPress.\nFor example, you want to store movies in your CMS. In the admin panel go to ",(0,r.kt)("inlineCode",{parentName:"p"},"Settings -> Custom data"),', scroll to the bottom and click "Add entity" button. This will open popup with several properties:'),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"Type. Custom entity type/name. Will be used in GraphQL queries. Must be unique. (Eg. Movie)"),(0,r.kt)("li",{parentName:"ul"},"List label. How to call many entities, will be used in the admin sidebar as a link title. (Eg. Movies)"),(0,r.kt)("li",{parentName:"ul"},"Entity label. How to call one entity. (Eg. Movie)"),(0,r.kt)("li",{parentName:"ul"},"Icon. Icon to display for the admin sidebar link.")),(0,r.kt)("p",null,"Click apply, it will create a new entity type. After that you can add custom fields to this entity. Click save and Movies link should appear in the sidebar under Blog link.  "),
1(0,r.kt)("p",null,"Same as with meta we don't want to bloat default tables, so custom entities are stored in their own table called ",(0,r.kt)("inlineCode",{parentName:"p"},"crw_custom_entity")),(0,r.kt)("h2",{id:"graphql"},"GraphQL"),(0,r.kt)("p",null,"You can query fields you need at the frontend with GraphQL. But there is a small inconvenience that we cannot dynamically change GraphQL types to include custom fields, so all fields are requested in GraphQL argument:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-graphql"},'query GetPostById {\n  getPostById(id: 15) {\n    title\n    customMeta(keys: ["key1", "key2"])\n  }\n}\n')),(0,r.kt)("p",null,"If the post has any meta data corresponding to provided keys it will return the following object:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-json"},'{\n  "data": {\n    "getPostById": {\n      "title": "My post",\n      "customMeta": {\n          "key1": "key 1 value",\n          "key2": "key 2 value"\n      }\n    }\n  }\n}\n')),(0,r.kt)("p",null,(0,r.kt)("inlineCode",{parentName:"p"},"customMeta")," will have null value if all requested keys aren't set."),(0,r.kt)("p",null,"And here's a basic example how to make a custom query to CMS API server in your code:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-js"},"import { gql } from '@apollo/client';\nimport { getGraphQLClient } from '@cromwell/core-frontend';\n\nconst client = getGraphQLClient();\nconst result = await client.query({\n  query: gql`\n    query GetPostById {\n      getPostById(id: 15) {\n        title\n        customMeta(keys: [\"key1\", \"key2\"])\n      }\n    }\n  `,\n});\nconst post = result.data?.getPostById;\n")),(0,r.kt)("p",null,"Custom entities should be requested via filter method:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-graphql"},'query GetFilteredCustomEntities {\n  getFilteredCustomEntities(\n    filterParams: {\n      entityType: "Brand"\n    }, \n    pagedParams: {\n      pageNumber: 1,\n      pageSize: 15\n    }) {\n    elements {\n      id\n      slug\n      entityType\n      customMeta(keys: ["key1", "key2"])\n    }\n    pagedMeta {\n      pageNumber\n      pageSize\n      totalElements\n      totalPages\n    }\n  }\n}\n')),(0,r.kt)("h3",{id:"sorting--filtering"},"Sorting & filtering"),(0,r.kt)("p",null,"Cromwell CMS has a base filter that allows to sort or filter over any meta key or entity default column.\nFor example, you want to get only entities with ",(0,r.kt)("inlineCode",{parentName:"p"},"key1")," that contains ",(0,r.kt)("inlineCode",{parentName:"p"},"text1")," in value (SQL LIKE operator) and sort by ",(0,r.kt)("inlineCode",{parentName:"p"},"key2"),":"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-graphql"},'query GetFilteredCustomEntities {\n  getFilteredCustomEntities(\n    filterParams: {\n      entityType: "Brand",\n      filters: [\n        {\n          key: "key1",\n          value: "text1",\n          inMeta: true\n        }\n      ]\n      sorts: [\n        {\n          key: "key2",\n          inMeta: true\n        }\n      ]\n    }, \n    pagedParams: {\n      pageNumber: 1,\n      pageSize: 15\n    }) {\n    elements {\n      id\n      slug\n      entityType\n      customMeta(keys: ["key1", "key2"])\n    }\n  }\n}\n')),(0,r.kt)("p",null,"Note that we have ",(0,r.kt)("inlineCode",{parentName:"p"},"inMeta: true")," to indicate that we want to work with data in meta. If ",(0,r.kt)("inlineCode",{parentName:"p"},"inMeta: false")," or not specified, then CMS will consider ",(0,r.kt)("inlineCode",{parentName:"p"},"key")," as a column name in entity table. For example, if you want to filter by post title, then provide ",(0,r.kt)("inlineCode",{parentName:"p"},'key: "title"'),"."),(0,r.kt)("h2",{id:"register-custom-entity-or-field-in-the-code"},"Register custom entity or field in the code"),(0,r.kt)("p",null,"If in your plugin you want to add a new custom entity there two options\n(look into ",(0,r.kt)("a",{parentName:"p",href:"/docs/development/plugin-development"},"Plugin development guide"),"\nto understand where to put this code):"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"Register dynamically on the frontend, without saving into DB. Note that user won't be able to delete it.")),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-js",metastring:'title="src/admin/index.tsx"',title:'"src/admin/index.tsx"'},"import { registerCustomEntity, registerCustomFieldOfType } from '@cromwell/admin-panel';\n\nregisterCustomEntity({\n  entityType: 'my_entity_name',\n  listLabel: 'My entities',\n  entityLabel: 'My entity',\n  columns: [\n    {\n      name: 'my_field',\n      label: 'My field',\n      type: 'Simple text'\n    }\n  ]\n});\n\nregisterCustomFieldOfType({\n    entityType: 'my_entity_name';\n    fieldType: 'Simple text',\n    key: 'my_field2',\n    id: 'my_entity_name-my_field2',\n    label: 'My Field 2', // Label on entity edit page\n    column: {\n      name: 'my_field2',\n      label: 'My Field 2' // Column name on entity table page\n    }\n})\n")),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"Create and save into database on plugin install. User will be able to modify and delete it.")),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-js",metastring:'title="src/backend/index.ts"',title:'"src/backend/index.ts"'},"import { registerAction } from '@cromwell/core-backend';\nimport { getRestApiClient } from '@cromwell/core-frontend';\n\nconst client = getGraphQLClient();\n\nregisterAction({\n  pluginName: 'your-plugin-name',\n  actionName: 'install_plugin',\n  action: async (payload) => {\n    if (payload.pluginName === 'your-plugin-name') {\n      const settings = await client.getAdminCmsSettings();\n      if (!settings.customEntities) settings.customEntities = [];\n      if (!settings.customFields) settings.customFields = [];\n      \n      settings.customEntities.push({\n          entityType: 'my_entity_name',\n          listLabel: 'My entities',\n          entityLabel: 'My entity',\n      });\n      settings.customFields.push({\n        /* ... */\n      })\n      await client.saveCmsSettings(settings);\n    }\n  }\n});\n")))}c.isMDXComponent=!0},5318:function(e,t,n){n.d(t,{Zo:function(){return p},kt:function(){return m}});var a=n(7378);function i(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function r(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);t&&(a=a.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,a)}return n}function o(e){for(var t=1;t<arguments.length;t++){var n=null!=arguments[t]?arguments[t]:{};
1t%2?r(Object(n),!0).forEach((function(t){i(e,t,n[t])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(n)):r(Object(n)).forEach((function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(n,t))}))}return e}function l(e,t){if(null==e)return{};var n,a,i=function(e,t){if(null==e)return{};var n,a,i={},r=Object.keys(e);for(a=0;a<r.length;a++)n=r[a],t.indexOf(n)>=0||(i[n]=e[n]);return i}(e,t);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);for(a=0;a<r.length;a++)n=r[a],t.indexOf(n)>=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(i[n]=e[n])}return i}var s=a.createContext({}),u=function(e){var t=a.useContext(s),n=t;return e&&(n="function"==typeof e?e(t):o(o({},t),e)),n},p=function(e){var t=u(e.components);return a.createElement(s.Provider,{value:t},e.children)},d={inlineCode:"code",wrapper:function(e){var t=e.children;return a.createElement(a.Fragment,{},t)}},c=a.forwardRef((function(e,t){var n=e.components,i=e.mdxType,r=e.originalType,s=e.parentName,p=l(e,["components","mdxType","originalType","parentName"]),c=u(n),m=i,y=c["".concat(s,".").concat(m)]||c[m]||d[m]||r;return n?a.createElement(y,o(o({ref:t},p),{},{components:n})):a.createElement(y,o({ref:t},p))}));function m(e,t){var n=arguments,i=t&&t.mdxType;if("string"==typeof e||i){var r=n.length,o=new Array(r);o[0]=c;var l={};for(var s in t)hasOwnProperty.call(t,s)&&(l[s]=t[s]);l.originalType=e,l.mdxType="string"==typeof e?e:i,o[1]=l;for(var u=2;u<r;u++)o[u]=n[u];return a.createElement.apply(null,o)}return a.createElement.apply(null,n)}c.displayName="MDXCreateElement"}}]);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.