Building a typed CMS for business data with PHP, OpenAPI, and MCP
In my previous article, I introduced theNeNeseries: a family of small, self-hosted business tools for teams operating in Japan. Previous. . .

In my previous article, I introduced theNeNeseries: a family of small, self-hosted business tools for teams operating in Japan. Previous article:https://dev. to/hideyukimori/i-am-building-self-hosted-business-tools-for-small-teams-in-japan-4i26This article is a deeper look at one of those tools:NeNe Records. Repository:https://github. com/hideyukiMORI/nene-recordsNeNe Records is an API-first typed CMS and flexible entity platform built onNENE2, my small PHP framework for AI-readable business APIs. It is not trying to be a WordPress clone. It is not trying to run WordPress plugins or themes. The goal is different:manage business data and public content through typed schemas, documented APIs, and clear AI tool boundaries. Why not just use WordPress?WordPress is useful. It has proven that a generic content model can support blogs, pages, shops, media, and many small business workflows. But when I think about business data, a few problems keep coming back:untyped metadataplugin-specific data shapeshooks and filters that can change behavior from many placesAPI contracts that depend heavily on pluginsAI integrations that may not know where the real application boundary isThepostmetamodel is flexible, but it often becomes stringly typed storage. That is fine for many websites. But for business data, I usually want the API to know more:this field is textthis field is an enumthis field is an imagethis field is a relationthis field is requiredthis record belongs to this organizationthis operation requires this roleThat is the space where NeNe Records lives. Typed records instead of metadata chaosThe core model is simple:Entity Type -> Field Definition -> RecordEnter fullscreen modeExit fullscreen modeAnEntity Typedefines what kind of data exists. Examples:postspagesproductseventsinternal documentsAField Definitiondescribes the shape of a field:textmarkdownhtmlblocksintenumbooldatetimeimagefilerelationARecordis the actual data. The important part is that the schema is not only a frontend concern. The API validates writes against the registered field definitions. The admin UI can provide a flexible editing experience, but the API remains the boundary that enforces the data shape. That is the main difference I care about:flexible editing, typed persistence. Current statusNeNe Records is already more than a schema experiment. It has a React admin UI, typed entity definitions, record management, media handling, public pages, OpenAPI documentation, and an OpenAPI-derived MCP tool catalog. It also supports multi-tenant organization scope, with ongoing work around WordPress migration and production deployment. It is still a work in progress, but the core shape is already running. The architectureNeNe Records follows the same general shape as the rest of the NeNe series:React Admin UI Public pages AI / MCP clients | v Documented HTTP API | v Use cases | v Repositories | v DatabaseEnter fullscreen modeExit fullscreen modeThe backend is PHP 8. 4 on NENE2. The admin UI is React and TypeScript. The API contract is OpenAPI 3. 1. MCP tools are derived from the documented API surface. The important rule is:MCP tools should call the application API, not the database. That is the same rule I wrote about in my previous article:https://dev. to/hideyukimori/mcp-should-not-mean-letting-ai-touch-your-database-57p1For a CMS-like product, this matters a lot. Content and business data are not just rows. They have:validation rulespermissionsorganization scopepublication stateredirectsSEO metadatamedia referencesaudit and migration concernsThose rules belong in the application boundary. API-first adminThe admin screen is not the source of truth. It is a client. It edits entity types, fields, records, media, users, and settings through the same API shape that other clients can understand. This makes the system easier to reason about. If a future tool needs to create a record, it should not need a hidden internal shortcut. It should call the same API that the admin UI calls. That is also why OpenAPI matters. The API contract is not only documentation for humans. It is also a map for:generated clientscontract testsMCP tool catalogsfuture automationPublic pages without a separate Node SSR serverNeNe Records is not only a headless CMS. It also supports public pages. The direction is a single-origin PHP application that can return crawlable HTML for public content, with a SPA shell on top. That includes work around:real permalinkscanonical URLsOGPTwitter cardsJSON-LDbreadcrumbssitemap. xmlrobots. txtredirect mapsI do not want every small business deployment to require a separate Node SSR service just to make public pages crawlable. For this project, the PHP application should be able to serve useful public HTML directly. WordPress migration, not WordPress compatibilityNeNe Records is not WordPress-compatible. That is intentional. But migration still matters. Many real teams already have content in WordPress. So NeNe Records has been growing a WXR import path:posts and pagestagsmedia attachmentsbody image URL replacement301 redirect mapsSEO metadata from common pluginsimport plan previewThe goal is not:run WordPress themes and plugins. The goal is:move content, URLs, media, and SEO intent into a typed API-first platform. That distinction is important. Compatibility with the WordPress runtime would pull the product back toward the same plugin/theme coupling I am trying to avoid. Migration is the useful part. Runtime compatibility is not the goal. Real-data testing with Aozora BunkoOne thing I have been doing recently is testing NeNe Records with real public-domain text data. For that, I prepared an importer usingAozora Bunkotext. In a test tenant, I imported works by Natsume Soseki as chapter-based records. The goal is not just to prove that records can be inserted. The goal is to see whether the system still feels coherent when the data is not a tiny demo. Things I want to observe:list performance with many recordspermalink tree behaviorchapter navigationpublic page renderingsitemap behaviorsearch and directory browsingadmin UI usability with larger content setsThe public test tenant is here:https://aozora. nene-records. com/In a browser, it is intended to show the public shell. If you call it withcurland the defaultAccept: */*, you may see an API-style JSON response instead. Normal browser access should resolve as HTML. This kind of real-data testing is valuable because CMS demos can be misleading. A CMS with five records can look finished. A CMS with hundreds or thousands of records starts to reveal where the model, navigation, and public presentation are weak. Why this is business software, not only a CMS experimentI call NeNe Records a typed CMS, but the broader idea is a flexible entity platform. The same primitives can describe more than blog posts:product catalogsinternal knowledge recordssimple CRM datapublic directoriesstructured landing pagesoperational recordsThe difference from an application framework is that Records keeps the schema editable from the product surface. The difference from a traditional CMS is that the API and field definitions are not treated as incidental plugin behavior. They are the center. What is still unfinishedI do not want to overstate the maturity. NeNe Records is still being refined. Important areas are still evolving:settings UIfiner-grained permissionstwo-factor authenticationproduction deployment pathshosted / managed optionsmore public-site polishlarge-data soak testingThe project is open source and usable for experimentation, but I do not want to present it as a finished CMS replacement. The honest position is:the shape is working, and the product is being hardened through real implementation. What I learned while building itThe biggest lesson is that "simple CMS" is not actually simple. Once you care about typed data, SEO, public pages, migration, multi-tenancy, media, admin UX, and AI boundaries, the system becomes a real product very quickly. That is why the architecture needs to stay boring. The boring parts are the parts I want to trust:HTTP APIsOpenAPI contractsuse casesrepositoriestyped field definitionsexplicit permissionsMCP behind the API boundaryThe product can become flexible only if the boundaries stay strict. LinksNeNe Records:https://github. com/hideyukiMORI/nene-recordsNENE2:https://github. com/hideyukiMORI/NENE2Aozora test tenant:https://aozora. nene-records. com/Previous NeNe series overview:https://dev. to/hideyukimori/i-am-building-self-hosted-business-tools-for-small-teams-in-japan-4i26MCP boundary article:https://dev. to/hideyukimori/mcp-should-not-mean-letting-ai-touch-your-database-57p1NeNe Records is still a work in progress. But the direction is clear:typed business data, public pages, OpenAPI contracts, and AI tools that stay behind the application boundary.