WordPress had a great idea in 2010. Let everything be a post. A page is a post. An attachment is a post. A product, a portfolio item, a recipe, a job listing, all of them are posts under the hood. The same engine renders all of it, the same database table stores all of it, and the same query API reaches all of it. That decision is why WordPress could grow sideways for fifteen years without breaking its own shape.
Squilla took the same idea and pushed it one floor up. Every entity is a content_node. Pages, blog posts, case studies, landing pages, docs articles, anything you invent next week. They all share one table, one query API, one rendering pipeline, and one admin form generator. I want to walk through what node types add on top of the custom post type pattern, because the wins are not flashy but they stack.
Schema lives in code, not scattered across plugins
If you have ever tried to figure out where a WordPress custom post type was registered, you know the pain. Some plugin called register_post_type in a hook that fires on init, and the only way to find it is grep. The schema for that post type, the fields it accepts, the labels, the supports array, all of it lives in PHP somewhere in a folder you do not own.
In Squilla a node type is declared in an extension manifest. The schema sits next to the code that owns it. When I activate the extension, the node type is registered. When I deactivate it, the node type goes away. There is no orphan state where a post type exists but the plugin that defined it is gone. The contract is explicit and the source of truth is one file.
Per-language fields out of the box
WordPress treats translations as an afterthought. You bolt on WPML or Polylang, you accept the duplicate-post model where each translation is its own post with its own ID, and you spend the rest of your career untangling which copy is the canonical one.
Squilla nodes carry a language_code from the moment they are born. Fields can be marked translatable in the schema and the store reads and writes per-locale rows with a default-language fallback. I do not have to think about it. I write the schema once, mark the right fields, and the admin form gives editors a language switcher. The query API filters by language. The renderer picks the right locale. This is the kind of thing that should have been built in everywhere a decade ago.
JSONB means new fields without a migration
This is the one that quietly changes how I work. A node type can grow a new field tomorrow and I do not need to write a SQL migration. The fields_data column is JSONB. The admin form rebuilds itself from the schema declared in the extension. I add a field to the manifest, redeploy, and the next time an editor opens the form the field is there.
The same goes for blocks. The blocks_data column is JSONB too, so a block type can evolve its field shape and old content keeps rendering through the template that knows how to read it. I am not saying schema-less is always right. I am saying schema-on-read with a typed manifest on top is a really nice place to live when you are iterating on what a content type even is.
One query API across every node type
This is the part that feels obvious once you have it and painful once you go back. CoreAPI nodes.query works the same way for a blog post, a case study, a landing page, and whatever you invent next. Filter by type, by taxonomy, by language, by status, by date, by custom field. Order, paginate, count. One signature, one mental model, one set of indexes to tune.
WordPress technically has WP_Query for everything too, but the meta_query escape hatch tells you the limits. Once you reach for postmeta joins you are off the happy path. With JSONB and proper indexes the happy path goes much further before it bends.
Routing that respects the type
Node types declare url_prefixes. A case_study lives at /case/{slug}. A blog post lives at /blog/{slug}. A docs article lives at /docs/{slug}. The router knows which prefix maps to which type, the theme knows which layout to apply, and the editor never has to think about slug collisions across types because the prefix gives each type its own namespace.
The honest take
Is any of this revolutionary? No. Every single piece exists somewhere in the WordPress ecosystem if you stack enough plugins. ACF gives you schema-ish fields. WPML gives you translations. Custom Post Type UI gives you a UI for registering types. Rewrite rules give you prefixes. The difference is that in Squilla all of it is one thing, defined once, owned by the kernel, and consistent across every extension that ever ships.
The wins are incremental. The schema lives in code. Translations are first class. Fields grow without migrations. One query API rules them all. Each one is small. Together they make the boring part of building a site feel boring in the good way, the way it should feel.
That boring uniformity is the feature. 🧱